Skip to content

Commit baee5db

Browse files
authored
feat: add problem report protocol (openwallet-foundation#560)
Signed-off-by: Amit Padmani <[email protected]>
1 parent 29baae7 commit baee5db

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+1075
-53
lines changed

packages/core/src/agent/Dispatcher.ts

+22-9
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { Lifecycle, scoped } from 'tsyringe'
1010
import { AgentConfig } from '../agent/AgentConfig'
1111
import { AriesFrameworkError } from '../error/AriesFrameworkError'
1212

13+
import { ProblemReportMessage } from './../modules/problem-reports/messages/ProblemReportMessage'
1314
import { EventEmitter } from './EventEmitter'
1415
import { AgentEventTypes } from './Events'
1516
import { MessageSender } from './MessageSender'
@@ -45,15 +46,27 @@ class Dispatcher {
4546
try {
4647
outboundMessage = await handler.handle(messageContext)
4748
} catch (error) {
48-
this.logger.error(`Error handling message with type ${message.type}`, {
49-
message: message.toJSON(),
50-
error,
51-
senderVerkey: messageContext.senderVerkey,
52-
recipientVerkey: messageContext.recipientVerkey,
53-
connectionId: messageContext.connection?.id,
54-
})
55-
56-
throw error
49+
const problemReportMessage = error.problemReport
50+
51+
if (problemReportMessage instanceof ProblemReportMessage && messageContext.connection) {
52+
problemReportMessage.setThread({
53+
threadId: messageContext.message.threadId,
54+
})
55+
outboundMessage = {
56+
payload: problemReportMessage,
57+
connection: messageContext.connection,
58+
}
59+
} else {
60+
this.logger.error(`Error handling message with type ${message.type}`, {
61+
message: message.toJSON(),
62+
error,
63+
senderVerkey: messageContext.senderVerkey,
64+
recipientVerkey: messageContext.recipientVerkey,
65+
connectionId: messageContext.connection?.id,
66+
})
67+
68+
throw error
69+
}
5770
}
5871

5972
if (outboundMessage && isOutboundServiceMessage(outboundMessage)) {

packages/core/src/agent/MessageReceiver.ts

+64-8
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,29 @@ import { Lifecycle, scoped } from 'tsyringe'
99

1010
import { AriesFrameworkError } from '../error'
1111
import { ConnectionService } from '../modules/connections/services/ConnectionService'
12+
import { ProblemReportError, ProblemReportMessage } from '../modules/problem-reports'
1213
import { JsonTransformer } from '../utils/JsonTransformer'
1314
import { MessageValidator } from '../utils/MessageValidator'
1415
import { replaceLegacyDidSovPrefixOnMessage } from '../utils/messageType'
1516

17+
import { CommonMessageType } from './../modules/common/messages/CommonMessageType'
1618
import { AgentConfig } from './AgentConfig'
1719
import { Dispatcher } from './Dispatcher'
1820
import { EnvelopeService } from './EnvelopeService'
21+
import { MessageSender } from './MessageSender'
1922
import { TransportService } from './TransportService'
23+
import { createOutboundMessage } from './helpers'
2024
import { InboundMessageContext } from './models/InboundMessageContext'
2125

26+
export enum ProblemReportReason {
27+
MessageParseFailure = 'message-parse-failure',
28+
}
2229
@scoped(Lifecycle.ContainerScoped)
2330
export class MessageReceiver {
2431
private config: AgentConfig
2532
private envelopeService: EnvelopeService
2633
private transportService: TransportService
34+
private messageSender: MessageSender
2735
private connectionService: ConnectionService
2836
private dispatcher: Dispatcher
2937
private logger: Logger
@@ -33,12 +41,14 @@ export class MessageReceiver {
3341
config: AgentConfig,
3442
envelopeService: EnvelopeService,
3543
transportService: TransportService,
44+
messageSender: MessageSender,
3645
connectionService: ConnectionService,
3746
dispatcher: Dispatcher
3847
) {
3948
this.config = config
4049
this.envelopeService = envelopeService
4150
this.transportService = transportService
51+
this.messageSender = messageSender
4252
this.connectionService = connectionService
4353
this.dispatcher = dispatcher
4454
this.logger = this.config.logger
@@ -84,15 +94,12 @@ export class MessageReceiver {
8494
unpackedMessage.message
8595
)
8696

87-
const message = await this.transformMessage(unpackedMessage)
97+
let message: AgentMessage | null = null
8898
try {
89-
await MessageValidator.validate(message)
99+
message = await this.transformMessage(unpackedMessage)
100+
await this.validateMessage(message)
90101
} catch (error) {
91-
this.logger.error(`Error validating message ${message.type}`, {
92-
errors: error,
93-
message: message.toJSON(),
94-
})
95-
102+
if (connection) await this.sendProblemReportMessage(error.message, connection, unpackedMessage)
96103
throw error
97104
}
98105

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

176183
if (!MessageClass) {
177-
throw new AriesFrameworkError(`No message class found for message type "${messageType}"`)
184+
throw new ProblemReportError(`No message class found for message type "${messageType}"`, {
185+
problemCode: ProblemReportReason.MessageParseFailure,
186+
})
178187
}
179188

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

183192
return message
184193
}
194+
195+
/**
196+
* Validate an AgentMessage instance.
197+
* @param message agent message to validate
198+
*/
199+
private async validateMessage(message: AgentMessage) {
200+
try {
201+
await MessageValidator.validate(message)
202+
} catch (error) {
203+
this.logger.error(`Error validating message ${message.type}`, {
204+
errors: error,
205+
message: message.toJSON(),
206+
})
207+
throw new ProblemReportError(`Error validating message ${message.type}`, {
208+
problemCode: ProblemReportReason.MessageParseFailure,
209+
})
210+
}
211+
}
212+
213+
/**
214+
* Send the problem report message (https://didcomm.org/notification/1.0/problem-report) to the recipient.
215+
* @param message error message to send
216+
* @param connection connection to send the message to
217+
* @param unpackedMessage received unpackedMessage
218+
*/
219+
private async sendProblemReportMessage(
220+
message: string,
221+
connection: ConnectionRecord,
222+
unpackedMessage: UnpackedMessageContext
223+
) {
224+
if (unpackedMessage.message['@type'] === CommonMessageType.ProblemReport) {
225+
throw new AriesFrameworkError(message)
226+
}
227+
const problemReportMessage = new ProblemReportMessage({
228+
description: {
229+
en: message,
230+
code: ProblemReportReason.MessageParseFailure,
231+
},
232+
})
233+
problemReportMessage.setThread({
234+
threadId: unpackedMessage.message['@id'],
235+
})
236+
const outboundMessage = createOutboundMessage(connection, problemReportMessage)
237+
if (outboundMessage) {
238+
await this.messageSender.sendMessage(outboundMessage)
239+
}
240+
}
185241
}

packages/core/src/decorators/signature/SignatureDecoratorUtils.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ describe('Decorators | Signature | SignatureDecoratorUtils', () => {
7676
try {
7777
await unpackAndVerifySignatureDecorator(wronglySignedData, wallet)
7878
} catch (error) {
79-
expect(error.message).toEqual('Signature is not valid!')
79+
expect(error.message).toEqual('Signature is not valid')
8080
}
8181
})
8282
})

packages/core/src/decorators/signature/SignatureDecoratorUtils.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { Wallet } from '../../wallet/Wallet'
22

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

3131
if (!isValid) {
32-
throw new AriesFrameworkError('Signature is not valid!')
32+
throw new ConnectionProblemReportError('Signature is not valid', {
33+
problemCode: ConnectionProblemReportReason.RequestProcessingError,
34+
})
3335
}
3436

3537
// TODO: return Connection instance instead of raw json
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export enum CommonMessageType {
22
Ack = 'https://didcomm.org/notification/1.0/ack',
3+
ProblemReport = 'https://didcomm.org/notification/1.0/problem-report',
34
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import type { ConnectionProblemReportReason } from '.'
2+
import type { ProblemReportErrorOptions } from '../../problem-reports'
3+
4+
import { ProblemReportError } from '../../problem-reports'
5+
import { ConnectionProblemReportMessage } from '../messages'
6+
7+
interface ConnectionProblemReportErrorOptions extends ProblemReportErrorOptions {
8+
problemCode: ConnectionProblemReportReason
9+
}
10+
export class ConnectionProblemReportError extends ProblemReportError {
11+
public problemReport: ConnectionProblemReportMessage
12+
13+
public constructor(public message: string, { problemCode }: ConnectionProblemReportErrorOptions) {
14+
super(message, { problemCode })
15+
this.problemReport = new ConnectionProblemReportMessage({
16+
description: {
17+
en: message,
18+
code: problemCode,
19+
},
20+
})
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
* Connection error code in RFC 160.
3+
*
4+
* @see https://github.com/hyperledger/aries-rfcs/blob/main/features/0160-connection-protocol/README.md#errors
5+
*/
6+
export enum ConnectionProblemReportReason {
7+
RequestNotAccepted = 'request_not_accepted',
8+
RequestProcessingError = 'request_processing_error',
9+
ResponseNotAccepted = 'response_not_accepted',
10+
ResponseProcessingError = 'response_processing_error',
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './ConnectionProblemReportError'
2+
export * from './ConnectionProblemReportReason'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import type { Handler, HandlerInboundMessage } from '../../../agent/Handler'
2+
import type { ConnectionService } from '../services'
3+
4+
import { ConnectionProblemReportMessage } from '../messages'
5+
6+
export class ConnectionProblemReportHandler implements Handler {
7+
private connectionService: ConnectionService
8+
public supportedMessages = [ConnectionProblemReportMessage]
9+
10+
public constructor(connectionService: ConnectionService) {
11+
this.connectionService = connectionService
12+
}
13+
14+
public async handle(messageContext: HandlerInboundMessage<ConnectionProblemReportHandler>) {
15+
await this.connectionService.processProblemReport(messageContext)
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import type { ProblemReportMessageOptions } from '../../problem-reports/messages/ProblemReportMessage'
2+
3+
import { Equals } from 'class-validator'
4+
5+
import { ProblemReportMessage } from '../../problem-reports/messages/ProblemReportMessage'
6+
7+
export type ConnectionProblemReportMessageOptions = ProblemReportMessageOptions
8+
9+
/**
10+
* @see https://github.com/hyperledger/aries-rfcs/blob/main/features/0035-report-problem/README.md
11+
*/
12+
export class ConnectionProblemReportMessage extends ProblemReportMessage {
13+
/**
14+
* Create new ConnectionProblemReportMessage instance.
15+
* @param options
16+
*/
17+
public constructor(options: ConnectionProblemReportMessageOptions) {
18+
super(options)
19+
}
20+
21+
@Equals(ConnectionProblemReportMessage.type)
22+
public readonly type = ConnectionProblemReportMessage.type
23+
public static readonly type = 'https://didcomm.org/connection/1.0/problem-report'
24+
}

packages/core/src/modules/connections/messages/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ export * from './ConnectionRequestMessage'
33
export * from './ConnectionResponseMessage'
44
export * from './TrustPingMessage'
55
export * from './TrustPingResponseMessage'
6+
export * from './ConnectionProblemReportMessage'

packages/core/src/modules/connections/models/ConnectionState.ts

+1
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ export enum ConnectionState {
1010
Requested = 'requested',
1111
Responded = 'responded',
1212
Complete = 'complete',
13+
None = 'none',
1314
}

packages/core/src/modules/connections/repository/ConnectionRecord.ts

+3
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export interface ConnectionRecordProps {
2929
imageUrl?: string
3030
multiUseInvitation: boolean
3131
mediatorId?: string
32+
errorMsg?: string
3233
}
3334

3435
export type CustomConnectionTags = TagsBase
@@ -68,6 +69,7 @@ export class ConnectionRecord
6869

6970
public threadId?: string
7071
public mediatorId?: string
72+
public errorMsg?: string
7173

7274
public static readonly type = 'ConnectionRecord'
7375
public readonly type = ConnectionRecord.type
@@ -94,6 +96,7 @@ export class ConnectionRecord
9496
this.imageUrl = props.imageUrl
9597
this.multiUseInvitation = props.multiUseInvitation
9698
this.mediatorId = props.mediatorId
99+
this.errorMsg = props.errorMsg
97100
}
98101
}
99102

0 commit comments

Comments
 (0)