mirror of
https://github.com/oxen-io/session-android.git
synced 2025-08-11 18:07:41 +00:00
implement interfaces
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package org.session.libsession.messaging
|
||||
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import org.session.libsession.messaging.jobs.AttachmentUploadJob
|
||||
import org.session.libsession.messaging.jobs.Job
|
||||
@@ -18,12 +19,14 @@ import org.session.libsession.messaging.threads.recipients.Recipient.RecipientSe
|
||||
import org.session.libsignal.libsignal.ecc.ECKeyPair
|
||||
import org.session.libsignal.libsignal.ecc.ECPrivateKey
|
||||
import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer
|
||||
import org.session.libsignal.service.api.messages.SignalServiceGroup
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||
|
||||
interface StorageProtocol {
|
||||
|
||||
// General
|
||||
fun getUserPublicKey(): String?
|
||||
fun getUserKeyPair(): ECKeyPair?
|
||||
fun getUserKeyPair(): Pair<String, ByteArray>?
|
||||
fun getUserDisplayName(): String?
|
||||
fun getUserProfileKey(): ByteArray?
|
||||
fun getUserProfilePictureURL(): String?
|
||||
@@ -34,10 +37,6 @@ interface StorageProtocol {
|
||||
|
||||
fun getOrGenerateRegistrationID(): Int
|
||||
|
||||
// Shared Sender Keys
|
||||
fun getClosedGroupPrivateKey(publicKey: String): ECPrivateKey?
|
||||
fun isClosedGroup(publicKey: String): Boolean
|
||||
|
||||
// Jobs
|
||||
fun persist(job: Job)
|
||||
fun markJobAsSucceeded(job: Job)
|
||||
@@ -97,6 +96,11 @@ interface StorageProtocol {
|
||||
fun setActive(groupID: String, value: Boolean)
|
||||
fun removeMember(groupID: String, member: Address)
|
||||
fun updateMembers(groupID: String, members: List<Address>)
|
||||
// Closed Group
|
||||
fun insertIncomingInfoMessage(context: Context, senderPublicKey: String, groupID: String, type0: SignalServiceProtos.GroupContext.Type, type1: SignalServiceGroup.Type,
|
||||
name: String, members: Collection<String>, admins: Collection<String>)
|
||||
fun insertOutgoingInfoMessage(context: Context, groupID: String, type: SignalServiceProtos.GroupContext.Type, name: String,
|
||||
members: Collection<String>, admins: Collection<String>, threadID: Long)
|
||||
|
||||
// Settings
|
||||
fun setProfileSharing(address: Address, value: Boolean)
|
||||
|
@@ -1,150 +0,0 @@
|
||||
package org.session.libsession.messaging.messages.signal;
|
||||
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.PointerAttachment;
|
||||
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview;
|
||||
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel;
|
||||
import org.session.libsession.messaging.threads.Address;
|
||||
import org.session.libsession.utilities.GroupUtil;
|
||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
||||
import org.session.libsignal.service.api.messages.SignalServiceAttachment;
|
||||
import org.session.libsignal.service.api.messages.SignalServiceGroup;
|
||||
import org.session.libsession.messaging.sending_receiving.contacts.Contact;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class IncomingMediaMessage {
|
||||
|
||||
private final Address from;
|
||||
private final Address groupId;
|
||||
private final String body;
|
||||
private final boolean push;
|
||||
private final long sentTimeMillis;
|
||||
private final int subscriptionId;
|
||||
private final long expiresIn;
|
||||
private final boolean expirationUpdate;
|
||||
private final QuoteModel quote;
|
||||
private final boolean unidentified;
|
||||
|
||||
private final List<Attachment> attachments = new LinkedList<>();
|
||||
private final List<Contact> sharedContacts = new LinkedList<>();
|
||||
private final List<LinkPreview> linkPreviews = new LinkedList<>();
|
||||
|
||||
public IncomingMediaMessage(Address from,
|
||||
Optional<Address> groupId,
|
||||
String body,
|
||||
long sentTimeMillis,
|
||||
List<Attachment> attachments,
|
||||
int subscriptionId,
|
||||
long expiresIn,
|
||||
boolean expirationUpdate,
|
||||
boolean unidentified)
|
||||
{
|
||||
this.from = from;
|
||||
this.groupId = groupId.orNull();
|
||||
this.sentTimeMillis = sentTimeMillis;
|
||||
this.body = body;
|
||||
this.push = false;
|
||||
this.subscriptionId = subscriptionId;
|
||||
this.expiresIn = expiresIn;
|
||||
this.expirationUpdate = expirationUpdate;
|
||||
this.quote = null;
|
||||
this.unidentified = unidentified;
|
||||
|
||||
this.attachments.addAll(attachments);
|
||||
}
|
||||
|
||||
public IncomingMediaMessage(Address from,
|
||||
long sentTimeMillis,
|
||||
int subscriptionId,
|
||||
long expiresIn,
|
||||
boolean expirationUpdate,
|
||||
boolean unidentified,
|
||||
Optional<String> body,
|
||||
Optional<SignalServiceGroup> group,
|
||||
Optional<List<SignalServiceAttachment>> attachments,
|
||||
Optional<QuoteModel> quote,
|
||||
Optional<List<Contact>> sharedContacts,
|
||||
Optional<List<LinkPreview>> linkPreviews,
|
||||
Optional<Attachment> sticker)
|
||||
{
|
||||
this.push = true;
|
||||
this.from = from;
|
||||
this.sentTimeMillis = sentTimeMillis;
|
||||
this.body = body.orNull();
|
||||
this.subscriptionId = subscriptionId;
|
||||
this.expiresIn = expiresIn;
|
||||
this.expirationUpdate = expirationUpdate;
|
||||
this.quote = quote.orNull();
|
||||
this.unidentified = unidentified;
|
||||
|
||||
if (group.isPresent()) this.groupId = Address.Companion.fromSerialized(GroupUtil.INSTANCE.getEncodedGroupID(group.get().getGroupId()));
|
||||
else this.groupId = null;
|
||||
|
||||
this.attachments.addAll(PointerAttachment.forPointers(attachments));
|
||||
this.sharedContacts.addAll(sharedContacts.or(Collections.emptyList()));
|
||||
this.linkPreviews.addAll(linkPreviews.or(Collections.emptyList()));
|
||||
|
||||
if (sticker.isPresent()) {
|
||||
this.attachments.add(sticker.get());
|
||||
}
|
||||
}
|
||||
|
||||
public int getSubscriptionId() {
|
||||
return subscriptionId;
|
||||
}
|
||||
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public List<Attachment> getAttachments() {
|
||||
return attachments;
|
||||
}
|
||||
|
||||
public Address getFrom() {
|
||||
return from;
|
||||
}
|
||||
|
||||
public Address getGroupId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
public boolean isPushMessage() {
|
||||
return push;
|
||||
}
|
||||
|
||||
public boolean isExpirationUpdate() {
|
||||
return expirationUpdate;
|
||||
}
|
||||
|
||||
public long getSentTimeMillis() {
|
||||
return sentTimeMillis;
|
||||
}
|
||||
|
||||
public long getExpiresIn() {
|
||||
return expiresIn;
|
||||
}
|
||||
|
||||
public boolean isGroupMessage() {
|
||||
return groupId != null;
|
||||
}
|
||||
|
||||
public QuoteModel getQuote() {
|
||||
return quote;
|
||||
}
|
||||
|
||||
public List<Contact> getSharedContacts() {
|
||||
return sharedContacts;
|
||||
}
|
||||
|
||||
public List<LinkPreview> getLinkPreviews() {
|
||||
return linkPreviews;
|
||||
}
|
||||
|
||||
public boolean isUnidentified() {
|
||||
return unidentified;
|
||||
}
|
||||
}
|
@@ -196,7 +196,7 @@ object OpenGroupAPI: DotNetAPI() {
|
||||
val userKeyPair = storage.getUserKeyPair() ?: throw Error.Generic
|
||||
val userDisplayName = storage.getUserDisplayName() ?: throw Error.Generic
|
||||
Thread {
|
||||
val signedMessage = message.sign(userKeyPair.privateKey.serialize())
|
||||
val signedMessage = message.sign(userKeyPair.second)
|
||||
if (signedMessage == null) {
|
||||
deferred.reject(Error.SigningFailed)
|
||||
} else {
|
||||
@@ -212,7 +212,7 @@ object OpenGroupAPI: DotNetAPI() {
|
||||
format.timeZone = TimeZone.getTimeZone("GMT")
|
||||
val dateAsString = data["created_at"] as String
|
||||
val timestamp = format.parse(dateAsString).time
|
||||
@Suppress("NAME_SHADOWING") val message = OpenGroupMessage(serverID, userKeyPair.hexEncodedPublicKey, userDisplayName, text, timestamp, openGroupMessageType, message.quote, message.attachments, null, signedMessage.signature, timestamp)
|
||||
@Suppress("NAME_SHADOWING") val message = OpenGroupMessage(serverID, userKeyPair.first, userDisplayName, text, timestamp, openGroupMessageType, message.quote, message.attachments, null, signedMessage.signature, timestamp)
|
||||
message
|
||||
} catch (exception: Exception) {
|
||||
Log.d("Loki", "Couldn't parse message for open group with ID: $channel on server: $server.")
|
||||
|
@@ -3,6 +3,7 @@ package org.session.libsession.messaging.sending_receiving
|
||||
import org.session.libsession.messaging.MessagingConfiguration
|
||||
import org.session.libsession.messaging.sending_receiving.MessageReceiver.Error
|
||||
import org.session.libsession.utilities.AESGCM
|
||||
import org.session.libsession.utilities.GroupUtil
|
||||
|
||||
import org.whispersystems.curve25519.Curve25519
|
||||
|
||||
@@ -38,16 +39,16 @@ object MessageReceiverDecryption {
|
||||
internal fun decryptWithSharedSenderKeys(envelope: SignalServiceProtos.Envelope): Pair<ByteArray, String> {
|
||||
// 1. ) Check preconditions
|
||||
val groupPublicKey = envelope.source
|
||||
if (!MessagingConfiguration.shared.storage.isClosedGroup(groupPublicKey)) { throw Error.InvalidGroupPublicKey }
|
||||
if (!GroupUtil.isClosedGroup(groupPublicKey)) { throw Error.InvalidGroupPublicKey }
|
||||
val data = envelope.content
|
||||
if (data.count() == 0) { throw Error.NoData }
|
||||
val groupPrivateKey = MessagingConfiguration.shared.storage.getClosedGroupPrivateKey(groupPublicKey) ?: throw Error.NoGroupPrivateKey
|
||||
val groupPrivateKey = MessagingConfiguration.shared.sskDatabase.getClosedGroupPrivateKey(groupPublicKey) ?: throw Error.NoGroupPrivateKey
|
||||
// 2. ) Parse the wrapper
|
||||
val wrapper = SignalServiceProtos.ClosedGroupCiphertextMessageWrapper.parseFrom(data)
|
||||
val ivAndCiphertext = wrapper.ciphertext.toByteArray()
|
||||
val ephemeralPublicKey = wrapper.ephemeralPublicKey.toByteArray()
|
||||
// 3. ) Decrypt the data inside
|
||||
val ephemeralSharedSecret = Curve25519.getInstance(Curve25519.BEST).calculateAgreement(ephemeralPublicKey, groupPrivateKey.serialize())
|
||||
val ephemeralSharedSecret = Curve25519.getInstance(Curve25519.BEST).calculateAgreement(ephemeralPublicKey, groupPrivateKey.toByteArray())
|
||||
val mac = Mac.getInstance("HmacSHA256")
|
||||
mac.init(SecretKeySpec("LOKI".toByteArray(), "HmacSHA256"))
|
||||
val symmetricKey = mac.doFinal(ephemeralSharedSecret)
|
||||
|
@@ -27,6 +27,7 @@ import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.session.libsignal.libsignal.logging.Log
|
||||
import org.session.libsignal.libsignal.util.Hex
|
||||
import org.session.libsignal.libsignal.util.guava.Optional
|
||||
import org.session.libsignal.service.api.messages.SignalServiceGroup
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||
import org.session.libsignal.service.loki.protocol.closedgroups.ClosedGroupRatchet
|
||||
import org.session.libsignal.service.loki.protocol.closedgroups.ClosedGroupRatchetCollectionType
|
||||
@@ -38,7 +39,9 @@ import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
internal fun MessageReceiver.isBlock(publicKey: String): Boolean {
|
||||
return SSKEnvironment.shared.blockManager.isRecipientIdBlocked(publicKey)
|
||||
val context = MessagingConfiguration.shared.context
|
||||
val recipient = Recipient.from(context, Address.fromSerialized(publicKey), false)
|
||||
return recipient.isBlocked
|
||||
}
|
||||
|
||||
fun MessageReceiver.handle(message: Message, proto: SignalServiceProtos.Content, openGroupID: String?) {
|
||||
@@ -46,13 +49,14 @@ fun MessageReceiver.handle(message: Message, proto: SignalServiceProtos.Content,
|
||||
is ReadReceipt -> handleReadReceipt(message)
|
||||
is TypingIndicator -> handleTypingIndicator(message)
|
||||
is ClosedGroupUpdate -> handleClosedGroupUpdate(message)
|
||||
is ExpirationTimerUpdate -> handleExpirationTimerUpdate(message)
|
||||
is ExpirationTimerUpdate -> handleExpirationTimerUpdate(message, proto)
|
||||
is VisibleMessage -> handleVisibleMessage(message, proto, openGroupID)
|
||||
}
|
||||
}
|
||||
|
||||
private fun MessageReceiver.handleReadReceipt(message: ReadReceipt) {
|
||||
SSKEnvironment.shared.readReceiptManager.processReadReceipts(message.sender!!, message.timestamps!!.asList(), message.receivedTimestamp!!)
|
||||
val context = MessagingConfiguration.shared.context
|
||||
SSKEnvironment.shared.readReceiptManager.processReadReceipts(context, message.sender!!, message.timestamps!!.asList(), message.receivedTimestamp!!)
|
||||
}
|
||||
|
||||
private fun MessageReceiver.handleTypingIndicator(message: TypingIndicator) {
|
||||
@@ -83,20 +87,25 @@ fun MessageReceiver.cancelTypingIndicatorsIfNeeded(senderPublicKey: String) {
|
||||
SSKEnvironment.shared.typingIndicators.didReceiveIncomingMessage(context, threadID.toLong(), address, 1)
|
||||
}
|
||||
|
||||
private fun MessageReceiver.handleExpirationTimerUpdate(message: ExpirationTimerUpdate) {
|
||||
private fun MessageReceiver.handleExpirationTimerUpdate(message: ExpirationTimerUpdate, proto: SignalServiceProtos.Content) {
|
||||
if (message.duration!! > 0) {
|
||||
setExpirationTimer(message.duration!!, message.sender!!, message.groupPublicKey)
|
||||
setExpirationTimer(message, proto)
|
||||
} else {
|
||||
disableExpirationTimer(message.sender!!, message.groupPublicKey)
|
||||
disableExpirationTimer(message, proto)
|
||||
}
|
||||
}
|
||||
|
||||
fun MessageReceiver.setExpirationTimer(duration: Int, senderPublicKey: String, groupPublicKey: String?) {
|
||||
|
||||
fun MessageReceiver.setExpirationTimer(message: ExpirationTimerUpdate, proto: SignalServiceProtos.Content) {
|
||||
val id = message.id?.toLong()
|
||||
val duration = message.duration!!
|
||||
val senderPublicKey = message.sender!!
|
||||
SSKEnvironment.shared.messageExpirationManager.setExpirationTimer(id, duration, senderPublicKey, proto)
|
||||
}
|
||||
|
||||
fun MessageReceiver.disableExpirationTimer(senderPublicKey: String, groupPublicKey: String?) {
|
||||
|
||||
fun MessageReceiver.disableExpirationTimer(message: ExpirationTimerUpdate, proto: SignalServiceProtos.Content) {
|
||||
val id = message.id?.toLong()
|
||||
val senderPublicKey = message.sender!!
|
||||
SSKEnvironment.shared.messageExpirationManager.disableExpirationTimer(id, senderPublicKey, proto)
|
||||
}
|
||||
|
||||
fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, proto: SignalServiceProtos.Content, openGroupID: String?) {
|
||||
@@ -125,14 +134,14 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, proto: SignalS
|
||||
// Update the user's local name if the message came from their master device
|
||||
TextSecurePreferences.setProfileName(context, displayName)
|
||||
}
|
||||
profileManager.setDisplayName(recipient, displayName)
|
||||
profileManager.setDisplayName(context, recipient, displayName)
|
||||
if (recipient.profileKey == null || !MessageDigest.isEqual(recipient.profileKey, newProfile.profileKey)) {
|
||||
profileManager.setProfileKey(recipient, newProfile.profileKey!!)
|
||||
profileManager.setUnidentifiedAccessMode(recipient, Recipient.UnidentifiedAccessMode.UNKNOWN)
|
||||
profileManager.setProfileKey(context, recipient, newProfile.profileKey!!)
|
||||
profileManager.setUnidentifiedAccessMode(context, recipient, Recipient.UnidentifiedAccessMode.UNKNOWN)
|
||||
val url = newProfile.profilePictureURL.orEmpty()
|
||||
profileManager.setProfilePictureURL(recipient, url)
|
||||
profileManager.setProfilePictureURL(context, recipient, url)
|
||||
if (userPublicKey == message.sender) {
|
||||
profileManager.updateOpenGroupProfilePicturesIfNeeded()
|
||||
profileManager.updateOpenGroupProfilePicturesIfNeeded(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -192,6 +201,7 @@ private fun MessageReceiver.handleClosedGroupUpdate(message: ClosedGroupUpdate)
|
||||
}
|
||||
|
||||
private fun MessageReceiver.handleNewGroup(message: ClosedGroupUpdate) {
|
||||
val context = MessagingConfiguration.shared.context
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val sskDatabase = MessagingConfiguration.shared.sskDatabase
|
||||
if (message.kind !is ClosedGroupUpdate.Kind.New) { return }
|
||||
@@ -241,12 +251,11 @@ private fun MessageReceiver.handleNewGroup(message: ClosedGroupUpdate) {
|
||||
// Notify the PN server
|
||||
PushNotificationAPI.performOperation(PushNotificationAPI.ClosedGroupOperation.Subscribe, groupPublicKey, userPublicKey)
|
||||
// Notify the user
|
||||
/* TODO
|
||||
insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins)
|
||||
*/
|
||||
storage.insertIncomingInfoMessage(context, message.sender!!, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins)
|
||||
}
|
||||
|
||||
private fun MessageReceiver.handleGroupUpdate(message: ClosedGroupUpdate) {
|
||||
val context = MessagingConfiguration.shared.context
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val sskDatabase = MessagingConfiguration.shared.sskDatabase
|
||||
if (message.kind !is ClosedGroupUpdate.Kind.Info) { return }
|
||||
@@ -272,6 +281,7 @@ private fun MessageReceiver.handleGroupUpdate(message: ClosedGroupUpdate) {
|
||||
val oldMembers = group.members.map { it.serialize() }.toSet()
|
||||
val userPublicKey = storage.getUserPublicKey()!!
|
||||
val wasUserRemoved = !members.contains(userPublicKey)
|
||||
val wasSenderRemoved = !members.contains(message.sender!!)
|
||||
if (members.toSet().intersect(oldMembers) != oldMembers.toSet()) {
|
||||
val allOldRatchets = sskDatabase.getAllClosedGroupRatchets(groupPublicKey, ClosedGroupRatchetCollectionType.Current)
|
||||
for (pair in allOldRatchets) {
|
||||
@@ -304,9 +314,9 @@ private fun MessageReceiver.handleGroupUpdate(message: ClosedGroupUpdate) {
|
||||
storage.updateTitle(groupID, name)
|
||||
storage.updateMembers(groupID, members.map { Address.fromSerialized(it) })
|
||||
// Notify the user if needed
|
||||
val infoType = if (wasUserRemoved) SignalServiceProtos.GroupContext.Type.QUIT else SignalServiceProtos.GroupContext.Type.UPDATE
|
||||
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
|
||||
// TODO insertOutgoingInfoMessage(context, groupID, infoType, name, members, admins, threadID)
|
||||
val type0 = if (wasSenderRemoved) SignalServiceProtos.GroupContext.Type.QUIT else SignalServiceProtos.GroupContext.Type.UPDATE
|
||||
val type1 = if (wasSenderRemoved) SignalServiceGroup.Type.QUIT else SignalServiceGroup.Type.UPDATE
|
||||
storage.insertIncomingInfoMessage(context, message.sender!!, groupID, type0, type1, name, members, admins)
|
||||
}
|
||||
|
||||
private fun MessageReceiver.handleSenderKeyRequest(message: ClosedGroupUpdate) {
|
||||
|
@@ -12,6 +12,7 @@ import org.session.libsession.messaging.sending_receiving.notifications.PushNoti
|
||||
import org.session.libsession.messaging.sending_receiving.MessageSender.Error
|
||||
import org.session.libsession.messaging.threads.Address
|
||||
import org.session.libsession.utilities.GroupUtil
|
||||
import org.session.libsession.utilities.SSKEnvironment
|
||||
|
||||
|
||||
import org.session.libsignal.libsignal.ecc.Curve
|
||||
@@ -27,6 +28,7 @@ import java.util.*
|
||||
fun MessageSender.createClosedGroup(name: String, members: Collection<String>): Promise<String, Exception> {
|
||||
val deferred = deferred<String, Exception>()
|
||||
// Prepare
|
||||
val context = MessagingConfiguration.shared.context
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val members = members
|
||||
val userPublicKey = storage.getUserPublicKey()!!
|
||||
@@ -64,9 +66,7 @@ fun MessageSender.createClosedGroup(name: String, members: Collection<String>):
|
||||
PushNotificationAPI.performOperation(PushNotificationAPI.ClosedGroupOperation.Subscribe, groupPublicKey, userPublicKey)
|
||||
// Notify the user
|
||||
val threadID =storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
|
||||
/* TODO
|
||||
insertOutgoingInfoMessage(context, groupID, GroupContext.Type.UPDATE, name, members, admins, threadID)
|
||||
*/
|
||||
storage.insertOutgoingInfoMessage(context, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, name, members, admins, threadID)
|
||||
// Fulfill the promise
|
||||
deferred.resolve(groupPublicKey)
|
||||
// Return
|
||||
@@ -75,6 +75,7 @@ fun MessageSender.createClosedGroup(name: String, members: Collection<String>):
|
||||
|
||||
fun MessageSender.update(groupPublicKey: String, members: Collection<String>, name: String): Promise<Unit, Exception> {
|
||||
val deferred = deferred<Unit, Exception>()
|
||||
val context = MessagingConfiguration.shared.context
|
||||
val storage = MessagingConfiguration.shared.storage
|
||||
val userPublicKey = storage.getUserPublicKey()!!
|
||||
val sskDatabase = MessagingConfiguration.shared.sskDatabase
|
||||
@@ -197,9 +198,7 @@ fun MessageSender.update(groupPublicKey: String, members: Collection<String>, na
|
||||
// Notify the user
|
||||
val infoType = if (isUserLeaving) SignalServiceProtos.GroupContext.Type.QUIT else SignalServiceProtos.GroupContext.Type.UPDATE
|
||||
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
|
||||
/* TODO
|
||||
insertOutgoingInfoMessage(context, groupID, infoType, name, members, admins, threadID)
|
||||
*/
|
||||
storage.insertOutgoingInfoMessage(context, groupID, infoType, name, members, admins, threadID)
|
||||
deferred.resolve(Unit)
|
||||
return deferred.promise
|
||||
}
|
||||
|
@@ -27,7 +27,6 @@ fun MessageSender.send(message: Message, address: Address) {
|
||||
|
||||
fun MessageSender.sendNonDurably(message: VisibleMessage, attachments: List<SignalServiceAttachment>, address: Address): Promise<Unit, Exception> {
|
||||
prep(attachments, message)
|
||||
// TODO: Deal with attachments
|
||||
return sendNonDurably(message, address)
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,23 @@
|
||||
package org.session.libsession.messaging.sending_receiving.attachments
|
||||
|
||||
data class DatabaseAttachmentAudioExtras(
|
||||
val attachmentId: AttachmentId,
|
||||
/** Small amount of normalized audio byte samples to visualise the content (e.g. draw waveform). */
|
||||
val visualSamples: ByteArray,
|
||||
/** Duration of the audio track in milliseconds. May be [DURATION_UNDEFINED] when it is not known. */
|
||||
val durationMs: Long = DURATION_UNDEFINED) {
|
||||
|
||||
companion object {
|
||||
const val DURATION_UNDEFINED = -1L
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return other != null &&
|
||||
other is DatabaseAttachmentAudioExtras &&
|
||||
other.attachmentId == attachmentId
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return attachmentId.hashCode()
|
||||
}
|
||||
}
|
@@ -2,8 +2,10 @@ package org.session.libsession.messaging.sending_receiving.notifications
|
||||
|
||||
import android.content.Context
|
||||
import org.session.libsession.messaging.threads.recipients.Recipient
|
||||
import org.session.libsignal.service.api.messages.SignalServiceGroup
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||
|
||||
interface NotificationProtocol {
|
||||
interface MessageNotifier {
|
||||
fun setVisibleThread(threadId: Long)
|
||||
fun setLastDesktopActivityTimestamp(timestamp: Long)
|
||||
fun notifyMessageDeliveryFailed(context: Context?, recipient: Recipient?, threadId: Long)
|
@@ -786,7 +786,7 @@ public class Recipient implements RecipientModifiedListener {
|
||||
private final UnidentifiedAccessMode unidentifiedAccessMode;
|
||||
private final boolean forceSmsSelection;
|
||||
|
||||
RecipientSettings(boolean blocked, long muteUntil,
|
||||
public RecipientSettings(boolean blocked, long muteUntil,
|
||||
@NonNull VibrateState messageVibrateState,
|
||||
@NonNull VibrateState callVibrateState,
|
||||
@Nullable Uri messageRingtone,
|
||||
|
@@ -4,7 +4,7 @@ import android.content.Intent;
|
||||
import android.provider.ContactsContract;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.thoughtcrime.securesms.database.Address;
|
||||
import org.session.libsession.messaging.threads.Address;
|
||||
|
||||
import static android.content.Intent.ACTION_INSERT_OR_EDIT;
|
||||
|
||||
|
@@ -82,7 +82,7 @@ open class DotNetAPI {
|
||||
private fun requestNewAuthToken(server: String): Promise<String, Exception> {
|
||||
Log.d("Loki", "Requesting auth token for server: $server.")
|
||||
val userKeyPair = MessagingConfiguration.shared.storage.getUserKeyPair() ?: throw Error.Generic
|
||||
val parameters: Map<String, Any> = mapOf( "pubKey" to userKeyPair.hexEncodedPublicKey )
|
||||
val parameters: Map<String, Any> = mapOf( "pubKey" to userKeyPair.first )
|
||||
return execute(HTTPVerb.GET, server, "loki/v1/get_challenge", false, parameters).map(SnodeAPI.sharedContext) { json ->
|
||||
try {
|
||||
val base64EncodedChallenge = json["cipherText64"] as String
|
||||
@@ -95,7 +95,7 @@ open class DotNetAPI {
|
||||
serverPublicKey = Hex.fromStringCondensed(hexEncodedServerPublicKey.removing05PrefixIfNeeded())
|
||||
}
|
||||
// The challenge is prefixed by the 16 bit IV
|
||||
val tokenAsData = DiffieHellman.decrypt(challenge, serverPublicKey, userKeyPair.privateKey.serialize())
|
||||
val tokenAsData = DiffieHellman.decrypt(challenge, serverPublicKey, userKeyPair.second)
|
||||
val token = tokenAsData.toString(Charsets.UTF_8)
|
||||
token
|
||||
} catch (exception: Exception) {
|
||||
|
@@ -3,14 +3,17 @@ package org.session.libsession.utilities
|
||||
import java.util.regex.Pattern
|
||||
|
||||
object DelimiterUtil {
|
||||
@JvmStatic
|
||||
fun escape(value: String, delimiter: Char): String {
|
||||
return value.replace("" + delimiter, "\\" + delimiter)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun unescape(value: String, delimiter: Char): String {
|
||||
return value.replace("\\" + delimiter, "" + delimiter)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun split(value: String, delimiter: Char): Array<String> {
|
||||
val regex = "(?<!\\\\)" + Pattern.quote(delimiter.toString() + "")
|
||||
return value.split(regex).toTypedArray()
|
||||
|
@@ -1,16 +1,17 @@
|
||||
package org.session.libsession.utilities
|
||||
|
||||
import android.content.Context
|
||||
import org.session.libsession.messaging.sending_receiving.notifications.NotificationProtocol
|
||||
import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier
|
||||
import org.session.libsession.messaging.threads.Address
|
||||
import org.session.libsession.messaging.threads.recipients.Recipient
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||
|
||||
class SSKEnvironment(
|
||||
val typingIndicators: TypingIndicatorsProtocol,
|
||||
val blockManager: BlockingManagerProtocol,
|
||||
val readReceiptManager: ReadReceiptManagerProtocol,
|
||||
val profileManager: ProfileManagerProtocol,
|
||||
val notificationManager: NotificationProtocol
|
||||
val notificationManager: MessageNotifier,
|
||||
val messageExpirationManager: MessageExpirationManagerProtocol
|
||||
) {
|
||||
interface TypingIndicatorsProtocol {
|
||||
fun didReceiveTypingStartedMessage(context: Context, threadId: Long, author: Address, device: Int)
|
||||
@@ -18,32 +19,33 @@ class SSKEnvironment(
|
||||
fun didReceiveIncomingMessage(context: Context, threadId: Long, author: Address, device: Int)
|
||||
}
|
||||
|
||||
interface BlockingManagerProtocol {
|
||||
fun isRecipientIdBlocked(publicKey: String): Boolean
|
||||
}
|
||||
|
||||
interface ReadReceiptManagerProtocol {
|
||||
fun processReadReceipts(fromRecipientId: String, sentTimestamps: List<Long>, readTimestamp: Long)
|
||||
fun processReadReceipts(context: Context, fromRecipientId: String, sentTimestamps: List<Long>, readTimestamp: Long)
|
||||
}
|
||||
|
||||
interface ProfileManagerProtocol {
|
||||
fun setDisplayName(recipient: Recipient, displayName: String)
|
||||
fun setProfilePictureURL(recipient: Recipient, profilePictureURL: String)
|
||||
fun setProfileKey(recipient: Recipient, profileKey: ByteArray)
|
||||
fun setUnidentifiedAccessMode(recipient: Recipient, unidentifiedAccessMode: Recipient.UnidentifiedAccessMode)
|
||||
fun updateOpenGroupProfilePicturesIfNeeded()
|
||||
fun setDisplayName(context: Context, recipient: Recipient, displayName: String)
|
||||
fun setProfilePictureURL(context: Context, recipient: Recipient, profilePictureURL: String)
|
||||
fun setProfileKey(context: Context, recipient: Recipient, profileKey: ByteArray)
|
||||
fun setUnidentifiedAccessMode(context: Context, recipient: Recipient, unidentifiedAccessMode: Recipient.UnidentifiedAccessMode)
|
||||
fun updateOpenGroupProfilePicturesIfNeeded(context: Context)
|
||||
}
|
||||
|
||||
interface MessageExpirationManagerProtocol {
|
||||
fun setExpirationTimer(messageID: Long?, duration: Int, senderPublicKey: String, content: SignalServiceProtos.Content)
|
||||
fun disableExpirationTimer(messageID: Long?, senderPublicKey: String, content: SignalServiceProtos.Content)
|
||||
}
|
||||
|
||||
companion object {
|
||||
lateinit var shared: SSKEnvironment
|
||||
|
||||
fun configure(typingIndicators: TypingIndicatorsProtocol,
|
||||
blockManager: BlockingManagerProtocol,
|
||||
readReceiptManager: ReadReceiptManagerProtocol,
|
||||
profileManager: ProfileManagerProtocol,
|
||||
notificationManager: NotificationProtocol) {
|
||||
notificationManager: MessageNotifier,
|
||||
messageExpirationManager: MessageExpirationManagerProtocol) {
|
||||
if (Companion::shared.isInitialized) { return }
|
||||
shared = SSKEnvironment(typingIndicators, blockManager, readReceiptManager, profileManager, notificationManager)
|
||||
shared = SSKEnvironment(typingIndicators, readReceiptManager, profileManager, notificationManager, messageExpirationManager)
|
||||
}
|
||||
}
|
||||
}
|
@@ -334,6 +334,7 @@ object TextSecurePreferences {
|
||||
return getBooleanPreference(context, INCOGNITO_KEYBORAD_PREF, true)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun isReadReceiptsEnabled(context: Context): Boolean {
|
||||
return getBooleanPreference(context, READ_RECEIPTS_PREF, false)
|
||||
}
|
||||
|
@@ -1,8 +1,15 @@
|
||||
package org.session.libsession.utilities
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import org.session.libsession.messaging.threads.Address
|
||||
import org.session.libsignal.libsignal.logging.Log
|
||||
import java.io.Closeable
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.util.*
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.ThreadPoolExecutor
|
||||
@@ -16,6 +23,21 @@ object Util {
|
||||
return Looper.myLooper() == Looper.getMainLooper()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@Throws(IOException::class)
|
||||
fun copy(`in`: InputStream, out: OutputStream): Long {
|
||||
val buffer = ByteArray(8192)
|
||||
var read: Int
|
||||
var total: Long = 0
|
||||
while (`in`.read(buffer).also { read = it } != -1) {
|
||||
out.write(buffer, 0, read)
|
||||
total += read.toLong()
|
||||
}
|
||||
`in`.close()
|
||||
out.close()
|
||||
return total
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun uri(uri: String?): Uri? {
|
||||
return if (uri == null) null else Uri.parse(uri)
|
||||
@@ -27,6 +49,16 @@ object Util {
|
||||
else getHandler()?.post(runnable)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun runOnMainDelayed(runnable: Runnable, delayMillis: Long) {
|
||||
getHandler()?.postDelayed(runnable, delayMillis)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun cancelRunnableOnMain(runnable: Runnable) {
|
||||
getHandler()?.removeCallbacks(runnable)
|
||||
}
|
||||
|
||||
private fun getHandler(): Handler? {
|
||||
if (handler == null) {
|
||||
synchronized(Util::class.java) {
|
||||
@@ -77,4 +109,55 @@ object Util {
|
||||
return Arrays.hashCode(objects)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun <K, V> getOrDefault(map: Map<K, V>, key: K, defaultValue: V): V? {
|
||||
return if (map.containsKey(key)) map[key] else defaultValue
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@Throws(IOException::class)
|
||||
fun getStreamLength(`in`: InputStream): Long {
|
||||
val buffer = ByteArray(4096)
|
||||
var totalSize = 0
|
||||
var read: Int
|
||||
while (`in`.read(buffer).also { read = it } != -1) {
|
||||
totalSize += read
|
||||
}
|
||||
return totalSize.toLong()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun toIntExact(value: Long): Int {
|
||||
if (value.toInt().compareTo(value) != 0){
|
||||
throw ArithmeticException("integer overflow")
|
||||
}
|
||||
return value.toInt()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun close(closeable: Closeable) {
|
||||
try {
|
||||
closeable.close()
|
||||
} catch (e: IOException) {
|
||||
Log.w("Loki", e)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun isOwnNumber(context: Context, address: Address): Boolean {
|
||||
return if (address.isGroup) false else TextSecurePreferences.getLocalNumber(context) == address.serialize()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun <T> partition(list: List<T>, partitionSize: Int): List<List<T>> {
|
||||
val results: MutableList<List<T>> = LinkedList()
|
||||
var index = 0
|
||||
while (index < list.size) {
|
||||
val subListSize = Math.min(partitionSize, list.size - index)
|
||||
results.add(list.subList(index, index + subListSize))
|
||||
index += partitionSize
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user