github.com/hyperledger/aries-framework-go@v0.3.2/pkg/didcomm/protocol/outofband/service.go (about) 1 /* 2 Copyright SecureKey Technologies Inc. All Rights Reserved. 3 Copyright Avast Software. All Rights Reserved. 4 5 SPDX-License-Identifier: Apache-2.0 6 */ 7 8 package outofband 9 10 import ( 11 "encoding/json" 12 "errors" 13 "fmt" 14 "strings" 15 16 "github.com/google/uuid" 17 "github.com/mitchellh/mapstructure" 18 19 "github.com/hyperledger/aries-framework-go/pkg/common/log" 20 "github.com/hyperledger/aries-framework-go/pkg/common/model" 21 didcommModel "github.com/hyperledger/aries-framework-go/pkg/didcomm/common/model" 22 "github.com/hyperledger/aries-framework-go/pkg/didcomm/common/service" 23 "github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/decorator" 24 "github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/didexchange" 25 "github.com/hyperledger/aries-framework-go/pkg/didcomm/transport" 26 "github.com/hyperledger/aries-framework-go/pkg/doc/did" 27 "github.com/hyperledger/aries-framework-go/pkg/internal/logutil" 28 "github.com/hyperledger/aries-framework-go/pkg/store/connection" 29 "github.com/hyperledger/aries-framework-go/spi/storage" 30 ) 31 32 const ( 33 // Name of this protocol service. 34 Name = "out-of-band" 35 // PIURI is the Out-of-Band protocol's protocol instance URI. 36 PIURI = "https://didcomm.org/out-of-band/1.0" 37 // oldPIURI is the old OOB protocol's protocol instance URI. 38 oldPIURI = "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/out-of-band/1.0" 39 // InvitationMsgType is the '@type' for the invitation message. 40 InvitationMsgType = PIURI + "/invitation" 41 // OldInvitationMsgType is the `@type` for the old invitation message. 42 OldInvitationMsgType = oldPIURI + "/invitation" 43 // HandshakeReuseMsgType is the '@type' for the reuse message. 44 HandshakeReuseMsgType = PIURI + "/handshake-reuse" 45 // HandshakeReuseAcceptedMsgType is the '@type' for the reuse-accepted message. 46 HandshakeReuseAcceptedMsgType = PIURI + "/handshake-reuse-accepted" 47 48 // TODO channel size - https://github.com/hyperledger/aries-framework-go/issues/246 49 callbackChannelSize = 10 50 51 contextKey = "context_%s" 52 ) 53 54 var logger = log.New(fmt.Sprintf("aries-framework/%s/service", Name)) 55 56 var errIgnoredDidEvent = errors.New("ignored") 57 58 // Options is a container for optional values provided by the user. 59 type Options interface { 60 // MyLabel is the label to share with the other agent in the subsequent did-exchange. 61 MyLabel() string 62 RouterConnections() []string 63 ReuseAnyConnection() bool 64 ReuseConnection() string 65 } 66 67 type didExchSvc interface { 68 RespondTo(*didexchange.OOBInvitation, []string) (string, error) 69 SaveInvitation(invitation *didexchange.OOBInvitation) error 70 } 71 72 type connectionRecorder interface { 73 SaveInvitation(string, interface{}) error 74 GetConnectionRecord(string) (*connection.Record, error) 75 GetConnectionIDByDIDs(string, string) (string, error) 76 QueryConnectionRecords() ([]*connection.Record, error) 77 } 78 79 // Service implements the Out-Of-Band protocol. 80 type Service struct { 81 service.Action 82 service.Message 83 callbackChannel chan *callback 84 didSvc didExchSvc 85 didEvents chan service.StateMsg 86 transientStore storage.Store 87 connections connectionRecorder 88 inboundHandler func() service.InboundHandler 89 chooseAttachmentFunc func(*attachmentHandlingState) (*decorator.Attachment, error) 90 extractDIDCommMsgBytesFunc func(*decorator.Attachment) ([]byte, error) 91 listenerFunc func() 92 messenger service.Messenger 93 myMediaTypeProfiles []string 94 initialized bool 95 } 96 97 type callback struct { 98 msg service.DIDCommMsg 99 myDID string 100 theirDID string 101 ctx *context 102 } 103 104 type attachmentHandlingState struct { 105 // ID becomes the parent thread ID of didexchange 106 ID string 107 ConnectionID string 108 Invitation *Invitation 109 Done bool 110 } 111 112 // Action contains helpful information about action. 113 type Action struct { 114 // Protocol instance ID 115 PIID string 116 Msg service.DIDCommMsgMap 117 ProtocolName string 118 MyDID string 119 TheirDID string 120 } 121 122 // context keeps payload needed for Continue function to proceed with the action. 123 type context struct { 124 Action 125 CurrentStateName string 126 Inbound bool 127 ReuseAnyConnection bool 128 ReuseConnection string 129 ConnectionID string 130 Invitation *Invitation 131 DIDExchangeInv *didexchange.OOBInvitation 132 MyLabel string 133 RouterConnections []string 134 } 135 136 // Provider provides this service's dependencies. 137 type Provider interface { 138 Service(id string) (interface{}, error) 139 StorageProvider() storage.Provider 140 ProtocolStateStorageProvider() storage.Provider 141 InboundDIDCommMessageHandler() func() service.InboundHandler 142 Messenger() service.Messenger 143 MediaTypeProfiles() []string 144 } 145 146 // New creates a new instance of the out-of-band service. 147 func New(p Provider) (*Service, error) { 148 svc := Service{} 149 150 err := svc.Initialize(p) 151 if err != nil { 152 return nil, err 153 } 154 155 return &svc, nil 156 } 157 158 // Initialize initializes the Service. If Initialize succeeds, any further call is a no-op. 159 func (s *Service) Initialize(prov interface{}) error { // nolint:funlen 160 if s.initialized { 161 return nil 162 } 163 164 p, ok := prov.(Provider) 165 if !ok { 166 return fmt.Errorf("expected provider of type `%T`, got type `%T`", Provider(nil), p) 167 } 168 169 svc, err := p.Service(didexchange.DIDExchange) 170 if err != nil { 171 return fmt.Errorf("failed to initialize outofband service : %w", err) 172 } 173 174 didSvc, ok := svc.(didExchSvc) 175 if !ok { 176 return errors.New("failed to cast the didexchange service to satisfy our dependency") 177 } 178 179 store, err := p.ProtocolStateStorageProvider().OpenStore(Name) 180 if err != nil { 181 return fmt.Errorf("failed to open the transientStore : %w", err) 182 } 183 184 err = p.ProtocolStateStorageProvider().SetStoreConfig(Name, 185 storage.StoreConfiguration{TagNames: []string{contextKey}}) 186 if err != nil { 187 return fmt.Errorf("failed to set transientStore config in protocol state transientStore: %w", err) 188 } 189 190 connectionRecorder, err := connection.NewRecorder(p) 191 if err != nil { 192 return fmt.Errorf("failed to open a connection.Lookup : %w", err) 193 } 194 195 s.callbackChannel = make(chan *callback, callbackChannelSize) 196 s.didSvc = didSvc 197 s.didEvents = make(chan service.StateMsg, callbackChannelSize) 198 s.transientStore = store 199 s.connections = connectionRecorder 200 s.inboundHandler = p.InboundDIDCommMessageHandler() 201 s.chooseAttachmentFunc = chooseAttachment 202 s.extractDIDCommMsgBytesFunc = extractDIDCommMsgBytes 203 s.messenger = p.Messenger() 204 s.myMediaTypeProfiles = p.MediaTypeProfiles() 205 206 s.listenerFunc = listener(s.callbackChannel, s.didEvents, s.handleCallback, s.handleDIDEvent) 207 208 didEventsSvc, ok := didSvc.(service.Event) 209 if !ok { 210 return errors.New("failed to cast didexchange service to service.Event") 211 } 212 213 if err = didEventsSvc.RegisterMsgEvent(s.didEvents); err != nil { 214 return fmt.Errorf("failed to register for didexchange protocol msgs : %w", err) 215 } 216 217 go s.listenerFunc() 218 219 s.initialized = true 220 221 return nil 222 } 223 224 // Name is this service's name. 225 func (s *Service) Name() string { 226 return Name 227 } 228 229 // Accept determines whether this service can handle the given type of message. 230 func (s *Service) Accept(msgType string) bool { 231 switch msgType { 232 case InvitationMsgType, HandshakeReuseMsgType, HandshakeReuseAcceptedMsgType, OldInvitationMsgType: 233 return true 234 } 235 236 return false 237 } 238 239 // HandleInbound handles inbound messages. 240 func (s *Service) HandleInbound(msg service.DIDCommMsg, didCommCtx service.DIDCommContext) (string, error) { 241 logger.Debugf("inbound message: %s", msg) 242 243 if !s.Accept(msg.Type()) { 244 return "", fmt.Errorf("unsupported message type %s", msg.Type()) 245 } 246 247 events := s.ActionEvent() 248 if events == nil { 249 return "", fmt.Errorf("no clients registered to handle action events for %s protocol", Name) 250 } 251 252 myContext, err := s.currentContext(msg, didCommCtx, nil) 253 if err != nil { 254 return "", fmt.Errorf("unable to load current context for msgID=%s: %w", msg.ID(), err) 255 } 256 257 if requiresApproval(msg) { 258 go func() { 259 s.requestApproval(myContext, events, msg) 260 }() 261 262 return "", nil 263 } 264 265 return "", s.handleContext(myContext) 266 } 267 268 func (s *Service) handleContext(ctx *context) error { // nolint:funlen 269 logger.Debugf("context: %+v", ctx) 270 271 current, err := stateFromName(ctx.CurrentStateName) 272 if err != nil { 273 return fmt.Errorf("unable to instantiate current state: %w", err) 274 } 275 276 deps := &dependencies{ 277 connections: s.connections, 278 didSvc: s.didSvc, 279 saveAttchStateFunc: s.save, 280 dispatchAttachmntFunc: s.dispatchInvitationAttachment, 281 } 282 283 var ( 284 stop bool 285 next state 286 finish finisher 287 ) 288 289 for !stop { 290 logger.Debugf("start executing state %s", current.Name()) 291 292 msgCopy := ctx.Msg.Clone() 293 294 go sendMsgEvent(service.PreState, current.Name(), &s.Message, msgCopy, &eventProps{ConnID: ctx.ConnectionID}) 295 296 sendPostStateMsg := func(props *eventProps) { 297 go sendMsgEvent(service.PostState, current.Name(), &s.Message, msgCopy, props) 298 } 299 300 next, finish, stop, err = current.Execute(ctx, deps) 301 if err != nil { 302 sendPostStateMsg(&eventProps{Err: err}) 303 304 return fmt.Errorf("failed to execute state %s: %w", current.Name(), err) 305 } 306 307 logger.Debugf("completed %s.Execute()", current.Name()) 308 309 ctx.CurrentStateName = next.Name() 310 311 err = s.updateContext(ctx, next, sendPostStateMsg) 312 if err != nil { 313 return fmt.Errorf("failed to update context: %w", err) 314 } 315 316 err = finish(s.messenger) 317 if err != nil { 318 sendPostStateMsg(&eventProps{Err: err}) 319 320 return fmt.Errorf("failed to execute finisher for state %s: %w", current.Name(), err) 321 } 322 323 sendPostStateMsg(&eventProps{ConnID: ctx.ConnectionID}) 324 325 logger.Debugf("end executing state %s", current.Name()) 326 327 current = next 328 } 329 330 return nil 331 } 332 333 func (s *Service) updateContext(ctx *context, next state, sendPostStateMsg func(*eventProps)) error { 334 if isTheEnd(next) { 335 err := s.deleteContext(ctx.PIID) 336 if err != nil { 337 sendPostStateMsg(&eventProps{Err: err}) 338 339 return fmt.Errorf("failed to delete context: %w", err) 340 } 341 342 logger.Debugf("deleted context: %+v", ctx) 343 344 return nil 345 } 346 347 err := s.saveContext(ctx.PIID, ctx) 348 if err != nil { 349 sendPostStateMsg(&eventProps{Err: err}) 350 351 return fmt.Errorf("failed to update context: %w", err) 352 } 353 354 logger.Debugf("updated context: %+v", ctx) 355 356 return nil 357 } 358 359 func (s *Service) requestApproval(ctx *context, events chan<- service.DIDCommAction, msg service.DIDCommMsg) { 360 event := service.DIDCommAction{ 361 ProtocolName: Name, 362 Message: msg, 363 Continue: func(args interface{}) { 364 var opts Options 365 366 switch t := args.(type) { 367 case Options: 368 opts = t 369 default: 370 opts = &userOptions{} 371 } 372 373 ctx.ReuseConnection = opts.ReuseConnection() 374 ctx.ReuseAnyConnection = opts.ReuseAnyConnection() 375 ctx.RouterConnections = opts.RouterConnections() 376 ctx.MyLabel = opts.MyLabel() 377 378 s.callbackChannel <- &callback{ 379 msg: msg, 380 myDID: ctx.MyDID, 381 theirDID: ctx.TheirDID, 382 ctx: ctx, 383 } 384 385 logger.Debugf("continued with options: %+v", opts) 386 }, 387 Stop: func(er error) { 388 logger.Infof("user requested protocol to stop: %s", er) 389 390 if err := s.deleteContext(ctx.PIID); err != nil { 391 logger.Errorf("delete context: %s", err) 392 } 393 }, 394 } 395 396 events <- event 397 398 logger.Debugf("dispatched event: %+v", event) 399 } 400 401 // Actions returns actions for the async usage. 402 func (s *Service) Actions() ([]Action, error) { 403 records, err := s.transientStore.Query(contextKey) 404 if err != nil { 405 return nil, fmt.Errorf("failed to query transientStore: %w", err) 406 } 407 408 defer storage.Close(records, logger) 409 410 var actions []Action 411 412 more, err := records.Next() 413 if err != nil { 414 return nil, fmt.Errorf("failed to get next set of data from records: %w", err) 415 } 416 417 for more { 418 value, errValue := records.Value() 419 if errValue != nil { 420 return nil, fmt.Errorf("failed to get value from records: %w", errValue) 421 } 422 423 var action Action 424 if errUnmarshal := json.Unmarshal(value, &action); errUnmarshal != nil { 425 return nil, fmt.Errorf("unmarshal: %w", errUnmarshal) 426 } 427 428 actions = append(actions, action) 429 430 var errNext error 431 432 more, errNext = records.Next() 433 if errNext != nil { 434 return nil, fmt.Errorf("failed to get next set of data from records: %w", errNext) 435 } 436 } 437 438 return actions, nil 439 } 440 441 // ActionContinue allows proceeding with the action by the piID. 442 func (s *Service) ActionContinue(piID string, opts Options) error { 443 ctx, err := s.loadContext(piID) 444 if err != nil { 445 return fmt.Errorf("load context: %w", err) 446 } 447 448 ctx.RouterConnections = opts.RouterConnections() 449 ctx.ReuseConnection = opts.ReuseConnection() 450 ctx.ReuseAnyConnection = opts.ReuseAnyConnection() 451 ctx.MyLabel = opts.MyLabel() 452 453 err = validateInvitationAcceptance(ctx.Msg, s.myMediaTypeProfiles, opts) 454 if err != nil { 455 return fmt.Errorf("unable to accept invitation: %w", err) 456 } 457 458 go func() { 459 s.callbackChannel <- &callback{ 460 msg: ctx.Msg, 461 myDID: ctx.MyDID, 462 theirDID: ctx.TheirDID, 463 ctx: ctx, 464 } 465 }() 466 467 return nil 468 } 469 470 // ActionStop allows stopping the action by the piID. 471 func (s *Service) ActionStop(piID string, _ error) error { 472 logger.Infof("user requested action to stop: piid=%s", piID) 473 474 ctx, err := s.loadContext(piID) 475 if err != nil { 476 return fmt.Errorf("get context: %w", err) 477 } 478 479 return s.deleteContext(ctx.PIID) 480 } 481 482 func (s *Service) loadContext(id string) (*context, error) { 483 src, err := s.transientStore.Get(fmt.Sprintf(contextKey, id)) 484 if err != nil { 485 return nil, fmt.Errorf("transientStore get: %w", err) 486 } 487 488 t := &context{} 489 if err := json.Unmarshal(src, t); err != nil { 490 return nil, err 491 } 492 493 return t, nil 494 } 495 496 func (s *Service) saveContext(id string, data *context) error { 497 src, err := json.Marshal(data) 498 if err != nil { 499 return fmt.Errorf("marshal transitional payload: %w", err) 500 } 501 502 return s.transientStore.Put(fmt.Sprintf(contextKey, id), src, storage.Tag{Name: contextKey}) 503 } 504 505 func (s *Service) deleteContext(id string) error { 506 return s.transientStore.Delete(fmt.Sprintf(contextKey, id)) 507 } 508 509 func sendMsgEvent( 510 t service.StateMsgType, stateID string, l *service.Message, msg service.DIDCommMsg, p service.EventProperties) { 511 stateMsg := service.StateMsg{ 512 ProtocolName: Name, 513 Type: t, 514 StateID: stateID, 515 Msg: msg, 516 Properties: p, 517 } 518 519 logger.Debugf("sending state msg: %+v\n", stateMsg) 520 521 for _, handler := range l.MsgEvents() { 522 handler <- stateMsg 523 } 524 } 525 526 // HandleOutbound handles outbound messages. 527 func (s *Service) HandleOutbound(_ service.DIDCommMsg, _, _ string) (string, error) { 528 // TODO implement 529 return "", errors.New("not implemented") 530 } 531 532 func (s *Service) currentContext(msg service.DIDCommMsg, ctx service.DIDCommContext, opts Options) (*context, error) { 533 if msg.Type() == InvitationMsgType || msg.Type() == HandshakeReuseMsgType { 534 myContext := &context{ 535 Action: Action{ 536 PIID: msg.ID(), 537 ProtocolName: Name, 538 Msg: msg.Clone(), 539 MyDID: ctx.MyDID(), 540 TheirDID: ctx.TheirDID(), 541 }, 542 Inbound: true, 543 } 544 545 stateName := StateNameInitial 546 if msg.Type() == HandshakeReuseMsgType { 547 stateName = StateNameAwaitResponse 548 } 549 550 myContext.CurrentStateName = stateName 551 552 if opts != nil { 553 myContext.RouterConnections = opts.RouterConnections() 554 myContext.ReuseConnection = opts.ReuseConnection() 555 myContext.ReuseAnyConnection = opts.ReuseAnyConnection() 556 myContext.MyLabel = opts.MyLabel() 557 } 558 559 return myContext, s.saveContext(msg.ID(), myContext) 560 } 561 562 thid, err := msg.ThreadID() 563 if err != nil { 564 return nil, fmt.Errorf("no thread id found in msg of type [%s]: %w", msg.Type(), err) 565 } 566 567 return s.loadContext(thid) 568 } 569 570 // AcceptInvitation from another agent and return the connection ID. 571 func (s *Service) AcceptInvitation(i *Invitation, options Options) (string, error) { 572 msg := service.NewDIDCommMsgMap(i) 573 574 err := validateInvitationAcceptance(msg, s.myMediaTypeProfiles, options) 575 if err != nil { 576 return "", fmt.Errorf("unable to accept invitation: %w", err) 577 } 578 579 ctx := &callback{ 580 msg: msg, 581 } 582 583 ctx.ctx, err = s.currentContext(msg, service.EmptyDIDCommContext(), options) 584 if err != nil { 585 return "", fmt.Errorf("failed to create context for invitation: %w", err) 586 } 587 588 connID, err := s.handleCallback(ctx) 589 if err != nil { 590 return "", fmt.Errorf("failed to accept invitation : %w", err) 591 } 592 593 return connID, nil 594 } 595 596 // SaveInvitation created by the outofband client. 597 func (s *Service) SaveInvitation(i *Invitation) error { 598 target, err := chooseTarget(i.Services) 599 if err != nil { 600 return fmt.Errorf("failed to choose a target to connect against : %w", err) 601 } 602 603 // TODO where should we save this invitation? - https://github.com/hyperledger/aries-framework-go/issues/1547 604 err = s.connections.SaveInvitation(i.ID+"-TODO", i) 605 if err != nil { 606 return fmt.Errorf("failed to save oob invitation : %w", err) 607 } 608 609 logger.Debugf("saved invitation: %+v", i) 610 611 err = s.didSvc.SaveInvitation(&didexchange.OOBInvitation{ 612 ID: uuid.New().String(), 613 ThreadID: i.ID, 614 TheirLabel: i.Label, 615 Target: target, 616 MediaTypeProfiles: i.Accept, 617 }) 618 if err != nil { 619 return fmt.Errorf("the didexchange service failed to save the oob invitation : %w", err) 620 } 621 622 return nil 623 } 624 625 func listener( 626 callbacks chan *callback, 627 didEvents chan service.StateMsg, 628 handleCallbackFunc func(*callback) (string, error), 629 handleDidEventFunc func(msg service.StateMsg) error) func() { 630 return func() { 631 for { 632 select { 633 case c := <-callbacks: 634 switch c.msg.Type() { 635 case InvitationMsgType, HandshakeReuseMsgType, OldInvitationMsgType: 636 _, err := handleCallbackFunc(c) 637 if err != nil { 638 logutil.LogError(logger, Name, "handleCallback", err.Error(), 639 logutil.CreateKeyValueString("msgType", c.msg.Type()), 640 logutil.CreateKeyValueString("msgID", c.msg.ID())) 641 642 continue 643 } 644 default: 645 logutil.LogError(logger, Name, "callbackChannel", "unsupported msg type", 646 logutil.CreateKeyValueString("msgType", c.msg.Type()), 647 logutil.CreateKeyValueString("msgID", c.msg.ID())) 648 } 649 case e := <-didEvents: 650 err := handleDidEventFunc(e) 651 if errors.Is(err, errIgnoredDidEvent) { 652 logutil.LogDebug(logger, Name, "handleDIDEvent", err.Error()) 653 } 654 655 if err != nil && !errors.Is(err, errIgnoredDidEvent) { 656 logutil.LogError(logger, Name, "handleDIDEvent", err.Error()) 657 } 658 } 659 } 660 } 661 } 662 663 func (s *Service) handleCallback(c *callback) (string, error) { 664 switch c.msg.Type() { 665 case InvitationMsgType, OldInvitationMsgType: 666 return s.handleInvitationCallback(c) 667 case HandshakeReuseMsgType: 668 return "", s.handleHandshakeReuseCallback(c) 669 default: 670 return "", fmt.Errorf("unsupported message type: %s", c.msg.Type()) 671 } 672 } 673 674 func (s *Service) handleInvitationCallback(c *callback) (string, error) { 675 logger.Debugf("input: %+v", c) 676 logger.Debugf("context: %+v", c.ctx) 677 678 err := validateInvitationAcceptance(c.msg, s.myMediaTypeProfiles, &userOptions{ 679 myLabel: c.ctx.MyLabel, 680 routerConnections: c.ctx.RouterConnections, 681 reuseAnyConn: c.ctx.ReuseAnyConnection, 682 reuseConn: c.ctx.ReuseConnection, 683 }) 684 if err != nil { 685 return "", fmt.Errorf("unable to handle invitation: %w", err) 686 } 687 688 c.ctx.DIDExchangeInv, c.ctx.Invitation, err = decodeDIDInvitationAndOOBInvitation(c) 689 if err != nil { 690 return "", fmt.Errorf("handleInvitationCallback: failed to decode callback message : %w", err) 691 } 692 693 err = s.handleContext(c.ctx) 694 if err != nil { 695 return "", fmt.Errorf("failed to handle invitation: %w", err) 696 } 697 698 return c.ctx.ConnectionID, nil 699 } 700 701 func (s *Service) handleHandshakeReuseCallback(c *callback) error { 702 logger.Debugf("input: %+v", c) 703 704 return s.handleContext(c.ctx) 705 } 706 707 func (s *Service) handleDIDEvent(e service.StateMsg) error { 708 logger.Debugf("input: %+v", e) 709 710 if e.Type != service.PostState || e.StateID != didexchange.StateIDCompleted { 711 return errIgnoredDidEvent 712 } 713 714 props, ok := e.Properties.(didcommModel.Event) 715 if !ok { 716 return fmt.Errorf("handleDIDEvent: failed to cast did state msg properties") 717 } 718 719 connID := props.ConnectionID() 720 721 record, err := s.connections.GetConnectionRecord(connID) 722 if err != nil { 723 return fmt.Errorf("handleDIDEvent: failed to get connection record: %w", err) 724 } 725 726 if record.ParentThreadID == "" { 727 return fmt.Errorf("handleDIDEvent: ParentThreadID is empty") 728 } 729 730 return s.dispatchInvitationAttachment(record.ParentThreadID, record.MyDID, record.TheirDID) 731 } 732 733 func (s *Service) dispatchInvitationAttachment(invID, myDID, theirDID string) error { 734 state, err := s.fetchAttachmentHandlingState(invID) 735 if err != nil { 736 return fmt.Errorf("failed to load attachment handling state : %w", err) 737 } 738 739 msg, err := s.extractDIDCommMsg(state) 740 if err != nil { 741 return fmt.Errorf("failed to extract DIDComm msg : %w", err) 742 } 743 744 state.Done = true 745 746 // Save state as Done before dispatching message because the out-of-band protocol 747 // has done its job in getting this far. The other protocol maintains its own state. 748 err = s.save(state) 749 if err != nil { 750 return fmt.Errorf("failed to update state : %w", err) 751 } 752 753 logger.Debugf("dispatching inbound message of type: %s", msg.Type()) 754 755 _, err = s.inboundHandler().HandleInbound(msg, service.NewDIDCommContext(myDID, theirDID, nil)) 756 if err != nil { 757 return fmt.Errorf("failed to dispatch message: %w", err) 758 } 759 760 return nil 761 } 762 763 func (s *Service) save(state *attachmentHandlingState) error { 764 bytes, err := json.Marshal(state) 765 if err != nil { 766 return fmt.Errorf("failed to save state=%+v : %w", state, err) 767 } 768 769 err = s.transientStore.Put(state.ID, bytes) 770 if err != nil { 771 return fmt.Errorf("failed to save state : %w", err) 772 } 773 774 return nil 775 } 776 777 func (s *Service) fetchAttachmentHandlingState(id string) (*attachmentHandlingState, error) { 778 bytes, err := s.transientStore.Get(id) 779 if err != nil { 780 return nil, fmt.Errorf("failed to fetch attachment handling state using id=%s : %w", id, err) 781 } 782 783 state := &attachmentHandlingState{} 784 785 err = json.Unmarshal(bytes, state) 786 if err != nil { 787 return nil, fmt.Errorf("failed to unmarshal state %+v : %w", state, err) 788 } 789 790 return state, nil 791 } 792 793 // TODO only 1 attached request is to be processed from the array as discussed in: 794 // - https://github.com/hyperledger/aries-rfcs/issues/468 795 // - https://github.com/hyperledger/aries-rfcs/issues/451 796 // This logic should be injected into the service. 797 func chooseAttachment(state *attachmentHandlingState) (*decorator.Attachment, error) { 798 if !state.Done && len(state.Invitation.Requests) > 0 { 799 return state.Invitation.Requests[0], nil 800 } 801 802 return nil, errors.New("not attachments in invitation") 803 } 804 805 func extractDIDCommMsgBytes(a *decorator.Attachment) ([]byte, error) { 806 bytes, err := a.Data.Fetch() 807 if err != nil { 808 return nil, fmt.Errorf("extractDIDCommMsgBytes: %w", err) 809 } 810 811 return bytes, nil 812 } 813 814 func (s *Service) extractDIDCommMsg(state *attachmentHandlingState) (service.DIDCommMsg, error) { 815 req, err := s.chooseAttachmentFunc(state) 816 if err != nil { 817 return nil, fmt.Errorf("failed to select an attachment: %w", err) 818 } 819 820 bytes, err := s.extractDIDCommMsgBytesFunc(req) 821 if err != nil { 822 return nil, fmt.Errorf("failed to extract didcomm message from attachment : %w", err) 823 } 824 825 msg, err := service.ParseDIDCommMsgMap(bytes) 826 if err != nil { 827 return nil, fmt.Errorf("failed to parse followup request : %w", err) 828 } 829 830 return msg, nil 831 } 832 833 func validateInvitationAcceptance(msg service.DIDCommMsg, myProfiles []string, opts Options) error { // nolint:gocyclo 834 if msg.Type() != InvitationMsgType { 835 return nil 836 } 837 838 if opts.ReuseAnyConnection() && opts.ReuseConnection() != "" { 839 return errors.New("cannot reuse any connection and also reuse a specific connection") 840 } 841 842 inv := &Invitation{} 843 844 err := msg.Decode(inv) 845 if err != nil { 846 return fmt.Errorf("validateInvitationAcceptance: failed to decode invitation: %w", err) 847 } 848 849 if opts.ReuseConnection() != "" { 850 _, err = did.Parse(opts.ReuseConnection()) 851 if err != nil { 852 return fmt.Errorf("validateInvitationAcceptance: not a valid DID [%s]: %w", opts.ReuseConnection(), err) 853 } 854 855 found := false 856 857 for i := range inv.Services { 858 found = opts.ReuseConnection() == inv.Services[i] 859 if found { 860 break 861 } 862 } 863 864 if !found { 865 return fmt.Errorf( 866 "validateInvitationAcceptance: did [%s] not found in invitation services", opts.ReuseConnection()) 867 } 868 } 869 870 if !matchMediaTypeProfiles(inv.Accept, myProfiles) { 871 return fmt.Errorf("no acceptable media type profile found in invitation, invitation Accept property: [%v], "+ 872 "agent mediatypeprofiles: [%v]", inv.Accept, myProfiles) 873 } 874 875 return nil 876 } 877 878 func matchMediaTypeProfiles(theirProfiles, myProfiles []string) bool { 879 if theirProfiles == nil { 880 // we use our preferred media type profile instead of confirming an overlap exists 881 return true 882 } 883 884 if myProfiles == nil { 885 myProfiles = transport.MediaTypeProfiles() 886 } 887 888 profiles := list2set(myProfiles) 889 890 for _, a := range theirProfiles { 891 if _, valid := profiles[a]; valid { 892 return true 893 } 894 } 895 896 return false 897 } 898 899 func list2set(list []string) map[string]struct{} { 900 set := map[string]struct{}{} 901 902 for _, e := range list { 903 set[e] = struct{}{} 904 } 905 906 return set 907 } 908 909 func decodeDIDInvitationAndOOBInvitation(c *callback) (*didexchange.OOBInvitation, *Invitation, error) { 910 oobInv := &Invitation{} 911 912 err := c.msg.Decode(oobInv) 913 if err != nil { 914 return nil, nil, fmt.Errorf("failed to decode out-of-band invitation mesesage : %w", err) 915 } 916 917 target, err := chooseTarget(oobInv.Services) 918 if err != nil { 919 return nil, nil, fmt.Errorf("failed to choose a target to connect against : %w", err) 920 } 921 922 didInv := &didexchange.OOBInvitation{ 923 ID: uuid.New().String(), 924 ThreadID: oobInv.ID, 925 TheirLabel: oobInv.Label, 926 Target: target, 927 MyLabel: c.ctx.MyLabel, 928 MediaTypeProfiles: oobInv.Accept, 929 } 930 931 return didInv, oobInv, nil 932 } 933 934 //nolint:funlen,gocognit,gocyclo 935 func chooseTarget(svcs []interface{}) (interface{}, error) { 936 for i := range svcs { 937 switch svc := svcs[i].(type) { 938 case string, *did.Service: 939 return svc, nil 940 case map[string]interface{}: 941 var s did.Service 942 943 decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{TagName: "json", Result: &s}) 944 if err != nil { 945 return nil, fmt.Errorf("failed to initialize decoder : %w", err) 946 } 947 948 err = decoder.Decode(svc) 949 //nolint:nestif 950 if err != nil { 951 var targetErr *mapstructure.Error 952 953 if errors.As(err, &targetErr) { 954 for _, er := range targetErr.Errors { 955 // TODO this error check depend on mapstructure decoding 'ServiceEndpoint' section of service. 956 // TODO Find a better way to build it. 957 // if serviceEndpoint is a string, explicitly convert it using model.NewDIDCommV1Endpoint(). 958 if strings.EqualFold(er, "'serviceEndpoint' expected a map, got 'string'") { 959 uri, ok := svc["serviceEndpoint"].(string) 960 if ok { 961 s.ServiceEndpoint = model.NewDIDCommV1Endpoint(uri) 962 return &s, nil 963 } 964 } else if strings.EqualFold(er, "'serviceEndpoint' expected a map, got 'slice'") { 965 // if serviceEndpoint is a slice, explicitly convert each entry using the following call: 966 // model.NewDIDCommV2Endpoint() 967 seps, ok := svc["serviceEndpoint"].([]interface{}) 968 if ok { 969 var ( 970 v2Endpoints []model.DIDCommV2Endpoint 971 errs []error 972 ) 973 974 for _, sep := range seps { 975 var v2Endpoint model.DIDCommV2Endpoint 976 977 endpointDecoder, e := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 978 TagName: "json", Result: &v2Endpoint, 979 }) 980 if e != nil { 981 errs = append(errs, fmt.Errorf("failed to initialize DIDComm V2 "+ 982 "ServiceEndpoint decoder: %w, skipping", e)) 983 984 continue 985 } 986 987 e = endpointDecoder.Decode(sep) 988 if e != nil { 989 errs = append(errs, fmt.Errorf("didComm V2 ServiceEndpoint decoding "+ 990 "failed: %w, skipping", e)) 991 992 continue 993 } 994 995 v2Endpoints = append(v2Endpoints, v2Endpoint) 996 } 997 998 if len(v2Endpoints) > 0 { 999 s.ServiceEndpoint = model.NewDIDCommV2Endpoint(v2Endpoints) 1000 return &s, nil 1001 } 1002 1003 if len(errs) > 0 { 1004 return nil, fmt.Errorf("failed to decode DIDComm V2 service endpoint of "+ 1005 "service block: %v", errs) 1006 } 1007 } 1008 } 1009 } 1010 } 1011 1012 return nil, fmt.Errorf("failed to decode service block : %w, svc: %#v", err, svc) 1013 } 1014 1015 return &s, nil 1016 } 1017 } 1018 1019 return nil, fmt.Errorf("invalid or no targets to choose from") 1020 } 1021 1022 func isTheEnd(s state) bool { 1023 _, ok := s.(*stateDone) 1024 1025 return ok 1026 } 1027 1028 type eventProps struct { 1029 ConnID string `json:"conn_id"` 1030 Err error `json:"err"` 1031 } 1032 1033 func (e *eventProps) ConnectionID() string { 1034 return e.ConnID 1035 } 1036 1037 func (e *eventProps) Error() error { 1038 return e.Err 1039 } 1040 1041 type userOptions struct { 1042 myLabel string 1043 routerConnections []string 1044 reuseAnyConn bool 1045 reuseConn string 1046 } 1047 1048 func (e *userOptions) MyLabel() string { 1049 return e.myLabel 1050 } 1051 1052 func (e *userOptions) RouterConnections() []string { 1053 return e.routerConnections 1054 } 1055 1056 func (e *userOptions) ReuseAnyConnection() bool { 1057 return e.reuseAnyConn 1058 } 1059 1060 func (e *userOptions) ReuseConnection() string { 1061 return e.reuseConn 1062 } 1063 1064 // All implements EventProperties interface. 1065 func (e *eventProps) All() map[string]interface{} { 1066 return map[string]interface{}{ 1067 "connectionID": e.ConnectionID(), 1068 "error": e.Error(), 1069 } 1070 }