@@ -6,6 +6,7 @@ import com.google.protobuf.kotlin.toByteString
6
6
import kotlinx.coroutines.ExperimentalCoroutinesApi
7
7
import org.junit.Assert
8
8
import org.junit.Assert.assertEquals
9
+ import org.junit.Assert.assertFalse
9
10
import org.junit.Assert.assertThrows
10
11
import org.junit.Assert.assertTrue
11
12
import org.junit.Before
@@ -101,7 +102,7 @@ class ConversationTest {
101
102
sender = bobClient.privateKeyBundleV1!! ,
102
103
recipient = aliceClient.privateKeyBundleV1?.toPublicKeyBundle()!! ,
103
104
message = encodedContent.toByteArray(),
104
- timestamp = someTimeAgo
105
+ timestamp = someTimeAgo,
105
106
)
106
107
// Overwrite contact as legacy
107
108
bobClient.publishUserContact(legacy = true )
@@ -111,22 +112,22 @@ class ConversationTest {
111
112
EnvelopeBuilder .buildFromTopic(
112
113
topic = Topic .userIntro(bob.walletAddress),
113
114
timestamp = someTimeAgo,
114
- message = MessageBuilder .buildFromMessageV1(v1 = messageV1).toByteArray()
115
+ message = MessageBuilder .buildFromMessageV1(v1 = messageV1).toByteArray(),
115
116
),
116
117
EnvelopeBuilder .buildFromTopic(
117
118
topic = Topic .userIntro(alice.walletAddress),
118
119
timestamp = someTimeAgo,
119
- message = MessageBuilder .buildFromMessageV1(v1 = messageV1).toByteArray()
120
+ message = MessageBuilder .buildFromMessageV1(v1 = messageV1).toByteArray(),
120
121
),
121
122
EnvelopeBuilder .buildFromTopic(
122
123
topic = Topic .directMessageV1(
123
124
bob.walletAddress,
124
- alice.walletAddress
125
+ alice.walletAddress,
125
126
),
126
127
timestamp = someTimeAgo,
127
- message = MessageBuilder .buildFromMessageV1(v1 = messageV1).toByteArray()
128
- )
129
- )
128
+ message = MessageBuilder .buildFromMessageV1(v1 = messageV1).toByteArray(),
129
+ ),
130
+ ),
130
131
)
131
132
var conversation = aliceClient.conversations.newConversation(bob.walletAddress)
132
133
assertEquals(conversation.peerAddress, bob.walletAddress)
@@ -137,7 +138,7 @@ class ConversationTest {
137
138
assertEquals(
138
139
" published more messages when we shouldn't have" ,
139
140
existingMessages,
140
- fakeApiClient.published.size
141
+ fakeApiClient.published.size,
141
142
)
142
143
assertEquals(conversation.peerAddress, alice.walletAddress)
143
144
assertEquals(conversation.createdAt, someTimeAgo)
@@ -147,19 +148,19 @@ class ConversationTest {
147
148
fun testCanFindExistingV2Conversation () {
148
149
val existingConversation = bobClient.conversations.newConversation(
149
150
alice.walletAddress,
150
- context = InvitationV1ContextBuilder .buildFromConversation(" http://example.com/2" )
151
+ context = InvitationV1ContextBuilder .buildFromConversation(" http://example.com/2" ),
151
152
)
152
153
var conversation: Conversation ? = null
153
154
fakeApiClient.assertNoPublish {
154
155
conversation = bobClient.conversations.newConversation(
155
156
alice.walletAddress,
156
- context = InvitationV1ContextBuilder .buildFromConversation(" http://example.com/2" )
157
+ context = InvitationV1ContextBuilder .buildFromConversation(" http://example.com/2" ),
157
158
)
158
159
}
159
160
assertEquals(
160
161
" made new conversation instead of using existing one" ,
161
162
conversation!! .topic,
162
- existingConversation.topic
163
+ existingConversation.topic,
163
164
)
164
165
}
165
166
@@ -182,11 +183,13 @@ class ConversationTest {
182
183
@Test
183
184
fun testCanLoadV2Messages () {
184
185
val bobConversation = bobClient.conversations.newConversation(
185
- aliceWallet.address, InvitationV1ContextBuilder .buildFromConversation(" hi" )
186
+ aliceWallet.address,
187
+ InvitationV1ContextBuilder .buildFromConversation(" hi" ),
186
188
)
187
189
188
190
val aliceConversation = aliceClient.conversations.newConversation(
189
- bobWallet.address, InvitationV1ContextBuilder .buildFromConversation(" hi" )
191
+ bobWallet.address,
192
+ InvitationV1ContextBuilder .buildFromConversation(" hi" ),
190
193
)
191
194
bobConversation.send(content = " hey alice" )
192
195
val messages = aliceConversation.messages()
@@ -199,7 +202,7 @@ class ConversationTest {
199
202
fun testVerifiesV2MessageSignature () {
200
203
val aliceConversation = aliceClient.conversations.newConversation(
201
204
bobWallet.address,
202
- context = InvitationV1ContextBuilder .buildFromConversation(conversationId = " hi" )
205
+ context = InvitationV1ContextBuilder .buildFromConversation(conversationId = " hi" ),
203
206
)
204
207
205
208
val codec = TextCodec ()
@@ -215,22 +218,27 @@ class ConversationTest {
215
218
val signature = preKey?.sign(digest)
216
219
val bundle = aliceClient.privateKeyBundleV1?.toV2()?.getPublicKeyBundle()
217
220
val signedContent = SignedContentBuilder .builderFromPayload(
218
- payload = originalPayload, sender = bundle, signature = signature
221
+ payload = originalPayload,
222
+ sender = bundle,
223
+ signature = signature,
219
224
)
220
225
val signedBytes = signedContent.toByteArray()
221
226
val ciphertext = Crypto .encrypt(
222
- aliceConversation.keyMaterial!! , signedBytes, additionalData = headerBytes
227
+ aliceConversation.keyMaterial!! ,
228
+ signedBytes,
229
+ additionalData = headerBytes,
223
230
)
224
231
val tamperedMessage =
225
232
MessageV2Builder .buildFromCipherText(headerBytes = headerBytes, ciphertext = ciphertext)
226
233
val tamperedEnvelope = EnvelopeBuilder .buildFromString(
227
234
topic = aliceConversation.topic,
228
235
timestamp = Date (),
229
- message = MessageBuilder .buildFromMessageV2(v2 = tamperedMessage).toByteArray()
236
+ message = MessageBuilder .buildFromMessageV2(v2 = tamperedMessage).toByteArray(),
230
237
)
231
238
aliceClient.publish(envelopes = listOf (tamperedEnvelope))
232
239
val bobConversation = bobClient.conversations.newConversation(
233
- aliceWallet.address, InvitationV1ContextBuilder .buildFromConversation(" hi" )
240
+ aliceWallet.address,
241
+ InvitationV1ContextBuilder .buildFromConversation(" hi" ),
234
242
)
235
243
assertThrows(" Invalid signature" , XMTPException ::class .java) {
236
244
bobConversation.decode(tamperedEnvelope)
@@ -247,7 +255,7 @@ class ConversationTest {
247
255
val aliceConversation = aliceClient.conversations.newConversation(bobWallet.address)
248
256
bobConversation.send(
249
257
text = MutableList (1000 ) { " A" }.toString(),
250
- sendOptions = SendOptions (compression = EncodedContentCompression .GZIP )
258
+ sendOptions = SendOptions (compression = EncodedContentCompression .GZIP ),
251
259
)
252
260
val messages = aliceConversation.messages()
253
261
assertEquals(1 , messages.size)
@@ -262,7 +270,7 @@ class ConversationTest {
262
270
val aliceConversation = aliceClient.conversations.newConversation(bobWallet.address)
263
271
bobConversation.send(
264
272
content = MutableList (1000 ) { " A" }.toString(),
265
- options = SendOptions (compression = EncodedContentCompression .DEFLATE )
273
+ options = SendOptions (compression = EncodedContentCompression .DEFLATE ),
266
274
)
267
275
val messages = aliceConversation.messages()
268
276
assertEquals(1 , messages.size)
@@ -273,15 +281,15 @@ class ConversationTest {
273
281
fun testCanSendGzipCompressedV2Messages () {
274
282
val bobConversation = bobClient.conversations.newConversation(
275
283
aliceWallet.address,
276
- InvitationV1ContextBuilder .buildFromConversation(conversationId = " hi" )
284
+ InvitationV1ContextBuilder .buildFromConversation(conversationId = " hi" ),
277
285
)
278
286
val aliceConversation = aliceClient.conversations.newConversation(
279
287
bobWallet.address,
280
- InvitationV1ContextBuilder .buildFromConversation(conversationId = " hi" )
288
+ InvitationV1ContextBuilder .buildFromConversation(conversationId = " hi" ),
281
289
)
282
290
bobConversation.send(
283
291
text = MutableList (1000 ) { " A" }.toString(),
284
- sendOptions = SendOptions (compression = EncodedContentCompression .GZIP )
292
+ sendOptions = SendOptions (compression = EncodedContentCompression .GZIP ),
285
293
)
286
294
val messages = aliceConversation.messages()
287
295
assertEquals(1 , messages.size)
@@ -293,15 +301,15 @@ class ConversationTest {
293
301
fun testCanSendDeflateCompressedV2Messages () {
294
302
val bobConversation = bobClient.conversations.newConversation(
295
303
aliceWallet.address,
296
- InvitationV1ContextBuilder .buildFromConversation(conversationId = " hi" )
304
+ InvitationV1ContextBuilder .buildFromConversation(conversationId = " hi" ),
297
305
)
298
306
val aliceConversation = aliceClient.conversations.newConversation(
299
307
bobWallet.address,
300
- InvitationV1ContextBuilder .buildFromConversation(conversationId = " hi" )
308
+ InvitationV1ContextBuilder .buildFromConversation(conversationId = " hi" ),
301
309
)
302
310
bobConversation.send(
303
311
content = MutableList (1000 ) { " A" }.toString(),
304
- options = SendOptions (compression = EncodedContentCompression .DEFLATE )
312
+ options = SendOptions (compression = EncodedContentCompression .DEFLATE ),
305
313
)
306
314
val messages = aliceConversation.messages()
307
315
assertEquals(1 , messages.size)
@@ -325,18 +333,18 @@ class ConversationTest {
325
333
val invitationv1 = InvitationV1 .newBuilder().build().createDeterministic(
326
334
sender = client.keys,
327
335
recipient = fakeContactClient.keys.getPublicKeyBundle(),
328
- context = invitationContext
336
+ context = invitationContext,
329
337
)
330
338
val senderBundle = client.privateKeyBundleV1?.toV2()
331
339
assertEquals(
332
340
senderBundle?.identityKey?.publicKey?.recoverWalletSignerPublicKey()?.walletAddress,
333
- fakeWallet.address
341
+ fakeWallet.address,
334
342
)
335
343
val invitation = SealedInvitationBuilder .buildFromV1(
336
344
sender = client.privateKeyBundleV1!! .toV2(),
337
345
recipient = contact.toSignedPublicKeyBundle(),
338
346
created = created,
339
- invitation = invitationv1
347
+ invitation = invitationv1,
340
348
)
341
349
val inviteHeader = invitation.v1.header
342
350
assertEquals(inviteHeader.sender.walletAddress, fakeWallet.address)
@@ -389,11 +397,13 @@ class ConversationTest {
389
397
@Test
390
398
fun testCanPaginateV2Messages () {
391
399
val bobConversation = bobClient.conversations.newConversation(
392
- alice.walletAddress, context = InvitationV1ContextBuilder .buildFromConversation(" hi" )
400
+ alice.walletAddress,
401
+ context = InvitationV1ContextBuilder .buildFromConversation(" hi" ),
393
402
)
394
403
395
404
val aliceConversation = aliceClient.conversations.newConversation(
396
- bob.walletAddress, context = InvitationV1ContextBuilder .buildFromConversation(" hi" )
405
+ bob.walletAddress,
406
+ context = InvitationV1ContextBuilder .buildFromConversation(" hi" ),
397
407
)
398
408
val date = Date ()
399
409
date.time = date.time - 1000000
@@ -425,8 +435,9 @@ class ConversationTest {
425
435
steveConversation.send(text = " hey alice 3" )
426
436
val messages = aliceClient.conversations.listBatchMessages(
427
437
listOf (
428
- Pair (steveConversation.topic, null ), Pair (bobConversation.topic, null )
429
- )
438
+ Pair (steveConversation.topic, null ),
439
+ Pair (bobConversation.topic, null ),
440
+ ),
430
441
)
431
442
val isSteveOrBobConversation = { topic: String ->
432
443
(topic.equals(steveConversation.topic) || topic.equals(bobConversation.topic))
@@ -457,8 +468,8 @@ class ConversationTest {
457
468
val messages = aliceClient.conversations.listBatchMessages(
458
469
listOf (
459
470
Pair (steveConversation.topic, Pagination (after = date)),
460
- Pair (bobConversation.topic, Pagination (after = date))
461
- )
471
+ Pair (bobConversation.topic, Pagination (after = date)),
472
+ ),
462
473
)
463
474
464
475
assertEquals(4 , messages.size)
@@ -468,7 +479,7 @@ class ConversationTest {
468
479
fun testImportV1ConversationFromJS () {
469
480
val jsExportJSONData =
470
481
(""" { "version": "v1", "peerAddress": "0x5DAc8E2B64b8523C11AF3e5A2E087c2EA9003f14", "createdAt": "2022-09-20T09:32:50.329Z" } """ ).toByteArray(
471
- StandardCharsets .UTF_8
482
+ StandardCharsets .UTF_8 ,
472
483
)
473
484
val conversation = aliceClient.importConversation(jsExportJSONData)
474
485
assertEquals(conversation.peerAddress, " 0x5DAc8E2B64b8523C11AF3e5A2E087c2EA9003f14" )
@@ -478,7 +489,7 @@ class ConversationTest {
478
489
fun testImportV2ConversationFromJS () {
479
490
val jsExportJSONData =
480
491
(""" {"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 ,
482
493
)
483
494
val conversation = aliceClient.importConversation(jsExportJSONData)
484
495
assertEquals(conversation.peerAddress, " 0x436D906d1339fC4E951769b1699051f020373D04" )
@@ -488,7 +499,7 @@ class ConversationTest {
488
499
fun testImportV2ConversationWithNoContextFromJS () {
489
500
val jsExportJSONData =
490
501
(""" {"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 ,
492
503
)
493
504
val conversation = aliceClient.importConversation(jsExportJSONData)
494
505
assertEquals(conversation.peerAddress, " 0x436D906d1339fC4E951769b1699051f020373D04" )
@@ -523,10 +534,10 @@ class ConversationTest {
523
534
sender = bobClient.privateKeyBundleV1!! ,
524
535
recipient = aliceClient.privateKeyBundleV1!! .toPublicKeyBundle(),
525
536
message = encodedContent.toByteArray(),
526
- timestamp = Date ()
527
- )
528
- ).toByteArray()
529
- )
537
+ timestamp = Date (),
538
+ ),
539
+ ).toByteArray(),
540
+ ),
530
541
)
531
542
assertEquals(" hi alice" , awaitItem().encodedContent.content.toStringUtf8())
532
543
awaitComplete()
@@ -549,10 +560,10 @@ class ConversationTest {
549
560
client = bobClient,
550
561
encodedContent,
551
562
topic = conversation.topic,
552
- keyMaterial = conversation.keyMaterial!!
553
- )
554
- ).toByteArray()
555
- )
563
+ keyMaterial = conversation.keyMaterial!! ,
564
+ ),
565
+ ).toByteArray(),
566
+ ),
556
567
)
557
568
assertEquals(" hi alice" , awaitItem().encodedContent.content.toStringUtf8())
558
569
awaitComplete()
@@ -573,10 +584,12 @@ class ConversationTest {
573
584
context = Context .newBuilder().build(),
574
585
peerAddress = " 0x2f25e33D7146602Ec08D43c1D6B1b65fc151A677" ,
575
586
client = aliceClient,
576
- header = Invitation .SealedInvitationHeaderV1 .newBuilder().build()
587
+ header = Invitation .SealedInvitationHeaderV1 .newBuilder().build(),
577
588
)
578
589
val envelope = EnvelopeBuilder .buildFromString(
579
- topic = topic, timestamp = Date (), message = envelopeMessage
590
+ topic = topic,
591
+ timestamp = Date (),
592
+ message = envelopeMessage,
580
593
)
581
594
assertThrows(" pre-key not signed by identity key" , XMTPException ::class .java) {
582
595
conversation.decodeEnvelope(envelope)
@@ -662,7 +675,7 @@ class ConversationTest {
662
675
34 ,
663
676
126 ,
664
677
219 ,
665
- 236
678
+ 236 ,
666
679
)
667
680
val bytes =
668
681
ints.foldIndexed(ByteArray (ints.size)) { i, a, v -> a.apply { set(i, v.toByte()) } }
@@ -774,4 +787,56 @@ class ConversationTest {
774
787
// Conversations you send a message to get marked as allowed
775
788
assertTrue(isNowAllowed)
776
789
}
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
+ }
777
842
}
0 commit comments