Skip to content

Commit 65a3ed1

Browse files
giovasdistilleryGiovani Gonzalez
and
Giovani Gonzalez
authored
Removing conversations that contain a no valid topic (#138)
* Adding regex to filter the conversations that contains a valid name topic * - Applying proper regex for valid topics and adding tests --------- Co-authored-by: Giovani Gonzalez <giovani@xmtp.com>
1 parent 18f5c84 commit 65a3ed1

File tree

2 files changed

+127
-51
lines changed

2 files changed

+127
-51
lines changed

library/src/androidTest/java/org/xmtp/android/library/ConversationTest.kt

+114-49
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import com.google.protobuf.kotlin.toByteString
66
import kotlinx.coroutines.ExperimentalCoroutinesApi
77
import org.junit.Assert
88
import org.junit.Assert.assertEquals
9+
import org.junit.Assert.assertFalse
910
import org.junit.Assert.assertThrows
1011
import org.junit.Assert.assertTrue
1112
import org.junit.Before
@@ -101,7 +102,7 @@ class ConversationTest {
101102
sender = bobClient.privateKeyBundleV1!!,
102103
recipient = aliceClient.privateKeyBundleV1?.toPublicKeyBundle()!!,
103104
message = encodedContent.toByteArray(),
104-
timestamp = someTimeAgo
105+
timestamp = someTimeAgo,
105106
)
106107
// Overwrite contact as legacy
107108
bobClient.publishUserContact(legacy = true)
@@ -111,22 +112,22 @@ class ConversationTest {
111112
EnvelopeBuilder.buildFromTopic(
112113
topic = Topic.userIntro(bob.walletAddress),
113114
timestamp = someTimeAgo,
114-
message = MessageBuilder.buildFromMessageV1(v1 = messageV1).toByteArray()
115+
message = MessageBuilder.buildFromMessageV1(v1 = messageV1).toByteArray(),
115116
),
116117
EnvelopeBuilder.buildFromTopic(
117118
topic = Topic.userIntro(alice.walletAddress),
118119
timestamp = someTimeAgo,
119-
message = MessageBuilder.buildFromMessageV1(v1 = messageV1).toByteArray()
120+
message = MessageBuilder.buildFromMessageV1(v1 = messageV1).toByteArray(),
120121
),
121122
EnvelopeBuilder.buildFromTopic(
122123
topic = Topic.directMessageV1(
123124
bob.walletAddress,
124-
alice.walletAddress
125+
alice.walletAddress,
125126
),
126127
timestamp = someTimeAgo,
127-
message = MessageBuilder.buildFromMessageV1(v1 = messageV1).toByteArray()
128-
)
129-
)
128+
message = MessageBuilder.buildFromMessageV1(v1 = messageV1).toByteArray(),
129+
),
130+
),
130131
)
131132
var conversation = aliceClient.conversations.newConversation(bob.walletAddress)
132133
assertEquals(conversation.peerAddress, bob.walletAddress)
@@ -137,7 +138,7 @@ class ConversationTest {
137138
assertEquals(
138139
"published more messages when we shouldn't have",
139140
existingMessages,
140-
fakeApiClient.published.size
141+
fakeApiClient.published.size,
141142
)
142143
assertEquals(conversation.peerAddress, alice.walletAddress)
143144
assertEquals(conversation.createdAt, someTimeAgo)
@@ -147,19 +148,19 @@ class ConversationTest {
147148
fun testCanFindExistingV2Conversation() {
148149
val existingConversation = bobClient.conversations.newConversation(
149150
alice.walletAddress,
150-
context = InvitationV1ContextBuilder.buildFromConversation("http://example.com/2")
151+
context = InvitationV1ContextBuilder.buildFromConversation("http://example.com/2"),
151152
)
152153
var conversation: Conversation? = null
153154
fakeApiClient.assertNoPublish {
154155
conversation = bobClient.conversations.newConversation(
155156
alice.walletAddress,
156-
context = InvitationV1ContextBuilder.buildFromConversation("http://example.com/2")
157+
context = InvitationV1ContextBuilder.buildFromConversation("http://example.com/2"),
157158
)
158159
}
159160
assertEquals(
160161
"made new conversation instead of using existing one",
161162
conversation!!.topic,
162-
existingConversation.topic
163+
existingConversation.topic,
163164
)
164165
}
165166

@@ -182,11 +183,13 @@ class ConversationTest {
182183
@Test
183184
fun testCanLoadV2Messages() {
184185
val bobConversation = bobClient.conversations.newConversation(
185-
aliceWallet.address, InvitationV1ContextBuilder.buildFromConversation("hi")
186+
aliceWallet.address,
187+
InvitationV1ContextBuilder.buildFromConversation("hi"),
186188
)
187189

188190
val aliceConversation = aliceClient.conversations.newConversation(
189-
bobWallet.address, InvitationV1ContextBuilder.buildFromConversation("hi")
191+
bobWallet.address,
192+
InvitationV1ContextBuilder.buildFromConversation("hi"),
190193
)
191194
bobConversation.send(content = "hey alice")
192195
val messages = aliceConversation.messages()
@@ -199,7 +202,7 @@ class ConversationTest {
199202
fun testVerifiesV2MessageSignature() {
200203
val aliceConversation = aliceClient.conversations.newConversation(
201204
bobWallet.address,
202-
context = InvitationV1ContextBuilder.buildFromConversation(conversationId = "hi")
205+
context = InvitationV1ContextBuilder.buildFromConversation(conversationId = "hi"),
203206
)
204207

205208
val codec = TextCodec()
@@ -215,22 +218,27 @@ class ConversationTest {
215218
val signature = preKey?.sign(digest)
216219
val bundle = aliceClient.privateKeyBundleV1?.toV2()?.getPublicKeyBundle()
217220
val signedContent = SignedContentBuilder.builderFromPayload(
218-
payload = originalPayload, sender = bundle, signature = signature
221+
payload = originalPayload,
222+
sender = bundle,
223+
signature = signature,
219224
)
220225
val signedBytes = signedContent.toByteArray()
221226
val ciphertext = Crypto.encrypt(
222-
aliceConversation.keyMaterial!!, signedBytes, additionalData = headerBytes
227+
aliceConversation.keyMaterial!!,
228+
signedBytes,
229+
additionalData = headerBytes,
223230
)
224231
val tamperedMessage =
225232
MessageV2Builder.buildFromCipherText(headerBytes = headerBytes, ciphertext = ciphertext)
226233
val tamperedEnvelope = EnvelopeBuilder.buildFromString(
227234
topic = aliceConversation.topic,
228235
timestamp = Date(),
229-
message = MessageBuilder.buildFromMessageV2(v2 = tamperedMessage).toByteArray()
236+
message = MessageBuilder.buildFromMessageV2(v2 = tamperedMessage).toByteArray(),
230237
)
231238
aliceClient.publish(envelopes = listOf(tamperedEnvelope))
232239
val bobConversation = bobClient.conversations.newConversation(
233-
aliceWallet.address, InvitationV1ContextBuilder.buildFromConversation("hi")
240+
aliceWallet.address,
241+
InvitationV1ContextBuilder.buildFromConversation("hi"),
234242
)
235243
assertThrows("Invalid signature", XMTPException::class.java) {
236244
bobConversation.decode(tamperedEnvelope)
@@ -247,7 +255,7 @@ class ConversationTest {
247255
val aliceConversation = aliceClient.conversations.newConversation(bobWallet.address)
248256
bobConversation.send(
249257
text = MutableList(1000) { "A" }.toString(),
250-
sendOptions = SendOptions(compression = EncodedContentCompression.GZIP)
258+
sendOptions = SendOptions(compression = EncodedContentCompression.GZIP),
251259
)
252260
val messages = aliceConversation.messages()
253261
assertEquals(1, messages.size)
@@ -262,7 +270,7 @@ class ConversationTest {
262270
val aliceConversation = aliceClient.conversations.newConversation(bobWallet.address)
263271
bobConversation.send(
264272
content = MutableList(1000) { "A" }.toString(),
265-
options = SendOptions(compression = EncodedContentCompression.DEFLATE)
273+
options = SendOptions(compression = EncodedContentCompression.DEFLATE),
266274
)
267275
val messages = aliceConversation.messages()
268276
assertEquals(1, messages.size)
@@ -273,15 +281,15 @@ class ConversationTest {
273281
fun testCanSendGzipCompressedV2Messages() {
274282
val bobConversation = bobClient.conversations.newConversation(
275283
aliceWallet.address,
276-
InvitationV1ContextBuilder.buildFromConversation(conversationId = "hi")
284+
InvitationV1ContextBuilder.buildFromConversation(conversationId = "hi"),
277285
)
278286
val aliceConversation = aliceClient.conversations.newConversation(
279287
bobWallet.address,
280-
InvitationV1ContextBuilder.buildFromConversation(conversationId = "hi")
288+
InvitationV1ContextBuilder.buildFromConversation(conversationId = "hi"),
281289
)
282290
bobConversation.send(
283291
text = MutableList(1000) { "A" }.toString(),
284-
sendOptions = SendOptions(compression = EncodedContentCompression.GZIP)
292+
sendOptions = SendOptions(compression = EncodedContentCompression.GZIP),
285293
)
286294
val messages = aliceConversation.messages()
287295
assertEquals(1, messages.size)
@@ -293,15 +301,15 @@ class ConversationTest {
293301
fun testCanSendDeflateCompressedV2Messages() {
294302
val bobConversation = bobClient.conversations.newConversation(
295303
aliceWallet.address,
296-
InvitationV1ContextBuilder.buildFromConversation(conversationId = "hi")
304+
InvitationV1ContextBuilder.buildFromConversation(conversationId = "hi"),
297305
)
298306
val aliceConversation = aliceClient.conversations.newConversation(
299307
bobWallet.address,
300-
InvitationV1ContextBuilder.buildFromConversation(conversationId = "hi")
308+
InvitationV1ContextBuilder.buildFromConversation(conversationId = "hi"),
301309
)
302310
bobConversation.send(
303311
content = MutableList(1000) { "A" }.toString(),
304-
options = SendOptions(compression = EncodedContentCompression.DEFLATE)
312+
options = SendOptions(compression = EncodedContentCompression.DEFLATE),
305313
)
306314
val messages = aliceConversation.messages()
307315
assertEquals(1, messages.size)
@@ -325,18 +333,18 @@ class ConversationTest {
325333
val invitationv1 = InvitationV1.newBuilder().build().createDeterministic(
326334
sender = client.keys,
327335
recipient = fakeContactClient.keys.getPublicKeyBundle(),
328-
context = invitationContext
336+
context = invitationContext,
329337
)
330338
val senderBundle = client.privateKeyBundleV1?.toV2()
331339
assertEquals(
332340
senderBundle?.identityKey?.publicKey?.recoverWalletSignerPublicKey()?.walletAddress,
333-
fakeWallet.address
341+
fakeWallet.address,
334342
)
335343
val invitation = SealedInvitationBuilder.buildFromV1(
336344
sender = client.privateKeyBundleV1!!.toV2(),
337345
recipient = contact.toSignedPublicKeyBundle(),
338346
created = created,
339-
invitation = invitationv1
347+
invitation = invitationv1,
340348
)
341349
val inviteHeader = invitation.v1.header
342350
assertEquals(inviteHeader.sender.walletAddress, fakeWallet.address)
@@ -389,11 +397,13 @@ class ConversationTest {
389397
@Test
390398
fun testCanPaginateV2Messages() {
391399
val bobConversation = bobClient.conversations.newConversation(
392-
alice.walletAddress, context = InvitationV1ContextBuilder.buildFromConversation("hi")
400+
alice.walletAddress,
401+
context = InvitationV1ContextBuilder.buildFromConversation("hi"),
393402
)
394403

395404
val aliceConversation = aliceClient.conversations.newConversation(
396-
bob.walletAddress, context = InvitationV1ContextBuilder.buildFromConversation("hi")
405+
bob.walletAddress,
406+
context = InvitationV1ContextBuilder.buildFromConversation("hi"),
397407
)
398408
val date = Date()
399409
date.time = date.time - 1000000
@@ -425,8 +435,9 @@ class ConversationTest {
425435
steveConversation.send(text = "hey alice 3")
426436
val messages = aliceClient.conversations.listBatchMessages(
427437
listOf(
428-
Pair(steveConversation.topic, null), Pair(bobConversation.topic, null)
429-
)
438+
Pair(steveConversation.topic, null),
439+
Pair(bobConversation.topic, null),
440+
),
430441
)
431442
val isSteveOrBobConversation = { topic: String ->
432443
(topic.equals(steveConversation.topic) || topic.equals(bobConversation.topic))
@@ -457,8 +468,8 @@ class ConversationTest {
457468
val messages = aliceClient.conversations.listBatchMessages(
458469
listOf(
459470
Pair(steveConversation.topic, Pagination(after = date)),
460-
Pair(bobConversation.topic, Pagination(after = date))
461-
)
471+
Pair(bobConversation.topic, Pagination(after = date)),
472+
),
462473
)
463474

464475
assertEquals(4, messages.size)
@@ -468,7 +479,7 @@ class ConversationTest {
468479
fun testImportV1ConversationFromJS() {
469480
val jsExportJSONData =
470481
(""" { "version": "v1", "peerAddress": "0x5DAc8E2B64b8523C11AF3e5A2E087c2EA9003f14", "createdAt": "2022-09-20T09:32:50.329Z" } """).toByteArray(
471-
StandardCharsets.UTF_8
482+
StandardCharsets.UTF_8,
472483
)
473484
val conversation = aliceClient.importConversation(jsExportJSONData)
474485
assertEquals(conversation.peerAddress, "0x5DAc8E2B64b8523C11AF3e5A2E087c2EA9003f14")
@@ -478,7 +489,7 @@ class ConversationTest {
478489
fun testImportV2ConversationFromJS() {
479490
val jsExportJSONData =
480491
(""" {"version":"v2","topic":"/xmtp/0/m-2SkdN5Qa0ZmiFI5t3RFbfwIS-OLv5jusqndeenTLvNg/proto","keyMaterial":"ATA1L0O2aTxHmskmlGKCudqfGqwA1H+bad3W/GpGOr8=","peerAddress":"0x436D906d1339fC4E951769b1699051f020373D04","createdAt":"2023-01-26T22:58:45.068Z","context":{"conversationId":"pat/messageid","metadata":{}}} """).toByteArray(
481-
StandardCharsets.UTF_8
492+
StandardCharsets.UTF_8,
482493
)
483494
val conversation = aliceClient.importConversation(jsExportJSONData)
484495
assertEquals(conversation.peerAddress, "0x436D906d1339fC4E951769b1699051f020373D04")
@@ -488,7 +499,7 @@ class ConversationTest {
488499
fun testImportV2ConversationWithNoContextFromJS() {
489500
val jsExportJSONData =
490501
(""" {"version":"v2","topic":"/xmtp/0/m-2SkdN5Qa0ZmiFI5t3RFbfwIS-OLv5jusqndeenTLvNg/proto","keyMaterial":"ATA1L0O2aTxHmskmlGKCudqfGqwA1H+bad3W/GpGOr8=","peerAddress":"0x436D906d1339fC4E951769b1699051f020373D04","createdAt":"2023-01-26T22:58:45.068Z"} """).toByteArray(
491-
StandardCharsets.UTF_8
502+
StandardCharsets.UTF_8,
492503
)
493504
val conversation = aliceClient.importConversation(jsExportJSONData)
494505
assertEquals(conversation.peerAddress, "0x436D906d1339fC4E951769b1699051f020373D04")
@@ -523,10 +534,10 @@ class ConversationTest {
523534
sender = bobClient.privateKeyBundleV1!!,
524535
recipient = aliceClient.privateKeyBundleV1!!.toPublicKeyBundle(),
525536
message = encodedContent.toByteArray(),
526-
timestamp = Date()
527-
)
528-
).toByteArray()
529-
)
537+
timestamp = Date(),
538+
),
539+
).toByteArray(),
540+
),
530541
)
531542
assertEquals("hi alice", awaitItem().encodedContent.content.toStringUtf8())
532543
awaitComplete()
@@ -549,10 +560,10 @@ class ConversationTest {
549560
client = bobClient,
550561
encodedContent,
551562
topic = conversation.topic,
552-
keyMaterial = conversation.keyMaterial!!
553-
)
554-
).toByteArray()
555-
)
563+
keyMaterial = conversation.keyMaterial!!,
564+
),
565+
).toByteArray(),
566+
),
556567
)
557568
assertEquals("hi alice", awaitItem().encodedContent.content.toStringUtf8())
558569
awaitComplete()
@@ -573,10 +584,12 @@ class ConversationTest {
573584
context = Context.newBuilder().build(),
574585
peerAddress = "0x2f25e33D7146602Ec08D43c1D6B1b65fc151A677",
575586
client = aliceClient,
576-
header = Invitation.SealedInvitationHeaderV1.newBuilder().build()
587+
header = Invitation.SealedInvitationHeaderV1.newBuilder().build(),
577588
)
578589
val envelope = EnvelopeBuilder.buildFromString(
579-
topic = topic, timestamp = Date(), message = envelopeMessage
590+
topic = topic,
591+
timestamp = Date(),
592+
message = envelopeMessage,
580593
)
581594
assertThrows("pre-key not signed by identity key", XMTPException::class.java) {
582595
conversation.decodeEnvelope(envelope)
@@ -662,7 +675,7 @@ class ConversationTest {
662675
34,
663676
126,
664677
219,
665-
236
678+
236,
666679
)
667680
val bytes =
668681
ints.foldIndexed(ByteArray(ints.size)) { i, a, v -> a.apply { set(i, v.toByte()) } }
@@ -774,4 +787,56 @@ class ConversationTest {
774787
// Conversations you send a message to get marked as allowed
775788
assertTrue(isNowAllowed)
776789
}
790+
791+
@Test
792+
fun testCanValidateTopicsInsideConversation() {
793+
val validId = "sdfsadf095b97a9284dcd82b2274856ccac8a21de57bebe34e7f9eeb855fb21126d3b8f"
794+
795+
// Creation of all known types of topics
796+
val privateStore = Topic.userPrivateStoreKeyBundle(validId).description
797+
val contact = Topic.contact(validId).description
798+
val userIntro = Topic.userIntro(validId).description
799+
val userInvite = Topic.userInvite(validId).description
800+
val directMessageV1 = Topic.directMessageV1(validId, "sd").description
801+
val directMessageV2 = Topic.directMessageV2(validId).description
802+
val preferenceList = Topic.preferenceList(validId).description
803+
val conversations = bobClient.conversations
804+
805+
// check if validation of topics accepts all types
806+
assertTrue(
807+
conversations.isValidTopic(privateStore) &&
808+
conversations.isValidTopic(contact) &&
809+
conversations.isValidTopic(userIntro) &&
810+
conversations.isValidTopic(userInvite) &&
811+
conversations.isValidTopic(directMessageV1) &&
812+
conversations.isValidTopic(directMessageV2) &&
813+
conversations.isValidTopic(preferenceList),
814+
)
815+
}
816+
817+
@Test
818+
fun testCannotValidateTopicsInsideConversation() {
819+
val invalidId = "��\\u0005�!\\u000b���5\\u00001\\u0007�蛨\\u001f\\u00172��.����K9K`�"
820+
821+
// Creation of all known types of topics
822+
val privateStore = Topic.userPrivateStoreKeyBundle(invalidId).description
823+
val contact = Topic.contact(invalidId).description
824+
val userIntro = Topic.userIntro(invalidId).description
825+
val userInvite = Topic.userInvite(invalidId).description
826+
val directMessageV1 = Topic.directMessageV1(invalidId, "sd").description
827+
val directMessageV2 = Topic.directMessageV2(invalidId).description
828+
val preferenceList = Topic.preferenceList(invalidId).description
829+
val conversations = bobClient.conversations
830+
831+
// check if validation of topics accepts all types
832+
assertFalse(
833+
conversations.isValidTopic(privateStore) &&
834+
conversations.isValidTopic(contact) &&
835+
conversations.isValidTopic(userIntro) &&
836+
conversations.isValidTopic(userInvite) &&
837+
conversations.isValidTopic(directMessageV1) &&
838+
conversations.isValidTopic(directMessageV2) &&
839+
conversations.isValidTopic(preferenceList),
840+
)
841+
}
777842
}

0 commit comments

Comments
 (0)