Skip to content

Commit d022e4c

Browse files
Pro features in messages (#1759)
1 parent 7b972ab commit d022e4c

File tree

20 files changed

+235
-138
lines changed

20 files changed

+235
-138
lines changed

app/build.gradle.kts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,13 @@ android {
133133
buildConfigField("String", "USER_AGENT", "\"OWA\"")
134134
buildConfigField("int", "CANONICAL_VERSION_CODE", "$canonicalVersionCode")
135135

136+
buildConfigField("org.thoughtcrime.securesms.pro.ProBackendConfig", "PRO_BACKEND_DEV", """
137+
new org.thoughtcrime.securesms.pro.ProBackendConfig(
138+
"https://pro-backend-dev.getsession.org",
139+
"fc947730f49eb01427a66e050733294d9e520e545c7a27125a780634e0860a27"
140+
)
141+
""".trimIndent())
142+
136143
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
137144
testInstrumentationRunnerArguments["clearPackageData"] = "true"
138145
testOptions {
@@ -388,15 +395,9 @@ dependencies {
388395
implementation(libs.androidx.work.runtime.ktx)
389396
implementation(libs.rxbinding)
390397

391-
if (hasIncludedLibSessionUtilProject) {
392-
implementation(
393-
group = libs.libsession.util.android.get().group,
394-
name = libs.libsession.util.android.get().name,
395-
version = "dev-snapshot"
396-
)
397-
} else {
398-
implementation(libs.libsession.util.android)
399-
}
398+
// If libsession_util project is included into the build, use that, otherwise use the published version
399+
findProject(":libsession-util-android")?.let(::implementation)
400+
?: implementation(libs.libsession.util.android)
400401

401402
implementation(libs.kryo)
402403
testImplementation(libs.junit)

app/src/main/java/org/session/libsession/messaging/messages/ProfileUpdateHandler.kt

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package org.session.libsession.messaging.messages
22

33
import com.google.protobuf.ByteString
4+
import network.loki.messenger.libsession_util.pro.ProProof
5+
import network.loki.messenger.libsession_util.protocol.DecodedPro
46
import network.loki.messenger.libsession_util.protocol.ProProfileFeature
57
import network.loki.messenger.libsession_util.util.BaseCommunityInfo
68
import network.loki.messenger.libsession_util.util.BitSet
@@ -77,9 +79,7 @@ class ProfileUpdateHandler @Inject constructor(
7779
profilePicture = updates.pic
7880
}
7981

80-
if (updates.proFeatures != null) {
81-
proFeatures = updates.proFeatures
82-
}
82+
proFeatures = updates.proFeatures
8383

8484
if (updates.profileUpdateTime != null) {
8585
profileUpdatedEpochSeconds = updates.profileUpdateTime.toEpochSeconds()
@@ -115,9 +115,7 @@ class ProfileUpdateHandler @Inject constructor(
115115
c.name = updates.name
116116
}
117117

118-
if (updates.proFeatures != null) {
119-
c.proFeatures = updates.proFeatures
120-
}
118+
c.proFeatures = updates.proFeatures
121119

122120
if (updates.profileUpdateTime != null) {
123121
c.profileUpdatedEpochSeconds = updates.profileUpdateTime.toEpochSeconds()
@@ -154,16 +152,15 @@ class ProfileUpdateHandler @Inject constructor(
154152
r.copy(
155153
name = updates.name ?: r.name,
156154
profilePic = updates.pic ?: r.profilePic,
157-
blocksCommunityMessagesRequests = updates.blocksCommunityMessageRequests ?: r.blocksCommunityMessagesRequests,
155+
blocksCommunityMessagesRequests = updates.blocksCommunityMessageRequests,
158156
proData = updates.proProof?.let {
159157
RecipientSettings.ProData(
160158
info = it,
161-
features = updates.proFeatures ?: BitSet()
159+
features = updates.proFeatures,
162160
)
163161
},
164162
)
165-
} else if (updates.blocksCommunityMessageRequests != null &&
166-
r.blocksCommunityMessagesRequests != updates.blocksCommunityMessageRequests) {
163+
} else if (r.blocksCommunityMessagesRequests != updates.blocksCommunityMessageRequests) {
167164
r.copy(blocksCommunityMessagesRequests = updates.blocksCommunityMessageRequests)
168165
} else {
169166
r
@@ -190,22 +187,17 @@ class ProfileUpdateHandler @Inject constructor(
190187
}
191188

192189
class Updates private constructor(
193-
// Name to update, must be non-blank if provided.
194-
val name: String? = null,
195-
val pic: UserPic? = null,
196-
val proProof: Conversation.ProProofInfo? = null,
197-
val proFeatures: BitSet<ProProfileFeature>? = null,
198-
val blocksCommunityMessageRequests: Boolean? = null,
190+
val name: String?,
191+
val pic: UserPic?,
192+
val proProof: Conversation.ProProofInfo?,
193+
val proFeatures: BitSet<ProProfileFeature>,
194+
val blocksCommunityMessageRequests: Boolean,
199195
val profileUpdateTime: Instant?,
200196
) {
201-
init {
202-
check(name == null || name.isNotBlank()) {
203-
"Name must be non-blank if provided"
204-
}
205-
}
206-
207197
companion object {
208-
fun create(content: SessionProtos.Content): Updates? {
198+
fun create(content: SessionProtos.Content,
199+
nowMills: Long,
200+
pro: DecodedPro?): Updates? {
209201
val profile: SessionProtos.LokiProfile
210202
val profilePicKey: ByteString?
211203

@@ -248,18 +240,31 @@ class ProfileUpdateHandler @Inject constructor(
248240
content.dataMessage.hasBlocksCommunityMessageRequests()) {
249241
content.dataMessage.blocksCommunityMessageRequests
250242
} else {
251-
null
243+
true
252244
}
253245

254-
if (name == null && pic == null && blocksCommunityMessageRequests == null) {
255-
// Nothing is updated..
256-
return null
246+
val proProofInfo: Conversation.ProProofInfo?
247+
val proFeatures: BitSet<ProProfileFeature>
248+
249+
if (pro?.status == ProProof.STATUS_VALID &&
250+
pro.proof != null &&
251+
pro.proof!!.expiryMs > nowMills) {
252+
proProofInfo = Conversation.ProProofInfo(
253+
genIndexHash = pro.proof!!.genIndexHashHex.hexToByteArray(),
254+
expiryMs = pro.proof!!.expiryMs,
255+
)
256+
proFeatures = pro.proProfileFeatures
257+
} else {
258+
proProofInfo = null
259+
proFeatures = BitSet()
257260
}
258261

259262
return Updates(
260263
name = name,
261264
pic = pic,
262265
blocksCommunityMessageRequests = blocksCommunityMessageRequests,
266+
proProof = proProofInfo,
267+
proFeatures = proFeatures,
263268
profileUpdateTime = if (profile.hasLastUpdateSeconds()) {
264269
Instant.ofEpochSecond(profile.lastUpdateSeconds)
265270
} else {

app/src/main/java/org/session/libsession/messaging/sending_receiving/GroupMessageHandler.kt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package org.session.libsession.messaging.sending_receiving
33
import kotlinx.coroutines.CoroutineScope
44
import kotlinx.coroutines.launch
55
import network.loki.messenger.libsession_util.ED25519
6+
import network.loki.messenger.libsession_util.protocol.DecodedPro
67
import org.session.libsession.database.StorageProtocol
78
import org.session.libsession.messaging.groups.GroupManagerV2
89
import org.session.libsession.messaging.messages.ProfileUpdateHandler
@@ -11,6 +12,7 @@ import org.session.libsession.messaging.utilities.MessageAuthentication.buildDel
1112
import org.session.libsession.messaging.utilities.MessageAuthentication.buildGroupInviteSignature
1213
import org.session.libsession.messaging.utilities.MessageAuthentication.buildInfoChangeSignature
1314
import org.session.libsession.messaging.utilities.MessageAuthentication.buildMemberChangeSignature
15+
import org.session.libsession.snode.SnodeClock
1416
import org.session.protos.SessionProtos
1517
import org.session.libsignal.utilities.AccountId
1618
import org.session.libsignal.utilities.IdPrefix
@@ -26,16 +28,22 @@ class GroupMessageHandler @Inject constructor(
2628
private val storage: StorageProtocol,
2729
private val groupManagerV2: GroupManagerV2,
2830
@param:ManagerScope private val scope: CoroutineScope,
31+
private val clock: SnodeClock,
2932
) {
30-
fun handleGroupUpdated(message: GroupUpdated, groupId: AccountId?, proto: SessionProtos.Content) {
33+
fun handleGroupUpdated(
34+
message: GroupUpdated,
35+
groupId: AccountId?,
36+
proto: SessionProtos.Content,
37+
pro: DecodedPro?
38+
) {
3139
val inner = message.inner
3240
if (groupId == null &&
3341
!inner.hasInviteMessage() && !inner.hasPromoteMessage()) {
3442
throw NullPointerException("Message wasn't polled from a closed group!")
3543
}
3644

3745
// Update profile if needed
38-
ProfileUpdateHandler.Updates.create(proto)?.let { updates ->
46+
ProfileUpdateHandler.Updates.create(proto, clock.currentTimeMills(), pro)?.let { updates ->
3947
profileUpdateHandler.handleProfileUpdate(
4048
senderId = AccountId(message.sender!!),
4149
updates = updates,

app/src/main/java/org/session/libsession/messaging/sending_receiving/MessageParser.kt

Lines changed: 38 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
package org.session.libsession.messaging.sending_receiving
22

33
import network.loki.messenger.libsession_util.SessionEncrypt
4+
import network.loki.messenger.libsession_util.pro.ProProof
45
import network.loki.messenger.libsession_util.protocol.DecodedEnvelope
56
import network.loki.messenger.libsession_util.protocol.DecodedPro
67
import network.loki.messenger.libsession_util.protocol.SessionProtocol
7-
import network.loki.messenger.libsession_util.util.BitSet
88
import network.loki.messenger.libsession_util.util.asSequence
99
import org.session.libsession.database.StorageProtocol
1010
import org.session.libsession.messaging.messages.Message
@@ -28,8 +28,10 @@ import org.session.libsignal.utilities.Base64
2828
import org.session.libsignal.utilities.Hex
2929
import org.session.libsignal.utilities.IdPrefix
3030
import org.session.protos.SessionProtos
31+
import org.thoughtcrime.securesms.pro.ProBackendConfig
3132
import java.util.concurrent.TimeUnit
3233
import javax.inject.Inject
34+
import javax.inject.Provider
3335
import javax.inject.Singleton
3436
import kotlin.math.abs
3537

@@ -39,17 +41,21 @@ class MessageParser @Inject constructor(
3941
private val storage: StorageProtocol,
4042
private val snodeClock: SnodeClock,
4143
private val prefs: TextSecurePreferences,
44+
private val proBackendConfig: Provider<ProBackendConfig>,
4245
) {
4346

44-
//TODO: Obtain proBackendKey from somewhere
45-
private val proBackendKey = ByteArray(32)
46-
4747
// A faster way to check if the user is blocked than to go through RecipientRepository
4848
private fun isUserBlocked(accountId: AccountId): Boolean {
4949
return configFactory.withUserConfigs { it.contacts.get(accountId.hexString) }
5050
?.blocked == true
5151
}
5252

53+
class ParseResult(
54+
val message: Message,
55+
val proto: SessionProtos.Content,
56+
val pro: DecodedPro?
57+
)
58+
5359

5460
private fun createMessageFromProto(proto: SessionProtos.Content, isGroupMessage: Boolean): Message {
5561
val message = ReadReceipt.fromProto(proto) ?:
@@ -77,7 +83,7 @@ class MessageParser @Inject constructor(
7783
currentUserId: AccountId,
7884
currentUserBlindedIDs: List<AccountId>,
7985
senderIdPrefix: IdPrefix
80-
): Pair<Message, SessionProtos.Content> {
86+
): ParseResult {
8187
return parseMessage(
8288
sender = AccountId(senderIdPrefix, decodedEnvelope.senderX25519PubKey.data),
8389
contentPlaintext = decodedEnvelope.contentPlainText.data,
@@ -101,7 +107,7 @@ class MessageParser @Inject constructor(
101107
isForGroup: Boolean,
102108
currentUserId: AccountId,
103109
currentUserBlindedIDs: List<AccountId>,
104-
): Pair<Message, SessionProtos.Content> {
110+
): ParseResult {
105111
val proto = SessionProtos.Content.parseFrom(contentPlaintext)
106112

107113
// Check signature
@@ -136,9 +142,11 @@ class MessageParser @Inject constructor(
136142

137143
// Only process pro features post pro launch
138144
if (prefs.forcePostPro()) {
139-
(message as? VisibleMessage)?.proFeatures = buildSet {
140-
pro?.proMessageFeatures?.asSequence()?.let(::addAll)
141-
pro?.proProfileFeatures?.asSequence()?.let(::addAll)
145+
if (pro?.status == ProProof.STATUS_VALID) {
146+
(message as? VisibleMessage)?.proFeatures = buildSet {
147+
addAll(pro.proMessageFeatures.asSequence())
148+
addAll(pro.proProfileFeatures.asSequence())
149+
}
142150
}
143151
}
144152

@@ -162,7 +170,11 @@ class MessageParser @Inject constructor(
162170
}
163171
storage.addReceivedMessageTimestamp(messageTimestampMs)
164172

165-
return message to proto
173+
return ParseResult(
174+
message = message,
175+
proto = proto,
176+
pro = pro
177+
)
166178
}
167179

168180

@@ -171,12 +183,11 @@ class MessageParser @Inject constructor(
171183
serverHash: String?,
172184
currentUserEd25519PrivKey: ByteArray,
173185
currentUserId: AccountId,
174-
): Pair<Message, SessionProtos.Content> {
186+
): ParseResult {
175187
val envelop = SessionProtocol.decodeFor1o1(
176188
myEd25519PrivKey = currentUserEd25519PrivKey,
177189
payload = data,
178-
nowEpochMs = snodeClock.currentTimeMills(),
179-
proBackendPubKey = proBackendKey,
190+
proBackendPubKey = proBackendConfig.get().ed25519PubKey,
180191
)
181192

182193
return parseMessage(
@@ -187,8 +198,8 @@ class MessageParser @Inject constructor(
187198
senderIdPrefix = IdPrefix.STANDARD,
188199
currentUserId = currentUserId,
189200
currentUserBlindedIDs = emptyList(),
190-
).also { (message, _) ->
191-
message.serverHash = serverHash
201+
).also { result ->
202+
result.message.serverHash = serverHash
192203
}
193204
}
194205

@@ -198,18 +209,17 @@ class MessageParser @Inject constructor(
198209
groupId: AccountId,
199210
currentUserEd25519PrivKey: ByteArray,
200211
currentUserId: AccountId,
201-
): Pair<Message, SessionProtos.Content> {
212+
): ParseResult {
202213
val keys = configFactory.withGroupConfigs(groupId) {
203214
it.groupKeys.groupKeys()
204215
}
205216

206217
val decoded = SessionProtocol.decodeForGroup(
207218
payload = data,
208219
myEd25519PrivKey = currentUserEd25519PrivKey,
209-
nowEpochMs = snodeClock.currentTimeMills(),
210220
groupEd25519PublicKey = groupId.pubKeyBytes,
211221
groupEd25519PrivateKeys = keys.toTypedArray(),
212-
proBackendPubKey = proBackendKey
222+
proBackendPubKey = proBackendConfig.get().ed25519PubKey,
213223
)
214224

215225
return parseMessage(
@@ -220,24 +230,24 @@ class MessageParser @Inject constructor(
220230
senderIdPrefix = IdPrefix.STANDARD,
221231
currentUserId = currentUserId,
222232
currentUserBlindedIDs = emptyList(),
223-
).also { (message, _) ->
224-
message.serverHash = serverHash
233+
).also { result ->
234+
result.message.serverHash = serverHash
225235
}
226236
}
227237

228238
fun parseCommunityMessage(
229239
msg: OpenGroupApi.Message,
230240
currentUserId: AccountId,
231241
currentUserBlindedIDs: List<AccountId>,
232-
): Pair<Message, SessionProtos.Content>? {
242+
): ParseResult? {
233243
if (msg.data.isNullOrBlank()) {
234244
return null
235245
}
236246

237247
val decoded = SessionProtocol.decodeForCommunity(
238248
payload = Base64.decode(msg.data),
239-
nowEpochMs = snodeClock.currentTimeMills(),
240-
proBackendPubKey = proBackendKey,
249+
timestampMs = (msg.posted * 1000).toLong(),
250+
proBackendPubKey = proBackendConfig.get().ed25519PubKey,
241251
)
242252

243253
val sender = AccountId(msg.sessionId)
@@ -252,8 +262,8 @@ class MessageParser @Inject constructor(
252262
sender = sender,
253263
messageTimestampMs = (msg.posted * 1000).toLong(),
254264
currentUserBlindedIDs = currentUserBlindedIDs,
255-
).also { (message, _) ->
256-
message.openGroupServerMessageID = msg.id
265+
).also { result ->
266+
result.message.openGroupServerMessageID = msg.id
257267
}
258268
}
259269

@@ -263,7 +273,7 @@ class MessageParser @Inject constructor(
263273
currentUserEd25519PrivKey: ByteArray,
264274
currentUserId: AccountId,
265275
currentUserBlindedIDs: List<AccountId>,
266-
): Pair<Message, SessionProtos.Content> {
276+
): ParseResult {
267277
val (senderId, plaintext) = SessionEncrypt.decryptForBlindedRecipient(
268278
ciphertext = Base64.decode(msg.message),
269279
myEd25519Privkey = currentUserEd25519PrivKey,
@@ -274,8 +284,8 @@ class MessageParser @Inject constructor(
274284

275285
val decoded = SessionProtocol.decodeForCommunity(
276286
payload = plaintext.data,
277-
nowEpochMs = snodeClock.currentTimeMills(),
278-
proBackendPubKey = proBackendKey,
287+
timestampMs = msg.postedAt * 1000L,
288+
proBackendPubKey = proBackendConfig.get().ed25519PubKey,
279289
)
280290

281291
val sender = Address.Standard(AccountId(senderId))

0 commit comments

Comments
 (0)