mirror of
https://github.com/oxen-io/session-android.git
synced 2024-12-24 00:37:47 +00:00
commit
67d53fc3f5
@ -38,7 +38,10 @@ dependencies {
|
||||
implementation 'androidx.core:core-ktx:1.3.2'
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
implementation 'com.google.android.material:material:1.2.1'
|
||||
implementation "com.google.protobuf:protobuf-java:$protobufVersion"
|
||||
testImplementation 'junit:junit:4.+'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
|
||||
|
||||
implementation project(":libsignal")
|
||||
}
|
@ -1,5 +1,12 @@
|
||||
package org.session.messaging.messages
|
||||
package org.session.libsession.messaging.messages
|
||||
|
||||
enum class Destination {
|
||||
sealed class Destination {
|
||||
|
||||
class Contact(val publicKey: String)
|
||||
class ClosedGroup(val groupPublicKey: String)
|
||||
class OpenGroup(val channel: Long, val server: String)
|
||||
|
||||
companion object {
|
||||
//TODO need to implement the equivalent to TSThread and then implement from(...)
|
||||
}
|
||||
}
|
@ -1,5 +1,30 @@
|
||||
package org.session.messaging.messages
|
||||
package org.session.libsession.messaging.messages
|
||||
|
||||
open class Message {
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||
|
||||
abstract class Message {
|
||||
|
||||
var id: String? = null
|
||||
var threadID: String? = null
|
||||
var sentTimestamp: Long? = null
|
||||
var receivedTimestamp: Long? = null
|
||||
var recipient: String? = null
|
||||
var sender: String? = null
|
||||
var groupPublicKey: String? = null
|
||||
var openGroupServerMessageID: Long? = null
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
val ttl = 2 * 24 * 60 * 60 * 1000 //TODO not sure about that declaration
|
||||
}
|
||||
|
||||
// validation
|
||||
open fun isValid(): Boolean {
|
||||
sentTimestamp = if (sentTimestamp!! > 0) sentTimestamp else return false
|
||||
receivedTimestamp = if (receivedTimestamp!! > 0) receivedTimestamp else return false
|
||||
return sender != null && recipient != null
|
||||
}
|
||||
|
||||
abstract fun toProto(): SignalServiceProtos.Content?
|
||||
|
||||
}
|
@ -1,4 +1,154 @@
|
||||
package org.session.messaging.messages.control
|
||||
package org.session.libsession.messaging.messages.control
|
||||
|
||||
class ClosedGroupUpdate : ControlMessage() {
|
||||
import com.google.protobuf.ByteString
|
||||
import org.session.libsignal.libsignal.logging.Log
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||
import org.session.libsignal.service.loki.protocol.closedgroups.ClosedGroupSenderKey
|
||||
|
||||
class ClosedGroupUpdate() : ControlMessage() {
|
||||
|
||||
var kind: Kind? = null
|
||||
|
||||
// Kind enum
|
||||
sealed class Kind {
|
||||
class New(val groupPublicKey: ByteArray, val name: String, val groupPrivateKey: ByteArray, val senderKeys: Collection<org.session.libsignal.service.loki.protocol.closedgroups.ClosedGroupSenderKey>, val members: Collection<ByteArray>, val admins: Collection<ByteArray>) : Kind()
|
||||
class Info(val groupPublicKey: ByteArray, val name: String, val senderKeys: Collection<org.session.libsignal.service.loki.protocol.closedgroups.ClosedGroupSenderKey>, val members: Collection<ByteArray>, val admins: Collection<ByteArray>) : Kind()
|
||||
class SenderKeyRequest(val groupPublicKey: ByteArray) : Kind()
|
||||
class SenderKey(val groupPublicKey: ByteArray, val senderKey: org.session.libsignal.service.loki.protocol.closedgroups.ClosedGroupSenderKey) : Kind()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "ClosedGroupUpdate"
|
||||
|
||||
fun fromProto(proto: SignalServiceProtos.Content): ClosedGroupUpdate? {
|
||||
val closedGroupUpdateProto = proto.dataMessage?.closedGroupUpdate ?: return null
|
||||
val groupPublicKey = closedGroupUpdateProto.groupPublicKey
|
||||
var kind: Kind
|
||||
when(closedGroupUpdateProto.type) {
|
||||
SignalServiceProtos.ClosedGroupUpdate.Type.NEW -> {
|
||||
val name = closedGroupUpdateProto.name ?: return null
|
||||
val groupPrivateKey = closedGroupUpdateProto.groupPrivateKey ?: return null
|
||||
val senderKeys = closedGroupUpdateProto.senderKeysList.map { ClosedGroupSenderKey.fromProto(it) }
|
||||
kind = Kind.New(
|
||||
groupPublicKey = groupPublicKey.toByteArray(),
|
||||
name = name,
|
||||
groupPrivateKey = groupPrivateKey.toByteArray(),
|
||||
senderKeys = senderKeys,
|
||||
members = closedGroupUpdateProto.membersList.map { it.toByteArray() },
|
||||
admins = closedGroupUpdateProto.adminsList.map { it.toByteArray() }
|
||||
)
|
||||
}
|
||||
SignalServiceProtos.ClosedGroupUpdate.Type.INFO -> {
|
||||
val name = closedGroupUpdateProto.name ?: return null
|
||||
val senderKeys = closedGroupUpdateProto.senderKeysList.map { ClosedGroupSenderKey.fromProto(it) }
|
||||
kind = Kind.Info(
|
||||
groupPublicKey = groupPublicKey.toByteArray(),
|
||||
name = name,
|
||||
senderKeys = senderKeys,
|
||||
members = closedGroupUpdateProto.membersList.map { it.toByteArray() },
|
||||
admins = closedGroupUpdateProto.adminsList.map { it.toByteArray() }
|
||||
)
|
||||
}
|
||||
SignalServiceProtos.ClosedGroupUpdate.Type.SENDER_KEY_REQUEST -> {
|
||||
kind = Kind.SenderKeyRequest(groupPublicKey = groupPublicKey.toByteArray())
|
||||
}
|
||||
SignalServiceProtos.ClosedGroupUpdate.Type.SENDER_KEY -> {
|
||||
val senderKeyProto = closedGroupUpdateProto.senderKeysList?.first() ?: return null
|
||||
kind = Kind.SenderKey(
|
||||
groupPublicKey = groupPublicKey.toByteArray(),
|
||||
senderKey = ClosedGroupSenderKey.fromProto(senderKeyProto)
|
||||
)
|
||||
}
|
||||
}
|
||||
return ClosedGroupUpdate(kind)
|
||||
}
|
||||
}
|
||||
|
||||
// constructor
|
||||
internal constructor(kind: Kind?) : this() {
|
||||
this.kind = kind
|
||||
}
|
||||
|
||||
// validation
|
||||
override fun isValid(): Boolean {
|
||||
if (!super.isValid()) return false
|
||||
val kind = kind ?: return false
|
||||
when(kind) {
|
||||
is Kind.New -> {
|
||||
return !kind.groupPublicKey.isEmpty() && !kind.name.isEmpty() && !kind.groupPrivateKey.isEmpty() && !kind.members.isEmpty() && !kind.admins.isEmpty()
|
||||
}
|
||||
is Kind.Info -> {
|
||||
return !kind.groupPublicKey.isEmpty() && !kind.name.isEmpty() && !kind.members.isEmpty() && !kind.admins.isEmpty()
|
||||
}
|
||||
is Kind.SenderKeyRequest -> {
|
||||
return !kind.groupPublicKey.isEmpty()
|
||||
}
|
||||
is Kind.SenderKey -> {
|
||||
return !kind.groupPublicKey.isEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun toProto(): SignalServiceProtos.Content? {
|
||||
val kind = kind
|
||||
if (kind == null) {
|
||||
Log.w(TAG, "Couldn't construct closed group update proto from: $this")
|
||||
return null
|
||||
}
|
||||
try {
|
||||
val closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdate.Builder = SignalServiceProtos.ClosedGroupUpdate.newBuilder()
|
||||
when (kind) {
|
||||
is Kind.New -> {
|
||||
closedGroupUpdate.groupPublicKey = ByteString.copyFrom(kind.groupPublicKey)
|
||||
closedGroupUpdate.type = SignalServiceProtos.ClosedGroupUpdate.Type.NEW
|
||||
closedGroupUpdate.name = kind.name
|
||||
closedGroupUpdate.groupPrivateKey = ByteString.copyFrom(kind.groupPrivateKey)
|
||||
closedGroupUpdate.addAllSenderKeys(kind.senderKeys.map { it.toProto() })
|
||||
closedGroupUpdate.addAllMembers(kind.members.map { ByteString.copyFrom(it) })
|
||||
closedGroupUpdate.addAllAdmins(kind.admins.map { ByteString.copyFrom(it) })
|
||||
}
|
||||
is Kind.Info -> {
|
||||
closedGroupUpdate.groupPublicKey = ByteString.copyFrom(kind.groupPublicKey)
|
||||
closedGroupUpdate.type = SignalServiceProtos.ClosedGroupUpdate.Type.INFO
|
||||
closedGroupUpdate.name = kind.name
|
||||
closedGroupUpdate.addAllSenderKeys(kind.senderKeys.map { it.toProto() })
|
||||
closedGroupUpdate.addAllMembers(kind.members.map { ByteString.copyFrom(it) })
|
||||
closedGroupUpdate.addAllAdmins(kind.admins.map { ByteString.copyFrom(it) })
|
||||
}
|
||||
is Kind.SenderKeyRequest -> {
|
||||
closedGroupUpdate.groupPublicKey = ByteString.copyFrom(kind.groupPublicKey)
|
||||
closedGroupUpdate.type = SignalServiceProtos.ClosedGroupUpdate.Type.SENDER_KEY_REQUEST
|
||||
}
|
||||
is Kind.SenderKey -> {
|
||||
closedGroupUpdate.groupPublicKey = ByteString.copyFrom(kind.groupPublicKey)
|
||||
closedGroupUpdate.type = SignalServiceProtos.ClosedGroupUpdate.Type.SENDER_KEY
|
||||
closedGroupUpdate.addAllSenderKeys(listOf( kind.senderKey.toProto() ))
|
||||
}
|
||||
}
|
||||
val contentProto = SignalServiceProtos.Content.newBuilder()
|
||||
val dataMessageProto = SignalServiceProtos.DataMessage.newBuilder()
|
||||
dataMessageProto.closedGroupUpdate = closedGroupUpdate.build()
|
||||
contentProto.dataMessage = dataMessageProto.build()
|
||||
return contentProto.build()
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Couldn't construct closed group update proto from: $this")
|
||||
return null
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// extension functions to class ClosedGroupSenderKey
|
||||
|
||||
private fun ClosedGroupSenderKey.Companion.fromProto(proto: SignalServiceProtos.ClosedGroupUpdate.SenderKey): ClosedGroupSenderKey {
|
||||
return ClosedGroupSenderKey(chainKey = proto.chainKey.toByteArray(), keyIndex = proto.keyIndex, publicKey = proto.publicKey.toByteArray())
|
||||
}
|
||||
|
||||
private fun ClosedGroupSenderKey.toProto(): SignalServiceProtos.ClosedGroupUpdate.SenderKey {
|
||||
val proto = SignalServiceProtos.ClosedGroupUpdate.SenderKey.newBuilder()
|
||||
proto.chainKey = ByteString.copyFrom(chainKey)
|
||||
proto.keyIndex = keyIndex
|
||||
proto.publicKey = ByteString.copyFrom(publicKey)
|
||||
return proto.build()
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
package org.session.messaging.messages.control
|
||||
package org.session.libsession.messaging.messages.control
|
||||
|
||||
import org.session.messaging.messages.Message
|
||||
import org.session.libsession.messaging.messages.Message
|
||||
|
||||
open class ControlMessage : Message() {
|
||||
abstract class ControlMessage : Message() {
|
||||
}
|
@ -1,4 +1,51 @@
|
||||
package org.session.messaging.messages.control
|
||||
package org.session.libsession.messaging.messages.control
|
||||
|
||||
class ExpirationTimerUpdate : ControlMessage() {
|
||||
import org.session.libsignal.libsignal.logging.Log
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||
|
||||
class ExpirationTimerUpdate() : ControlMessage() {
|
||||
|
||||
var duration: Int? = 0
|
||||
|
||||
companion object {
|
||||
const val TAG = "ExpirationTimerUpdate"
|
||||
|
||||
fun fromProto(proto: SignalServiceProtos.Content): ExpirationTimerUpdate? {
|
||||
val dataMessageProto = proto.dataMessage ?: return null
|
||||
val isExpirationTimerUpdate = (dataMessageProto.flags and SignalServiceProtos.DataMessage.Flags.EXPIRATION_TIMER_UPDATE_VALUE) != 0 //TODO validate that 'and' operator equivalent to Swift '&'
|
||||
if (!isExpirationTimerUpdate) return null
|
||||
val duration = dataMessageProto.expireTimer
|
||||
return ExpirationTimerUpdate(duration)
|
||||
}
|
||||
}
|
||||
|
||||
//constructor
|
||||
internal constructor(duration: Int) : this() {
|
||||
this.duration = duration
|
||||
}
|
||||
|
||||
// validation
|
||||
override fun isValid(): Boolean {
|
||||
if (!super.isValid()) return false
|
||||
return duration != null
|
||||
}
|
||||
|
||||
override fun toProto(): SignalServiceProtos.Content? {
|
||||
val duration = duration
|
||||
if (duration == null) {
|
||||
Log.w(TAG, "Couldn't construct expiration timer update proto from: $this")
|
||||
return null
|
||||
}
|
||||
val dataMessageProto = SignalServiceProtos.DataMessage.newBuilder()
|
||||
dataMessageProto.flags = SignalServiceProtos.DataMessage.Flags.EXPIRATION_TIMER_UPDATE_VALUE
|
||||
dataMessageProto.expireTimer = duration
|
||||
val contentProto = SignalServiceProtos.Content.newBuilder()
|
||||
try {
|
||||
contentProto.dataMessage = dataMessageProto.build()
|
||||
return contentProto.build()
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Couldn't construct expiration timer update proto from: $this")
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,53 @@
|
||||
package org.session.messaging.messages.control
|
||||
package org.session.libsession.messaging.messages.control
|
||||
|
||||
class ReadReceipt : ControlMessage() {
|
||||
import org.session.libsignal.libsignal.logging.Log
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||
|
||||
class ReadReceipt() : ControlMessage() {
|
||||
|
||||
var timestamps: LongArray? = null
|
||||
|
||||
companion object {
|
||||
const val TAG = "ReadReceipt"
|
||||
|
||||
fun fromProto(proto: SignalServiceProtos.Content): ReadReceipt? {
|
||||
val receiptProto = proto.receiptMessage ?: return null
|
||||
if (receiptProto.type != SignalServiceProtos.ReceiptMessage.Type.READ) return null
|
||||
val timestamps = receiptProto.timestampList
|
||||
if (timestamps.isEmpty()) return null
|
||||
return ReadReceipt(timestamps = timestamps.toLongArray())
|
||||
}
|
||||
}
|
||||
|
||||
//constructor
|
||||
internal constructor(timestamps: LongArray?) : this() {
|
||||
this.timestamps = timestamps
|
||||
}
|
||||
|
||||
// validation
|
||||
override fun isValid(): Boolean {
|
||||
if (!super.isValid()) return false
|
||||
val timestamps = timestamps ?: return false
|
||||
if (timestamps.isNotEmpty()) { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
override fun toProto(): SignalServiceProtos.Content? {
|
||||
val timestamps = timestamps
|
||||
if (timestamps == null) {
|
||||
Log.w(ExpirationTimerUpdate.TAG, "Couldn't construct read receipt proto from: $this")
|
||||
return null
|
||||
}
|
||||
val receiptProto = SignalServiceProtos.ReceiptMessage.newBuilder()
|
||||
receiptProto.type = SignalServiceProtos.ReceiptMessage.Type.READ
|
||||
receiptProto.addAllTimestamp(timestamps.asIterable())
|
||||
val contentProto = SignalServiceProtos.Content.newBuilder()
|
||||
try {
|
||||
contentProto.receiptMessage = receiptProto.build()
|
||||
return contentProto.build()
|
||||
} catch (e: Exception) {
|
||||
Log.w(ExpirationTimerUpdate.TAG, "Couldn't construct read receipt proto from: $this")
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,75 @@
|
||||
package org.session.messaging.messages.control
|
||||
package org.session.libsession.messaging.messages.control
|
||||
|
||||
class TypingIndicator : ControlMessage() {
|
||||
import org.session.libsignal.libsignal.logging.Log
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||
|
||||
class TypingIndicator() : ControlMessage() {
|
||||
|
||||
companion object {
|
||||
const val TAG = "TypingIndicator"
|
||||
|
||||
//val ttl: 30 * 1000 //TODO
|
||||
|
||||
fun fromProto(proto: SignalServiceProtos.Content): TypingIndicator? {
|
||||
val typingIndicatorProto = proto.typingMessage ?: return null
|
||||
val kind = Kind.fromProto(typingIndicatorProto.action)
|
||||
return TypingIndicator(kind = kind)
|
||||
}
|
||||
}
|
||||
|
||||
// Kind enum
|
||||
enum class Kind {
|
||||
STARTED,
|
||||
STOPPED,
|
||||
;
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun fromProto(proto: SignalServiceProtos.TypingMessage.Action): Kind =
|
||||
when (proto) {
|
||||
SignalServiceProtos.TypingMessage.Action.STARTED -> STARTED
|
||||
SignalServiceProtos.TypingMessage.Action.STOPPED -> STOPPED
|
||||
}
|
||||
}
|
||||
|
||||
fun toProto(): SignalServiceProtos.TypingMessage.Action {
|
||||
when (this) {
|
||||
STARTED -> return SignalServiceProtos.TypingMessage.Action.STARTED
|
||||
STOPPED -> return SignalServiceProtos.TypingMessage.Action.STOPPED
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var kind: Kind? = null
|
||||
|
||||
//constructor
|
||||
internal constructor(kind: Kind) : this() {
|
||||
this.kind = kind
|
||||
}
|
||||
|
||||
// validation
|
||||
override fun isValid(): Boolean {
|
||||
if (!super.isValid()) return false
|
||||
return kind != null
|
||||
}
|
||||
|
||||
override fun toProto(): SignalServiceProtos.Content? {
|
||||
val timestamp = sentTimestamp
|
||||
val kind = kind
|
||||
if (timestamp == null || kind == null) {
|
||||
Log.w(TAG, "Couldn't construct typing indicator proto from: $this")
|
||||
return null
|
||||
}
|
||||
val typingIndicatorProto = SignalServiceProtos.TypingMessage.newBuilder()
|
||||
typingIndicatorProto.timestamp = timestamp
|
||||
typingIndicatorProto.action = kind.toProto()
|
||||
val contentProto = SignalServiceProtos.Content.newBuilder()
|
||||
try {
|
||||
contentProto.typingMessage = typingIndicatorProto.build()
|
||||
return contentProto.build()
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Couldn't construct typing indicator proto from: $this")
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,37 @@
|
||||
package org.session.messaging.messages.control.unused
|
||||
package org.session.libsession.messaging.messages.control.unused
|
||||
|
||||
import org.session.messaging.messages.control.ControlMessage
|
||||
import com.google.protobuf.ByteString
|
||||
import org.session.libsession.messaging.messages.control.ControlMessage
|
||||
import org.session.libsession.messaging.messages.control.TypingIndicator
|
||||
import org.session.libsignal.libsignal.logging.Log
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||
import java.security.SecureRandom
|
||||
|
||||
class NullMessage : ControlMessage() {
|
||||
class NullMessage() : ControlMessage() {
|
||||
|
||||
|
||||
companion object {
|
||||
const val TAG = "NullMessage"
|
||||
|
||||
fun fromProto(proto: SignalServiceProtos.Content): NullMessage? {
|
||||
if (proto.nullMessage == null) return null
|
||||
return NullMessage()
|
||||
}
|
||||
}
|
||||
|
||||
override fun toProto(): SignalServiceProtos.Content? {
|
||||
val nullMessageProto = SignalServiceProtos.NullMessage.newBuilder()
|
||||
val sr = SecureRandom()
|
||||
val paddingSize = sr.nextInt(512)
|
||||
val padding = ByteArray(paddingSize)
|
||||
nullMessageProto.padding = ByteString.copyFrom(padding)
|
||||
val contentProto = SignalServiceProtos.Content.newBuilder()
|
||||
try {
|
||||
contentProto.nullMessage = nullMessageProto.build()
|
||||
return contentProto.build()
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Couldn't construct null message proto from: $this")
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,81 @@
|
||||
package org.session.messaging.messages.control.unused
|
||||
package org.session.libsession.messaging.messages.control.unused
|
||||
|
||||
import org.session.messaging.messages.control.ControlMessage
|
||||
import com.google.protobuf.ByteString
|
||||
import org.session.libsession.messaging.messages.control.ControlMessage
|
||||
import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate
|
||||
import org.session.libsession.messaging.messages.control.TypingIndicator
|
||||
import org.session.libsignal.libsignal.logging.Log
|
||||
import org.session.libsignal.libsignal.state.PreKeyBundle
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||
import java.security.SecureRandom
|
||||
|
||||
class SessionRequest : ControlMessage() {
|
||||
class SessionRequest() : ControlMessage() {
|
||||
|
||||
var preKeyBundle: PreKeyBundle? = null
|
||||
|
||||
companion object {
|
||||
const val TAG = "SessionRequest"
|
||||
|
||||
fun fromProto(proto: SignalServiceProtos.Content): SessionRequest? {
|
||||
if (proto.nullMessage == null) return null
|
||||
val preKeyBundleProto = proto.preKeyBundleMessage ?: return null
|
||||
val registrationID: Int = 0
|
||||
//TODO looks like database stuff here
|
||||
/*iOS code: Configuration.shared.storage.with { transaction in
|
||||
registrationID = Configuration.shared.storage.getOrGenerateRegistrationID(using: transaction)
|
||||
}*/
|
||||
val preKeyBundle = PreKeyBundle(
|
||||
registrationID,
|
||||
1,
|
||||
preKeyBundleProto.preKeyId,
|
||||
null, //TODO preKeyBundleProto.preKey,
|
||||
0, //TODO preKeyBundleProto.signedKey,
|
||||
null, //TODO preKeyBundleProto.signedKeyId,
|
||||
preKeyBundleProto.signature.toByteArray(),
|
||||
null, //TODO preKeyBundleProto.identityKey
|
||||
)
|
||||
return SessionRequest(preKeyBundle)
|
||||
}
|
||||
}
|
||||
|
||||
//constructor
|
||||
internal constructor(preKeyBundle: PreKeyBundle) : this() {
|
||||
this.preKeyBundle = preKeyBundle
|
||||
}
|
||||
|
||||
// validation
|
||||
override fun isValid(): Boolean {
|
||||
if (!super.isValid()) return false
|
||||
return preKeyBundle != null
|
||||
}
|
||||
|
||||
override fun toProto(): SignalServiceProtos.Content? {
|
||||
val preKeyBundle = preKeyBundle
|
||||
if (preKeyBundle == null) {
|
||||
Log.w(TAG, "Couldn't construct session request proto from: $this")
|
||||
return null
|
||||
}
|
||||
val nullMessageProto = SignalServiceProtos.NullMessage.newBuilder()
|
||||
val sr = SecureRandom()
|
||||
val paddingSize = sr.nextInt(512)
|
||||
val padding = ByteArray(paddingSize)
|
||||
nullMessageProto.padding = ByteString.copyFrom(padding)
|
||||
val preKeyBundleProto = SignalServiceProtos.PreKeyBundleMessage.newBuilder()
|
||||
//TODO preKeyBundleProto.identityKey = preKeyBundle.identityKey
|
||||
preKeyBundleProto.deviceId = preKeyBundle.deviceId
|
||||
preKeyBundleProto.preKeyId = preKeyBundle.preKeyId
|
||||
//TODO preKeyBundleProto.preKey = preKeyBundle.preKeyPublic
|
||||
preKeyBundleProto.signedKeyId = preKeyBundle.signedPreKeyId
|
||||
//TODO preKeyBundleProto.signedKey = preKeyBundle.signedPreKeyPublic
|
||||
preKeyBundleProto.signature = ByteString.copyFrom(preKeyBundle.signedPreKeySignature)
|
||||
val contentProto = SignalServiceProtos.Content.newBuilder()
|
||||
try {
|
||||
contentProto.nullMessage = nullMessageProto.build()
|
||||
contentProto.preKeyBundleMessage = preKeyBundleProto.build()
|
||||
return contentProto.build()
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Couldn't construct session request proto from: $this")
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
package org.session.libsession.messaging.messages.visible
|
||||
|
||||
import android.util.Size
|
||||
import android.webkit.MimeTypeMap
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||
import java.io.File
|
||||
import java.net.URL
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
class Attachment : VisibleMessage<SignalServiceProtos.AttachmentPointer?>() {
|
||||
|
||||
var fileName: String? = null
|
||||
var contentType: String? = null
|
||||
var key: ByteArray? = null
|
||||
var digest: ByteArray? = null
|
||||
var kind: Kind? = null
|
||||
var caption: String? = null
|
||||
var size: Size? = null
|
||||
var sizeInBytes: Int? = 0
|
||||
var url: String? = null
|
||||
|
||||
companion object {
|
||||
fun fromProto(proto: SignalServiceProtos.AttachmentPointer): Attachment? {
|
||||
val result = Attachment()
|
||||
result.fileName = proto.fileName
|
||||
fun inferContentType(): String {
|
||||
val fileName = result.fileName ?: return "application/octet-stream" //TODO find equivalent to OWSMimeTypeApplicationOctetStream
|
||||
val fileExtension = File(fileName).extension
|
||||
val mimeTypeMap = MimeTypeMap.getSingleton()
|
||||
return mimeTypeMap.getMimeTypeFromExtension(fileExtension) ?: "application/octet-stream" //TODO check that it's correct
|
||||
}
|
||||
result.contentType = proto.contentType ?: inferContentType()
|
||||
result.key = proto.key.toByteArray()
|
||||
result.digest = proto.digest.toByteArray()
|
||||
val kind: Kind
|
||||
if (proto.hasFlags() && (proto.flags and SignalServiceProtos.AttachmentPointer.Flags.VOICE_MESSAGE_VALUE) > 0) { //TODO validate that 'and' operator = swift '&'
|
||||
kind = Kind.VOICEMESSAGE
|
||||
} else {
|
||||
kind = Kind.GENERIC
|
||||
}
|
||||
result.kind = kind
|
||||
result.caption = if (proto.hasCaption()) proto.caption else null
|
||||
val size: Size
|
||||
if (proto.hasWidth() && proto.width > 0 && proto.hasHeight() && proto.height > 0) {
|
||||
size = Size(proto.width, proto.height)
|
||||
} else {
|
||||
size = Size(0,0) //TODO check that it's equivalent to swift: CGSize.zero
|
||||
}
|
||||
result.size = size
|
||||
result.sizeInBytes = if (proto.size > 0) proto.size else null
|
||||
result. url = proto.url
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
enum class Kind {
|
||||
VOICEMESSAGE,
|
||||
GENERIC
|
||||
}
|
||||
|
||||
// validation
|
||||
override fun isValid(): Boolean {
|
||||
if (!super.isValid()) return false
|
||||
// key and digest can be nil for open group attachments
|
||||
return (contentType != null && kind != null && size != null && sizeInBytes != null && url != null)
|
||||
}
|
||||
|
||||
override fun toProto(transaction: String): SignalServiceProtos.AttachmentPointer? {
|
||||
TODO("Not implemented")
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
package org.session.libsession.messaging.messages.visible
|
||||
|
||||
import org.session.libsignal.libsignal.logging.Log
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||
|
||||
class BaseVisibleMessage() : VisibleMessage<SignalServiceProtos.Content?>() {
|
||||
|
||||
var text: String? = null
|
||||
var attachmentIDs = ArrayList<String>()
|
||||
var quote: Quote? = null
|
||||
var linkPreview: LinkPreview? = null
|
||||
var contact: Contact? = null
|
||||
var profile: Profile? = null
|
||||
|
||||
companion object {
|
||||
const val TAG = "BaseVisibleMessage"
|
||||
|
||||
fun fromProto(proto: SignalServiceProtos.Content): BaseVisibleMessage? {
|
||||
val dataMessage = proto.dataMessage ?: return null
|
||||
val result = BaseVisibleMessage()
|
||||
result.text = dataMessage.body
|
||||
// Attachments are handled in MessageReceiver
|
||||
val quoteProto = dataMessage.quote
|
||||
quoteProto?.let {
|
||||
val quote = Quote.fromProto(quoteProto)
|
||||
quote?.let { result.quote = quote }
|
||||
}
|
||||
val linkPreviewProto = dataMessage.previewList.first()
|
||||
linkPreviewProto?.let {
|
||||
val linkPreview = LinkPreview.fromProto(linkPreviewProto)
|
||||
linkPreview?.let { result.linkPreview = linkPreview }
|
||||
}
|
||||
// TODO Contact
|
||||
val profile = Profile.fromProto(dataMessage)
|
||||
profile?.let { result.profile = profile }
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
// validation
|
||||
override fun isValid(): Boolean {
|
||||
if (!super.isValid()) return false
|
||||
if (attachmentIDs.isNotEmpty()) return true
|
||||
val text = text?.trim() ?: return false
|
||||
if (text.isNotEmpty()) return true
|
||||
return false
|
||||
}
|
||||
|
||||
override fun toProto(transaction: String): SignalServiceProtos.Content? {
|
||||
val proto = SignalServiceProtos.Content.newBuilder()
|
||||
var attachmentIDs = this.attachmentIDs
|
||||
val dataMessage: SignalServiceProtos.DataMessage.Builder
|
||||
// Profile
|
||||
val profile = profile
|
||||
val profileProto = profile?.toSSProto()
|
||||
if (profileProto != null) {
|
||||
dataMessage = profileProto.toBuilder()
|
||||
} else {
|
||||
dataMessage = SignalServiceProtos.DataMessage.newBuilder()
|
||||
}
|
||||
// Text
|
||||
text?.let { dataMessage.body = text }
|
||||
// Quote
|
||||
val quotedAttachmentID = quote?.attachmentID
|
||||
quotedAttachmentID?.let {
|
||||
val index = attachmentIDs.indexOf(quotedAttachmentID)
|
||||
if (index >= 0) { attachmentIDs.removeAt(index) }
|
||||
}
|
||||
val quote = quote
|
||||
quote?.let {
|
||||
val quoteProto = quote.toProto(transaction)
|
||||
if (quoteProto != null) dataMessage.quote = quoteProto
|
||||
}
|
||||
//Link preview
|
||||
val linkPreviewAttachmentID = linkPreview?.attachmentID
|
||||
linkPreviewAttachmentID?.let {
|
||||
val index = attachmentIDs.indexOf(quotedAttachmentID)
|
||||
if (index >= 0) { attachmentIDs.removeAt(index) }
|
||||
}
|
||||
val linkPreview = linkPreview
|
||||
linkPreview?.let {
|
||||
val linkPreviewProto = linkPreview.toProto(transaction)
|
||||
linkPreviewProto?.let {
|
||||
dataMessage.addAllPreview(listOf(linkPreviewProto))
|
||||
}
|
||||
}
|
||||
//Attachments
|
||||
// TODO I'm blocking on that one...
|
||||
//swift: let attachments = attachmentIDs.compactMap { TSAttachmentStream.fetch(uniqueId: $0, transaction: transaction) }
|
||||
|
||||
// TODO Contact
|
||||
// Build
|
||||
try {
|
||||
proto.dataMessage = dataMessage.build()
|
||||
return proto.build()
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Couldn't construct visible message proto from: $this")
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,4 +1,16 @@
|
||||
package org.session.messaging.messages.visible
|
||||
package org.session.libsession.messaging.messages.visible
|
||||
|
||||
internal class Contact {
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||
|
||||
class Contact : VisibleMessage<SignalServiceProtos.DataMessage.Contact?>() {
|
||||
|
||||
companion object {
|
||||
fun fromProto(proto: SignalServiceProtos.Content): Contact? {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
|
||||
override fun toProto(transaction: String): SignalServiceProtos.DataMessage.Contact? {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
@ -1,4 +1,58 @@
|
||||
package org.session.messaging.messages.visible
|
||||
package org.session.libsession.messaging.messages.visible
|
||||
|
||||
internal class LinkPreview {
|
||||
import org.session.libsession.messaging.messages.control.TypingIndicator
|
||||
import org.session.libsignal.libsignal.logging.Log
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||
|
||||
class LinkPreview() : VisibleMessage<SignalServiceProtos.DataMessage.Preview?>(){
|
||||
|
||||
var title: String? = null
|
||||
var url: String? = null
|
||||
var attachmentID: String? = null
|
||||
|
||||
companion object {
|
||||
const val TAG = "LinkPreview"
|
||||
|
||||
fun fromProto(proto: SignalServiceProtos.DataMessage.Preview): LinkPreview? {
|
||||
val title = proto.title
|
||||
val url = proto.url
|
||||
return LinkPreview(title, url, null)
|
||||
}
|
||||
}
|
||||
|
||||
//constructor
|
||||
internal constructor(title: String?, url: String, attachmentID: String?) : this() {
|
||||
this.title = title
|
||||
this.url = url
|
||||
this.attachmentID = attachmentID
|
||||
}
|
||||
|
||||
|
||||
// validation
|
||||
override fun isValid(): Boolean {
|
||||
if (!super.isValid()) return false
|
||||
return (title != null && url != null && attachmentID != null)
|
||||
}
|
||||
|
||||
override fun toProto(transaction: String): SignalServiceProtos.DataMessage.Preview? {
|
||||
val url = url
|
||||
if (url == null) {
|
||||
Log.w(TAG, "Couldn't construct link preview proto from: $this")
|
||||
return null
|
||||
}
|
||||
val linkPreviewProto = SignalServiceProtos.DataMessage.Preview.newBuilder()
|
||||
linkPreviewProto.url = url
|
||||
title?.let { linkPreviewProto.title = title }
|
||||
val attachmentID = attachmentID
|
||||
attachmentID?.let {
|
||||
//TODO database stuff
|
||||
}
|
||||
// Build
|
||||
try {
|
||||
return linkPreviewProto.build()
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Couldn't construct link preview proto from: $this")
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,64 @@
|
||||
package org.session.messaging.messages.visible
|
||||
package org.session.libsession.messaging.messages.visible
|
||||
|
||||
internal class Profile {
|
||||
import com.google.protobuf.ByteString
|
||||
import org.session.libsignal.libsignal.logging.Log
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||
|
||||
class Profile() : VisibleMessage<SignalServiceProtos.DataMessage?>() {
|
||||
|
||||
var displayName: String? = null
|
||||
var profileKey: ByteArray? = null
|
||||
var profilePictureURL: String? = null
|
||||
|
||||
companion object {
|
||||
const val TAG = "Profile"
|
||||
|
||||
fun fromProto(proto: SignalServiceProtos.DataMessage): Profile? {
|
||||
val profileProto = proto.profile ?: return null
|
||||
val displayName = profileProto.displayName ?: return null
|
||||
val profileKey = proto.profileKey
|
||||
val profilePictureURL = profileProto.profilePictureURL
|
||||
profileKey?.let {
|
||||
val profilePictureURL = profilePictureURL
|
||||
profilePictureURL?.let {
|
||||
return Profile(displayName = displayName, profileKey = profileKey.toByteArray(), profilePictureURL = profilePictureURL)
|
||||
}
|
||||
}
|
||||
return Profile(displayName)
|
||||
}
|
||||
}
|
||||
|
||||
//constructor
|
||||
internal constructor(displayName: String, profileKey: ByteArray? = null, profilePictureURL: String? = null) : this() {
|
||||
this.displayName = displayName
|
||||
this.profileKey = profileKey
|
||||
this.profilePictureURL = profilePictureURL
|
||||
}
|
||||
|
||||
fun toSSProto(): SignalServiceProtos.DataMessage? {
|
||||
return this.toProto("")
|
||||
}
|
||||
|
||||
override fun toProto(transaction: String): SignalServiceProtos.DataMessage? {
|
||||
val displayName = displayName
|
||||
if (displayName == null) {
|
||||
Log.w(TAG, "Couldn't construct link preview proto from: $this")
|
||||
return null
|
||||
}
|
||||
val dataMessageProto = SignalServiceProtos.DataMessage.newBuilder()
|
||||
val profileProto = SignalServiceProtos.LokiUserProfile.newBuilder()
|
||||
profileProto.displayName = displayName
|
||||
val profileKey = profileKey
|
||||
profileKey?.let { dataMessageProto.profileKey = ByteString.copyFrom(profileKey) }
|
||||
val profilePictureURL = profilePictureURL
|
||||
profilePictureURL?.let { profileProto.profilePictureURL = profilePictureURL }
|
||||
// Build
|
||||
try {
|
||||
dataMessageProto.profile = profileProto.build()
|
||||
return dataMessageProto.build()
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Couldn't construct profile proto from: $this")
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,72 @@
|
||||
package org.session.messaging.messages.visible
|
||||
package org.session.libsession.messaging.messages.visible
|
||||
|
||||
internal class Quote {
|
||||
import org.session.libsignal.libsignal.logging.Log
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||
|
||||
class Quote() : VisibleMessage<SignalServiceProtos.DataMessage.Quote?>() {
|
||||
|
||||
var timestamp: Long? = 0
|
||||
var publicKey: String? = null
|
||||
var text: String? = null
|
||||
var attachmentID: String? = null
|
||||
|
||||
companion object {
|
||||
const val TAG = "Quote"
|
||||
|
||||
fun fromProto(proto: SignalServiceProtos.DataMessage.Quote): Quote? {
|
||||
val timestamp = proto.id
|
||||
val publicKey = proto.author
|
||||
val text = proto.text
|
||||
return Quote(timestamp, publicKey, text, null)
|
||||
}
|
||||
}
|
||||
|
||||
//constructor
|
||||
internal constructor(timestamp: Long, publicKey: String, text: String?, attachmentID: String?) : this() {
|
||||
this.timestamp = timestamp
|
||||
this.publicKey = publicKey
|
||||
this.text = text
|
||||
this.attachmentID = attachmentID
|
||||
}
|
||||
|
||||
|
||||
// validation
|
||||
override fun isValid(): Boolean {
|
||||
if (!super.isValid()) return false
|
||||
return (timestamp != null && publicKey != null)
|
||||
}
|
||||
|
||||
override fun toProto(transaction: String): SignalServiceProtos.DataMessage.Quote? {
|
||||
val timestamp = timestamp
|
||||
val publicKey = publicKey
|
||||
if (timestamp == null || publicKey == null) {
|
||||
Log.w(TAG, "Couldn't construct quote proto from: $this")
|
||||
return null
|
||||
}
|
||||
val quoteProto = SignalServiceProtos.DataMessage.Quote.newBuilder()
|
||||
quoteProto.id = timestamp
|
||||
quoteProto.author = publicKey
|
||||
text?.let { quoteProto.text = text }
|
||||
addAttachmentsIfNeeded(quoteProto, transaction)
|
||||
// Build
|
||||
try {
|
||||
return quoteProto.build()
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Couldn't construct quote proto from: $this")
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun addAttachmentsIfNeeded(quoteProto: SignalServiceProtos.DataMessage.Quote.Builder, transaction: String) {
|
||||
val attachmentID = attachmentID ?: return
|
||||
//TODO databas stuff
|
||||
val quotedAttachmentProto = SignalServiceProtos.DataMessage.Quote.QuotedAttachment.newBuilder()
|
||||
//TODO more database related stuff
|
||||
//quotedAttachmentProto.contentType =
|
||||
try {
|
||||
quoteProto.addAttachments(quotedAttachmentProto.build())
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Couldn't construct quoted attachment proto from: $this")
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,15 @@
|
||||
package org.session.messaging.messages.visible
|
||||
package org.session.libsession.messaging.messages.visible
|
||||
|
||||
import org.session.messaging.messages.Message
|
||||
import org.session.libsession.messaging.messages.Message
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||
|
||||
class VisibleMessage : Message() {
|
||||
abstract class VisibleMessage<out T: com.google.protobuf.MessageOrBuilder?> : Message() {
|
||||
|
||||
abstract fun toProto(transaction: String): T
|
||||
|
||||
final override fun toProto(): SignalServiceProtos.Content? {
|
||||
//we don't need to implement this method in subclasses
|
||||
//TODO it just needs an equivalent to swift: preconditionFailure("Use toProto(using:) if that exists...
|
||||
TODO("Not implemented")
|
||||
}
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
package org.session.messaging.messages.visible.attachments
|
||||
|
||||
internal class Attachment {
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user