github.com/hyperledger/aries-framework-go@v0.3.2/pkg/didcomm/protocol/issuecredential/service.go (about)

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