Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
de0494d
WIP
ThomasSession Dec 11, 2025
2cd727b
wip
ThomasSession Dec 12, 2025
3b6a66e
Error cleanup
ThomasSession Dec 12, 2025
4ad98ae
Adding back snodeClock
ThomasSession Dec 15, 2025
635642f
Old snodeclock
ThomasSession Dec 15, 2025
ce89777
bootstrap logic in snodedirectory
ThomasSession Dec 15, 2025
e289b08
Separate population of the snode pool
ThomasSession Dec 15, 2025
07e2e5b
MAke sure we populate the snode pool on app startup
ThomasSession Dec 15, 2025
abba38c
Making sure we populate the snode pool when building the path(s)
ThomasSession Dec 15, 2025
1c36a33
SessionClient first step
ThomasSession Dec 15, 2025
6e0d2d2
Adding more from old SnodeAPI
ThomasSession Dec 15, 2025
b0dba90
tweaks
ThomasSession Dec 15, 2025
57b0495
todo
ThomasSession Dec 16, 2025
38255eb
Delegating to the new OnionErrorManager
ThomasSession Dec 16, 2025
e7bac14
small tweaks
ThomasSession Dec 16, 2025
5b06f7b
fixes
ThomasSession Dec 16, 2025
8681f6d
invoke overlaods
ThomasSession Dec 16, 2025
832076f
better deserialization
ThomasSession Dec 16, 2025
24b3452
Batching from old code
ThomasSession Dec 17, 2025
00a8c71
Missing methods
ThomasSession Dec 17, 2025
47d8808
cleaned path manager
ThomasSession Dec 17, 2025
a886beb
Removing access to SnodeAPI part1 - moved getSingleTargetSnode in swa…
ThomasSession Dec 17, 2025
1ffd850
SnodeAPI pt2
ThomasSession Dec 17, 2025
10f8d66
Merge branch 'dev' into feature/onion-request-refactor
ThomasSession Dec 17, 2025
01881f0
Cleanup of SnodeAPI pt3
ThomasSession Dec 17, 2025
13e61b6
Last part of the SnodeAPI switch
ThomasSession Dec 17, 2025
02b7159
interface implementations
ThomasSession Dec 17, 2025
b7eb26d
Hilt inhjection
ThomasSession Dec 17, 2025
f99c9d0
Removing Result in favor of a direct OnionResponse
ThomasSession Dec 18, 2025
d9ff024
Removing OnionRequestAPI usage
ThomasSession Dec 18, 2025
efddee8
Fixing hilt dependencies
ThomasSession Dec 18, 2025
a697d2e
Using V3 where needed
ThomasSession Dec 18, 2025
9d29735
Better separation of concerns. No one calls SessionDirectly except th…
ThomasSession Dec 22, 2025
d08990d
Merge branch 'dev' into feature/onion-request-refactor
ThomasSession Jan 6, 2026
593aa49
Adding snode to errors when possible. Removed loud logs
ThomasSession Jan 6, 2026
b45702c
Keeping reference to previous errors during the retry logic
ThomasSession Jan 6, 2026
1b092e9
Snodeclock resync
ThomasSession Jan 6, 2026
79dc0ed
tightening the coroutines
ThomasSession Jan 6, 2026
ab116c1
Removing NotifyPNServerJob
ThomasSession Jan 6, 2026
b9a0504
Reworking error management
ThomasSession Jan 6, 2026
4b20c09
Using Path
ThomasSession Jan 6, 2026
5923d70
Giving the Clients their own retry logic and error management
ThomasSession Jan 7, 2026
2be1ea8
Handle the bad snode in COS for Snode destination
ThomasSession Jan 7, 2026
9e920c8
Making sure mutating the Path is safe when done from different threads
ThomasSession Jan 7, 2026
d936b1f
Moving COS responsibility in the Clients
ThomasSession Jan 7, 2026
010f10c
Retrying logic
ThomasSession Jan 7, 2026
069bdb3
Moving retry strategy at the calling sites
ThomasSession Jan 7, 2026
cd1f00a
Moving now private call to new function inclient
ThomasSession Jan 7, 2026
801552d
cleanup
ThomasSession Jan 7, 2026
1a9d435
Retry logic
ThomasSession Jan 7, 2026
847f2fb
updating tests
ThomasSession Jan 7, 2026
9f117f7
Merge branch 'dev' into feature/onion-request-refactor
ThomasSession Jan 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import org.session.libsession.messaging.groups.GroupManagerV2
import org.session.libsession.messaging.groups.LegacyGroupDeprecationManager
import org.session.libsession.messaging.jobs.MessageSendJob
import org.session.libsession.messaging.notifications.TokenFetcher
import org.session.libsession.snode.SnodeClock
import org.session.libsession.network.ServerClient
import org.session.libsession.network.SnodeClock
import org.session.libsession.network.onion.PathManager
import org.session.libsession.utilities.ConfigFactoryProtocol
import org.session.libsession.utilities.Device
import org.session.libsession.utilities.TextSecurePreferences
Expand All @@ -28,14 +30,16 @@ class MessagingModuleConfiguration @Inject constructor(
val configFactory: ConfigFactoryProtocol,
val tokenFetcher: TokenFetcher,
val groupManagerV2: GroupManagerV2,
val clock: SnodeClock,
val preferences: TextSecurePreferences,
val deprecationManager: LegacyGroupDeprecationManager,
val recipientRepository: RecipientRepository,
val avatarUtils: AvatarUtils,
val proStatusManager: ProStatusManager,
val messageSendJobFactory: MessageSendJob.Factory,
val json: Json,
val snodeClock: SnodeClock,
val serverClient: ServerClient,
val pathManager: PathManager
) {

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@ package org.session.libsession.messaging.file_server
import android.util.Base64
import kotlinx.coroutines.CancellationException
import network.loki.messenger.libsession_util.Curve25519
import network.loki.messenger.libsession_util.ED25519
import network.loki.messenger.libsession_util.util.BlindKeyAPI
import okhttp3.Headers.Companion.toHeaders
import okhttp3.HttpUrl
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import org.session.libsession.database.StorageProtocol
import org.session.libsession.snode.OnionRequestAPI
import org.session.libsession.snode.utilities.await
import org.session.libsession.network.ServerClient
import org.session.libsession.network.SnodeClock
import org.session.libsignal.utilities.ByteArraySlice
import org.session.libsignal.utilities.HTTP
import org.session.libsignal.utilities.Hex
Expand All @@ -30,6 +29,8 @@ import kotlin.time.Duration.Companion.milliseconds
@Singleton
class FileServerApi @Inject constructor(
private val storage: StorageProtocol,
private val serverClient: ServerClient,
private val snodeClock: SnodeClock
) {

companion object {
Expand Down Expand Up @@ -59,7 +60,10 @@ class FileServerApi @Inject constructor(
* Always `true` under normal circumstances. You might want to disable
* this when running over Lokinet.
*/
val useOnionRouting: Boolean = true
val useOnionRouting: Boolean = true,

// Computed fresh for each attempt (after clock resync etc.)
val dynamicHeaders: (suspend () -> Map<String, String>)? = null
)

private fun createBody(body: ByteArray?, parameters: Any?): RequestBody? {
Expand All @@ -75,35 +79,44 @@ class FileServerApi @Inject constructor(
}


private suspend fun send(request: Request): SendResponse {
val urlBuilder = request.fileServer.url
.newBuilder()
.addPathSegments(request.endpoint)
if (request.verb == HTTP.Verb.GET) {
for ((key, value) in request.queryParameters) {
urlBuilder.addQueryParameter(key, value)
}
}
val requestBuilder = okhttp3.Request.Builder()
.url(urlBuilder.build())
.headers(request.headers.toHeaders())
when (request.verb) {
HTTP.Verb.GET -> requestBuilder.get()
HTTP.Verb.PUT -> requestBuilder.put(createBody(request.body, request.parameters) ?: RequestBody.EMPTY)
HTTP.Verb.POST -> requestBuilder.post(createBody(request.body, request.parameters) ?: RequestBody.EMPTY)
HTTP.Verb.DELETE -> requestBuilder.delete(createBody(request.body, request.parameters))
}
private suspend fun send(request: Request, operationName: String): SendResponse {
return if (request.useOnionRouting) {
try {
val response = OnionRequestAPI.sendOnionRequest(
request = requestBuilder.build(),
server = request.fileServer.url.host,
val response = serverClient.send(
operationName = operationName,
requestFactory = {
val urlBuilder = request.fileServer.url
.newBuilder()
.addPathSegments(request.endpoint)

if (request.verb == HTTP.Verb.GET) {
for ((k, v) in request.queryParameters) urlBuilder.addQueryParameter(k, v)
}

val computed = request.dynamicHeaders?.invoke().orEmpty()
val mergedHeaders = (request.headers + computed)

val builder = okhttp3.Request.Builder()
.url(urlBuilder.build())
.headers(mergedHeaders.toHeaders())

when (request.verb) {
HTTP.Verb.GET -> builder.get()
HTTP.Verb.PUT -> builder.put(createBody(request.body, request.parameters) ?: RequestBody.EMPTY)
HTTP.Verb.POST -> builder.post(createBody(request.body, request.parameters) ?: RequestBody.EMPTY)
HTTP.Verb.DELETE -> builder.delete(createBody(request.body, request.parameters))
}

builder.build()
},
serverBaseUrl = request.fileServer.url.host,
x25519PublicKey =
Hex.toStringCondensed(
Curve25519.pubKeyFromED25519(Hex.fromStringCondensed(request.fileServer.ed25519PublicKeyHex))
)
).await()
)

//todo ONION in the new architecture, an Onionresponse should always be in 200..299, otherwise an OnionError is thrown
check(response.code in 200..299) {
"Error response from file server: ${response.code}"
}
Expand Down Expand Up @@ -144,7 +157,7 @@ class FileServerApi @Inject constructor(
}
}
)
val response = send(request)
val response = send(request, "FileServer.upload")
val json = JsonUtil.fromJson(response.body, Map::class.java)
val id = json["id"]!!.toString()
val expiresEpochSeconds = (json.getOrDefault("expires", null) as? Number)?.toLong()
Expand All @@ -169,7 +182,7 @@ class FileServerApi @Inject constructor(
verb = HTTP.Verb.GET,
endpoint = "file/$fileId"
)
return send(request)
return send(request, "FileServer.download")
}

suspend fun renew(fileId: String,
Expand All @@ -184,7 +197,7 @@ class FileServerApi @Inject constructor(
"X-FS-TTL" to it.inWholeSeconds.toString()
}
} ?: mapOf()
))
), "FileServer.renew")

resp.expires
}
Expand Down Expand Up @@ -308,8 +321,6 @@ class FileServerApi @Inject constructor(
?: throw (Error.NoEd25519KeyPair)

val blindedKeys = BlindKeyAPI.blindVersionKeyPair(secretKey)
val timestamp = System.currentTimeMillis().milliseconds.inWholeSeconds // The current timestamp in seconds
val signature = BlindKeyAPI.blindVersionSign(secretKey, timestamp)

// The hex encoded version-blinded public key with a 07 prefix
val blindedPkHex = "07" + blindedKeys.pubKey.data.toHexString()
Expand All @@ -319,15 +330,21 @@ class FileServerApi @Inject constructor(
verb = HTTP.Verb.GET,
endpoint = "session_version",
queryParameters = mapOf("platform" to "android"),
headers = mapOf(
"X-FS-Pubkey" to blindedPkHex,
"X-FS-Timestamp" to timestamp.toString(),
"X-FS-Signature" to Base64.encodeToString(signature, Base64.NO_WRAP)
)
headers = mapOf("X-FS-Pubkey" to blindedPkHex),
// dynamic ones recomputed every attempt:
dynamicHeaders = {
val timestamp = snodeClock.currentTimeSeconds()
val signature = BlindKeyAPI.blindVersionSign(secretKey, timestamp)

mapOf(
"X-FS-Timestamp" to timestamp.toString(),
"X-FS-Signature" to Base64.encodeToString(signature, Base64.NO_WRAP)
)
}
)

// transform the promise into a coroutine
val result = send(request)
val result = send(request, "FileServer.getClientVersion")

// map out the result
return JsonUtil.fromJson(result.body, Map::class.java).let {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import org.session.libsession.messaging.sending_receiving.attachments.Attachment
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentState
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
import org.session.libsession.messaging.utilities.Data
import org.session.libsession.snode.OnionRequestAPI
import org.session.libsession.network.model.OnionError
import org.session.libsession.utilities.Address
import org.session.libsession.utilities.DecodedAudio
import org.session.libsession.utilities.InputStreamMediaDataSource
Expand Down Expand Up @@ -94,7 +94,7 @@ class AttachmentDownloadJob @AssistedInject constructor(
} else if (exception == Error.NoAttachment
|| exception == Error.NoThread
|| exception == Error.NoSender
|| (exception is OnionRequestAPI.HTTPRequestFailedAtDestinationException && exception.statusCode == 400)
|| (exception is OnionError.DestinationError && exception.status?.code == 400) //todo ONION this is matching old behaviour. Do we want this kind of error handling here?
|| exception is NonRetryableException) {
attachment?.let { id ->
Log.d("AttachmentDownloadJob", "Setting attachment state = failed, have attachment")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import org.session.libsession.messaging.messages.control.GroupUpdated
import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.messaging.utilities.Data
import org.session.libsession.messaging.utilities.MessageAuthentication.buildGroupInviteSignature
import org.session.libsession.snode.SnodeAPI
import org.session.libsession.network.SnodeClock
import org.session.libsession.utilities.ConfigFactoryProtocol
import org.session.libsession.utilities.getGroup
import org.session.libsession.utilities.withGroupConfigs
Expand All @@ -34,6 +34,7 @@ class InviteContactsJob @AssistedInject constructor(
@Assisted val isReinvite: Boolean,
private val configFactory: ConfigFactoryProtocol,
private val messageSender: MessageSender,
private val snodeClock: SnodeClock

) : Job {

Expand Down Expand Up @@ -71,7 +72,7 @@ class InviteContactsJob @AssistedInject constructor(
configs.groupInfo.getName() to configs.groupKeys.makeSubAccount(memberSessionId)
}

val timestamp = SnodeAPI.nowWithOffset
val timestamp = snodeClock.currentTimeMills()
val signature = ED25519.sign(
ed25519PrivateKey = adminKey.data,
message = buildGroupInviteSignature(memberId, timestamp),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,6 @@ class JobQueue : JobDelegate {
while (isActive) {
when (val job = queue.receive()) {
is InviteContactsJob,
is NotifyPNServerJob,
is AttachmentUploadJob,
is MessageSendJob -> {
txQueue.send(job)
Expand Down Expand Up @@ -219,7 +218,6 @@ class JobQueue : JobDelegate {
AttachmentUploadJob.KEY,
AttachmentDownloadJob.KEY,
MessageSendJob.KEY,
NotifyPNServerJob.KEY,
OpenGroupDeleteJob.KEY,
InviteContactsJob.KEY,
)
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ class SessionJobManagerFactories @Inject constructor(
AttachmentDownloadJob.KEY to attachmentDownloadJobFactory,
AttachmentUploadJob.KEY to attachmentUploadJobFactory,
MessageSendJob.KEY to messageSendJobFactory,
NotifyPNServerJob.KEY to NotifyPNServerJob.DeserializeFactory(),
TrimThreadJob.KEY to trimThreadFactory,
OpenGroupDeleteJob.KEY to deleteJobFactory,
InviteContactsJob.KEY to inviteContactsJobFactory,
Expand Down
Loading