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  }