@@ -3,32 +3,38 @@ package org.xmtp.android.library
3
3
import androidx.test.ext.junit.runners.AndroidJUnit4
4
4
import com.google.protobuf.kotlin.toByteStringUtf8
5
5
import org.junit.Assert.assertEquals
6
+ import org.junit.Assert.assertTrue
6
7
import org.junit.Test
7
8
import org.junit.runner.RunWith
9
+ import org.xmtp.android.library.Crypto.Companion.verifyHmacSignature
8
10
import org.xmtp.android.library.codecs.CompositeCodec
9
11
import org.xmtp.android.library.codecs.ContentCodec
10
12
import org.xmtp.android.library.codecs.ContentTypeId
11
13
import org.xmtp.android.library.codecs.ContentTypeIdBuilder
12
14
import org.xmtp.android.library.codecs.DecodedComposite
13
15
import org.xmtp.android.library.codecs.EncodedContent
14
16
import org.xmtp.android.library.codecs.TextCodec
17
+ import org.xmtp.android.library.messages.InvitationV1ContextBuilder
18
+ import org.xmtp.android.library.messages.MessageV2Builder
19
+ import org.xmtp.android.library.messages.PrivateKeyBuilder
15
20
import org.xmtp.android.library.messages.walletAddress
21
+ import java.time.Instant
16
22
17
23
data class NumberCodec (
18
24
override var contentType : ContentTypeId = ContentTypeIdBuilder .builderFromAuthorityId(
19
25
authorityId = "example.com",
20
26
typeId = "number",
21
27
versionMajor = 1,
22
- versionMinor = 1
23
- )
28
+ versionMinor = 1,
29
+ ),
24
30
) : ContentCodec<Double> {
25
31
override fun encode (content : Double ): EncodedContent {
26
32
return EncodedContent .newBuilder().also {
27
33
it.type = ContentTypeIdBuilder .builderFromAuthorityId(
28
34
authorityId = " example.com" ,
29
35
typeId = " number" ,
30
36
versionMajor = 1 ,
31
- versionMinor = 1
37
+ versionMinor = 1 ,
32
38
)
33
39
it.content = mapOf (Pair (" number" , content)).toString().toByteStringUtf8()
34
40
}.build()
@@ -37,10 +43,13 @@ data class NumberCodec(
37
43
override fun decode (content : EncodedContent ): Double =
38
44
content.content.toStringUtf8().filter { it.isDigit() || it == ' .' }.toDouble()
39
45
46
+ override fun shouldPush (content : Double ): Boolean = false
47
+
40
48
override fun fallback (content : Double ): String? {
41
49
return " Error: This app does not support numbers."
42
50
}
43
51
}
52
+
44
53
@RunWith(AndroidJUnit4 ::class )
45
54
class CodecTest {
46
55
@@ -53,7 +62,7 @@ class CodecTest {
53
62
aliceClient.conversations.newConversation(fixtures.bob.walletAddress)
54
63
aliceConversation.send(
55
64
content = 3.14 ,
56
- options = SendOptions (contentType = NumberCodec ().contentType)
65
+ options = SendOptions (contentType = NumberCodec ().contentType),
57
66
)
58
67
val messages = aliceConversation.messages()
59
68
assertEquals(messages.size, 1 )
@@ -75,7 +84,7 @@ class CodecTest {
75
84
val source = DecodedComposite (encodedContent = textContent)
76
85
aliceConversation.send(
77
86
content = source,
78
- options = SendOptions (contentType = CompositeCodec ().contentType)
87
+ options = SendOptions (contentType = CompositeCodec ().contentType),
79
88
)
80
89
val messages = aliceConversation.messages()
81
90
val decoded: DecodedComposite ? = messages[0 ].content()
@@ -95,12 +104,12 @@ class CodecTest {
95
104
val source = DecodedComposite (
96
105
parts = listOf (
97
106
DecodedComposite (encodedContent = textContent),
98
- DecodedComposite (parts = listOf (DecodedComposite (encodedContent = numberContent)))
99
- )
107
+ DecodedComposite (parts = listOf (DecodedComposite (encodedContent = numberContent))),
108
+ ),
100
109
)
101
110
aliceConversation.send(
102
111
content = source,
103
- options = SendOptions (contentType = CompositeCodec ().contentType)
112
+ options = SendOptions (contentType = CompositeCodec ().contentType),
104
113
)
105
114
val messages = aliceConversation.messages()
106
115
val decoded: DecodedComposite ? = messages[0 ].content()
@@ -109,4 +118,93 @@ class CodecTest {
109
118
assertEquals(" sup" , part1.content())
110
119
assertEquals(3.14 , part2.content())
111
120
}
121
+
122
+ @Test
123
+ fun testCanGetPushInfoBeforeDecoded () {
124
+ val codec = NumberCodec ()
125
+ Client .register(codec = codec)
126
+ val fixtures = fixtures()
127
+ val aliceClient = fixtures.aliceClient!!
128
+ val aliceConversation =
129
+ aliceClient.conversations.newConversation(fixtures.bob.walletAddress)
130
+ aliceConversation.send(
131
+ content = 3.14 ,
132
+ options = SendOptions (contentType = codec.contentType),
133
+ )
134
+ val messages = aliceConversation.messages()
135
+ assert (messages.isNotEmpty())
136
+
137
+ val message = MessageV2Builder .buildEncode(
138
+ client = aliceClient,
139
+ encodedContent = messages[0 ].encodedContent,
140
+ topic = aliceConversation.topic,
141
+ keyMaterial = aliceConversation.keyMaterial!! ,
142
+ codec = codec,
143
+ )
144
+
145
+ assertEquals(false , message.shouldPush)
146
+ assertEquals(true , message.senderHmac?.isNotEmpty())
147
+ }
148
+
149
+ @Test
150
+ fun testReturnsAllHMACKeys () {
151
+ val alix = PrivateKeyBuilder ()
152
+ val clientOptions =
153
+ ClientOptions (api = ClientOptions .Api (env = XMTPEnvironment .LOCAL , isSecure = false ))
154
+ val alixClient = Client ().create(alix, clientOptions)
155
+ val conversations = mutableListOf<Conversation >()
156
+ repeat(5 ) {
157
+ val account = PrivateKeyBuilder ()
158
+ val client = Client ().create(account, clientOptions)
159
+ conversations.add(
160
+ alixClient.conversations.newConversation(
161
+ client.address,
162
+ context = InvitationV1ContextBuilder .buildFromConversation(conversationId = " hi" )
163
+ )
164
+ )
165
+ }
166
+
167
+ val thirtyDayPeriodsSinceEpoch = Instant .now().epochSecond / 60 / 60 / 24 / 30
168
+
169
+ val hmacKeys = alixClient.conversations.getHmacKeys()
170
+
171
+ val topics = hmacKeys.hmacKeysMap.keys
172
+ conversations.forEach { convo ->
173
+ assertTrue(topics.contains(convo.topic))
174
+ }
175
+
176
+ val topicHmacs = mutableMapOf<String , ByteArray >()
177
+ val headerBytes = ByteArray (10 )
178
+
179
+ conversations.forEach { conversation ->
180
+ val topic = conversation.topic
181
+ val payload = TextCodec ().encode(content = " Hello, world!" )
182
+
183
+ val message = MessageV2Builder .buildEncode(
184
+ client = alixClient,
185
+ encodedContent = payload,
186
+ topic = topic,
187
+ keyMaterial = headerBytes,
188
+ codec = TextCodec ()
189
+ )
190
+
191
+ val keyMaterial = conversation.keyMaterial
192
+ val info = " $thirtyDayPeriodsSinceEpoch -${alixClient.address} "
193
+ val key = Crypto .deriveKey(keyMaterial!! , ByteArray (0 ), info.toByteArray())
194
+ val hmac = Crypto .calculateMac(key, headerBytes)
195
+
196
+ topicHmacs[topic] = hmac
197
+ }
198
+
199
+ hmacKeys.hmacKeysMap.forEach { (topic, hmacData) ->
200
+ hmacData.valuesList.forEachIndexed { idx, hmacKeyThirtyDayPeriod ->
201
+ val valid = verifyHmacSignature(
202
+ hmacKeyThirtyDayPeriod.hmacKey.toByteArray(),
203
+ topicHmacs[topic]!! ,
204
+ headerBytes
205
+ )
206
+ assertTrue(valid == (idx == 1 ))
207
+ }
208
+ }
209
+ }
112
210
}
0 commit comments