Skip to content

Commit

Permalink
feat(mercury): Report Problem Protocol (#21)
Browse files Browse the repository at this point in the history
Initial commit for ATL-1776
Aries RFC 0035: Report Problem Protocol 1.0
DIF: Report Problem Protocol 2.0
New module protocol-report_problem Boilerplate code for ReportProblem case classes
  • Loading branch information
FabioPinheiro authored Sep 16, 2022
1 parent 57ed209 commit f5a3711
Show file tree
Hide file tree
Showing 7 changed files with 318 additions and 3 deletions.
1 change: 1 addition & 0 deletions mercury/prism-mediator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- Protocols:
- [Invitation-Protocol](./protocol-invitation/Invitation-Protocol.md)
- [Mercury-Mailbox-Protocol](./protocol-mercury-mailbox/Mercury-Mailbox-Protocol.md)
- [Report-Problem-Protocol](protocol-report-problem/Report-Problem-Protocol.md)
- [Routing-Protocol](./protocol-routing/Routing-Protocol.md)
- [Quick start](./QuickStart.md)
- [UseCases](./UseCases.md)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,17 @@ given Conversion[PackEncryptedResult, EncryptedMessage] with {
given Conversion[Message, org.didcommx.didcomm.message.Message] with {
def apply(msg: Message): org.didcommx.didcomm.message.Message = {
val attachments = msg.attachments.map { e => e: XAttachment } // cast
new MessageBuilder(msg.id, msg.body.asJava, msg.piuri)
val aux = new MessageBuilder(msg.id, msg.body.asJava, msg.piuri)
.from(msg.from.value)
.to(Seq(msg.to.value).asJava)
.createdTime(msg.createdTime)
.expiresTime(msg.createdTime + msg.expiresTimePlus)
.attachments(attachments.toList.asJava) // TODO test
.build()

msg.ack.foreach(str => aux.ack(str))
msg.thid.foreach(str => aux.thid(str))
msg.pthid.foreach(str => aux.pthid(str))
aux.build()
}
}

Expand Down
8 changes: 7 additions & 1 deletion mercury/prism-mediator/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,11 @@ lazy val protocolMercuryMailbox = project
.settings(libraryDependencies += D.zio.value)
.dependsOn(models, protocolInvitation, protocolRouting)

lazy val protocolReportProblem = project
.in(file("protocol-report-problem"))
.settings(name := "protocol-report_problem", version := VERSION)
.dependsOn(models)

lazy val protocolRouting = project
.in(file("protocol-routing"))
.settings(name := "mercury-protocol-routing-2_0", version := VERSION)
Expand Down Expand Up @@ -189,8 +194,9 @@ lazy val mediator = project
)
.dependsOn(agentDidcommx, resolver)
.dependsOn(
protocolInvitation,
protocolConnection,
protocolInvitation,
protocolMercuryMailbox,
protocolReportProblem,
protocolRouting,
)
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,7 @@ case class Message(
createdTime: Long = LocalDateTime.now().toEpochSecond(ZoneOffset.of("Z")),
expiresTimePlus: Long = 1000,
attachments: Seq[Attachment] = Seq.empty, // id -> data (data is also a json)
thid: Option[String] = None,
pthid: Option[String] = None,
ack: Seq[String] = Seq.empty,
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Report Problem Protocol 1.0 & 2.0

This Protocol is parte of Aries (RFC 0035).
Describes how to report errors and warnings in a powerful, interoperable way.

- Version 1.0 - see [https://github.com/hyperledger/aries-rfcs/tree/main/features/0035-report-problem]
- Version 2.0:
- see [https://identity.foundation/didcomm-messaging/spec/#problem-reports]
- see [https://didcomm.org/report-problem/2.0/]

NOTE: In this context never reference to `Error` or `Warning`. Always reference as `Problem`.

TODO: Support [l10n](https://github.com/hyperledger/aries-rfcs/blob/main/features/0043-l10n/README.md) in the Future.

## PIURI

- Version 1.0:
- `https://didcomm.org/report-problem/1.0`
- Version 2.0:
- `https://didcomm.org/report-problem/2.0/problem-report`

## Notes

The protocol is one-way, a simple one-step notification protocol:

## Roles

- `notifier` - Who sends notification.
- `notified` - Who receive notification.
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package io.iohk.atala.mercury.protocol.reportproblem.v1

import io.iohk.atala.mercury.model.Message
import io.iohk.atala.mercury.model.PIURI

/** ReportProblem
*
* Example:
* @see
* https://github.com/hyperledger/aries-rfcs/tree/main/features/0035-report-problem#the-problem-report-message-type
*
* {{{
* {
* "@type" : "https://didcomm.org/report-problem/1.0/problem-report",
* "@id" : "an identifier that can be used to discuss this error message",
* "~thread" : "info about the threading context in which the error occurred (if any)",
* "description" : { "en": "localized message", "code": "symbolic-name-for-error" },
* "problem_items" : [ {"<item descrip>": "value"} ],
* "who_retries" : "enum: you | me | both | none",
* "fix_hint" : { "en": "localized error-instance-specific hint of how to fix issue"},
* "impact" : "enum: message | thread | connection",
* "where" : "enum: you | me | other - enum: cloud | edge | wire | agency | ..",
* "noticed_time" : "<time>",
* "tracking_uri" : "",
* "escalation_uri" : ""
* }
* }}}
*/
final case class ReportProblem(
// `@type`: String,
`@id`: Option[String] = None,
`~thread`: Option[String] = None,
description: Description,
problem_items: Option[Map[ItemDescrip, String]] = None, // key/value - TODO does `value` here a String?
who_retries: Option[String] = None,
fix_hint: Option[String] = None,
impact: Option[Impact] = None,
where: Option[Where] = None,
noticed_time: Option[ISO8601UTC] = None,
tracking_uri: Option[URI] = None,
escalation_uri: Option[URI] = None,
) {
// assert(`@type` == "https://didcomm.org/report-problem/1.0/problem-report") // this is something for the parser TODO
def `@type`: PIURI = "https://didcomm.org/report-problem/1.0/problem-report"
}

object ReportProblem {
def toMessage(obj: ReportProblem): Message = {
// FIXME this doesn't seems to full fit the DIDComm message
Message(
from = ???,
to = ???,
body = ???,
id = obj.`@id`.getOrElse(java.util.UUID.randomUUID.toString()),
piuri = obj.`@type`,
)
}
def fromMessage(msg: Message): ReportProblem = ??? // TODO FIXME
}

final case class Description( // TODO this will be +- a Map
en: Option[String] = None,
code: String
)

type ItemDescrip = String

enum WhoRetries {
case you extends WhoRetries
case me extends WhoRetries
case both extends WhoRetries
case none extends WhoRetries
}

enum Impact {
case message extends Impact
case thread extends Impact
case connection extends Impact
}

type Where = String
// enum Where {
// case you extends Where
// case me extends Where
// case other extends Where // FIXME enum: cloud | edge | wire | agency | ..
// }

type ISO8601UTC = String
type URI = String
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package io.iohk.atala.mercury.protocol.reportproblem.v2

import io.iohk.atala.mercury.model.Message

/** ReportProblem
*
* @see
* https://identity.foundation/didcomm-messaging/spec/#problem-reports
*
* @param pthid
* REQUIRED. The value is the thid of the thread in which the problem occurred. (Thus, the problem report begins a
* new child thread, of which the triggering context is the parent. The parent context can react immediately to the
* problem, or can suspend progress while troubleshooting occurs.)
*
* @param ack
* OPTIONAL. It SHOULD be included if the problem in question was triggered directly by a preceding message.
* (Contrast problems arising from a timeout or a user deciding to cancel a transaction, which can arise independent
* of a preceding message. In such cases, ack MAY still be used, but there is no strong recommendation.)
*
* @param code
* REQUIRED. Deserves a rich explanation; see Problem Codes below.
*
* @param comment
* OPTIONAL but recommended. Contains human-friendly text describing the problem. If the field is present, the text
* MUST be statically associated with code, meaning that each time circumstances trigger a problem with the same
* code, the value of comment will be the same. This enables localization and cached lookups, and it has some
* cybersecurity benefits. The value of comment supports simple interpolation with args (see next), where args are
* referenced as {1}, {2}, and so forth.
*
* @param args
* OPTIONAL. Contains situation-specific values that are interpolated into the value of comment, providing extra
* detail for human readers. Each unique problem code has a definition for the args it takes. In this example,
* e.p.xfer.cant-use-endpoint apparently expects two values in args: the first is a URL and the second is a DID.
* Missing or null args MUST be replaced with a question mark character (?) during interpolation; extra args MUST be
* appended to the main text as comma-separated values.
*
* @param escalate_to
* OPTIONAL. Provides a URI where additional help on the issue can be received.
*/
final case class ReportProblem(
pthid: String,
ack: Option[Seq[String]],
code: ProblemCode,
comment: Option[String],
args: Option[Seq[String]],
escalate_to: Option[String],
)
object ReportProblem {

/** {{{
* {
* "type": "https://didcomm.org/report-problem/2.0/problem-report",
* "id": "7c9de639-c51c-4d60-ab95-103fa613c805",
* "pthid": "1e513ad4-48c9-444e-9e7e-5b8b45c5e325",
* "ack": ["1e513ad4-48c9-444e-9e7e-5b8b45c5e325"],
* "body": {
* "code": "e.p.xfer.cant-use-endpoint",
* "comment": "Unable to use the {1} endpoint for {2}.",
* "args": [ "https://agents.r.us/inbox", "did:sov:C805sNYhMrjHiqZDTUASHg" ],
* "escalate_to": "mailto:[email protected]"
* }
* }
* }}}
*
* @param obj
* @return
*/
def reportProblemToMessagem(problem: ReportProblem, msg: Message): Message = {
assert(problem.pthid == msg.id) // This is a reply!
Message(
from = msg.to,
to = msg.from,
body = Map(
"code" -> problem.code.value,
"comment" -> problem.comment,
"args" -> problem.args,
"escalate_to" -> problem.escalate_to,
),
ack = problem.ack.getOrElse(Seq.empty),
pthid = Some(problem.pthid)
)
}
}

/** ProblemCode
*
* @see
* https://identity.foundation/didcomm-messaging/spec/#problem-codes
*/

opaque type ProblemCode = String

object ProblemCode {
def apply(value: String): ProblemCode = {
assert(true) // TODO regex to check value
value
}
}

extension (problemCode: ProblemCode) {
def sorter: Sorter = problemCode.charAt(0) match
case 'e' => Sorter.e // error semantics
case 'w' => Sorter.w // warning semantics
def scope: Scope = problemCode.charAt(2) match
case 'p' => Scope.p // error semantics
case 'm' => Scope.m // warning semantics
def descriptors: Array[Descriptor] = problemCode.split('.').drop(2)
def value = problemCode
}

/** @see
* https://identity.foundation/didcomm-messaging/spec/#sorter
*
* @param e:
* This problem clearly defeats the intentions of at least one of the parties. It is therefore an error. A situation
* with error semantics might be that a protocol requires payment, but a payment attempt was rejected.
*
* @param w:
* The consequences of this problem are not obvious to the reporter; evaluating its effects requires judgment from a
* human or from some other party or system. Thus, the message constitutes a warning from the sender’s perspective. A
* situation with warning semantics might be that a sender is only able to encrypt a message for some of the
* recipient’s keyAgreement keys instead of all of them (perhaps due to an imperfect overlap of supported crypto
* types). The sender in such a situation might not know whether the recipient considers this an error.
*/
enum Sorter {
case e extends Sorter
case w extends Sorter
}

/** @see
* https://identity.foundation/didcomm-messaging/spec/#scope
*
* @param p:
* The protocol within which the error occurs (and any co-protocols started by and depended on by the protocol) is
* abandoned or reset. In simple two-party request-response protocols, the p reset scope is common and appropriate.
* However, if a protocol is complex and long-lived, the p reset scope may be undesirable. Consider a situation where
* a protocol helps a person apply for college, and the problem code is e.p.payment-failed. With such a p reset
* scope, the entire apply-for-college workflow (collecting letters of recommendation, proving qualifications,
* filling out various forms) is abandoned when the payment fails. The p scope is probably too aggressive for such a
* situation.
*
* @param m:
* The error was triggered by the previous message on the thread; the scope is one message. The outcome is that the
* problematic message is rejected (has no effect). If the protocol is a chess game, and the problem code is
* e.m.invalid-move, then someone’s invalid move is rejected, and it is still their turn.
*
* A formal state name from the sender’s state machine in the active protocol. This means the error represented a
* partial failure of the protocol, but the protocol as a whole is not abandoned. Instead, the sender uses the scope to
* indicate what state it reverts to. If the protocol is one that helps a person apply for college, and the problem
* code is e.get-pay-details.payment-failed, then the sender is saying that, because of the error, it is moving back to
* the get-pay-details state in the larger workflow.
*/
enum Scope {
case p extends Scope
case m extends Scope
}

/** Descriptors After the sorter and the scope, problem codes consist of one or more descriptors. These are kebab-case
* tokens separated by the . character, where the semantics get progressively more detailed reading left to right.
* Senders of problem reports SHOULD include at least one descriptor in their problem code, and SHOULD use the most
* specific descriptor they can. Recipients MAY specialize their reactions to problems in a very granular way, or MAY
* examine only a prefix of a problem code.
*
* The following descriptor tokens are defined. They can be used by themselves, or as prefixes to more specific
* descriptors. Additional descriptors — particularly more granular ones — may be defined in individual protocols.
*
* @see
* https://identity.foundation/didcomm-messaging/spec/#descriptors
*
* | Token | Value of comment string |
* |:-------------|:--------------------------------------------------|
* | trust | Failed to achieve required trust. |
* | trust.crypto | Cryptographic operation failed. |
* | xfer | Unable to transport data. |
* | did | DID is unusable. |
* | msg | Bad message. |
* | me | Internal error. |
* | me.res | A required resource is inadequate or unavailable. |
* | req | Circumstances don’t satisfy requirements. |
* | req.time | Failed to satisfy timing constraints. |
* | legal | Failed for legal reasons. |
*/
type Descriptor = String

0 comments on commit f5a3711

Please sign in to comment.