@ -2,18 +2,20 @@
package org.session.libsession.snode
import android.content.Context
import android.os.Build
import com.goterl.lazysodium.LazySodiumAndroid
import com.goterl.lazysodium.SodiumAndroid
import com.goterl.lazysodium.exceptions.SodiumException
import com.goterl.lazysodium.interfaces.AEAD
import com.goterl.lazysodium.interfaces.GenericHash
import com.goterl.lazysodium.interfaces.PwHash
import com.goterl.lazysodium.interfaces.SecretBox
import com.goterl.lazysodium.interfaces.Sign
import com.goterl.lazysodium.utils.Key
import nl.komponents.kovenant.*
import nl.komponents.kovenant.functional.bind
import nl.komponents.kovenant.functional.map
import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.utilities.MessageWrapper
import org.session.libsignal.crypto.getRandomElement
import org.session.libsignal.database.LokiAPIDatabaseProtocol
@ -22,6 +24,7 @@ import org.session.libsignal.utilities.*
import org.session.libsignal.utilities.Base64
import java.security.SecureRandom
import java.util.*
import kotlin.Pair
object SnodeAPI {
private val sodium by lazy { LazySodiumAndroid ( SodiumAndroid ( ) ) }
@ -53,7 +56,7 @@ object SnodeAPI {
private val targetSwarmSnodeCount = 2
private val useOnionRequests = true
internal val useTestnet = fals e
internal val useTestnet = tru e
// Error
internal sealed class Error ( val description : String ) : Exception ( description ) {
@ -102,7 +105,7 @@ object SnodeAPI {
" params " to mapOf (
" active_only " to true ,
" limit " to 256 ,
" fields " to mapOf ( " public_ip " to true , " storage_port " to true , " pubkey_x25519 " to true , " pubkey_ed25519 " to true )
" fields " to mapOf ( " public_ip " to true , " storage_port " to true , " pubkey_x25519 " to true , " pubkey_ed25519 " to true )
)
)
val deferred = deferred < Snode , Exception > ( )
@ -284,6 +287,13 @@ object SnodeAPI {
}
}
fun getNetworkTime ( snode : Snode ) : Promise < Pair < Snode , Long > , Exception > {
return invoke ( Snode . Method . Info , snode , null , emptyMap ( ) ) . map { rawResponse ->
val timestamp = rawResponse [ " timestamp " ] as ? Long ?: - 1
snode to timestamp
}
}
fun sendMessage ( message : SnodeMessage ) : Promise < Set < RawResponsePromise > , Exception > {
val destination = if ( useTestnet ) message . recipient . removing05PrefixIfNeeded ( ) else message . recipient
return retryIfNeeded ( maxRetryCount ) {
@ -321,6 +331,35 @@ object SnodeAPI {
}
}
fun deleteAllMessages ( context : Context ) : Promise < Map < String , Boolean > , Exception > {
return retryIfNeeded ( maxRetryCount ) {
// considerations: timestamp off in retrying logic, not being able to re-sign with latest timestamp? do we just not retry this as it will be synchronous
val module = MessagingModuleConfiguration . shared
val userED25519KeyPair = module . keyPairProvider ( ) ?: return @retryIfNeeded Promise . ofFail ( Error . Generic )
val userPublicKey = module . storage . getUserPublicKey ( ) ?: return @retryIfNeeded Promise . ofFail ( Error . Generic )
getSingleTargetSnode ( userPublicKey ) . bind { snode ->
retryIfNeeded ( maxRetryCount ) {
getNetworkTime ( snode ) . bind { ( _ , timestamp ) ->
val signature = ByteArray ( Sign . BYTES )
val data = ( Snode . Method . DeleteAll . rawValue + timestamp . toString ( ) ) . toByteArray ( )
sodium . cryptoSignDetached ( signature , data , data . size . toLong ( ) , userED25519KeyPair . secretKey . asBytes )
val deleteMessageParams = mapOf (
" pubkey " to userPublicKey ,
" pubkey_ed25519 " to userED25519KeyPair . publicKey . asHexString ,
" timestamp " to timestamp ,
" signature " to Base64 . encodeBytes ( signature )
)
invoke ( Snode . Method . DeleteAll , snode , userPublicKey , deleteMessageParams ) . map { rawResponse -> parseDeletions ( userPublicKey , timestamp , rawResponse ) } . fail { e ->
Log . e ( " Loki " , " Failed to clear data " , e )
}
}
}
}
}
}
fun parseRawMessagesResponse ( rawResponse : RawResponse , snode : Snode , publicKey : String ) : List < SignalServiceProtos . Envelope > {
val messages = rawResponse [ " messages " ] as ? List < * >
return if ( messages != null ) {
@ -378,6 +417,43 @@ object SnodeAPI {
}
}
}
@Suppress ( " UNCHECKED_CAST " )
private fun parseDeletions ( userPublicKey : String , timestamp : Long , rawResponse : RawResponse ) : Map < String , Boolean > {
val swarms = rawResponse [ " swarm " ] as ? Map < String , Any > ?: return mapOf ( )
val swarmResponsesValid = swarms . mapNotNull { ( nodePubKeyHex , rawMap ) ->
val map = rawMap as ? Map < String , Any > ?: return @mapNotNull null
/ * * Deletes all messages owned by the given pubkey on this SN and broadcasts the delete request to
* all other swarm members .
* Returns dict of :
* - " swarms " dict mapping ed25519 pubkeys ( in hex ) of swarm members to dict values of :
* - " failed " and other failure keys -- see `recursive` .
* - " deleted " : hashes of deleted messages .
* - " signature " : signature of ( PUBKEY _HEX || TIMESTAMP || DELETEDHASH [ 0 ] || .. . || DELETEDHASH [ N ] ) , signed
* by the node ' s ed25519 pubkey .
* /
// failure
val failed = map [ " failed " ] as ? Boolean ?: false
val code = map [ " code " ] as ? String
val reason = map [ " reason " ] as ? String
nodePubKeyHex to if ( failed ) {
Log . e ( " Loki " , " Failed to delete all from $nodePubKeyHex with error code $code and reason $reason " )
false
} else {
// success
val deleted = map [ " deleted " ] as List < String > // list of deleted hashes
val signature = map [ " signature " ] as String
val nodePubKey = Key . fromHexString ( nodePubKeyHex )
// signature of ( PUBKEY_HEX || TIMESTAMP || DELETEDHASH[0] || ... || DELETEDHASH[N] )
val message = ( userPublicKey + timestamp . toString ( ) + deleted . fold ( " " ) { a , v -> a + v } ) . toByteArray ( )
sodium . cryptoSignVerifyDetached ( Base64 . decode ( signature ) , message , message . size , nodePubKey . asBytes )
}
}
return swarmResponsesValid . toMap ( )
}
// endregion
// Error Handling