github.com/hyperledger/aries-framework-go@v0.3.2/pkg/didcomm/protocol/issuecredential/service.go (about) 1 /* 2 Copyright SecureKey Technologies Inc. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package issuecredential 8 9 import ( 10 "encoding/json" 11 "errors" 12 "fmt" 13 "strings" 14 15 "github.com/google/uuid" 16 17 "github.com/hyperledger/aries-framework-go/pkg/common/log" 18 "github.com/hyperledger/aries-framework-go/pkg/didcomm/common/service" 19 "github.com/hyperledger/aries-framework-go/spi/storage" 20 ) 21 22 const ( 23 // Name defines the protocol name. 24 Name = "issue-credential" 25 // SpecV2 defines the protocol spec V2. 26 SpecV2 = "https://didcomm.org/issue-credential/2.0/" 27 // ProposeCredentialMsgTypeV2 defines the protocol propose-credential message type. 28 ProposeCredentialMsgTypeV2 = SpecV2 + "propose-credential" 29 // OfferCredentialMsgTypeV2 defines the protocol offer-credential message type. 30 OfferCredentialMsgTypeV2 = SpecV2 + "offer-credential" 31 // RequestCredentialMsgTypeV2 defines the protocol request-credential message type. 32 RequestCredentialMsgTypeV2 = SpecV2 + "request-credential" 33 // IssueCredentialMsgTypeV2 defines the protocol issue-credential message type. 34 IssueCredentialMsgTypeV2 = SpecV2 + "issue-credential" 35 // AckMsgTypeV2 defines the protocol ack message type. 36 AckMsgTypeV2 = SpecV2 + "ack" 37 // ProblemReportMsgTypeV2 defines the protocol problem-report message type. 38 ProblemReportMsgTypeV2 = SpecV2 + "problem-report" 39 // CredentialPreviewMsgTypeV2 defines the protocol credential-preview inner object type. 40 CredentialPreviewMsgTypeV2 = SpecV2 + "credential-preview" 41 42 // SpecV3 defines the protocol spec V3. 43 SpecV3 = "https://didcomm.org/issue-credential/3.0/" 44 // ProposeCredentialMsgTypeV3 defines the protocol propose-credential message type. 45 ProposeCredentialMsgTypeV3 = SpecV3 + "propose-credential" 46 // OfferCredentialMsgTypeV3 defines the protocol offer-credential message type. 47 OfferCredentialMsgTypeV3 = SpecV3 + "offer-credential" 48 // RequestCredentialMsgTypeV3 defines the protocol request-credential message type. 49 RequestCredentialMsgTypeV3 = SpecV3 + "request-credential" 50 // IssueCredentialMsgTypeV3 defines the protocol issue-credential message type. 51 IssueCredentialMsgTypeV3 = SpecV3 + "issue-credential" 52 // AckMsgTypeV3 defines the protocol ack message type. 53 AckMsgTypeV3 = SpecV3 + "ack" 54 // ProblemReportMsgTypeV3 defines the protocol problem-report message type. 55 ProblemReportMsgTypeV3 = SpecV3 + "problem-report" 56 // CredentialPreviewMsgTypeV3 defines the protocol credential-preview inner object type. 57 CredentialPreviewMsgTypeV3 = SpecV3 + "credential-preview" 58 ) 59 60 const ( 61 stateNameKey = "state_name_" 62 transitionalPayloadKey = "transitionalPayload_%s" 63 ) 64 65 // nolint:gochecknoglobals 66 var ( 67 logger = log.New("aries-framework/issuecredential/service") 68 initialHandler = HandlerFunc(func(_ Metadata) error { 69 return nil 70 }) 71 errProtocolStopped = errors.New("protocol was stopped") 72 ) 73 74 // customError is a wrapper to determine custom error against internal error. 75 type customError struct{ error } 76 77 // transitionalPayload keeps payload needed for Continue function to proceed with the action. 78 type transitionalPayload struct { 79 Action 80 StateName string 81 IsV3 bool 82 Properties map[string]interface{} 83 } 84 85 // MetaData type to store data for internal usage. 86 type MetaData struct { 87 transitionalPayload 88 state state 89 msgClone service.DIDCommMsg 90 inbound bool 91 properties map[string]interface{} 92 credentialNames []string 93 // keeps offer credential payload, 94 // allows filling the message by providing an option function. 95 offerCredentialV2 *OfferCredentialV2 96 proposeCredentialV2 *ProposeCredentialV2 97 requestCredentialV2 *RequestCredentialV2 98 issueCredentialV2 *IssueCredentialV2 99 offerCredentialV3 *OfferCredentialV3 100 proposeCredentialV3 *ProposeCredentialV3 101 requestCredentialV3 *RequestCredentialV3 102 issueCredentialV3 *IssueCredentialV3 103 // err is used to determine whether callback was stopped 104 // e.g the user received an action event and executes Stop(err) function 105 // in that case `err` is equal to `err` which was passing to Stop function. 106 err error 107 } 108 109 // Message is the didcomm message. 110 func (md *MetaData) Message() service.DIDCommMsg { 111 return md.msgClone 112 } 113 114 // OfferCredentialV2 didcomm message. 115 func (md *MetaData) OfferCredentialV2() *OfferCredentialV2 { 116 return md.offerCredentialV2 117 } 118 119 // OfferCredentialV3 didcomm message. 120 func (md *MetaData) OfferCredentialV3() *OfferCredentialV3 { 121 return md.offerCredentialV3 122 } 123 124 // ProposeCredentialV2 didcomm message. 125 func (md *MetaData) ProposeCredentialV2() *ProposeCredentialV2 { 126 return md.proposeCredentialV2 127 } 128 129 // ProposeCredentialV3 didcomm message. 130 func (md *MetaData) ProposeCredentialV3() *ProposeCredentialV3 { 131 return md.proposeCredentialV3 132 } 133 134 // RequestCredentialV2 didcomm message. 135 func (md *MetaData) RequestCredentialV2() *RequestCredentialV2 { 136 return md.requestCredentialV2 137 } 138 139 // RequestCredentialV3 didcomm message. 140 func (md *MetaData) RequestCredentialV3() *RequestCredentialV3 { 141 return md.requestCredentialV3 142 } 143 144 // IssueCredentialV2 didcomm message. 145 func (md *MetaData) IssueCredentialV2() *IssueCredentialV2 { 146 return md.issueCredentialV2 147 } 148 149 // IssueCredentialV3 didcomm message. 150 func (md *MetaData) IssueCredentialV3() *IssueCredentialV3 { 151 return md.issueCredentialV3 152 } 153 154 // CredentialNames are the names with which to save credentials with. 155 func (md *MetaData) CredentialNames() []string { 156 return md.credentialNames 157 } 158 159 // StateName returns the name of the currently executing state. 160 func (md *MetaData) StateName() string { 161 return md.state.Name() 162 } 163 164 // Properties returns metadata properties. 165 func (md *MetaData) Properties() map[string]interface{} { 166 return md.properties 167 } 168 169 // Action contains helpful information about action. 170 type Action struct { 171 // Protocol instance ID 172 PIID string 173 Msg service.DIDCommMsgMap 174 MyDID string 175 TheirDID string 176 } 177 178 // Opt describes option signature for the Continue function. 179 type Opt func(md *MetaData) 180 181 // WithProposeCredential allows providing ProposeCredential message 182 // USAGE: This message should be provided after receiving an OfferCredential message. 183 func WithProposeCredential(msg *ProposeCredentialParams) Opt { 184 return func(md *MetaData) { 185 if md.IsV3 { 186 md.proposeCredentialV3 = msg.AsV3() 187 } else { 188 md.proposeCredentialV2 = msg.AsV2() 189 } 190 } 191 } 192 193 // WithProposeCredentialV2 allows providing ProposeCredentialV2 message 194 // USAGE: This message should be provided after receiving an OfferCredentialV2 message. 195 func WithProposeCredentialV2(msg *ProposeCredentialV2) Opt { 196 return func(md *MetaData) { 197 md.proposeCredentialV2 = msg 198 } 199 } 200 201 // WithProposeCredentialV3 allows providing ProposeCredentialV3 message 202 // USAGE: This message should be provided after receiving an OfferCredentialV3 message. 203 func WithProposeCredentialV3(msg *ProposeCredentialV3) Opt { 204 return func(md *MetaData) { 205 md.proposeCredentialV3 = msg 206 } 207 } 208 209 // WithRequestCredential allows providing RequestCredential message 210 // USAGE: This message should be provided after receiving an OfferCredential message. 211 func WithRequestCredential(msg *RequestCredentialParams) Opt { 212 return func(md *MetaData) { 213 if md.IsV3 { 214 md.requestCredentialV3 = msg.AsV3() 215 } else { 216 md.requestCredentialV2 = msg.AsV2() 217 } 218 } 219 } 220 221 // WithRequestCredentialV2 allows providing RequestCredentialV2 message 222 // USAGE: This message should be provided after receiving an OfferCredentialV2 message. 223 func WithRequestCredentialV2(msg *RequestCredentialV2) Opt { 224 return func(md *MetaData) { 225 md.requestCredentialV2 = msg 226 } 227 } 228 229 // WithRequestCredentialV3 allows providing RequestCredentialV3 message 230 // USAGE: This message should be provided after receiving an OfferCredentialV3 message. 231 func WithRequestCredentialV3(msg *RequestCredentialV3) Opt { 232 return func(md *MetaData) { 233 md.requestCredentialV3 = msg 234 } 235 } 236 237 // WithOfferCredential allows providing OfferCredential message 238 // USAGE: This message should be provided after receiving a ProposeCredential message. 239 func WithOfferCredential(msg *OfferCredentialParams) Opt { 240 return func(md *MetaData) { 241 if md.IsV3 { 242 md.offerCredentialV3 = msg.AsV3() 243 } else { 244 md.offerCredentialV2 = msg.AsV2() 245 } 246 } 247 } 248 249 // WithOfferCredentialV2 allows providing OfferCredentialV2 message 250 // USAGE: This message should be provided after receiving a ProposeCredentialV2 message. 251 func WithOfferCredentialV2(msg *OfferCredentialV2) Opt { 252 return func(md *MetaData) { 253 md.offerCredentialV2 = msg 254 } 255 } 256 257 // WithOfferCredentialV3 allows providing OfferCredentialV3 message 258 // USAGE: This message should be provided after receiving a ProposeCredentialV3 message. 259 func WithOfferCredentialV3(msg *OfferCredentialV3) Opt { 260 return func(md *MetaData) { 261 md.offerCredentialV3 = msg 262 } 263 } 264 265 // WithIssueCredential allows providing IssueCredential message 266 // USAGE: This message should be provided after receiving a RequestCredential message. 267 func WithIssueCredential(msg *IssueCredentialParams) Opt { 268 return func(md *MetaData) { 269 if md.IsV3 { 270 md.issueCredentialV3 = msg.AsV3() 271 } else { 272 md.issueCredentialV2 = msg.AsV2() 273 } 274 } 275 } 276 277 // WithIssueCredentialV2 allows providing IssueCredentialV2 message 278 // USAGE: This message should be provided after receiving a RequestCredentialV2 message. 279 func WithIssueCredentialV2(msg *IssueCredentialV2) Opt { 280 return func(md *MetaData) { 281 md.issueCredentialV2 = msg 282 } 283 } 284 285 // WithIssueCredentialV3 allows providing IssueCredentialV3 message 286 // USAGE: This message should be provided after receiving a RequestCredentialV3 message. 287 func WithIssueCredentialV3(msg *IssueCredentialV3) Opt { 288 return func(md *MetaData) { 289 md.issueCredentialV3 = msg 290 } 291 } 292 293 // WithFriendlyNames allows providing names for the credentials. 294 // USAGE: This function should be used when the Holder receives IssueCredentialV2 message. 295 func WithFriendlyNames(names ...string) Opt { 296 return func(md *MetaData) { 297 md.credentialNames = names 298 } 299 } 300 301 // WithProperties allows providing custom properties. 302 func WithProperties(props map[string]interface{}) Opt { 303 return func(md *MetaData) { 304 if len(md.properties) == 0 { 305 md.properties = props 306 307 return 308 } 309 310 for k, v := range props { 311 md.properties[k] = v 312 } 313 } 314 } 315 316 // Provider contains dependencies for the protocol and is typically created by using aries.Context(). 317 type Provider interface { 318 Messenger() service.Messenger 319 StorageProvider() storage.Provider 320 } 321 322 // Service for the issuecredential protocol. 323 type Service struct { 324 service.Action 325 service.Message 326 store storage.Store 327 callbacks chan *MetaData 328 messenger service.Messenger 329 middleware Handler 330 initialized bool 331 } 332 333 // New returns the issuecredential service. 334 func New(p Provider) (*Service, error) { 335 svc := Service{} 336 337 err := svc.Initialize(p) 338 if err != nil { 339 return nil, err 340 } 341 342 return &svc, nil 343 } 344 345 // Initialize initializes the Service. If Initialize succeeds, any further call is a no-op. 346 func (s *Service) Initialize(prov interface{}) error { 347 if s.initialized { 348 return nil 349 } 350 351 p, ok := prov.(Provider) 352 if !ok { 353 return fmt.Errorf("expected provider of type `%T`, got type `%T`", Provider(nil), p) 354 } 355 356 store, err := p.StorageProvider().OpenStore(Name) 357 if err != nil { 358 return err 359 } 360 361 err = p.StorageProvider().SetStoreConfig(Name, storage.StoreConfiguration{TagNames: []string{transitionalPayloadKey}}) 362 if err != nil { 363 return fmt.Errorf("failed to set store config: %w", err) 364 } 365 366 s.messenger = p.Messenger() 367 s.store = store 368 s.callbacks = make(chan *MetaData) 369 s.middleware = initialHandler 370 371 // start the listener 372 go s.startInternalListener() 373 374 s.initialized = true 375 376 return nil 377 } 378 379 // Use allows providing middlewares. 380 func (s *Service) Use(items ...Middleware) { 381 var handler Handler = initialHandler 382 for i := len(items) - 1; i >= 0; i-- { 383 handler = items[i](handler) 384 } 385 386 s.middleware = handler 387 } 388 389 // AddMiddleware appends the given Middleware to the chain of middlewares. 390 func (s *Service) AddMiddleware(mw ...Middleware) { 391 for i := len(mw) - 1; i >= 0; i-- { 392 s.middleware = mw[i](s.middleware) 393 } 394 } 395 396 // HandleInbound handles inbound message (issuecredential protocol). 397 func (s *Service) HandleInbound(msg service.DIDCommMsg, ctx service.DIDCommContext) (string, error) { 398 logger.Debugf("handling inbound: %+v", msg) 399 400 aEvent := s.ActionEvent() 401 402 // throw error if there is no action event registered for inbound messages 403 if aEvent == nil { 404 return "", errors.New("no clients are registered to handle the message") 405 } 406 407 md, err := s.doHandle(msg, false) 408 if err != nil { 409 return "", fmt.Errorf("doHandle: %w", err) 410 } 411 412 // sets inbound payload 413 md.inbound = true 414 md.MyDID = ctx.MyDID() 415 md.TheirDID = ctx.TheirDID() 416 417 // trigger action event based on message type for inbound messages 418 if canTriggerActionEvents(msg) { 419 err = s.saveTransitionalPayload(md.PIID, &md.transitionalPayload) 420 if err != nil { 421 return "", fmt.Errorf("save transitional payload: %w", err) 422 } 423 424 aEvent <- s.newDIDCommActionMsg(md) 425 426 return "", nil 427 } 428 429 // if no action event is triggered, continue the execution 430 if err = s.handle(md); err != nil { 431 return "", fmt.Errorf("handle inbound: %w", err) 432 } 433 434 return msg.ThreadID() 435 } 436 437 // HandleOutbound handles outbound message (issuecredential protocol). 438 func (s *Service) HandleOutbound(msg service.DIDCommMsg, myDID, theirDID string) (string, error) { 439 md, err := s.doHandle(msg, true) 440 if err != nil { 441 return "", fmt.Errorf("doHandle: %w", err) 442 } 443 444 // sets outbound payload 445 md.MyDID = myDID 446 md.TheirDID = theirDID 447 448 if err = s.handle(md); err != nil { 449 return "", fmt.Errorf("handle outbound: %w", err) 450 } 451 452 return msg.ThreadID() 453 } 454 455 func (s *Service) getCurrentStateNameAndPIID(msg service.DIDCommMsg) (string, string, error) { 456 piID, err := getPIID(msg) 457 if errors.Is(err, service.ErrThreadIDNotFound) { 458 msg.SetID(uuid.New().String(), service.WithVersion(getDIDVersion(getVersion(msg.Type())))) 459 460 return msg.ID(), stateNameStart, nil 461 } 462 463 if err != nil { 464 return "", "", fmt.Errorf("piID: %w", err) 465 } 466 467 stateName, err := s.currentStateName(piID) 468 if err != nil { 469 return "", "", fmt.Errorf("currentStateName: %w", err) 470 } 471 472 return piID, stateName, nil 473 } 474 475 func (s *Service) doHandle(msg service.DIDCommMsg, outbound bool) (*MetaData, error) { 476 piID, stateName, err := s.getCurrentStateNameAndPIID(msg) 477 if err != nil { 478 return nil, fmt.Errorf("getCurrentStateNameAndPIID: %w", err) 479 } 480 481 protocolVersion := getVersion(msg.Type()) 482 483 current := stateFromName(stateName, protocolVersion) 484 485 next, err := nextState(msg, outbound) 486 if err != nil { 487 return nil, fmt.Errorf("nextState: %w", err) 488 } 489 490 if !current.CanTransitionTo(next) { 491 return nil, fmt.Errorf("invalid state transition: %s -> %s", current.Name(), next.Name()) 492 } 493 494 return &MetaData{ 495 transitionalPayload: transitionalPayload{ 496 StateName: next.Name(), 497 Action: Action{ 498 Msg: msg.Clone(), 499 PIID: piID, 500 }, 501 IsV3: protocolVersion == SpecV3, 502 Properties: next.Properties(), 503 }, 504 properties: next.Properties(), 505 state: next, 506 msgClone: msg.Clone(), 507 }, nil 508 } 509 510 // startInternalListener listens to messages in go channel for callback messages from clients. 511 func (s *Service) startInternalListener() { 512 for msg := range s.callbacks { 513 // if no error do handle 514 if msg.err == nil { 515 msg.err = s.handle(msg) 516 } 517 518 // no error - continue 519 if msg.err == nil { 520 continue 521 } 522 523 logger.Errorf("abandoning: %s", msg.err) 524 msg.state = &abandoning{V: getVersion(msg.Msg.Type()), Code: codeInternalError} 525 526 if err := s.handle(msg); err != nil { 527 logger.Errorf("listener handle: %s", err) 528 } 529 } 530 } 531 532 func isNoOp(s state) bool { 533 _, ok := s.(*noOp) 534 return ok 535 } 536 537 func (s *Service) handle(md *MetaData) error { 538 var ( 539 current = md.state 540 actions []stateAction 541 stateName string 542 ) 543 544 for !isNoOp(current) { 545 stateName = current.Name() 546 547 next, action, err := s.execute(current, md) 548 if err != nil { 549 return fmt.Errorf("execute: %w", err) 550 } 551 552 actions = append(actions, action) 553 554 if !isNoOp(next) && !current.CanTransitionTo(next) { 555 return fmt.Errorf("invalid state transition: %s --> %s", current.Name(), next.Name()) 556 } 557 558 current = next 559 } 560 561 if err := s.saveStateName(md.PIID, stateName); err != nil { 562 return fmt.Errorf("failed to persist state %s: %w", stateName, err) 563 } 564 565 for _, action := range actions { 566 if err := action(s.messenger); err != nil { 567 return fmt.Errorf("action %s: %w", stateName, err) 568 } 569 } 570 571 return nil 572 } 573 574 func getPIID(msg service.DIDCommMsg) (string, error) { 575 if pthID := msg.ParentThreadID(); pthID != "" { 576 return pthID, nil 577 } 578 579 return msg.ThreadID() 580 } 581 582 func (s *Service) saveStateName(piID, stateName string) error { 583 return s.store.Put(stateNameKey+piID, []byte(stateName)) 584 } 585 586 func (s *Service) currentStateName(piID string) (string, error) { 587 src, err := s.store.Get(stateNameKey + piID) 588 if errors.Is(err, storage.ErrDataNotFound) { 589 return stateNameStart, nil 590 } 591 592 return string(src), err 593 } 594 595 // nolint: gocyclo 596 // stateFromName returns the state by given name. 597 func stateFromName(name, v string) state { 598 switch name { 599 case stateNameStart: 600 return &start{} 601 case stateNameAbandoning: 602 return &abandoning{V: v} 603 case stateNameDone: 604 return &done{V: v} 605 case stateNameProposalReceived: 606 return &proposalReceived{V: v} 607 case stateNameOfferSent: 608 return &offerSent{V: v} 609 case stateNameRequestReceived: 610 return &requestReceived{V: v} 611 case stateNameCredentialIssued: 612 return &credentialIssued{V: v} 613 case stateNameProposalSent: 614 return &proposalSent{V: v} 615 case stateNameOfferReceived: 616 return &offerReceived{V: v} 617 case stateNameRequestSent: 618 return &requestSent{V: v} 619 case stateNameCredentialReceived: 620 return &credentialReceived{V: v} 621 default: 622 return &noOp{} 623 } 624 } 625 626 func nextState(msg service.DIDCommMsg, outbound bool) (state, error) { 627 switch msg.Type() { 628 case ProposeCredentialMsgTypeV2, ProposeCredentialMsgTypeV3: 629 if outbound { 630 return &proposalSent{V: getVersion(msg.Type())}, nil 631 } 632 633 return &proposalReceived{V: getVersion(msg.Type())}, nil 634 case OfferCredentialMsgTypeV2, OfferCredentialMsgTypeV3: 635 if outbound { 636 return &offerSent{V: getVersion(msg.Type())}, nil 637 } 638 639 return &offerReceived{V: getVersion(msg.Type())}, nil 640 case RequestCredentialMsgTypeV2, RequestCredentialMsgTypeV3: 641 if outbound { 642 return &requestSent{V: getVersion(msg.Type())}, nil 643 } 644 645 return &requestReceived{V: getVersion(msg.Type())}, nil 646 case IssueCredentialMsgTypeV2, IssueCredentialMsgTypeV3: 647 return &credentialReceived{V: getVersion(msg.Type()), properties: redirectInfo(msg)}, nil 648 case ProblemReportMsgTypeV2, ProblemReportMsgTypeV3: 649 return &abandoning{V: getVersion(msg.Type()), properties: redirectInfo(msg)}, nil 650 case AckMsgTypeV2, AckMsgTypeV3: 651 return &done{V: getVersion(msg.Type())}, nil 652 default: 653 return nil, fmt.Errorf("unrecognized msgType: %s", msg.Type()) 654 } 655 } 656 657 func (s *Service) saveTransitionalPayload(id string, data *transitionalPayload) error { 658 src, err := json.Marshal(data) 659 if err != nil { 660 return fmt.Errorf("marshal transitional payload: %w", err) 661 } 662 663 return s.store.Put(fmt.Sprintf(transitionalPayloadKey, id), src, storage.Tag{Name: transitionalPayloadKey}) 664 } 665 666 // canTriggerActionEvents checks if the incoming message can trigger an action event. 667 func canTriggerActionEvents(msg service.DIDCommMsg) bool { 668 return msg.Type() == ProposeCredentialMsgTypeV2 || 669 msg.Type() == OfferCredentialMsgTypeV2 || 670 msg.Type() == IssueCredentialMsgTypeV2 || 671 msg.Type() == RequestCredentialMsgTypeV2 || 672 msg.Type() == ProblemReportMsgTypeV2 || 673 msg.Type() == ProposeCredentialMsgTypeV3 || 674 msg.Type() == OfferCredentialMsgTypeV3 || 675 msg.Type() == IssueCredentialMsgTypeV3 || 676 msg.Type() == RequestCredentialMsgTypeV3 || 677 msg.Type() == ProblemReportMsgTypeV3 678 } 679 680 func (s *Service) getTransitionalPayload(id string) (*transitionalPayload, error) { 681 src, err := s.store.Get(fmt.Sprintf(transitionalPayloadKey, id)) 682 if err != nil { 683 return nil, fmt.Errorf("store get: %w", err) 684 } 685 686 t := &transitionalPayload{} 687 688 err = json.Unmarshal(src, t) 689 if err != nil { 690 return nil, fmt.Errorf("unmarshal transitional payload: %w", err) 691 } 692 693 return t, err 694 } 695 696 func (s *Service) deleteTransitionalPayload(id string) error { 697 return s.store.Delete(fmt.Sprintf(transitionalPayloadKey, id)) 698 } 699 700 // ActionContinue allows proceeding with the action by the piID. 701 func (s *Service) ActionContinue(piID string, opts ...Opt) error { 702 tPayload, err := s.getTransitionalPayload(piID) 703 if err != nil { 704 return fmt.Errorf("get transitional payload: %w", err) 705 } 706 707 md := &MetaData{ 708 transitionalPayload: *tPayload, 709 state: stateFromName(tPayload.StateName, getVersion(tPayload.Msg.Type())), 710 msgClone: tPayload.Msg.Clone(), 711 inbound: true, 712 properties: tPayload.Properties, 713 } 714 715 for _, opt := range opts { 716 opt(md) 717 } 718 719 if err := s.deleteTransitionalPayload(md.PIID); err != nil { 720 return fmt.Errorf("delete transitional payload: %w", err) 721 } 722 723 s.processCallback(md) 724 725 return nil 726 } 727 728 // ActionStop allows stopping the action by the piID. 729 func (s *Service) ActionStop(piID string, cErr error, opts ...Opt) error { 730 tPayload, err := s.getTransitionalPayload(piID) 731 if err != nil { 732 return fmt.Errorf("get transitional payload: %w", err) 733 } 734 735 md := &MetaData{ 736 transitionalPayload: *tPayload, 737 state: stateFromName(tPayload.StateName, getVersion(tPayload.Msg.Type())), 738 msgClone: tPayload.Msg.Clone(), 739 inbound: true, 740 properties: map[string]interface{}{}, 741 } 742 743 for _, opt := range opts { 744 opt(md) 745 } 746 747 if err := s.deleteTransitionalPayload(md.PIID); err != nil { 748 return fmt.Errorf("delete transitional payload: %w", err) 749 } 750 751 if cErr == nil { 752 cErr = errProtocolStopped 753 } 754 755 md.err = customError{error: cErr} 756 s.processCallback(md) 757 758 return nil 759 } 760 761 // Actions returns actions for the async usage. 762 func (s *Service) Actions() ([]Action, error) { 763 records, err := s.store.Query(transitionalPayloadKey) 764 if err != nil { 765 return nil, fmt.Errorf("failed to query the store: %w", err) 766 } 767 768 defer storage.Close(records, logger) 769 770 var actions []Action 771 772 more, err := records.Next() 773 if err != nil { 774 return nil, fmt.Errorf("failed to get next record: %w", err) 775 } 776 777 for more { 778 value, errValue := records.Value() 779 if errValue != nil { 780 return nil, fmt.Errorf("failed to get value: %w", errValue) 781 } 782 783 var action Action 784 if errUnmarshal := json.Unmarshal(value, &action); errUnmarshal != nil { 785 return nil, fmt.Errorf("unmarshal: %w", errUnmarshal) 786 } 787 788 actions = append(actions, action) 789 790 more, err = records.Next() 791 if err != nil { 792 return nil, fmt.Errorf("failed to get next record: %w", err) 793 } 794 } 795 796 return actions, nil 797 } 798 799 func (s *Service) processCallback(msg *MetaData) { 800 // pass the callback data to internal channel. This is created to unblock consumer go routine and wrap the callback 801 // channel internally. 802 s.callbacks <- msg 803 } 804 805 // newDIDCommActionMsg creates new DIDCommAction message. 806 func (s *Service) newDIDCommActionMsg(md *MetaData) service.DIDCommAction { 807 // create the message for the channel 808 // trigger the registered action event 809 return service.DIDCommAction{ 810 ProtocolName: Name, 811 Message: md.msgClone, 812 Continue: func(opt interface{}) { 813 if fn, ok := opt.(Opt); ok { 814 fn(md) 815 } 816 817 if err := s.deleteTransitionalPayload(md.PIID); err != nil { 818 logger.Errorf("delete transitional payload", err) 819 } 820 821 s.processCallback(md) 822 }, 823 Stop: func(cErr error) { 824 if err := s.deleteTransitionalPayload(md.PIID); err != nil { 825 logger.Errorf("delete transitional payload", err) 826 } 827 828 if cErr == nil { 829 cErr = errProtocolStopped 830 } 831 832 md.err = customError{error: cErr} 833 s.processCallback(md) 834 }, 835 Properties: newEventProps(md), 836 } 837 } 838 839 func getVersion(t string) string { 840 if strings.HasPrefix(t, SpecV2) { 841 return SpecV2 842 } 843 844 return SpecV3 845 } 846 847 func getDIDVersion(v string) service.Version { 848 if v == SpecV3 { 849 return service.V2 850 } 851 852 return service.V1 853 } 854 855 func (s *Service) execute(next state, md *MetaData) (state, stateAction, error) { 856 md.state = next 857 s.sendMsgEvents(md, next.Name(), service.PreState) 858 859 defer s.sendMsgEvents(md, next.Name(), service.PostState) 860 861 md.properties = newEventProps(md).All() 862 863 if err := s.middleware.Handle(md); err != nil { 864 return nil, nil, fmt.Errorf("middleware: %w", err) 865 } 866 867 exec := next.ExecuteOutbound 868 if md.inbound { 869 exec = next.ExecuteInbound 870 } 871 872 return exec(md) 873 } 874 875 // sendMsgEvents triggers the message events. 876 func (s *Service) sendMsgEvents(md *MetaData, stateID string, stateType service.StateMsgType) { 877 // trigger the message events 878 for _, handler := range s.MsgEvents() { 879 handler <- service.StateMsg{ 880 ProtocolName: Name, 881 Type: stateType, 882 Msg: md.msgClone, 883 StateID: stateID, 884 Properties: newEventProps(md), 885 } 886 } 887 } 888 889 // Name returns service name. 890 func (s *Service) Name() string { 891 return Name 892 } 893 894 // Accept msg checks the msg type. 895 func (s *Service) Accept(msgType string) bool { 896 switch msgType { 897 case ProposeCredentialMsgTypeV2, OfferCredentialMsgTypeV2, RequestCredentialMsgTypeV2, 898 IssueCredentialMsgTypeV2, AckMsgTypeV2, ProblemReportMsgTypeV2: 899 return true 900 case ProposeCredentialMsgTypeV3, OfferCredentialMsgTypeV3, RequestCredentialMsgTypeV3, 901 IssueCredentialMsgTypeV3, AckMsgTypeV3, ProblemReportMsgTypeV3: 902 return true 903 } 904 905 return false 906 } 907 908 // redirectInfo reads web redirect info decorator from given DIDComm Msg. 909 func redirectInfo(msg service.DIDCommMsg) map[string]interface{} { 910 var redirectInfo struct { 911 WebRedirectV2 map[string]interface{} `json:"~web-redirect,omitempty"` 912 WebRedirectV3 map[string]interface{} `json:"web_redirect,omitempty"` 913 } 914 915 err := msg.Decode(&redirectInfo) 916 if err != nil { 917 // Don't fail protocol, in case of error while reading webredirect info. 918 logger.Warnf("failed to decode redirect info: %s", err) 919 } 920 921 if msg.Type() == IssueCredentialMsgTypeV3 { 922 return redirectInfo.WebRedirectV3 923 } 924 925 return redirectInfo.WebRedirectV2 926 }