Skip to content

Commit

Permalink
feat: add problem report protocol (#560)
Browse files Browse the repository at this point in the history
Signed-off-by: Amit Padmani <amit@northernblock.io>
  • Loading branch information
nbAmit committed Dec 13, 2021
1 parent 29baae7 commit baee5db
Show file tree
Hide file tree
Showing 46 changed files with 1,075 additions and 53 deletions.
31 changes: 22 additions & 9 deletions packages/core/src/agent/Dispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Lifecycle, scoped } from 'tsyringe'
import { AgentConfig } from '../agent/AgentConfig'
import { AriesFrameworkError } from '../error/AriesFrameworkError'

import { ProblemReportMessage } from './../modules/problem-reports/messages/ProblemReportMessage'
import { EventEmitter } from './EventEmitter'
import { AgentEventTypes } from './Events'
import { MessageSender } from './MessageSender'
Expand Down Expand Up @@ -45,15 +46,27 @@ class Dispatcher {
try {
outboundMessage = await handler.handle(messageContext)
} catch (error) {
this.logger.error(`Error handling message with type ${message.type}`, {
message: message.toJSON(),
error,
senderVerkey: messageContext.senderVerkey,
recipientVerkey: messageContext.recipientVerkey,
connectionId: messageContext.connection?.id,
})

throw error
const problemReportMessage = error.problemReport

if (problemReportMessage instanceof ProblemReportMessage && messageContext.connection) {
problemReportMessage.setThread({
threadId: messageContext.message.threadId,
})
outboundMessage = {
payload: problemReportMessage,
connection: messageContext.connection,
}
} else {
this.logger.error(`Error handling message with type ${message.type}`, {
message: message.toJSON(),
error,
senderVerkey: messageContext.senderVerkey,
recipientVerkey: messageContext.recipientVerkey,
connectionId: messageContext.connection?.id,
})

throw error
}
}

if (outboundMessage && isOutboundServiceMessage(outboundMessage)) {
Expand Down
72 changes: 64 additions & 8 deletions packages/core/src/agent/MessageReceiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,29 @@ import { Lifecycle, scoped } from 'tsyringe'

import { AriesFrameworkError } from '../error'
import { ConnectionService } from '../modules/connections/services/ConnectionService'
import { ProblemReportError, ProblemReportMessage } from '../modules/problem-reports'
import { JsonTransformer } from '../utils/JsonTransformer'
import { MessageValidator } from '../utils/MessageValidator'
import { replaceLegacyDidSovPrefixOnMessage } from '../utils/messageType'

import { CommonMessageType } from './../modules/common/messages/CommonMessageType'
import { AgentConfig } from './AgentConfig'
import { Dispatcher } from './Dispatcher'
import { EnvelopeService } from './EnvelopeService'
import { MessageSender } from './MessageSender'
import { TransportService } from './TransportService'
import { createOutboundMessage } from './helpers'
import { InboundMessageContext } from './models/InboundMessageContext'

export enum ProblemReportReason {
MessageParseFailure = 'message-parse-failure',
}
@scoped(Lifecycle.ContainerScoped)
export class MessageReceiver {
private config: AgentConfig
private envelopeService: EnvelopeService
private transportService: TransportService
private messageSender: MessageSender
private connectionService: ConnectionService
private dispatcher: Dispatcher
private logger: Logger
Expand All @@ -33,12 +41,14 @@ export class MessageReceiver {
config: AgentConfig,
envelopeService: EnvelopeService,
transportService: TransportService,
messageSender: MessageSender,
connectionService: ConnectionService,
dispatcher: Dispatcher
) {
this.config = config
this.envelopeService = envelopeService
this.transportService = transportService
this.messageSender = messageSender
this.connectionService = connectionService
this.dispatcher = dispatcher
this.logger = this.config.logger
Expand Down Expand Up @@ -84,15 +94,12 @@ export class MessageReceiver {
unpackedMessage.message
)

const message = await this.transformMessage(unpackedMessage)
let message: AgentMessage | null = null
try {
await MessageValidator.validate(message)
message = await this.transformMessage(unpackedMessage)
await this.validateMessage(message)
} catch (error) {
this.logger.error(`Error validating message ${message.type}`, {
errors: error,
message: message.toJSON(),
})

if (connection) await this.sendProblemReportMessage(error.message, connection, unpackedMessage)
throw error
}

Expand Down Expand Up @@ -174,12 +181,61 @@ export class MessageReceiver {
const MessageClass = this.dispatcher.getMessageClassForType(messageType)

if (!MessageClass) {
throw new AriesFrameworkError(`No message class found for message type "${messageType}"`)
throw new ProblemReportError(`No message class found for message type "${messageType}"`, {
problemCode: ProblemReportReason.MessageParseFailure,
})
}

// Cast the plain JSON object to specific instance of Message extended from AgentMessage
const message = JsonTransformer.fromJSON(unpackedMessage.message, MessageClass)

return message
}

/**
* Validate an AgentMessage instance.
* @param message agent message to validate
*/
private async validateMessage(message: AgentMessage) {
try {
await MessageValidator.validate(message)
} catch (error) {
this.logger.error(`Error validating message ${message.type}`, {
errors: error,
message: message.toJSON(),
})
throw new ProblemReportError(`Error validating message ${message.type}`, {
problemCode: ProblemReportReason.MessageParseFailure,
})
}
}

/**
* Send the problem report message (https://didcomm.org/notification/1.0/problem-report) to the recipient.
* @param message error message to send
* @param connection connection to send the message to
* @param unpackedMessage received unpackedMessage
*/
private async sendProblemReportMessage(
message: string,
connection: ConnectionRecord,
unpackedMessage: UnpackedMessageContext
) {
if (unpackedMessage.message['@type'] === CommonMessageType.ProblemReport) {
throw new AriesFrameworkError(message)
}
const problemReportMessage = new ProblemReportMessage({
description: {
en: message,
code: ProblemReportReason.MessageParseFailure,
},
})
problemReportMessage.setThread({
threadId: unpackedMessage.message['@id'],
})
const outboundMessage = createOutboundMessage(connection, problemReportMessage)
if (outboundMessage) {
await this.messageSender.sendMessage(outboundMessage)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ describe('Decorators | Signature | SignatureDecoratorUtils', () => {
try {
await unpackAndVerifySignatureDecorator(wronglySignedData, wallet)
} catch (error) {
expect(error.message).toEqual('Signature is not valid!')
expect(error.message).toEqual('Signature is not valid')
}
})
})
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Wallet } from '../../wallet/Wallet'

import { AriesFrameworkError } from '../../error'
import { ConnectionProblemReportError, ConnectionProblemReportReason } from '../../modules/connections/errors'
import { BufferEncoder } from '../../utils/BufferEncoder'
import { JsonEncoder } from '../../utils/JsonEncoder'
import { Buffer } from '../../utils/buffer'
Expand Down Expand Up @@ -29,7 +29,9 @@ export async function unpackAndVerifySignatureDecorator(
const isValid = await wallet.verify(signerVerkey, signedData, signature)

if (!isValid) {
throw new AriesFrameworkError('Signature is not valid!')
throw new ConnectionProblemReportError('Signature is not valid', {
problemCode: ConnectionProblemReportReason.RequestProcessingError,
})
}

// TODO: return Connection instance instead of raw json
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export enum CommonMessageType {
Ack = 'https://didcomm.org/notification/1.0/ack',
ProblemReport = 'https://didcomm.org/notification/1.0/problem-report',
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { ConnectionProblemReportReason } from '.'
import type { ProblemReportErrorOptions } from '../../problem-reports'

import { ProblemReportError } from '../../problem-reports'
import { ConnectionProblemReportMessage } from '../messages'

interface ConnectionProblemReportErrorOptions extends ProblemReportErrorOptions {
problemCode: ConnectionProblemReportReason
}
export class ConnectionProblemReportError extends ProblemReportError {
public problemReport: ConnectionProblemReportMessage

public constructor(public message: string, { problemCode }: ConnectionProblemReportErrorOptions) {
super(message, { problemCode })
this.problemReport = new ConnectionProblemReportMessage({
description: {
en: message,
code: problemCode,
},
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* Connection error code in RFC 160.
*
* @see https://github.com/hyperledger/aries-rfcs/blob/main/features/0160-connection-protocol/README.md#errors
*/
export enum ConnectionProblemReportReason {
RequestNotAccepted = 'request_not_accepted',
RequestProcessingError = 'request_processing_error',
ResponseNotAccepted = 'response_not_accepted',
ResponseProcessingError = 'response_processing_error',
}
2 changes: 2 additions & 0 deletions packages/core/src/modules/connections/errors/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './ConnectionProblemReportError'
export * from './ConnectionProblemReportReason'
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { Handler, HandlerInboundMessage } from '../../../agent/Handler'
import type { ConnectionService } from '../services'

import { ConnectionProblemReportMessage } from '../messages'

export class ConnectionProblemReportHandler implements Handler {
private connectionService: ConnectionService
public supportedMessages = [ConnectionProblemReportMessage]

public constructor(connectionService: ConnectionService) {
this.connectionService = connectionService
}

public async handle(messageContext: HandlerInboundMessage<ConnectionProblemReportHandler>) {
await this.connectionService.processProblemReport(messageContext)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { ProblemReportMessageOptions } from '../../problem-reports/messages/ProblemReportMessage'

import { Equals } from 'class-validator'

import { ProblemReportMessage } from '../../problem-reports/messages/ProblemReportMessage'

export type ConnectionProblemReportMessageOptions = ProblemReportMessageOptions

/**
* @see https://github.com/hyperledger/aries-rfcs/blob/main/features/0035-report-problem/README.md
*/
export class ConnectionProblemReportMessage extends ProblemReportMessage {
/**
* Create new ConnectionProblemReportMessage instance.
* @param options
*/
public constructor(options: ConnectionProblemReportMessageOptions) {
super(options)
}

@Equals(ConnectionProblemReportMessage.type)
public readonly type = ConnectionProblemReportMessage.type
public static readonly type = 'https://didcomm.org/connection/1.0/problem-report'
}
1 change: 1 addition & 0 deletions packages/core/src/modules/connections/messages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from './ConnectionRequestMessage'
export * from './ConnectionResponseMessage'
export * from './TrustPingMessage'
export * from './TrustPingResponseMessage'
export * from './ConnectionProblemReportMessage'
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ export enum ConnectionState {
Requested = 'requested',
Responded = 'responded',
Complete = 'complete',
None = 'none',
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export interface ConnectionRecordProps {
imageUrl?: string
multiUseInvitation: boolean
mediatorId?: string
errorMsg?: string
}

export type CustomConnectionTags = TagsBase
Expand Down Expand Up @@ -68,6 +69,7 @@ export class ConnectionRecord

public threadId?: string
public mediatorId?: string
public errorMsg?: string

public static readonly type = 'ConnectionRecord'
public readonly type = ConnectionRecord.type
Expand All @@ -94,6 +96,7 @@ export class ConnectionRecord
this.imageUrl = props.imageUrl
this.multiUseInvitation = props.multiUseInvitation
this.mediatorId = props.mediatorId
this.errorMsg = props.errorMsg
}
}

Expand Down
Loading

0 comments on commit baee5db

Please sign in to comment.