diff --git a/mercury/prism-mediator/build.sbt b/mercury/prism-mediator/build.sbt index 577993a015..d143fb3fa8 100644 --- a/mercury/prism-mediator/build.sbt +++ b/mercury/prism-mediator/build.sbt @@ -17,6 +17,7 @@ def didScalaAUX = lazy val V = new { val munit = "1.0.0-M6" // "0.7.29" + val munitZio = "0.1.1" // https://mvnrepository.com/artifact/dev.zio/zio val zio = "2.0.0" @@ -42,6 +43,9 @@ lazy val D = new { // For munit https://scalameta.org/munit/docs/getting-started.html#scalajs-setup val munit = Def.setting("org.scalameta" %% "munit" % V.munit % Test) + // For munit zio https://github.com/poslegm/munit-zio + val munitZio = Def.setting("com.github.poslegm" %% "munit-zio" % V.munitZio % Test) + } // ######################### @@ -74,7 +78,15 @@ lazy val protocolInvitation = project .in(file("protocol-invitation")) .settings(name := "mercury-protocol-invitation", version := VERSION) .settings(libraryDependencies += D.zio.value) - .settings(libraryDependencies ++= Seq(D.circeCore.value, D.circeGeneric.value, D.circeParser.value)) + .settings( + libraryDependencies ++= Seq( + D.circeCore.value, + D.circeGeneric.value, + D.circeParser.value, + D.munit.value, + D.munitZio.value + ) + ) .dependsOn(models) lazy val protocolConnection = project diff --git a/mercury/prism-mediator/mediator/src/main/scala/io/iohk/atala/mercury/mediator/Endpoints.scala b/mercury/prism-mediator/mediator/src/main/scala/io/iohk/atala/mercury/mediator/Endpoints.scala index 58b166fda6..d6988b1234 100644 --- a/mercury/prism-mediator/mediator/src/main/scala/io/iohk/atala/mercury/mediator/Endpoints.scala +++ b/mercury/prism-mediator/mediator/src/main/scala/io/iohk/atala/mercury/mediator/Endpoints.scala @@ -1,3 +1,4 @@ +/* package io.iohk.atala.mercury.mediator import io.circe.generic.auto.* @@ -102,3 +103,4 @@ object Endpoints { sendMessageServerEndpoint ) } + */ diff --git a/mercury/prism-mediator/mediator/src/main/scala/io/iohk/atala/mercury/mediator/Mediator.scala b/mercury/prism-mediator/mediator/src/main/scala/io/iohk/atala/mercury/mediator/Mediator.scala index 369ffc0e23..bf9202b404 100644 --- a/mercury/prism-mediator/mediator/src/main/scala/io/iohk/atala/mercury/mediator/Mediator.scala +++ b/mercury/prism-mediator/mediator/src/main/scala/io/iohk/atala/mercury/mediator/Mediator.scala @@ -1,3 +1,4 @@ +/* package io.iohk.atala.mercury.mediator import cats.syntax.all._ @@ -63,3 +64,4 @@ object Mediator extends ZIOAppDefault { .provide(MediatorDidComm.mediator ++ MailStorage.layer) .exitCode } + */ diff --git a/mercury/prism-mediator/protocol-invitation/Invitation-Protocol.md b/mercury/prism-mediator/protocol-invitation/Invitation-Protocol.md index 43135b4451..d46e28aba0 100644 --- a/mercury/prism-mediator/protocol-invitation/Invitation-Protocol.md +++ b/mercury/prism-mediator/protocol-invitation/Invitation-Protocol.md @@ -1,57 +1,49 @@ # Invitation Protocol -This Protocol is parte of the DIDComm Messaging Specification. +This Protocol is parte of the **DIDComm Messaging Specification** but also **Aries RFC 0434: Out-of-Band Protocol 1.1** Its a out-of-band style protocol. +The out-of-band protocol is used when you wish to engage with another agent and you don't have a DIDComm connection to use for the interaction. + See [https://identity.foundation/didcomm-messaging/spec/#invitation] +See [https://github.com/hyperledger/aries-rfcs/blob/main/features/0434-outofband/README.md] ## PIURI -`https://didcomm.org/out-of-band/2.0/invitation` +Version 1.0: `https://didcomm.org/out-of-band/1.0/invitation` + +Version 2.0: `https://didcomm.org/out-of-band/2.0/invitation` ### Roles -- Invitee +- Sender - Will create the message `https://didcomm.org/out-of-band/2.0/invitation` -- Inviter +- Receiver - Will accept the invitation ### Notes - Invitation has expiry date -### Invitee create invitation message - -```mermaid -stateDiagram-v2 - [*] --> [*] -``` - -### Inviter accepting invitation (Flow Diagram) +### Sender create invitation message (Flow Diagram) ```mermaid stateDiagram-v2 - [*] --> Invited:Send Invitation - Invited --> Requested:Recieve connection request - Requested --> Responded:Send Connection Response - Responded --> Invited: Send Connection Error Retry if possible stay in same state - Responded --> Completed:Recieve Acknowledgement - Completed --> Responded:Recieve Acknowledgement Error Retry if possible stay in same state - Completed --> [*] + [*] --> Initial + Initial --> await_response:Send out-of-band invitation message + await_response --> done:receive DIDCOMM response message(single use) + await_response --> await_response:recieve DIDCOMM response message(multi use message) + await_response --> error:recieve problem report response + done --> [*] ``` ---- - -### Invitee Confirming (Flow Diagram) +### Receiver accepting invitation (Flow Diagram) ```mermaid stateDiagram-v2 - [*] --> Invited:Recieve Invitation - Invited --> Requested: Send Connection Request - Requested --> Responded: Recieve Connection Response - Responded --> Invited: Send Connection Error Response Retry if possible stay in same state - Responded --> Completed: Send Acknowledgement - Completed --> Responded: Recieve Acknowledgement Error Retry if possible stay in same sate - Completed --> [*] + [*] --> Initial + Initial --> prepare_response:recieve out-of-band invitation message + prepare_response --> done:send DIDCOMM response message + done --> [*] ``` diff --git a/mercury/prism-mediator/protocol-invitation/src/main/scala/io/iohk/atala/mercury/protocol/invitation/AttachmentDescriptor.scala b/mercury/prism-mediator/protocol-invitation/src/main/scala/io/iohk/atala/mercury/protocol/invitation/AttachmentDescriptor.scala new file mode 100644 index 0000000000..49cfefda41 --- /dev/null +++ b/mercury/prism-mediator/protocol-invitation/src/main/scala/io/iohk/atala/mercury/protocol/invitation/AttachmentDescriptor.scala @@ -0,0 +1,41 @@ +package io.iohk.atala.mercury.protocol.invitation +import java.util.{Base64 => JBase64} +import io.circe.Encoder +import io.circe.generic.semiauto._ +import io.circe.syntax._ + +sealed trait AttachmentData + +final case class Base64(base64: String) extends AttachmentData + +/** @see + * https://github.com/hyperledger/aries-rfcs/tree/main/concepts/0017-attachments + * @param `@id` + * @param `mime-type` + * @param data + * @param filename + * @param lastmod_time + * @param byte_count + * @param description + */ +final case class AttachmentDescriptor( + `@id`: Option[String] = None, + `mime-type`: Option[String] = None, + data: Base64 = Base64(""), + filename: Option[String] = None, + lastmod_time: Option[String] = None, + byte_count: Option[Int] = None, + description: Option[String] = None +) + +object AttachmentDescriptor { + def buildAttachment[A: Encoder]( + id: Option[String] = None, + payload: A, + mimeType: Option[String] = Some("application/json") + ): AttachmentDescriptor = { + val encoded = JBase64.getUrlEncoder.encodeToString(payload.asJson.noSpaces.getBytes) + AttachmentDescriptor(id, mimeType, Base64(encoded)) + } + +} diff --git a/mercury/prism-mediator/protocol-invitation/src/main/scala/io/iohk/atala/mercury/protocol/invitation/Invitation.scala b/mercury/prism-mediator/protocol-invitation/src/main/scala/io/iohk/atala/mercury/protocol/invitation/Invitation.scala deleted file mode 100644 index 6f9e1b3d16..0000000000 --- a/mercury/prism-mediator/protocol-invitation/src/main/scala/io/iohk/atala/mercury/protocol/invitation/Invitation.scala +++ /dev/null @@ -1,17 +0,0 @@ -package io.iohk.atala.mercury.protocol.invitation - -import io.iohk.atala.mercury.model.PIURI - -object Invitation { - def `type`: PIURI = "https://atalaprism.io/mercury/out-of-band/1.0/invitation" - -} - -case class Invitation( - id: String, - `@type`: String, - label: String, - body: Body, - handshake_protocols: Seq[String], - service: Seq[Service] // FIXME service: Seq[ServiceType] -) diff --git a/mercury/prism-mediator/protocol-invitation/src/main/scala/io/iohk/atala/mercury/protocol/invitation/InvitationCodec.scala b/mercury/prism-mediator/protocol-invitation/src/main/scala/io/iohk/atala/mercury/protocol/invitation/InvitationCodec.scala new file mode 100644 index 0000000000..5dacbbe5d1 --- /dev/null +++ b/mercury/prism-mediator/protocol-invitation/src/main/scala/io/iohk/atala/mercury/protocol/invitation/InvitationCodec.scala @@ -0,0 +1,51 @@ +package io.iohk.atala.mercury.protocol.invitation +import cats.implicits._ +import io.circe.syntax._ +import io.circe.generic.semiauto._ +import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder} +import io.circe.{Decoder, Encoder, HCursor, Json} +import io.iohk.atala.mercury.protocol.invitation.v1.{Invitation => InvitationV1} +import io.iohk.atala.mercury.protocol.invitation.v2.{Invitation => InvitationV2} + +object InvitationCodec { + + implicit val serviceEncoder: Encoder[Service] = deriveEncoder[Service] + implicit val serviceDecoder: Decoder[Service] = deriveDecoder[Service] + + implicit val didEncoder: Encoder[Did] = (a: Did) => Json.fromString(a.did) + implicit val didDecoder: Decoder[Did] = (c: HCursor) => c.value.as[String].map(did => Did(did)) + + implicit val serviceTypeEncoder: Encoder[ServiceType] = Encoder.instance { + case service @ Service(_, _, _, _, _) => service.asJson + case did @ Did(_) => did.asJson + } + + implicit val serviceTypeDecoder: Decoder[ServiceType] = + List[Decoder[ServiceType]]( + Decoder[Service].widen, + Decoder[Did].widen + ).reduceLeft(_ or _) + + implicit val base64Encoder: Encoder[Base64] = deriveEncoder[Base64] + implicit val base64eDecoder: Decoder[Base64] = deriveDecoder[Base64] + + implicit val attachmentDescriptorEncoder: Encoder[AttachmentDescriptor] = deriveEncoder[AttachmentDescriptor] + implicit val attachmentDescriptorDecoder: Decoder[AttachmentDescriptor] = deriveDecoder[AttachmentDescriptor] + + implicit val invitationEncoderV1: Encoder[InvitationV1] = (entity: InvitationV1) => + Json.obj( + "@id" -> Json.fromString(entity.`@id`), + "@type" -> Json.fromString(entity.`@type`), + "label" -> Json.fromString(entity.label), + "goal" -> Json.fromString(entity.goal), + "goal_code" -> Json.fromString(entity.goal_code), + "accept" -> entity.accept.asJson, + "handshake_protocols" -> entity.handshake_protocols.asJson, + "requests~attach" -> entity.`requests~attach`.asJson, + "services" -> entity.services.asJson + ) + implicit val invitationDecoderV1: Decoder[InvitationV1] = deriveDecoder[InvitationV1] + + implicit val invitationEncoderV2: Encoder[InvitationV2] = deriveEncoder[InvitationV2] + implicit val invitationDecoderV2: Decoder[InvitationV2] = deriveDecoder[InvitationV2] +} diff --git a/mercury/prism-mediator/protocol-invitation/src/main/scala/io/iohk/atala/mercury/protocol/invitation/OutOfBand.scala b/mercury/prism-mediator/protocol-invitation/src/main/scala/io/iohk/atala/mercury/protocol/invitation/OutOfBand.scala index 58efaac836..92f2fc34f2 100644 --- a/mercury/prism-mediator/protocol-invitation/src/main/scala/io/iohk/atala/mercury/protocol/invitation/OutOfBand.scala +++ b/mercury/prism-mediator/protocol-invitation/src/main/scala/io/iohk/atala/mercury/protocol/invitation/OutOfBand.scala @@ -1,52 +1,25 @@ package io.iohk.atala.mercury.protocol.invitation -import io.circe.syntax.EncoderOps -import io.circe.generic.auto._ -import java.util.Base64 - -// TODO split into files - -case class CreateInvitation(goal: String, goal_code: String) - -case class CreateInvitationResponse(alias: String, invitation: Invitation, invitationUrl: String) - -case class Body(goal: String, goal_code: String, accept: Seq[String]) - -sealed trait ServiceType - -case class Service( - id: String, - serviceEndpoint: String, - `type`: String, - recipientKeys: Seq[String], - routingKeys: Seq[String] -) extends ServiceType - -case class Did(did: String) extends ServiceType - -object CreateInvitationResponse { - val accepts = Seq("didcomm/v2") - - def apply(goal: String, goal_code: String): CreateInvitationResponse = { - val body = Body(goal, goal_code, accepts) - val service = Service( - id = "did:prism:PR6vs6GEZ8rHaVgjg2WodM#did-communication", - serviceEndpoint = "http://localhost:8080/create-connection", - `type` = "did-communication", - recipientKeys = Seq("did:prism:PR6vs6GEZ8rHaVgjg2WodM"), - routingKeys = Seq("did:prism:PR6vs6GEZ8rHaVgjg2WodM") - ) - val invitation = Invitation( - id = "f3375429-b116-4224-b55f-563d7ef461f1", - `@type` = "https://didcomm.org/out-of-band/2.0/invitation", - label = "Mediator Invitation", - body = body, - handshake_protocols = Seq("https://didcomm.org/didexchange/1.0"), - service = Seq(service) - ) +import java.net.URL +import java.{util => ju} +import io.iohk.atala.mercury.protocol.invitation.v2._ +import io.iohk.atala.mercury.protocol.invitation.InvitationCodec._ +import io.circe._ +import io.circe.parser._ + +object OutOfBand { + + def parseLink(url: String): Option[String] = parseLink(new URL(url)) + def parseLink(url: URL): Option[String] = (url.getQuery() match { + case str if str.startsWith("_oob=") => Some(str.drop(5)) + case _ => None + }).map { e => + val decoder = ju.Base64.getUrlDecoder() + String(decoder.decode(e)) + } - val encodedString = Base64.getUrlEncoder.encodeToString(invitation.asJson.noSpaces.getBytes) - val invitationUrl = s"http://localhost:8080/invitation?_oob=$encodedString" - CreateInvitationResponse(alias = "Mediator", invitation, invitationUrl) + def parseInvitation(url: String): Option[Invitation] = { + parseLink(url).map(e => parse(e).getOrElse(???).as[Invitation].getOrElse(???)) } + } diff --git a/mercury/prism-mediator/protocol-invitation/src/main/scala/io/iohk/atala/mercury/protocol/invitation/ServiceType.scala b/mercury/prism-mediator/protocol-invitation/src/main/scala/io/iohk/atala/mercury/protocol/invitation/ServiceType.scala new file mode 100644 index 0000000000..eaaa0344d8 --- /dev/null +++ b/mercury/prism-mediator/protocol-invitation/src/main/scala/io/iohk/atala/mercury/protocol/invitation/ServiceType.scala @@ -0,0 +1,27 @@ +package io.iohk.atala.mercury.protocol.invitation +import cats.implicits._ +import io.circe.syntax._ +import io.circe.generic.semiauto._ +import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder} +import io.circe.{Decoder, Encoder, HCursor, Json} + +sealed trait ServiceType + +/** Service block + * @see + * https://github.com/hyperledger/aries-rfcs/tree/main/features/0434-outofband + * @param id + * @param `type` + * @param recipientKeys + * @param routingKeys + * @param serviceEndpoint + */ +case class Service( + id: String, + `type`: String, + recipientKeys: Seq[String], + routingKeys: Option[Seq[String]], + serviceEndpoint: String, +) extends ServiceType + +case class Did(did: String) extends ServiceType diff --git a/mercury/prism-mediator/protocol-invitation/src/main/scala/io/iohk/atala/mercury/protocol/invitation/package.scala b/mercury/prism-mediator/protocol-invitation/src/main/scala/io/iohk/atala/mercury/protocol/invitation/package.scala new file mode 100644 index 0000000000..a1c1693bb2 --- /dev/null +++ b/mercury/prism-mediator/protocol-invitation/src/main/scala/io/iohk/atala/mercury/protocol/invitation/package.scala @@ -0,0 +1,11 @@ +package io.iohk.atala.mercury.protocol + +import java.util.UUID + +package object invitation { + + /** provides new msg id + * @return + */ + def getNewMsgId: String = UUID.randomUUID().toString +} diff --git a/mercury/prism-mediator/protocol-invitation/src/main/scala/io/iohk/atala/mercury/protocol/invitation/v1/Invitation.scala b/mercury/prism-mediator/protocol-invitation/src/main/scala/io/iohk/atala/mercury/protocol/invitation/v1/Invitation.scala new file mode 100644 index 0000000000..b77159fbc2 --- /dev/null +++ b/mercury/prism-mediator/protocol-invitation/src/main/scala/io/iohk/atala/mercury/protocol/invitation/v1/Invitation.scala @@ -0,0 +1,35 @@ +package io.iohk.atala.mercury.protocol.invitation.v1 +import cats.implicits._ +import io.circe.syntax._ +import io.circe.generic.semiauto._ +import io.circe.{Encoder, Json} +import io.iohk.atala.mercury.model.PIURI + +import scala.annotation.targetName +import io.iohk.atala.mercury.protocol.invitation.AttachmentDescriptor +import io.iohk.atala.mercury.protocol.invitation.ServiceType + +/** Out-Of-Band invitation Example + * @see + * https://github.com/hyperledger/aries-rfcs/tree/main/features/0434-outofband + * + * @param `id` + * @param label + * @param goal + * @param goal_code + * @param handshake_protocols + * @param `request~attach` + * @param services + */ +final case class Invitation( + `@id`: String = io.iohk.atala.mercury.protocol.invitation.getNewMsgId, + label: String, + goal: String, + goal_code: String, + accept: Seq[String], + handshake_protocols: Seq[String], + `requests~attach`: Seq[AttachmentDescriptor], + services: Seq[ServiceType] +) { + val `@type`: PIURI = "https://didcomm.org/out-of-band/2.0/invitation" +} diff --git a/mercury/prism-mediator/protocol-invitation/src/main/scala/io/iohk/atala/mercury/protocol/invitation/v2/Invitation.scala b/mercury/prism-mediator/protocol-invitation/src/main/scala/io/iohk/atala/mercury/protocol/invitation/v2/Invitation.scala new file mode 100644 index 0000000000..1ad1504817 --- /dev/null +++ b/mercury/prism-mediator/protocol-invitation/src/main/scala/io/iohk/atala/mercury/protocol/invitation/v2/Invitation.scala @@ -0,0 +1,29 @@ +package io.iohk.atala.mercury.protocol.invitation.v2 +import cats.implicits._ +import io.circe.syntax._ +import io.circe.generic.semiauto._ +import io.circe.{Encoder, Json} +import io.iohk.atala.mercury.model.PIURI + +import scala.annotation.targetName +import io.iohk.atala.mercury.protocol.invitation.AttachmentDescriptor + +/** Out-Of-Band invitation + * @see + * https://identity.foundation/didcomm-messaging/spec/#invitation + */ +final case class Invitation( + `type`: PIURI, + id: String, + from: String, + body: Body, + attachments: Option[AttachmentDescriptor] // TODO +) { + assert(`type` == "https://didcomm.org/out-of-band/2.0/invitation") +} + +case class Body( + goal_code: String, + goal: String, + accept: Seq[String] +) diff --git a/mercury/prism-mediator/protocol-invitation/src/test/scala/io/iohk/atala/mercury/protocol/invitation/v1/InvitationV1Spec.scala b/mercury/prism-mediator/protocol-invitation/src/test/scala/io/iohk/atala/mercury/protocol/invitation/v1/InvitationV1Spec.scala new file mode 100644 index 0000000000..2584c80006 --- /dev/null +++ b/mercury/prism-mediator/protocol-invitation/src/test/scala/io/iohk/atala/mercury/protocol/invitation/v1/InvitationV1Spec.scala @@ -0,0 +1,70 @@ +package io.iohk.atala.mercury.protocol.invitation.v1 + +import munit.* +import zio.* +import cats.implicits._ +import io.circe.syntax._ +import io.circe.Json +import io.circe.generic.auto._ +import io.circe.parser._ + +import io.iohk.atala.mercury.protocol.invitation._ +import io.iohk.atala.mercury.protocol.invitation.InvitationCodec._ + +class InvitationV1Spec extends ZSuite { + + test("out-of-band invitation") { + val payload = "attachmentData" + val payloadBase64 = java.util.Base64.getUrlEncoder.encodeToString(Json.fromString(payload).noSpaces.getBytes) + + val expectedJson = parse(s"""{ + | "@id": "f3375429-b116-4224-b55f-563d7ef461f1", + | "@type": "https://didcomm.org/out-of-band/2.0/invitation", + | "label": "Faber College", + | "goal": "To issue a Faber College Graduate credential", + | "goal_code": "issue-vc", + | "accept": [ + | "didcomm/aip2;env=rfc587", + | "didcomm/aip2;env=rfc19" + | ], + | "handshake_protocols": [ + | "https://didcomm.org/didexchange/1.0", + | "https://didcomm.org/connections/1.0" + | ], + | "requests~attach": [ + | { + | "@id": "request-0", + | "mime-type": "application/json", + | "data": {"base64" : "$payloadBase64"} + | } + | ], + | "services": ["did:sov:LjgpST2rjsoxYegQDRm7EL"] + |}""".stripMargin).getOrElse(Json.Null) + + val service = Service( + id = "did:prism:PR6vs6GEZ8rHaVgjg2WodM#did-communication", + `type` = "did-communication", + recipientKeys = Seq("did:prism:PR6vs6GEZ8rHaVgjg2WodM"), + routingKeys = Some(Seq("did:prism:PR6vs6GEZ8rHaVgjg2WodM")), + serviceEndpoint = "http://localhost:8080/service" + ) + val did = Did("did:sov:LjgpST2rjsoxYegQDRm7EL") + val accepts = Seq("didcomm/aip2;env=rfc587", "didcomm/aip2;env=rfc19") + val handshakeProtocols = Seq("https://didcomm.org/didexchange/1.0", "https://didcomm.org/connections/1.0") + + val attachmentDescriptor = AttachmentDescriptor.buildAttachment(id = Some("request-0"), payload = payload) + + val invitation = Invitation( + `@id` = "f3375429-b116-4224-b55f-563d7ef461f1", + label = "Faber College", + goal = "To issue a Faber College Graduate credential", + goal_code = "issue-vc", + accept = accepts, + handshake_protocols = handshakeProtocols, + `requests~attach` = Seq(attachmentDescriptor), + services = Seq(did) + ) + val result = invitation.asJson.deepDropNullValues + assertEquals(result, expectedJson) + } +} diff --git a/mercury/prism-mediator/protocol-invitation/src/test/scala/io/iohk/atala/mercury/protocol/invitation/v2/OutOfBandSpec.scala b/mercury/prism-mediator/protocol-invitation/src/test/scala/io/iohk/atala/mercury/protocol/invitation/v2/OutOfBandSpec.scala new file mode 100644 index 0000000000..4ac93bf811 --- /dev/null +++ b/mercury/prism-mediator/protocol-invitation/src/test/scala/io/iohk/atala/mercury/protocol/invitation/v2/OutOfBandSpec.scala @@ -0,0 +1,26 @@ +package io.iohk.atala.mercury.protocol.invitation + +import munit.* +import io.iohk.atala.mercury.protocol.invitation.v2._ + +class OutOfBandSpec extends FunSuite { + + test("out-of-band (_oob URL) messagem parsing into Invitation") { + val link = + "http://127.0.0.1:8000?_oob=eyJ0eXBlIjoiaHR0cHM6Ly9kaWRjb21tLm9yZy9vdXQtb2YtYmFuZC8yLjAvaW52aXRhdGlvbiIsImlkIjoiNDIxZGJiYzgtNTdjYS00MzQxLWFhM2EtZjViNDIxNWM1NjhmIiwiZnJvbSI6ImRpZDpwZWVyOjIuRXo2TFNtTG1XbVR2d2pnTFN1VWFFUUhkSFNGV1B3eWliZ3pvbVdqRm1uQzZGaExuVS5WejZNa3ROZ0xoNE4xdTlLTmhEaXFlOEtaOGJzTHpMY3FzaWZvTmlVdEJvU3M5anhmLlNleUpwWkNJNkltNWxkeTFwWkNJc0luUWlPaUprYlNJc0luTWlPaUpvZEhSd09pOHZNVEkzTGpBdU1DNHhPamd3TURBaUxDSmhJanBiSW1ScFpHTnZiVzB2ZGpJaVhYMCIsImJvZHkiOnsiZ29hbF9jb2RlIjoicmVxdWVzdC1tZWRpYXRlIiwiZ29hbCI6IlJlcXVlc3RNZWRpYXRlIiwiYWNjZXB0IjpbImRpZGNvbW0vdjIiLCJkaWRjb21tL2FpcDI7ZW52PXJmYzU4NyJdfX0" + + val ret = OutOfBand.parseInvitation(link) + + assert(ret.isInstanceOf[Some[Invitation]]) + + val expected = Invitation( + "https://didcomm.org/out-of-band/2.0/invitation", + "421dbbc8-57ca-4341-aa3a-f5b4215c568f", + "did:peer:2.Ez6LSmLmWmTvwjgLSuUaEQHdHSFWPwyibgzomWjFmnC6FhLnU.Vz6MktNgLh4N1u9KNhDiqe8KZ8bsLzLcqsifoNiUtBoSs9jxf.SeyJpZCI6Im5ldy1pZCIsInQiOiJkbSIsInMiOiJodHRwOi8vMTI3LjAuMC4xOjgwMDAiLCJhIjpbImRpZGNvbW0vdjIiXX0", + Body("request-mediate", "RequestMediate", Seq("didcomm/v2", "didcomm/aip2;env=rfc587")), + None + ) + assertEquals(ret, Some(expected)) + } + +}