github.com/hyperledger/aries-framework-go@v0.3.2/pkg/didcomm/protocol/introduce/service.go (about) 1 /* 2 Copyright SecureKey Technologies Inc. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package introduce 8 9 import ( 10 "encoding/json" 11 "errors" 12 "fmt" 13 "sort" 14 "time" 15 16 "github.com/google/uuid" 17 18 "github.com/hyperledger/aries-framework-go/pkg/common/log" 19 "github.com/hyperledger/aries-framework-go/pkg/didcomm/common/model" 20 "github.com/hyperledger/aries-framework-go/pkg/didcomm/common/service" 21 "github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/decorator" 22 "github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/outofband" 23 "github.com/hyperledger/aries-framework-go/spi/storage" 24 ) 25 26 const ( 27 // Introduce protocol name. 28 Introduce = "introduce" 29 // IntroduceSpec defines the introduce spec. 30 IntroduceSpec = "https://didcomm.org/introduce/1.0/" 31 // ProposalMsgType defines the introduce proposal message type. 32 ProposalMsgType = IntroduceSpec + "proposal" 33 // RequestMsgType defines the introduce request message type. 34 RequestMsgType = IntroduceSpec + "request" 35 // ResponseMsgType defines the introduce response message type. 36 ResponseMsgType = IntroduceSpec + "response" 37 // AckMsgType defines the introduce ack message type. 38 AckMsgType = IntroduceSpec + "ack" 39 // ProblemReportMsgType defines the introduce problem-report message type. 40 ProblemReportMsgType = IntroduceSpec + "problem-report" 41 stateOOBInitial = "initial" 42 ) 43 44 const ( 45 maxIntroducees = 2 46 participantsKey = "participants_%s_%s" 47 stateNameKey = "state_name_" 48 transitionalPayloadKey = "transitionalPayload_%s" 49 metadataKey = "metadata_%s" 50 jsonMetadata = "_internal_metadata" 51 ) 52 53 var ( 54 logger = log.New("aries-framework/introduce/service") 55 56 errProtocolStopped = errors.New("protocol was stopped") 57 ) 58 59 // customError is a wrapper to determine custom error against internal error. 60 type customError struct{ error } 61 62 // Recipient keeps information needed for the service 63 // 'To' field is needed for the proposal message 64 // 'MyDID' and 'TheirDID' fields are needed for sending messages e.g report-problem, proposal, ack etc. 65 type Recipient struct { 66 To *To `json:"to"` 67 Goal string `json:"goal"` 68 GoalCode string `json:"goal_code"` 69 MyDID string `json:"my_did,omitempty"` 70 TheirDID string `json:"their_did,omitempty"` 71 } 72 73 // Action contains helpful information about action. 74 type Action struct { 75 // Protocol instance ID 76 PIID string 77 Msg service.DIDCommMsgMap 78 MyDID string 79 TheirDID string 80 } 81 82 // transitionalPayload keeps payload needed for Continue function to proceed with the action. 83 type transitionalPayload struct { 84 Action 85 StateName string 86 } 87 88 // metaData type to store data for internal usage. 89 type metaData struct { 90 transitionalPayload 91 state state 92 msgClone service.DIDCommMsg 93 participants []*participant 94 rejected bool 95 inbound bool 96 saveMetadata func(msg service.DIDCommMsgMap, thID string) error 97 // err is used to determine whether callback was stopped 98 // e.g the user received an action event and executes Stop(err) function 99 // in that case `err` is equal to `err` which was passing to Stop function 100 err error 101 } 102 103 // Service for introduce protocol. 104 type Service struct { 105 service.Action 106 service.Message 107 store storage.Store 108 callbacks chan *metaData 109 oobEvent chan service.StateMsg 110 messenger service.Messenger 111 initialized bool 112 } 113 114 // Provider contains dependencies for the DID exchange protocol and is typically created by using aries.Context(). 115 type Provider interface { 116 Messenger() service.Messenger 117 StorageProvider() storage.Provider 118 Service(id string) (interface{}, error) 119 } 120 121 // New returns introduce service. 122 func New(p Provider) (*Service, error) { 123 svc := Service{} 124 125 err := svc.Initialize(p) 126 if err != nil { 127 return nil, err 128 } 129 130 return &svc, nil 131 } 132 133 // Initialize initializes the Service. If Initialize succeeds, any further call is a no-op. 134 func (s *Service) Initialize(prov interface{}) error { 135 if s.initialized { 136 return nil 137 } 138 139 p, ok := prov.(Provider) 140 if !ok { 141 return fmt.Errorf("expected provider of type `%T`, got type `%T`", Provider(nil), prov) 142 } 143 144 store, err := p.StorageProvider().OpenStore(Introduce) 145 if err != nil { 146 return err 147 } 148 149 err = p.StorageProvider().SetStoreConfig(Introduce, 150 storage.StoreConfiguration{TagNames: []string{transitionalPayloadKey, participantsKey}}) 151 if err != nil { 152 return fmt.Errorf("failed to set store configuration: %w", err) 153 } 154 155 oobSvc, err := p.Service(outofband.Name) 156 if err != nil { 157 return fmt.Errorf("load the %s service: %w", outofband.Name, err) 158 } 159 160 oobService, ok := oobSvc.(service.Event) 161 if !ok { 162 return fmt.Errorf("cast service to service.Event") 163 } 164 165 s.messenger = p.Messenger() 166 s.store = store 167 s.callbacks = make(chan *metaData) 168 s.oobEvent = make(chan service.StateMsg) 169 170 if err = oobService.RegisterMsgEvent(s.oobEvent); err != nil { 171 return fmt.Errorf("oob register msg event: %w", err) 172 } 173 174 // start the listener 175 go s.startInternalListener() 176 177 s.initialized = true 178 179 return nil 180 } 181 182 // startInternalListener listens to messages in gochannel for callback messages from clients. 183 func (s *Service) startInternalListener() { 184 for { 185 select { 186 case msg := <-s.callbacks: 187 // if no error or it was rejected do handle 188 if msg.err == nil || msg.rejected { 189 msg.err = s.handle(msg) 190 } 191 192 // no error - continue 193 if msg.err == nil { 194 continue 195 } 196 197 msg.state = &abandoning{Code: codeInternalError} 198 199 logInternalError(msg.err) 200 201 if err := s.handle(msg); err != nil { 202 logger.Errorf("listener handle: %s", err) 203 } 204 case event := <-s.oobEvent: 205 if err := s.OOBMessageReceived(event); err != nil { 206 logger.Errorf("listener oob message received: %s", err) 207 } 208 } 209 } 210 } 211 212 func logInternalError(err error) { 213 if !errors.As(err, &customError{}) { 214 logger.Errorf("go to abandoning: %v", err) 215 } 216 } 217 218 func getPIID(msg service.DIDCommMsgMap) (string, error) { 219 piID := msg.Metadata()[metaPIID] 220 if piID, ok := piID.(string); ok && piID != "" { 221 return piID, nil 222 } 223 224 return threadID(msg) 225 } 226 227 func threadID(msg service.DIDCommMsgMap) (string, error) { 228 if pthID := msg.ParentThreadID(); pthID != "" { 229 return pthID, nil 230 } 231 232 thID, err := msg.ThreadID() 233 if errors.Is(err, service.ErrThreadIDNotFound) { 234 msg["@id"] = uuid.New().String() 235 return msg["@id"].(string), nil 236 } 237 238 return thID, err 239 } 240 241 func (s *Service) doHandle(msg service.DIDCommMsg, outbound bool) (*metaData, error) { 242 msgMap := msg.Clone() 243 244 piID, err := getPIID(msgMap) 245 if err != nil { 246 return nil, fmt.Errorf("piID: %w", err) 247 } 248 249 stateName, err := s.currentStateName(piID) 250 if err != nil { 251 return nil, fmt.Errorf("currentStateName: %w", err) 252 } 253 254 current := stateFromName(stateName) 255 256 next, err := nextState(msgMap, outbound) 257 if err != nil { 258 return nil, fmt.Errorf("nextState: %w", err) 259 } 260 261 if !current.CanTransitionTo(next) { 262 return nil, fmt.Errorf("invalid state transition: %s -> %s", current.Name(), next.Name()) 263 } 264 265 return &metaData{ 266 transitionalPayload: transitionalPayload{ 267 StateName: next.Name(), 268 Action: Action{ 269 Msg: msg.Clone(), 270 PIID: piID, 271 }, 272 }, 273 saveMetadata: s.saveMetadata, 274 state: next, 275 msgClone: msgMap.Clone(), 276 }, nil 277 } 278 279 // OOBMessageReceived is used to finish the state machine 280 // the function should be called by the out-of-band service after receiving an oob message. 281 func (s *Service) OOBMessageReceived(msg service.StateMsg) error { 282 // TODO we should verify if this parent thread ID is an instance of the introduce protocol 283 if msg.StateID != stateOOBInitial || msg.Type != service.PreState || msg.Msg.ParentThreadID() == "" { 284 return nil 285 } 286 287 // NOTE: the message is being used internally. 288 // Do not modify the payload such as ID and Thread. 289 _, err := s.HandleInbound(service.NewDIDCommMsgMap(&model.Ack{ 290 Type: AckMsgType, 291 ID: uuid.New().String(), 292 Thread: &decorator.Thread{ID: msg.Msg.ParentThreadID()}, 293 }), service.EmptyDIDCommContext()) 294 295 return err 296 } 297 298 // HandleInbound handles inbound message (introduce protocol). 299 func (s *Service) HandleInbound(msg service.DIDCommMsg, ctx service.DIDCommContext) (string, error) { 300 aEvent := s.ActionEvent() 301 302 // throw error if there is no action event registered for inbound messages 303 if aEvent == nil { 304 return "", errors.New("no clients are registered to handle the message") 305 } 306 307 if err := s.populateMetadata(msg.(service.DIDCommMsgMap)); err != nil { 308 return "", fmt.Errorf("populate metadata: %w", err) 309 } 310 311 md, err := s.doHandle(msg, false) 312 if err != nil { 313 return "", fmt.Errorf("doHandle: %w", err) 314 } 315 316 // sets inbound payload 317 md.inbound = true 318 md.MyDID = ctx.MyDID() 319 md.TheirDID = ctx.TheirDID() 320 321 // trigger action event based on message type for inbound messages 322 if canTriggerActionEvents(msg) { 323 err = s.saveTransitionalPayload(md.PIID, md.transitionalPayload) 324 if err != nil { 325 return "", fmt.Errorf("save transitional payload: %w", err) 326 } 327 328 aEvent <- s.newDIDCommActionMsg(md) 329 330 return md.PIID, nil 331 } 332 333 // if no action event is triggered, continue the execution 334 return md.PIID, s.handle(md) 335 } 336 337 // HandleOutbound handles outbound message (introduce protocol). 338 func (s *Service) HandleOutbound(msg service.DIDCommMsg, myDID, theirDID string) (string, error) { 339 md, err := s.doHandle(msg, true) 340 if err != nil { 341 return "", fmt.Errorf("doHandle: %w", err) 342 } 343 344 // sets outbound payload 345 md.MyDID = myDID 346 md.TheirDID = theirDID 347 348 return md.PIID, s.handle(md) 349 } 350 351 // sendMsgEvents triggers the message events. 352 func (s *Service) sendMsgEvents(md *metaData, stateID string, stateType service.StateMsgType) { 353 // trigger the message events 354 for _, handler := range s.MsgEvents() { 355 handler <- service.StateMsg{ 356 ProtocolName: Introduce, 357 Type: stateType, 358 Msg: md.msgClone, 359 StateID: stateID, 360 Properties: newEventProps(md), 361 } 362 } 363 } 364 365 // newDIDCommActionMsg creates new DIDCommAction message. 366 func (s *Service) newDIDCommActionMsg(md *metaData) service.DIDCommAction { 367 // create the message for the channel 368 // trigger the registered action event 369 actionStop := func(err error) { 370 // if introducee received Proposal rejected must be true 371 if md.Msg.Type() == ProposalMsgType { 372 md.rejected = true 373 } 374 375 md.err = err 376 s.processCallback(md) 377 } 378 379 return service.DIDCommAction{ 380 ProtocolName: Introduce, 381 Message: md.msgClone, 382 Continue: func(opt interface{}) { 383 if fn, ok := opt.(Opt); ok { 384 fn(md.Msg.Metadata()) 385 } 386 387 if md.Msg.Type() == RequestMsgType { 388 if md.Msg.Metadata()[metaRecipients] == nil { 389 md.err = errors.New("no recipients") 390 } 391 } 392 393 if err := s.deleteTransitionalPayload(md.PIID); err != nil { 394 logger.Errorf("delete transitional payload", err) 395 } 396 397 s.processCallback(md) 398 }, 399 Stop: func(err error) { 400 if err == nil { 401 err = errProtocolStopped 402 } 403 404 actionStop(customError{error: err}) 405 }, 406 Properties: newEventProps(md), 407 } 408 } 409 410 // ActionContinue allows proceeding with the action by the piID. 411 func (s *Service) ActionContinue(piID string, opt Opt) error { 412 tPayload, err := s.getTransitionalPayload(piID) 413 if err != nil { 414 return fmt.Errorf("get transitional payload: %w", err) 415 } 416 417 md := &metaData{ 418 transitionalPayload: *tPayload, 419 state: stateFromName(tPayload.StateName), 420 msgClone: tPayload.Msg.Clone(), 421 inbound: true, 422 saveMetadata: s.saveMetadata, 423 } 424 425 if opt != nil { 426 opt(md.Msg.Metadata()) 427 } 428 429 if md.Msg.Type() == RequestMsgType { 430 if md.Msg.Metadata()[metaRecipients] == nil { 431 return errors.New("no recipients") 432 } 433 } 434 435 if err := s.deleteTransitionalPayload(md.PIID); err != nil { 436 logger.Errorf("delete transitional payload", err) 437 } 438 439 s.processCallback(md) 440 441 return nil 442 } 443 444 // ActionStop allows stopping the action by the piID. 445 func (s *Service) ActionStop(piID string, cErr error) error { 446 tPayload, err := s.getTransitionalPayload(piID) 447 if err != nil { 448 return fmt.Errorf("get transitional payload: %w", err) 449 } 450 451 md := &metaData{ 452 transitionalPayload: *tPayload, 453 state: stateFromName(tPayload.StateName), 454 msgClone: tPayload.Msg.Clone(), 455 inbound: true, 456 saveMetadata: s.saveMetadata, 457 } 458 459 if err := s.deleteTransitionalPayload(md.PIID); err != nil { 460 return fmt.Errorf("delete transitional payload: %w", err) 461 } 462 463 // if introducee received Proposal rejected must be true 464 if md.Msg.Type() == ProposalMsgType { 465 md.rejected = true 466 } 467 468 if cErr == nil { 469 cErr = errProtocolStopped 470 } 471 472 md.err = customError{error: cErr} 473 s.processCallback(md) 474 475 return nil 476 } 477 478 func (s *Service) processCallback(msg *metaData) { 479 // pass the callback data to internal channel. This is created to unblock consumer go routine and wrap the callback 480 // channel internally. 481 s.callbacks <- msg 482 } 483 484 func nextState(msg service.DIDCommMsg, outbound bool) (state, error) { 485 switch msg.Type() { 486 case RequestMsgType: 487 if outbound { 488 return &requesting{}, nil 489 } 490 491 return &arranging{}, nil 492 case ProposalMsgType: 493 if outbound { 494 return &arranging{}, nil 495 } 496 497 return &deciding{}, nil 498 case ResponseMsgType: 499 return &arranging{}, nil 500 case ProblemReportMsgType: 501 return &abandoning{}, nil 502 case AckMsgType: 503 return &done{}, nil 504 default: 505 return nil, fmt.Errorf("unrecognized msgType: %s", msg.Type()) 506 } 507 } 508 509 func (s *Service) currentStateName(piID string) (string, error) { 510 src, err := s.store.Get(stateNameKey + piID) 511 if errors.Is(err, storage.ErrDataNotFound) { 512 return stateNameStart, nil 513 } 514 515 return string(src), err 516 } 517 518 // Actions returns actions for the async usage. 519 func (s *Service) Actions() ([]Action, error) { 520 records, err := s.store.Query(transitionalPayloadKey) 521 if err != nil { 522 return nil, fmt.Errorf("failed to query store: %w", err) 523 } 524 525 defer storage.Close(records, logger) 526 527 var actions []Action 528 529 more, err := records.Next() 530 if err != nil { 531 return nil, fmt.Errorf("failed to get next record: %w", err) 532 } 533 534 for more { 535 var action Action 536 537 value, err := records.Value() 538 if err != nil { 539 return nil, fmt.Errorf("failed to get value from records: %w", err) 540 } 541 542 if errUnmarshal := json.Unmarshal(value, &action); errUnmarshal != nil { 543 return nil, fmt.Errorf("unmarshal: %w", errUnmarshal) 544 } 545 546 actions = append(actions, action) 547 548 more, err = records.Next() 549 if err != nil { 550 return nil, fmt.Errorf("failed to get next record: %w", err) 551 } 552 } 553 554 return actions, nil 555 } 556 557 func (s *Service) deleteTransitionalPayload(id string) error { 558 return s.store.Delete(fmt.Sprintf(transitionalPayloadKey, id)) 559 } 560 561 func (s *Service) saveTransitionalPayload(id string, data transitionalPayload) error { 562 src, err := json.Marshal(data) 563 if err != nil { 564 return fmt.Errorf("marshal transitional payload: %w", err) 565 } 566 567 return s.store.Put(fmt.Sprintf(transitionalPayloadKey, id), src, storage.Tag{Name: transitionalPayloadKey}) 568 } 569 570 func (s *Service) getTransitionalPayload(id string) (*transitionalPayload, error) { 571 src, err := s.store.Get(fmt.Sprintf(transitionalPayloadKey, id)) 572 if err != nil { 573 return nil, fmt.Errorf("store get: %w", err) 574 } 575 576 t := &transitionalPayload{} 577 578 err = json.Unmarshal(src, t) 579 if err != nil { 580 return nil, fmt.Errorf("unmarshal transitional payload: %w", err) 581 } 582 583 return t, err 584 } 585 586 func (s *Service) saveStateName(piID, stateName string) error { 587 return s.store.Put(stateNameKey+piID, []byte(stateName)) 588 } 589 590 // nolint: gocyclo 591 // stateFromName returns the state by given name. 592 func stateFromName(name string) state { 593 switch name { 594 case stateNameNoop: 595 return &noOp{} 596 case stateNameStart: 597 return &start{} 598 case stateNameDone: 599 return &done{} 600 case stateNameArranging: 601 return &arranging{} 602 case stateNameDelivering: 603 return &delivering{} 604 case stateNameConfirming: 605 return &confirming{} 606 case stateNameAbandoning: 607 return &abandoning{} 608 case stateNameRequesting: 609 return &requesting{} 610 case stateNameDeciding: 611 return &deciding{} 612 case stateNameWaiting: 613 return &waiting{} 614 default: 615 return &noOp{} 616 } 617 } 618 619 // canTriggerActionEvents checks if the incoming message can trigger an action event. 620 func canTriggerActionEvents(msg service.DIDCommMsg) bool { 621 switch msg.Type() { 622 case ProposalMsgType, RequestMsgType, ProblemReportMsgType: 623 return true 624 } 625 626 return false 627 } 628 629 func isNoOp(s state) bool { 630 _, ok := s.(*noOp) 631 return ok 632 } 633 634 // isSkipProposal is a helper function to determine whether this is skip proposal or not. 635 func isSkipProposal(md *metaData) bool { 636 if md.Msg.Metadata()[metaSkipProposal] == nil { 637 return false 638 } 639 640 return md.Msg.Metadata()[metaSkipProposal].(bool) 641 } 642 643 func (s *Service) handle(md *metaData) error { 644 if err := s.saveResponse(md); err != nil { 645 return err 646 } 647 648 var ( 649 current = md.state 650 actions []stateAction 651 stateName string 652 ) 653 654 for !isNoOp(current) { 655 stateName = current.Name() 656 657 next, action, err := s.execute(current, md) 658 if err != nil { 659 return fmt.Errorf("execute: %w", err) 660 } 661 662 actions = append(actions, action) 663 664 if !isNoOp(next) && !current.CanTransitionTo(next) { 665 return fmt.Errorf("invalid state transition: %s --> %s", current.Name(), next.Name()) 666 } 667 668 current = next 669 } 670 671 if err := s.saveStateName(md.PIID, stateName); err != nil { 672 return fmt.Errorf("failed to persist state %s: %w", stateName, err) 673 } 674 675 for _, action := range actions { 676 if err := action(); err != nil { 677 return err 678 } 679 } 680 681 return nil 682 } 683 684 func contextOOBMessage(msg service.DIDCommMsg) map[string]interface{} { 685 var oobMsg map[string]interface{} 686 687 switch v := msg.Metadata()[metaOOBMessage].(type) { 688 case service.DIDCommMsgMap: 689 oobMsg = v.Clone() 690 691 for k := range v.Metadata() { 692 delete(oobMsg, k) 693 } 694 case map[string]interface{}: 695 oobMsg = v 696 } 697 698 return oobMsg 699 } 700 701 type participant struct { 702 OOBMessage map[string]interface{} 703 Approve bool 704 Message service.DIDCommMsgMap 705 MyDID string 706 TheirDID string 707 ThreadID string 708 CreatedAt time.Time 709 } 710 711 func (s *Service) saveResponse(md *metaData) error { 712 // ignore if message is not response 713 if md.Msg.Type() != ResponseMsgType { 714 return nil 715 } 716 717 // checks whether response was already handled 718 for _, p := range md.participants { 719 if p.Message.ID() == md.Msg.ID() { 720 return nil 721 } 722 } 723 724 r := Response{} 725 if err := md.Msg.Decode(&r); err != nil { 726 return err 727 } 728 729 thID, err := md.Msg.ThreadID() 730 if err != nil { 731 return fmt.Errorf("threadID: %w", err) 732 } 733 734 err = s.saveParticipant(md.PIID, &participant{ 735 OOBMessage: r.OOBMessage, 736 Approve: r.Approve, 737 Message: md.Msg, 738 MyDID: md.MyDID, 739 TheirDID: md.TheirDID, 740 ThreadID: thID, 741 CreatedAt: time.Now(), 742 }) 743 if err != nil { 744 return fmt.Errorf("save participant: %w", err) 745 } 746 747 md.participants, err = s.getParticipants(md.PIID) 748 749 return err 750 } 751 752 func (s *Service) saveParticipant(piID string, p *participant) error { 753 src, err := json.Marshal(p) 754 if err != nil { 755 return fmt.Errorf("marshal: %w", err) 756 } 757 758 return s.store.Put(fmt.Sprintf(participantsKey, piID, uuid.New().String()), src, storage.Tag{ 759 Name: participantsKey, 760 Value: piID, 761 }) 762 } 763 764 func (s *Service) getParticipants(piID string) ([]*participant, error) { 765 records, err := s.store.Query(fmt.Sprintf("%s:%s", participantsKey, piID)) 766 if err != nil { 767 return nil, fmt.Errorf("failed to query store: %w", err) 768 } 769 770 defer storage.Close(records, logger) 771 772 var participants []*participant 773 774 more, err := records.Next() 775 if err != nil { 776 return nil, fmt.Errorf("failed to get next record: %w", err) 777 } 778 779 for more { 780 value, err := records.Value() 781 if err != nil { 782 return nil, fmt.Errorf("failed to get value from records: %w", err) 783 } 784 785 var participant *participant 786 if errUnmarshal := json.Unmarshal(value, &participant); errUnmarshal != nil { 787 return nil, fmt.Errorf("unmarshal: %w", errUnmarshal) 788 } 789 790 participants = append(participants, participant) 791 792 more, err = records.Next() 793 if err != nil { 794 return nil, fmt.Errorf("failed to get next record: %w", err) 795 } 796 } 797 798 sort.Slice(participants, func(i, j int) bool { 799 return participants[i].CreatedAt.UnixNano() < participants[j].CreatedAt.UnixNano() 800 }) 801 802 return participants, nil 803 } 804 805 func (s *Service) execute(next state, md *metaData) (state, stateAction, error) { 806 md.state = next 807 s.sendMsgEvents(md, next.Name(), service.PreState) 808 809 defer s.sendMsgEvents(md, next.Name(), service.PostState) 810 811 var ( 812 followup state 813 err error 814 action func() error 815 ) 816 817 if md.inbound { 818 followup, action, err = next.ExecuteInbound(s.messenger, md) 819 } else { 820 followup, action, err = next.ExecuteOutbound(s.messenger, md) 821 } 822 823 if err != nil { 824 return nil, nil, fmt.Errorf("execute state %s %w", next.Name(), err) 825 } 826 827 return followup, action, nil 828 } 829 830 func (s *Service) populateMetadata(msg service.DIDCommMsgMap) error { 831 thID, err := msg.ThreadID() 832 if err != nil { 833 return fmt.Errorf("threadID: %w", err) 834 } 835 836 rec, err := s.store.Get(fmt.Sprintf(metadataKey, thID)) 837 if errors.Is(err, storage.ErrDataNotFound) { 838 return nil 839 } 840 841 if err != nil { 842 return fmt.Errorf("get record: %w", err) 843 } 844 845 res := map[string]interface{}{} 846 847 err = json.Unmarshal(rec, &res) 848 if err != nil { 849 return fmt.Errorf("get record: %w", err) 850 } 851 852 msg[jsonMetadata] = res 853 854 return nil 855 } 856 857 func (s *Service) saveMetadata(msg service.DIDCommMsgMap, thID string) error { 858 metadata := msg.Metadata() 859 if len(metadata) == 0 { 860 return nil 861 } 862 863 src, err := json.Marshal(metadata) 864 if err != nil { 865 return fmt.Errorf("marshal: %w", err) 866 } 867 868 return s.store.Put(fmt.Sprintf(metadataKey, thID), src) 869 } 870 871 // Name returns service name. 872 func (s *Service) Name() string { 873 return Introduce 874 } 875 876 // Accept msg checks the msg type. 877 func (s *Service) Accept(msgType string) bool { 878 switch msgType { 879 case ProposalMsgType, RequestMsgType, ResponseMsgType, AckMsgType, ProblemReportMsgType: 880 return true 881 } 882 883 return false 884 }