Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Send problem report on missing protocol/version or wrong role #65

Merged
merged 10 commits into from
Aug 14, 2023
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ inThisBuild(

/** Versions */
lazy val V = new {
val scalaDID = "0.1.0-M6"
val scalaDID = "0.1.0-M7"
// val scalajsJavaSecureRandom = "1.0.0"

// FIXME another bug in the test framework https://github.com/scalameta/munit/issues/554
Expand Down
6 changes: 4 additions & 2 deletions mediator/src/main/scala/io/iohk/atala/mediator/Error.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import fmgp.did.*
import fmgp.did.comm.*
import zio.json.*

trait MediatorError
sealed trait MediatorError

case class MediatorException(fail: MediatorError) extends Exception(fail.toString())

Expand All @@ -20,8 +20,9 @@ object MediatorThrowable {
}

// Storage
case class StorageException(fail: StorageError) extends Exception(fail.toString())

trait StorageError extends MediatorError {
sealed trait StorageError { // extends MediatorError {
def error: String
}

Expand All @@ -35,6 +36,7 @@ object StorageThrowable {
def apply(throwable: Throwable) = new StorageThrowable(throwable.getClass.getName() + ":" + throwable.getMessage)
}

// ProtocolError
sealed trait ProtocolError extends MediatorError {
def piuri: PIURI
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package io.iohk.atala.mediator.actions

import fmgp.crypto.error.*
import fmgp.did.*
import fmgp.did.comm.*
import fmgp.did.comm.Operations.*
import fmgp.did.comm.protocol.*
import fmgp.did.comm.protocol.basicmessage2.*
import fmgp.did.comm.protocol.trustping2.*
import io.iohk.atala.mediator.*
import io.iohk.atala.mediator.comm.*
import io.iohk.atala.mediator.db.*
import io.iohk.atala.mediator.protocols.NullProtocolExecuter
import zio.*
import zio.json.*
import io.iohk.atala.mediator.protocols.MissingProtocolExecuter

object ActionUtils {

def packResponse(
plaintextMessage: Option[PlaintextMessage],
action: Action
): ZIO[Operations & Agent & Resolver & MessageDispatcher, MediatorError, Option[EncryptedMessage]] =
action match {
case _: NoReply.type => ZIO.succeed(None)
case action: AnyReply =>
val reply = action.msg
for {
msg <- {
reply.from match
case Some(value) => authEncrypt(reply)
case None => anonEncrypt(reply)
}.mapError(fail => MediatorDidError(fail))
// TODO forward message
maybeSyncReplyMsg <- reply.to.map(_.toSeq) match // TODO improve
case None => ZIO.logWarning("Have a reply but the field 'to' is missing") *> ZIO.none
case Some(Seq()) => ZIO.logWarning("Have a reply but the field 'to' is empty") *> ZIO.none
case Some(send2DIDs) =>
ZIO
.foreach(send2DIDs)(to =>
val job: ZIO[MessageDispatcher & (Resolver & Any), MediatorError, Matchable] = for {
messageDispatcher <- ZIO.service[MessageDispatcher]
resolver <- ZIO.service[Resolver]
doc <- resolver
.didDocument(to)
.mapError(fail => MediatorDidError(fail))
mURL = doc.service.toSeq.flatten
.filter(_.`type` match {
case str: String => str == DIDService.TYPE_DIDCommMessaging
case seq: Seq[String] => seq.contains(DIDService.TYPE_DIDCommMessaging)
}) match {
case head +: next => // FIXME discarte the next
head.getServiceEndpointAsURIs.headOption // TODO head
case Seq() => None // TODO
}
jobToRun <- mURL match
case None => ZIO.logWarning(s"No url to send message")
case Some(url) => {
ZIO.log(s"Send to url: $url") *>
messageDispatcher
.send(
msg,
url,
None
// url match // FIXME REMOVE (use for local env)
// case http if http.startsWith("http://") => Some(url.drop(7).split(':').head.split('/').head)
// case https if https.startsWith("https://") =>
// Some(url.drop(8).split(':').head.split('/').head)
// case _ => None
)
.catchAll { case DispatcherError(error) => ZIO.logWarning(s"Dispatch Error: $error") }
}

} yield (jobToRun)
action match
case Reply(_) => job
case SyncReplyOnly(_) => ZIO.unit
case AsyncReplyOnly(_) => job
) *> ZIO
.succeed(msg)
.when(
{
plaintextMessage.map(_.return_route).contains(ReturnRoute.all)
&& {
plaintextMessage.flatMap(_.from.map(_.asTO)) match {
case None => false
case Some(replyTo) => send2DIDs.contains(replyTo)
}
}
} || action.isInstanceOf[SyncReplyOnly]
)
} yield maybeSyncReplyMsg
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,124 +13,66 @@ import io.iohk.atala.mediator.db.*
import io.iohk.atala.mediator.protocols.NullProtocolExecuter
import zio.*
import zio.json.*
import io.iohk.atala.mediator.protocols.MissingProtocolExecuter
//TODO pick a better name // maybe "Protocol" only

trait ProtocolExecuter[-R] {
trait ProtocolExecuter[-R, +E] { // <: MediatorError | StorageError] {

def suportedPIURI: Seq[PIURI]

/** @return can return a Sync Reply Msg */
def execute[R1 <: R](plaintextMessage: PlaintextMessage): ZIO[R1, MediatorError, Option[EncryptedMessage]] =
def execute[R1 <: R](
plaintextMessage: PlaintextMessage
): ZIO[R1, E, Option[EncryptedMessage]] =
program(plaintextMessage) *> ZIO.none

def program[R1 <: R](plaintextMessage: PlaintextMessage): ZIO[R1, MediatorError, Action]
def program[R1 <: R](plaintextMessage: PlaintextMessage): ZIO[R1, E, Action]
}

object ProtocolExecuter {
type Services = Resolver & Agent & Operations & MessageDispatcher
type Erros = MediatorError | StorageError
}
case class ProtocolExecuterCollection[-R](executers: ProtocolExecuter[R]*) extends ProtocolExecuter[R] {
case class ProtocolExecuterCollection[-R <: Agent, +E](
executers: ProtocolExecuter[R, E]*
) extends ProtocolExecuter[R, E] {

override def suportedPIURI: Seq[PIURI] = executers.flatMap(_.suportedPIURI)

def selectExecutersFor(piuri: PIURI) = executers.find(_.suportedPIURI.contains(piuri))

override def execute[R1 <: R](
plaintextMessage: PlaintextMessage,
): ZIO[R1, MediatorError, Option[EncryptedMessage]] =
): ZIO[R1, E, Option[EncryptedMessage]] =
selectExecutersFor(plaintextMessage.`type`) match
case None => NullProtocolExecuter.execute(plaintextMessage)
// case None => NullProtocolExecuter.execute(plaintextMessage)
case None => MissingProtocolExecuter.execute(plaintextMessage)
case Some(px) => px.execute(plaintextMessage)

override def program[R1 <: R](
plaintextMessage: PlaintextMessage,
): ZIO[R1, MediatorError, Action] =
): ZIO[R1, E, Action] =
selectExecutersFor(plaintextMessage.`type`) match
case None => NullProtocolExecuter.program(plaintextMessage)
// case None => NullProtocolExecuter.program(plaintextMessage)
case None => MissingProtocolExecuter.program(plaintextMessage)
case Some(px) => px.program(plaintextMessage)
}

trait ProtocolExecuterWithServices[-R <: ProtocolExecuter.Services] extends ProtocolExecuter[R] {
trait ProtocolExecuterWithServices[
-R <: ProtocolExecuter.Services,
+E >: MediatorError // ProtocolExecuter.Erros
] extends ProtocolExecuter[R, E] {

override def execute[R1 <: R](
plaintextMessage: PlaintextMessage,
// context: Context
): ZIO[R1, MediatorError, Option[EncryptedMessage]] =
): ZIO[R1, E, Option[EncryptedMessage]] =
program(plaintextMessage)
.tap(v => ZIO.logDebug(v.toString)) // DEBUG
.flatMap {
case _: NoReply.type => ZIO.succeed(None)
case action: AnyReply =>
val reply = action.msg
for {
msg <- {
reply.from match
case Some(value) => authEncrypt(reply)
case None => anonEncrypt(reply)
}.mapError(fail => MediatorDidError(fail))
// TODO forward message
maybeSyncReplyMsg <- reply.to.map(_.toSeq) match // TODO improve
case None => ZIO.logWarning("Have a reply but the field 'to' is missing") *> ZIO.none
case Some(Seq()) => ZIO.logWarning("Have a reply but the field 'to' is empty") *> ZIO.none
case Some(send2DIDs) =>
ZIO
.foreach(send2DIDs)(to =>
val job: ZIO[MessageDispatcher & (Resolver & Any), MediatorError, Matchable] = for {
messageDispatcher <- ZIO.service[MessageDispatcher]
resolver <- ZIO.service[Resolver]
doc <- resolver
.didDocument(to)
.mapError(fail => MediatorDidError(fail))
mURL = doc.service.toSeq.flatten
.filter(_.`type` match {
case str: String => str == DIDService.TYPE_DIDCommMessaging
case seq: Seq[String] => seq.contains(DIDService.TYPE_DIDCommMessaging)
}) match {
case head +: next => // FIXME discarte the next
head.getServiceEndpointAsURIs.headOption // TODO head
case Seq() => None // TODO
}
jobToRun <- mURL match
case None => ZIO.logWarning(s"No url to send message")
case Some(url) => {
ZIO.log(s"Send to url: $url") *>
messageDispatcher
.send(
msg,
url, // "http://localhost:8080", // FIXME REMOVE (use for local env)
None
// url match // FIXME REMOVE (use for local env)
// case http if http.startsWith("http://") => Some(url.drop(7).split(':').head.split('/').head)
// case https if https.startsWith("https://") =>
// Some(url.drop(8).split(':').head.split('/').head)
// case _ => None
)
.catchAll { case DispatcherError(error) => ZIO.logWarning(s"Dispatch Error: $error") }
}

} yield (jobToRun)
action match
case Reply(_) => job
case SyncReplyOnly(_) => ZIO.unit
case AsyncReplyOnly(_) => job
) *> ZIO
.succeed(msg)
.when(
{
plaintextMessage.return_route.contains(ReturnRoute.all)
&& {
plaintextMessage.from.map(_.asTO) match {
case None => false
case Some(replyTo) => send2DIDs.contains(replyTo)
}
}
} || action.isInstanceOf[SyncReplyOnly]
)
} yield maybeSyncReplyMsg
}
.flatMap(action => ActionUtils.packResponse(Some(plaintextMessage), action))

override def program[R1 <: R](
plaintextMessage: PlaintextMessage,
// context: Context
): ZIO[R1, MediatorError, Action]
): ZIO[R1, E, Action]
}
Loading