diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt index 772a87fb67..afdc6d9f23 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt @@ -66,8 +66,6 @@ object OpenGroupManager { storage.removeLastMessageServerID(room, server) // Store the public key storage.setOpenGroupPublicKey(server,publicKey) - // Get an auth token - OpenGroupApi.getAuthToken(room, server).get() // Get capabilities val capabilities = OpenGroupApi.getCapabilities(room, server).get() // Get group info diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/LokiPushNotificationManager.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/LokiPushNotificationManager.kt index 6d135c6b1e..adaec0e17a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/LokiPushNotificationManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/LokiPushNotificationManager.kt @@ -1,12 +1,14 @@ package org.thoughtcrime.securesms.notifications import android.content.Context +import nl.komponents.kovenant.Promise import nl.komponents.kovenant.functional.map import okhttp3.MediaType import okhttp3.Request import okhttp3.RequestBody import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI import org.session.libsession.snode.OnionRequestAPI +import org.session.libsession.snode.Version import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.utilities.JsonUtil import org.session.libsignal.utilities.Log @@ -43,7 +45,7 @@ object LokiPushNotificationManager { val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) val request = Request.Builder().url(url).post(body) retryIfNeeded(maxRetryCount) { - OnionRequestAPI.sendOnionRequest(request.build(), server, pnServerPublicKey, OnionRequestAPI.Version.V2).map { json -> + getResponseBody(request.build()).map { json -> val code = json["code"] as? Int if (code != null && code != 0) { TextSecurePreferences.setIsUsingFCM(context, false) @@ -72,7 +74,7 @@ object LokiPushNotificationManager { val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) val request = Request.Builder().url(url).post(body) retryIfNeeded(maxRetryCount) { - OnionRequestAPI.sendOnionRequest(request.build(), server, pnServerPublicKey, OnionRequestAPI.Version.V2).map { json -> + getResponseBody(request.build()).map { json -> val code = json["code"] as? Int if (code != null && code != 0) { TextSecurePreferences.setIsUsingFCM(context, true) @@ -100,7 +102,7 @@ object LokiPushNotificationManager { val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) val request = Request.Builder().url(url).post(body) retryIfNeeded(maxRetryCount) { - OnionRequestAPI.sendOnionRequest(request.build(), server, pnServerPublicKey, OnionRequestAPI.Version.V2).map { json -> + getResponseBody(request.build()).map { json -> val code = json["code"] as? Int if (code == null || code == 0) { Log.d("Loki", "Couldn't subscribe/unsubscribe closed group: $closedGroupPublicKey due to error: ${json["message"] as? String ?: "null"}.") @@ -110,4 +112,10 @@ object LokiPushNotificationManager { } } } + + private fun getResponseBody(request: Request): Promise, Exception> { + return OnionRequestAPI.sendOnionRequest(request, server, pnServerPublicKey, Version.V2).map { response -> + JsonUtil.fromJson(response.body, Map::class.java) + } + } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/file_server/FileServerApi.kt b/libsession/src/main/java/org/session/libsession/messaging/file_server/FileServerApi.kt index 7d376253a1..d713e6fd77 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/file_server/FileServerApi.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/file_server/FileServerApi.kt @@ -74,7 +74,9 @@ object FileServerApi { HTTP.Verb.DELETE -> requestBuilder.delete(createBody(request.parameters)) } return if (request.useOnionRouting) { - OnionRequestAPI.sendOnionRequest(requestBuilder.build(), server, serverPublicKey).fail { e -> + OnionRequestAPI.sendOnionRequest(requestBuilder.build(), server, serverPublicKey).map { + JsonUtil.fromJson(it.body, Map::class.java) + }.fail { e -> Log.e("Loki", "File server request failed.", e) } } else { diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/NotifyPNServerJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/NotifyPNServerJob.kt index 2b001cd7f4..5c393c97b5 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/NotifyPNServerJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/NotifyPNServerJob.kt @@ -13,6 +13,7 @@ import org.session.libsession.messaging.sending_receiving.notifications.PushNoti import org.session.libsession.messaging.utilities.Data import org.session.libsession.snode.SnodeMessage import org.session.libsession.snode.OnionRequestAPI +import org.session.libsession.snode.Version import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.JsonUtil @@ -38,10 +39,10 @@ class NotifyPNServerJob(val message: SnodeMessage) : Job { val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) val request = Request.Builder().url(url).post(body) retryIfNeeded(4) { - OnionRequestAPI.sendOnionRequest(request.build(), server, PushNotificationAPI.serverPublicKey, OnionRequestAPI.Version.V2).map { json -> - val code = json["code"] as? Int + OnionRequestAPI.sendOnionRequest(request.build(), server, PushNotificationAPI.serverPublicKey, Version.V2).map { response -> + val code = response.info["code"] as? Int if (code == null || code == 0) { - Log.d("Loki", "Couldn't notify PN server due to error: ${json["message"] as? String ?: "null"}.") + Log.d("Loki", "Couldn't notify PN server due to error: ${response.info["message"] as? String ?: "null"}.") } }.fail { exception -> Log.d("Loki", "Couldn't notify PN server due to error: $exception.") diff --git a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupApi.kt b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupApi.kt index e0d4be73eb..909d610522 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupApi.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupApi.kt @@ -7,9 +7,9 @@ import com.fasterxml.jackson.databind.type.TypeFactory import com.goterl.lazysodium.LazySodiumAndroid import com.goterl.lazysodium.SodiumAndroid import com.goterl.lazysodium.interfaces.GenericHash +import java.util.concurrent.TimeUnit import kotlinx.coroutines.flow.MutableSharedFlow import nl.komponents.kovenant.Promise -import nl.komponents.kovenant.functional.bind import nl.komponents.kovenant.functional.map import okhttp3.Headers import okhttp3.HttpUrl @@ -19,7 +19,7 @@ import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.sending_receiving.pollers.OpenGroupPoller.Companion.maxInactivityPeriod import org.session.libsession.messaging.utilities.SodiumUtilities import org.session.libsession.snode.OnionRequestAPI -import org.session.libsession.utilities.AESGCM +import org.session.libsession.snode.OnionResponse import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.utilities.Base64.decode import org.session.libsignal.utilities.Base64.encodeBytes @@ -32,9 +32,7 @@ import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.JsonUtil import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.removing05PrefixIfNeeded -import org.session.libsignal.utilities.toHexString import org.whispersystems.curve25519.Curve25519 -import java.util.concurrent.TimeUnit import kotlin.collections.component1 import kotlin.collections.component2 import kotlin.collections.set @@ -109,7 +107,6 @@ object OpenGroupApi { val queryParameters: Map = mapOf(), val parameters: Any? = null, val headers: Map = mapOf(), - val isAuthRequired: Boolean = true, /** * Always `true` under normal circumstances. You might want to disable * this when running over Lokinet. @@ -124,7 +121,13 @@ object OpenGroupApi { return RequestBody.create(MediaType.get("application/json"), parametersAsJSON) } - private fun send(request: Request): Promise, Exception> { + private fun getResponseBodyJson(request: Request): Promise, Exception> { + return send(request).map { + JsonUtil.fromJson(it.body, Map::class.java) + } + } + + private fun send(request: Request): Promise { val url = HttpUrl.parse(request.server) ?: return Promise.ofFail(Error.InvalidURL) val urlBuilder = HttpUrl.Builder() .scheme(url.scheme()) @@ -136,7 +139,7 @@ object OpenGroupApi { urlBuilder.addQueryParameter(key, value) } } - fun execute(token: String?): Promise, Exception> { + fun execute(): Promise { val publicKey = MessagingModuleConfiguration.shared.storage.getOpenGroupPublicKey(request.server) ?: return Promise.ofFail(Error.NoPublicKey) @@ -191,15 +194,10 @@ object OpenGroupApi { headers["X-SOGS-Timestamp"] = "$timestamp" headers["X-SOGS-Pubkey"] = pubKey headers["X-SOGS-Signature"] = encodeBytes(signature) - headers.forEach { entry -> Log.d("Loki", "${entry.key}: ${entry.value}") } val requestBuilder = okhttp3.Request.Builder() .url(urlRequest) .headers(Headers.of(headers)) - if (request.isAuthRequired) { - if (token.isNullOrEmpty()) throw IllegalStateException("No auth token for request.") - requestBuilder.header("Authorization", token) - } when (request.verb) { GET -> requestBuilder.get() PUT -> requestBuilder.put(createBody(request.parameters)!!) @@ -209,31 +207,13 @@ object OpenGroupApi { if (!request.room.isNullOrEmpty()) { requestBuilder.header("Room", request.room) } - if (request.useOnionRouting) { - 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. - if (e is OnionRequestAPI.HTTPRequestFailedAtDestinationException && e.statusCode == 401) { - val storage = MessagingModuleConfiguration.shared.storage - if (request.room != null) { - storage.removeAuthToken(request.room, request.server) - } - } - } + return if (request.useOnionRouting) { + OnionRequestAPI.sendOnionRequest(requestBuilder.build(), request.server, publicKey) } else { - return Promise.ofFail(IllegalStateException("It's currently not allowed to send non onion routed requests.")) + Promise.ofFail(IllegalStateException("It's currently not allowed to send non onion routed requests.")) } } - return if (request.isAuthRequired) { - getAuthToken(request.room!!, request.server).bind { execute(it) } - } else { - execute(null) - } + return execute() } fun downloadOpenGroupProfilePicture( @@ -244,78 +224,14 @@ object OpenGroupApi { verb = GET, room = roomID, server = server, - endpoint = "rooms/$roomID/image", - isAuthRequired = false + endpoint = "rooms/$roomID/image" ) - return send(request).map { json -> + return getResponseBodyJson(request).map { json -> val result = json["result"] as? String ?: throw Error.ParsingFailed decode(result) } } - // region Authorization - fun getAuthToken(room: String, server: String): Promise { - val storage = MessagingModuleConfiguration.shared.storage - return storage.getAuthToken(room, server)?.let { - Promise.of(it) - } ?: run { - requestNewAuthToken(room, server) - .bind { claimAuthToken(it, room, server) } - .success { authToken -> - storage.setAuthToken(room, server, authToken) - } - } - } - - fun requestNewAuthToken(room: String, server: String): Promise { - val (publicKey, privateKey) = MessagingModuleConfiguration.shared.storage.getUserX25519KeyPair() - .let { it.publicKey.serialize() to it.privateKey.serialize() } - ?: return Promise.ofFail(Error.Generic) - val queryParameters = mutableMapOf("public_key" to publicKey.toHexString()) - val request = Request( - GET, - room, - server, - "auth_token_challenge", - queryParameters, - isAuthRequired = false, - parameters = null - ) - return send(request).map { json -> - val challenge = json["challenge"] as? Map<*, *> ?: throw Error.ParsingFailed - val base64EncodedCiphertext = - challenge["ciphertext"] as? String ?: throw Error.ParsingFailed - val base64EncodedEphemeralPublicKey = - challenge["ephemeral_public_key"] as? String ?: throw Error.ParsingFailed - val ciphertext = decode(base64EncodedCiphertext) - val ephemeralPublicKey = decode(base64EncodedEphemeralPublicKey) - val symmetricKey = AESGCM.generateSymmetricKey(ephemeralPublicKey, privateKey) - val tokenAsData = try { - AESGCM.decrypt(ciphertext, symmetricKey) - } catch (e: Exception) { - throw Error.DecryptionFailed - } - tokenAsData.toHexString() - } - } - - fun claimAuthToken( - authToken: String, - room: String, - server: String - ): Promise { - val parameters = - mapOf("public_key" to MessagingModuleConfiguration.shared.storage.getUserPublicKey()!!) - val headers = mapOf("Authorization" to authToken) - val request = Request( - verb = POST, room = room, server = server, endpoint = "claim_auth_token", - parameters = parameters, headers = headers, isAuthRequired = false - ) - return send(request).map { authToken } - } - - // endregion - // region Upload/Download fun upload(file: ByteArray, room: String, server: String): Promise { val base64EncodedFile = encodeBytes(file) @@ -327,14 +243,14 @@ object OpenGroupApi { endpoint = "files", parameters = parameters ) - return send(request).map { json -> + return getResponseBodyJson(request).map { json -> (json["result"] as? Number)?.toLong() ?: throw Error.ParsingFailed } } fun download(file: Long, room: String, server: String): Promise { val request = Request(verb = GET, room = room, server = server, endpoint = "files/$file") - return send(request).map { json -> + return getResponseBodyJson(request).map { json -> val base64EncodedFile = json["result"] as? String ?: throw Error.ParsingFailed decode(base64EncodedFile) ?: throw Error.ParsingFailed } @@ -356,7 +272,7 @@ object OpenGroupApi { endpoint = "messages", parameters = jsonMessage ) - return send(request).map { json -> + return getResponseBodyJson(request).map { json -> @Suppress("UNCHECKED_CAST") val rawMessage = json["message"] as? Map ?: throw Error.ParsingFailed val result = OpenGroupMessageV2.fromJSON(rawMessage) ?: throw Error.ParsingFailed @@ -381,7 +297,7 @@ object OpenGroupApi { endpoint = "messages", queryParameters = queryParameters ) - return send(request).map { json -> + return getResponseBodyJson(request).map { json -> @Suppress("UNCHECKED_CAST") val rawMessages = json["messages"] as? List> ?: throw Error.ParsingFailed @@ -443,7 +359,8 @@ object OpenGroupApi { endpoint = "deleted_messages", queryParameters = queryParameters ) - return send(request).map { json -> + return send(request).map { response -> + val json = JsonUtil.fromJson(response.body, Map::class.java) val type = TypeFactory.defaultInstance() .constructCollectionType(List::class.java, MessageDeletion::class.java) val idsAsString = JsonUtil.toJson(json["ids"]) @@ -466,7 +383,7 @@ object OpenGroupApi { fun getModerators(room: String, server: String): Promise, Exception> { val request = Request(verb = GET, room = room, server = server, endpoint = "moderators") - return send(request).map { json -> + return getResponseBodyJson(request).map { json -> @Suppress("UNCHECKED_CAST") val moderatorsJson = json["moderators"] as? List ?: throw Error.ParsingFailed val id = "$server.$room" @@ -519,11 +436,10 @@ object OpenGroupApi { // region General @Suppress("UNCHECKED_CAST") - fun batch( + fun poll( rooms: List, server: String ): Promise, Exception> { - val authTokenRequests = rooms.associateWith { room -> getAuthToken(room, server) } val storage = MessagingModuleConfiguration.shared.storage val context = MessagingModuleConfiguration.shared.context val timeSinceLastOpen = this.timeSinceLastOpen @@ -535,15 +451,9 @@ object OpenGroupApi { TextSecurePreferences.setLastOpenDate(context) } val requests = rooms.mapNotNull { room -> - val authToken = try { - authTokenRequests[room]?.get() - } catch (e: Exception) { - Log.e("Loki", "Failed to get auth token for $room.", e) - null - } ?: return@mapNotNull null BatchRequest( roomID = room, - authToken = authToken, + authToken = "", fromDeletionServerID = if (useMessageLimit) null else storage.getLastDeletionServerID( room, server @@ -559,22 +469,13 @@ object OpenGroupApi { room = null, server = server, endpoint = "batch", - isAuthRequired = false, parameters = mapOf("requests" to requests) ) - return send(request = request).map { json -> + return getResponseBodyJson(request = request).map { json -> val results = json["results"] as? List<*> ?: throw Error.ParsingFailed results.mapNotNull { json -> if (json !is Map<*, *>) return@mapNotNull null val roomID = json["room_id"] as? String ?: return@mapNotNull null - // 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. - val statusCode = json["status_code"] as? Int ?: return@mapNotNull null - if (statusCode == 401) { - // delete auth token and return null - storage.removeAuthToken(roomID, server) - } // Moderators val moderators = json["moderators"] as? List ?: return@mapNotNull null handleModerators("$server.$roomID", moderators) @@ -632,10 +533,9 @@ object OpenGroupApi { verb = GET, room = null, server = server, - endpoint = "rooms/$room", - isAuthRequired = false + endpoint = "rooms/$room" ) - return send(request).map { json -> + return getResponseBodyJson(request).map { json -> val rawRoom = json["room"] as? Map<*, *> ?: throw Error.ParsingFailed val id = rawRoom["id"] as? String ?: throw Error.ParsingFailed val name = rawRoom["name"] as? String ?: throw Error.ParsingFailed @@ -650,14 +550,13 @@ object OpenGroupApi { room = null, server = server, endpoint = "rooms", - isAuthRequired = false, isBlinded = true ) - return send(request).map { json -> - val rawRooms = json["rooms"] as? List> ?: throw Error.ParsingFailed + return send(request).map { response -> + val rawRooms = JsonUtil.fromJson(response.body, List::class.java) ?: throw Error.ParsingFailed rawRooms.mapNotNull { val roomJson = it as? Map<*, *> ?: return@mapNotNull null - val id = roomJson["id"] as? String ?: return@mapNotNull null + val id = roomJson["token"] as? String ?: return@mapNotNull null val name = roomJson["name"] as? String ?: return@mapNotNull null val imageID = roomJson["image_id"] as? String Info(id, name, imageID) @@ -667,7 +566,7 @@ object OpenGroupApi { fun getMemberCount(room: String, server: String): Promise { val request = Request(verb = GET, room = room, server = server, endpoint = "active_users") - return send(request).map { json -> + return getResponseBodyJson(request).map { json -> val activeUserCount = json["active_users"] as? Int ?: throw Error.ParsingFailed val storage = MessagingModuleConfiguration.shared.storage storage.setUserCount(room, server, activeUserCount) @@ -677,7 +576,7 @@ object OpenGroupApi { fun getCapabilities(room: String, server: String): Promise, Exception> { val request = Request(verb = GET, room = room, server = server, endpoint = "capabilities") - return send(request).map { json -> + return getResponseBodyJson(request).map { json -> json["capabilities"] as? List ?: throw Error.ParsingFailed } } 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 ab51fb6fee..80bf5ce60f 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 @@ -12,8 +12,12 @@ import org.session.libsession.messaging.messages.control.ClosedGroupControlMessa import org.session.libsession.messaging.messages.control.ConfigurationMessage import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate import org.session.libsession.messaging.messages.control.UnsendRequest -import org.session.libsession.messaging.messages.visible.* -import org.session.libsession.messaging.open_groups.* +import org.session.libsession.messaging.messages.visible.LinkPreview +import org.session.libsession.messaging.messages.visible.Profile +import org.session.libsession.messaging.messages.visible.Quote +import org.session.libsession.messaging.messages.visible.VisibleMessage +import org.session.libsession.messaging.open_groups.OpenGroupApi +import org.session.libsession.messaging.open_groups.OpenGroupMessageV2 import org.session.libsession.messaging.utilities.MessageWrapper import org.session.libsession.snode.RawResponsePromise import org.session.libsession.snode.SnodeAPI diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt index fa667382e4..f793cd6e4b 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt @@ -7,6 +7,7 @@ import okhttp3.Request import okhttp3.RequestBody import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.snode.OnionRequestAPI +import org.session.libsession.snode.Version import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.utilities.retryIfNeeded import org.session.libsignal.utilities.JsonUtil @@ -38,12 +39,12 @@ object PushNotificationAPI { val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) val request = Request.Builder().url(url).post(body) retryIfNeeded(maxRetryCount) { - OnionRequestAPI.sendOnionRequest(request.build(), server, serverPublicKey, OnionRequestAPI.Version.V2).map { json -> - val code = json["code"] as? Int + OnionRequestAPI.sendOnionRequest(request.build(), server, serverPublicKey, Version.V2).map { response -> + val code = response.info["code"] as? Int if (code != null && code != 0) { TextSecurePreferences.setIsUsingFCM(context, false) } else { - Log.d("Loki", "Couldn't disable FCM due to error: ${json["message"] as? String ?: "null"}.") + Log.d("Loki", "Couldn't disable FCM due to error: ${response.info["message"] as? String ?: "null"}.") } }.fail { exception -> Log.d("Loki", "Couldn't disable FCM due to error: ${exception}.") @@ -66,14 +67,14 @@ object PushNotificationAPI { val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) val request = Request.Builder().url(url).post(body) retryIfNeeded(maxRetryCount) { - OnionRequestAPI.sendOnionRequest(request.build(), server, serverPublicKey, OnionRequestAPI.Version.V2).map { json -> - val code = json["code"] as? Int + OnionRequestAPI.sendOnionRequest(request.build(), server, serverPublicKey, Version.V2).map { response -> + val code = response.info["code"] as? Int if (code != null && code != 0) { TextSecurePreferences.setIsUsingFCM(context, true) TextSecurePreferences.setFCMToken(context, token) TextSecurePreferences.setLastFCMUploadTime(context, System.currentTimeMillis()) } else { - Log.d("Loki", "Couldn't register for FCM due to error: ${json["message"] as? String ?: "null"}.") + Log.d("Loki", "Couldn't register for FCM due to error: ${response.info["message"] as? String ?: "null"}.") } }.fail { exception -> Log.d("Loki", "Couldn't register for FCM due to error: ${exception}.") @@ -93,10 +94,10 @@ object PushNotificationAPI { val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) val request = Request.Builder().url(url).post(body) retryIfNeeded(maxRetryCount) { - OnionRequestAPI.sendOnionRequest(request.build(), server, serverPublicKey, OnionRequestAPI.Version.V2).map { json -> - val code = json["code"] as? Int + OnionRequestAPI.sendOnionRequest(request.build(), server, serverPublicKey, Version.V2).map { response -> + val code = response.info["code"] as? Int if (code == null || code == 0) { - Log.d("Loki", "Couldn't subscribe/unsubscribe closed group: $closedGroupPublicKey due to error: ${json["message"] as? String ?: "null"}.") + Log.d("Loki", "Couldn't subscribe/unsubscribe closed group: $closedGroupPublicKey due to error: ${response.info["message"] as? String ?: "null"}.") } }.fail { exception -> Log.d("Loki", "Couldn't subscribe/unsubscribe closed group: $closedGroupPublicKey due to error: ${exception}.") diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt index 63a6c39ff3..871d9c4bed 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt @@ -45,7 +45,7 @@ class OpenGroupPoller(private val server: String, private val executorService: S val storage = MessagingModuleConfiguration.shared.storage val rooms = storage.getAllV2OpenGroups().values.filter { it.server == server }.map { it.room } rooms.forEach { downloadGroupAvatarIfNeeded(it) } - return OpenGroupApi.batch(rooms, server).successBackground { responses -> + return OpenGroupApi.poll(rooms, server).successBackground { responses -> responses.forEach { (room, response) -> val openGroupID = "$server.$room" handleNewMessages(room, openGroupID, response.messages) 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 7ddeb06fe0..5b3416a546 100644 --- a/libsession/src/main/java/org/session/libsession/snode/OnionRequestAPI.kt +++ b/libsession/src/main/java/org/session/libsession/snode/OnionRequestAPI.kt @@ -348,8 +348,12 @@ object OnionRequestAPI { /** * Sends an onion request to `destination`. Builds new paths as needed. */ - private fun sendOnionRequest(destination: Destination, payload: ByteArray, version: Version): Promise, Exception> { - val deferred = deferred, Exception>() + private fun sendOnionRequest( + destination: Destination, + payload: ByteArray, + version: Version + ): Promise { + val deferred = deferred() lateinit var guardSnode: Snode buildOnionForDestination(payload, destination, version).success { result -> guardSnode = result.guardSnode @@ -439,7 +443,13 @@ object OnionRequestAPI { /** * Sends an onion request to `snode`. Builds new paths as needed. */ - internal fun sendOnionRequest(method: Snode.Method, parameters: Map<*, *>, snode: Snode, version: Version, publicKey: String? = null): Promise, Exception> { + internal fun sendOnionRequest( + method: Snode.Method, + parameters: Map<*, *>, + snode: Snode, + version: Version, + publicKey: String? = null + ): Promise { val payload = mapOf( "method" to method.rawValue, "params" to parameters @@ -461,7 +471,12 @@ 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, version: Version = Version.V4): Promise, Exception> { + fun sendOnionRequest( + request: Request, + server: String, + x25519PublicKey: String, + version: Version = Version.V4 + ): Promise { val url = request.url() val payload = generatePayload(request, server, version) val destination = Destination.Server(url.host(), version.value, x25519PublicKey, url.scheme(), url.port()) @@ -518,7 +533,7 @@ object OnionRequestAPI { destinationSymmetricKey: ByteArray, destination: Destination, version: Version, - deferred: Deferred, Exception> + deferred: Deferred ) { if (version == Version.V4) { try { @@ -563,25 +578,24 @@ object OnionRequestAPI { } // If there is no data in the response then just return the ResponseInfo - if (info.length < "l${infoLength}${info}e".length) { - return deferred.resolve(JsonUtil.fromJson(info, Map::class.java)) + if (plaintextString.length <= "l${infoLength}${info}e".length) { + return deferred.resolve(OnionResponse(responseInfo, null)) } // Extract the response data as well val data = plaintextString.substring(infoEndIndex) - val dataParts = data.split(":") - val dataLength = dataParts.firstOrNull()?.length - if (dataParts.size <= 1 || dataLength == null) return deferred.reject(Exception("Invalid JSON")) + val dataParts = data.split(":".toRegex(), 2) + val dataLength = dataParts.firstOrNull()?.toIntOrNull() + if (dataParts.size <= 1 || dataLength == null) return deferred.reject(Exception("Invalid response")) val dataString = dataParts.last().dropLast(1) - return deferred.resolve(JsonUtil.fromJson(dataString, Map::class.java)) + return deferred.resolve(OnionResponse(responseInfo, dataString.encodeToByteArray())) } catch (exception: Exception) { deferred.reject(exception) } } else { - val bodyAsString = response.decodeToString() val json = try { - JsonUtil.fromJson(bodyAsString, Map::class.java) + JsonUtil.fromJson(response, Map::class.java) } catch (exception: Exception) { - mapOf( "result" to bodyAsString) + mapOf( "result" to response.decodeToString()) } val base64EncodedIVAndCiphertext = json["result"] as? String ?: return deferred.reject(Exception("Invalid JSON")) val ivAndCiphertext = Base64.decode(base64EncodedIVAndCiphertext) @@ -624,7 +638,7 @@ object OnionRequestAPI { ) return deferred.reject(exception) } - deferred.resolve(body) + deferred.resolve(OnionResponse(body, JsonUtil.toJson(body).toByteArray())) } else -> { if (statusCode != 200) { @@ -635,7 +649,7 @@ object OnionRequestAPI { ) return deferred.reject(exception) } - deferred.resolve(json) + deferred.resolve(OnionResponse(json, JsonUtil.toJson(json).toByteArray())) } } } catch (exception: Exception) { @@ -647,11 +661,15 @@ object OnionRequestAPI { } } // endregion - - enum class Version(val value: String) { - V2("/loki/v2/lsrpc"), - V3("/loki/v3/lsrpc"), - V4("/oxen/v4/lsrpc"); - } - } + +enum class Version(val value: String) { + V2("/loki/v2/lsrpc"), + V3("/loki/v3/lsrpc"), + V4("/oxen/v4/lsrpc"); +} + +data class OnionResponse( + val info: Map<*, *>, + val body: ByteArray? = null +) diff --git a/libsession/src/main/java/org/session/libsession/snode/OnionRequestEncryption.kt b/libsession/src/main/java/org/session/libsession/snode/OnionRequestEncryption.kt index 109371f31c..3e31a52e5a 100644 --- a/libsession/src/main/java/org/session/libsession/snode/OnionRequestEncryption.kt +++ b/libsession/src/main/java/org/session/libsession/snode/OnionRequestEncryption.kt @@ -35,12 +35,12 @@ object OnionRequestEncryption { internal fun encryptPayloadForDestination( payload: ByteArray, destination: Destination, - version: OnionRequestAPI.Version + version: Version ): Promise { val deferred = deferred() ThreadUtils.queue { try { - val plaintext = if (version == OnionRequestAPI.Version.V4) { + val plaintext = if (version == Version.V4) { payload } else { // Wrapping isn't needed for file server or open group onion requests 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 8496c2aecb..2201194169 100644 --- a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt +++ b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt @@ -79,13 +79,15 @@ object SnodeAPI { snode: Snode, parameters: Map, publicKey: String? = null, - version: OnionRequestAPI.Version = OnionRequestAPI.Version.V3 + version: Version = Version.V3 ): RawResponsePromise { val url = "${snode.address}:${snode.port}/storage_rpc/v1" + val deferred = deferred, Exception>() if (useOnionRequests) { - return OnionRequestAPI.sendOnionRequest(method, parameters, snode, version, publicKey) + OnionRequestAPI.sendOnionRequest(method, parameters, snode, version, publicKey).map { + deferred.resolve(JsonUtil.fromJson(it.body, Map::class.java)) + }.fail { deferred.reject(it) } } else { - val deferred = deferred, Exception>() ThreadUtils.queue { val payload = mapOf( "method" to method.rawValue, "params" to parameters ) try { @@ -102,8 +104,8 @@ object SnodeAPI { deferred.reject(exception) } } - return deferred.promise } + return deferred.promise } internal fun getRandomSnode(): Promise {