Skip to content

Commit 26b1575

Browse files
authored
Implement message forwarding (#24)
Signed-off-by: conanoc <conanoc@gmail.com>
1 parent a1de508 commit 26b1575

File tree

15 files changed

+116
-28
lines changed

15 files changed

+116
-28
lines changed

ariesframework/build.gradle

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ dependencies {
7070

7171
ext["githubUsername"] = null
7272
ext["githubToken"] = null
73-
ext["version"] = "2.0.0"
73+
ext["version"] = null
7474

7575
def secretPropsFile = project.rootProject.file("local.properties")
7676
if (secretPropsFile.exists()) {
@@ -83,7 +83,11 @@ if (secretPropsFile.exists()) {
8383
ext["version"] = System.getenv("VERSION")
8484
}
8585

86-
ext["version"] = ext["version"].replaceFirst("v", "")
86+
if (ext["version"] != null) {
87+
ext["version"] = ext["version"].replaceFirst("v", "")
88+
} else {
89+
ext["version"] = "2.0.0"
90+
}
8791

8892
def getExtraString(name) {
8993
try {

ariesframework/src/androidTest/java/org/hyperledger/ariesframework/TestHelper.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import org.hyperledger.ariesframework.proofs.models.AutoAcceptProof
2323
import org.slf4j.LoggerFactory
2424
import java.io.File
2525
import java.util.UUID
26+
import kotlin.time.Duration
2627
import kotlin.time.Duration.Companion.seconds
2728

2829
object TestHelper {
@@ -182,13 +183,15 @@ object TestHelper {
182183
)
183184
}
184185

185-
suspend fun makeConnection(agentA: Agent, agentB: Agent): Pair<ConnectionRecord, ConnectionRecord> {
186+
suspend fun makeConnection(agentA: Agent, agentB: Agent, waitFor: Duration = 0.1.seconds): Pair<ConnectionRecord, ConnectionRecord> {
186187
logger.debug("Making connection")
187188
val message = agentA.connections.createConnection()
188189
val invitation = message.payload as ConnectionInvitationMessage
189190
var agentAConnection = message.connection
190191
var agentBConnection = agentB.connections.receiveInvitation(invitation)
191192

193+
delay(waitFor)
194+
192195
agentAConnection = agentA.connectionRepository.getById(agentAConnection.id)
193196
agentBConnection = agentB.connectionRepository.getById(agentBConnection.id)
194197
check(agentAConnection.state == ConnectionState.Complete && agentBConnection.state == ConnectionState.Complete) {

ariesframework/src/androidTest/java/org/hyperledger/ariesframework/agent/AgentTest.kt

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class AgentTest {
2222
lateinit var agent: Agent
2323
private val mediatorInvitationUrl = "http://10.0.2.2:3001/invitation"
2424
private val agentInvitationUrl = "http://10.0.2.2:3002/invitation"
25+
private val publicMediatorUrl = "https://public.mediator.indiciotech.io?c_i=eyJAdHlwZSI6ICJkaWQ6c292OkJ6Q2JzTlloTXJqSGlxWkRUVUFTSGc7c3BlYy9jb25uZWN0aW9ucy8xLjAvaW52aXRhdGlvbiIsICJAaWQiOiAiMDVlYzM5NDItYTEyOS00YWE3LWEzZDQtYTJmNDgwYzNjZThhIiwgInNlcnZpY2VFbmRwb2ludCI6ICJodHRwczovL3B1YmxpYy5tZWRpYXRvci5pbmRpY2lvdGVjaC5pbyIsICJyZWNpcGllbnRLZXlzIjogWyJDc2dIQVpxSktuWlRmc3h0MmRIR3JjN3U2M3ljeFlEZ25RdEZMeFhpeDIzYiJdLCAibGFiZWwiOiAiSW5kaWNpbyBQdWJsaWMgTWVkaWF0b3IifQ==" // ktlint-disable max-line-length
2526

2627
@After
2728
fun tearDown() = runTest {
@@ -31,7 +32,7 @@ class AgentTest {
3132
@Test @LargeTest
3233
fun testConnection() = runTest {
3334
val context = InstrumentationRegistry.getInstrumentation().targetContext
34-
val url = "https://public.mediator.indiciotech.io?c_i=eyJAdHlwZSI6ICJkaWQ6c292OkJ6Q2JzTlloTXJqSGlxWkRUVUFTSGc7c3BlYy9jb25uZWN0aW9ucy8xLjAvaW52aXRhdGlvbiIsICJAaWQiOiAiMDVlYzM5NDItYTEyOS00YWE3LWEzZDQtYTJmNDgwYzNjZThhIiwgInNlcnZpY2VFbmRwb2ludCI6ICJodHRwczovL3B1YmxpYy5tZWRpYXRvci5pbmRpY2lvdGVjaC5pbyIsICJyZWNpcGllbnRLZXlzIjogWyJDc2dIQVpxSktuWlRmc3h0MmRIR3JjN3U2M3ljeFlEZ25RdEZMeFhpeDIzYiJdLCAibGFiZWwiOiAiSW5kaWNpbyBQdWJsaWMgTWVkaWF0b3IifQ==" // ktlint-disable max-line-length
35+
val url = publicMediatorUrl
3536
val invitation = ConnectionInvitationMessage.fromUrl(url)
3637

3738
agent = Agent(context, TestHelper.getBaseConfig())
@@ -169,4 +170,34 @@ class AgentTest {
169170

170171
delay(120.seconds)
171172
}
173+
174+
// For two agents behind mediators to connect, message forward is needed.
175+
@Test @LargeTest
176+
fun testMessageForward() = runBlocking {
177+
val context = InstrumentationRegistry.getInstrumentation().targetContext
178+
val aliceConfig = TestHelper.getBaseConfig("alice")
179+
aliceConfig.mediatorPickupStrategy = MediatorPickupStrategy.Implicit
180+
aliceConfig.mediatorConnectionsInvite = publicMediatorUrl
181+
aliceConfig.mediatorPollingInterval = 1
182+
183+
val alice = Agent(context, aliceConfig)
184+
agent = alice
185+
alice.initialize()
186+
187+
val faberConfig = TestHelper.getBaseConfig("faber")
188+
faberConfig.mediatorPickupStrategy = MediatorPickupStrategy.Implicit
189+
faberConfig.mediatorConnectionsInvite = publicMediatorUrl
190+
faberConfig.mediatorPollingInterval = 1
191+
192+
val faber = Agent(context, faberConfig)
193+
faber.initialize()
194+
195+
val (aliceConnection, faberConnection) = TestHelper.makeConnection(alice, faber, 3.seconds)
196+
assertEquals(aliceConnection.state, ConnectionState.Complete)
197+
assertEquals(faberConnection.state, ConnectionState.Complete)
198+
199+
// alice will be reset on tearDown
200+
faber.reset()
201+
}
202+
172203
}

ariesframework/src/main/java/org/hyperledger/ariesframework/agent/AgentMessage.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,14 +68,14 @@ object MessageSerializer : JsonContentPolymorphicSerializer<AgentMessage>(AgentM
6868
return if (serializers.containsKey(type)) {
6969
serializers[type]!!
7070
} else {
71-
logger.warn("Message type $type is not registered for JSON decoding")
71+
logger.error("Message type $type is not registered for JSON decoding")
7272
AgentMessage.serializer()
7373
}
7474
}
7575

7676
fun encodeToString(message: AgentMessage): String {
7777
if (!serializers.containsKey(message.type)) {
78-
logger.warn("Message type ${message.type} is not registered for JSON encoding")
78+
logger.error("Message type ${message.type} is not registered for JSON encoding")
7979
return Json.encodeToString(message)
8080
}
8181
return encoder.encodeToString(serializers[message.type]!!, message)

ariesframework/src/main/java/org/hyperledger/ariesframework/agent/MessageSender.kt

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import org.hyperledger.ariesframework.connection.models.didauth.DidComm
1111
import org.hyperledger.ariesframework.connection.models.didauth.DidCommService
1212
import org.hyperledger.ariesframework.connection.repository.ConnectionRecord
1313
import org.hyperledger.ariesframework.routing.messages.BatchPickupMessage
14+
import org.hyperledger.ariesframework.routing.messages.ForwardMessage
1415
import org.slf4j.LoggerFactory
1516

1617
class MessageSender(val agent: Agent) {
@@ -115,15 +116,15 @@ class MessageSender(val agent: Agent) {
115116
return listOf(service)
116117
}
117118
if (connection.outOfBandInvitation != null) {
118-
return connection.outOfBandInvitation!!.services.mapNotNull { it.asDidDocService() as DidComm }
119+
return connection.outOfBandInvitation!!.services.mapNotNull { it.asDidCommService() }
119120
}
120121
}
121122

122123
return emptyList()
123124
}
124125

125126
private suspend fun sendMessageToService(message: AgentMessage, service: DidComm, senderKey: String, connectionId: String) {
126-
val keys = EnvelopeKeys(service.recipientKeys, emptyList(), senderKey)
127+
val keys = EnvelopeKeys(service.recipientKeys, service.routingKeys ?: emptyList(), senderKey)
127128

128129
val outboundPackage = packMessage(message, keys, service.serviceEndpoint, connectionId)
129130
val outboundTransport = outboundTransportForEndpoint(service.serviceEndpoint)
@@ -132,9 +133,18 @@ class MessageSender(val agent: Agent) {
132133
}
133134

134135
private suspend fun packMessage(message: AgentMessage, keys: EnvelopeKeys, endpoint: String, connectionId: String): OutboundPackage {
135-
val encryptedMessage = agent.wallet.pack(message, keys.recipientKeys, keys.senderKey)
136+
var encryptedMessage = agent.wallet.pack(message, keys.recipientKeys, keys.senderKey)
137+
138+
var recipientKeys = keys.recipientKeys
139+
for (routingKey in keys.routingKeys) {
140+
val forwardMessage = ForwardMessage(recipientKeys[0], encryptedMessage)
141+
if (agent.agentConfig.useLegacyDidSovPrefix) {
142+
forwardMessage.replaceNewDidCommPrefixWithLegacyDidSov()
143+
}
144+
recipientKeys = listOf(routingKey)
145+
encryptedMessage = agent.wallet.pack(forwardMessage, recipientKeys, keys.senderKey)
146+
}
136147

137-
// TODO: support message forwarding
138148
return OutboundPackage(encryptedMessage, message.requestResponse(), endpoint, connectionId)
139149
}
140150

ariesframework/src/main/java/org/hyperledger/ariesframework/agent/WsOutboundTransport.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,15 @@ class WsOutboundTransport(val agent: Agent) : OutboundTransport, WebSocketListen
5656
}
5757

5858
override fun onMessage(webSocket: WebSocket, text: String) {
59-
logger.debug("Received message: {}", text)
59+
logger.debug("Agent ${agent.agentConfig.label} received a message string")
6060
val encryptedMessage = Json.decodeFromString<EncryptedMessage>(text)
6161
GlobalScope.launch {
6262
agent.receiveMessage(encryptedMessage)
6363
}
6464
}
6565

6666
override fun onMessage(webSocket: WebSocket, bytes: ByteString) {
67-
logger.debug("Received message: {}", bytes.utf8())
67+
logger.debug("Agent ${agent.agentConfig.label} received a message bytes.")
6868
val encryptedMessage = Json.decodeFromString<EncryptedMessage>(bytes.utf8())
6969
GlobalScope.launch {
7070
agent.receiveMessage(encryptedMessage)

ariesframework/src/main/java/org/hyperledger/ariesframework/connection/ConnectionService.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ class ConnectionService(val agent: Agent) {
300300
suspend fun createResponse(connectionId: String): OutboundMessage {
301301
logger.debug("Creating connection response for connection: $connectionId")
302302
var connectionRecord = connectionRepository.getById(connectionId)
303+
logger.debug("Connection record: {}", Json.encodeToString(connectionRecord))
303304
assert(connectionRecord.state == ConnectionState.Requested)
304305
assert(connectionRecord.role == ConnectionRole.Inviter)
305306
val threadId = connectionRecord.threadId ?: throw Exception("Connection record with id ${connectionRecord.id} has no thread id.")

ariesframework/src/main/java/org/hyperledger/ariesframework/connection/models/didauth/DidCommService.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@ open class DidCommService(
1212
override val routingKeys: List<String>? = null,
1313
override val priority: Int? = 0,
1414
override val accept: List<String>? = null,
15-
) : DidComm
15+
) : DidDocService(), DidComm

ariesframework/src/main/java/org/hyperledger/ariesframework/connection/models/didauth/DidDocService.kt

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
package org.hyperledger.ariesframework.connection.models.didauth
22

3+
import kotlinx.serialization.Serializable
34
import kotlinx.serialization.modules.SerializersModule
45
import kotlinx.serialization.modules.polymorphic
56
import kotlinx.serialization.modules.subclass
67

7-
interface DidDocService {
8-
val id: String
9-
val serviceEndpoint: String
8+
@Serializable
9+
sealed class DidDocService {
10+
abstract val id: String
11+
abstract val serviceEndpoint: String
1012
}
1113

12-
interface DidComm : DidDocService {
14+
interface DidComm {
15+
val id: String
16+
val serviceEndpoint: String
1317
val recipientKeys: List<String>
1418
val routingKeys: List<String>?
1519
val priority: Int?

ariesframework/src/main/java/org/hyperledger/ariesframework/connection/models/didauth/DidDocumentService.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ import kotlinx.serialization.Serializable
88
class DidDocumentService(
99
override val id: String,
1010
override val serviceEndpoint: String,
11-
) : DidDocService
11+
) : DidDocService()

ariesframework/src/main/java/org/hyperledger/ariesframework/connection/models/didauth/IndyAgentService.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@ class IndyAgentService(
1212
override val routingKeys: List<String>? = null,
1313
override val priority: Int? = 0,
1414
override val accept: List<String>? = null,
15-
) : DidComm
15+
) : DidDocService(), DidComm

ariesframework/src/main/java/org/hyperledger/ariesframework/oob/models/OutOfBandDidCommService.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import org.hyperledger.ariesframework.util.DIDParser
1616

1717
@Serializable(with = OutOfBandDidCommServiceSerializer::class)
1818
abstract class OutOfBandDidCommService {
19-
abstract fun asDidDocService(): DidDocService?
19+
abstract fun asDidCommService(): DidCommService?
2020
}
2121

2222
object OutOfBandDidCommServiceSerializer : JsonContentPolymorphicSerializer<OutOfBandDidCommService>(OutOfBandDidCommService::class) {
@@ -35,7 +35,7 @@ class OutOfBandDidDocumentService(
3535
val routingKeys: List<String>? = null,
3636
val accept: List<String>? = null,
3737
) : OutOfBandDidCommService() {
38-
override fun asDidDocService(): DidDocService? {
38+
override fun asDidCommService(): DidCommService? {
3939
return DidCommService(
4040
id,
4141
serviceEndpoint,
@@ -49,7 +49,7 @@ class OutOfBandDidDocumentService(
4949
class PublicDidService(
5050
val did: String,
5151
) : OutOfBandDidCommService() {
52-
override fun asDidDocService(): DidDocService? {
52+
override fun asDidCommService(): DidCommService? {
5353
return null
5454
}
5555
}

ariesframework/src/main/java/org/hyperledger/ariesframework/routing/MediationRecipient.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import org.hyperledger.ariesframework.routing.handlers.MediationDenyHandler
2121
import org.hyperledger.ariesframework.routing.handlers.MediationGrantHandler
2222
import org.hyperledger.ariesframework.routing.messages.BatchMessage
2323
import org.hyperledger.ariesframework.routing.messages.BatchPickupMessage
24+
import org.hyperledger.ariesframework.routing.messages.ForwardMessage
2425
import org.hyperledger.ariesframework.routing.messages.KeylistUpdate
2526
import org.hyperledger.ariesframework.routing.messages.KeylistUpdateAction
2627
import org.hyperledger.ariesframework.routing.messages.KeylistUpdateMessage
@@ -32,6 +33,7 @@ import org.hyperledger.ariesframework.routing.repository.MediationRecord
3233
import org.hyperledger.ariesframework.routing.repository.MediationRepository
3334
import org.hyperledger.ariesframework.routing.repository.MediationRole
3435
import org.hyperledger.ariesframework.routing.repository.MediationState
36+
import org.hyperledger.ariesframework.util.DIDParser
3537
import org.slf4j.LoggerFactory
3638
import java.util.Timer
3739
import kotlin.concurrent.timer
@@ -66,6 +68,7 @@ class MediationRecipient(private val agent: Agent, private val dispatcher: Dispa
6668
MessageSerializer.registerMessage(MediationDenyMessage.type, MediationDenyMessage::class)
6769
MessageSerializer.registerMessage(MediationGrantMessage.type, MediationGrantMessage::class)
6870
MessageSerializer.registerMessage(MediationRequestMessage.type, MediationRequestMessage::class)
71+
MessageSerializer.registerMessage(ForwardMessage.type, ForwardMessage::class)
6972
}
7073

7174
suspend fun getRouting(): Routing {
@@ -224,7 +227,13 @@ class MediationRecipient(private val agent: Agent, private val dispatcher: Dispa
224227
mediationRecord.assertState(MediationState.Requested)
225228

226229
mediationRecord.endpoint = message.endpoint
227-
mediationRecord.routingKeys = message.routingKeys
230+
mediationRecord.routingKeys = message.routingKeys.map {key ->
231+
if (key.startsWith("did:key:")) {
232+
DIDParser.convertDidKeyToVerkey(key)
233+
} else {
234+
key
235+
}
236+
}
228237
mediationRecord.state = MediationState.Granted
229238
repository.update(mediationRecord)
230239
agent.eventBus.publish(AgentEvents.MediationEvent(mediationRecord.copy()))
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.hyperledger.ariesframework.routing.messages
2+
3+
import kotlinx.serialization.SerialName
4+
import kotlinx.serialization.Serializable
5+
import org.hyperledger.ariesframework.EncryptedMessage
6+
import org.hyperledger.ariesframework.agent.AgentMessage
7+
8+
@Serializable
9+
class ForwardMessage(
10+
val to: String,
11+
@SerialName("msg")
12+
val message: EncryptedMessage,
13+
) : AgentMessage(generateId(), ForwardMessage.type) {
14+
companion object {
15+
const val type = "https://didcomm.org/routing/1.0/forward"
16+
}
17+
}

ariesframework/src/test/java/org/hyperledger/ariesframework/SerializationTest.kt

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import kotlinx.serialization.json.jsonPrimitive
1111
import kotlinx.serialization.modules.SerializersModule
1212
import kotlinx.serialization.modules.polymorphic
1313
import kotlinx.serialization.modules.subclass
14+
import org.hyperledger.ariesframework.connection.models.didauth.DidDocService
1415
import org.hyperledger.ariesframework.proofs.models.RequestedCredentials
1516
import org.hyperledger.ariesframework.util.concurrentForEach
1617
import org.hyperledger.ariesframework.util.concurrentMap
@@ -29,6 +30,14 @@ sealed class Service {
2930
abstract val id: String
3031
}
3132

33+
interface DidComm {
34+
val id: String
35+
val serviceEndpoint: String
36+
val recipientKeys: List<String>
37+
val routingKeys: List<String>?
38+
val priority: Int?
39+
}
40+
3241
@Serializable
3342
@SerialName("DidDocumentService")
3443
data class Service1(
@@ -40,11 +49,11 @@ data class Service1(
4049
@SerialName("IndyAgent")
4150
data class Service2(
4251
override val id: String,
43-
val serviceEndpoint: String,
44-
val recipientKeys: List<String>,
45-
val routingKeys: List<String>,
46-
val priority: Int,
47-
) : Service()
52+
override val serviceEndpoint: String,
53+
override val recipientKeys: List<String>,
54+
override val routingKeys: List<String>,
55+
override val priority: Int,
56+
) : Service(), DidComm
4857

4958
@Serializable
5059
@SerialName("did-communication")

0 commit comments

Comments
 (0)