github.com/hyperledger/aries-framework-go@v0.3.2/pkg/didcomm/protocol/introduce/states.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  	"errors"
    11  	"fmt"
    12  
    13  	"github.com/google/uuid"
    14  
    15  	"github.com/hyperledger/aries-framework-go/pkg/didcomm/common/model"
    16  	"github.com/hyperledger/aries-framework-go/pkg/didcomm/common/service"
    17  	"github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/decorator"
    18  )
    19  
    20  const (
    21  	codeNotApproved     = "not approved"
    22  	codeRequestDeclined = "request declined"
    23  	codeNoOOBMessage    = "no out-of-band message"
    24  	codeInternalError   = "internal error"
    25  )
    26  
    27  const (
    28  	// common states.
    29  	stateNameNoop       = "noop"
    30  	stateNameStart      = "start"
    31  	stateNameAbandoning = "abandoning"
    32  	stateNameDone       = "done"
    33  
    34  	// introducer states.
    35  	stateNameArranging  = "arranging"
    36  	stateNameDelivering = "delivering"
    37  	stateNameConfirming = "confirming"
    38  
    39  	// introducee states.
    40  	stateNameRequesting = "requesting"
    41  	stateNameDeciding   = "deciding"
    42  	stateNameWaiting    = "waiting"
    43  )
    44  
    45  // state action for network call.
    46  type stateAction func() error
    47  
    48  // The introduce protocol's state.
    49  type state interface {
    50  	// Name of this state.
    51  	Name() string
    52  	// Whether this state allows transitioning into the next state.
    53  	CanTransitionTo(next state) bool
    54  	// Executes this state, returning a followup state to be immediately executed as well.
    55  	// The 'noOp' state should be returned if the state has no followup.
    56  	ExecuteInbound(messenger service.Messenger, msg *metaData) (state, stateAction, error)
    57  	ExecuteOutbound(messenger service.Messenger, msg *metaData) (state, stateAction, error)
    58  }
    59  
    60  func zeroAction() error { return nil }
    61  
    62  // noOp state.
    63  type noOp struct{}
    64  
    65  func (s *noOp) Name() string {
    66  	return stateNameNoop
    67  }
    68  
    69  func (s *noOp) CanTransitionTo(_ state) bool {
    70  	return false
    71  }
    72  
    73  func (s *noOp) ExecuteInbound(_ service.Messenger, _ *metaData) (state, stateAction, error) {
    74  	return nil, nil, errors.New("cannot execute no-op")
    75  }
    76  
    77  func (s *noOp) ExecuteOutbound(_ service.Messenger, _ *metaData) (state, stateAction, error) {
    78  	return nil, nil, errors.New("cannot execute no-op")
    79  }
    80  
    81  // start state.
    82  type start struct{}
    83  
    84  func (s *start) Name() string {
    85  	return stateNameStart
    86  }
    87  
    88  func (s *start) CanTransitionTo(next state) bool {
    89  	// Introducer can go to arranging or delivering state
    90  	// Introducee can go to deciding
    91  	switch next.Name() {
    92  	case stateNameArranging, stateNameDeciding, stateNameRequesting, stateNameAbandoning:
    93  		return true
    94  	}
    95  
    96  	return false
    97  }
    98  
    99  func (s *start) ExecuteInbound(_ service.Messenger, _ *metaData) (state, stateAction, error) {
   100  	return nil, nil, errors.New("start: ExecuteInbound function is not supposed to be used")
   101  }
   102  
   103  func (s *start) ExecuteOutbound(_ service.Messenger, _ *metaData) (state, stateAction, error) {
   104  	return nil, nil, errors.New("start: ExecuteOutbound function is not supposed to be used")
   105  }
   106  
   107  // done state.
   108  type done struct{}
   109  
   110  func (s *done) Name() string {
   111  	return stateNameDone
   112  }
   113  
   114  func (s *done) CanTransitionTo(next state) bool {
   115  	// done is the last state there is no possibility for the next state
   116  	return false
   117  }
   118  
   119  func (s *done) ExecuteInbound(_ service.Messenger, _ *metaData) (state, stateAction, error) {
   120  	return &noOp{}, zeroAction, nil
   121  }
   122  
   123  func (s *done) ExecuteOutbound(_ service.Messenger, _ *metaData) (state, stateAction, error) {
   124  	return nil, nil, errors.New("done: ExecuteOutbound function is not supposed to be used")
   125  }
   126  
   127  // arranging state.
   128  type arranging struct{}
   129  
   130  func (s *arranging) Name() string {
   131  	return stateNameArranging
   132  }
   133  
   134  func (s *arranging) CanTransitionTo(next state) bool {
   135  	return next.Name() == stateNameArranging || next.Name() == stateNameDone ||
   136  		next.Name() == stateNameAbandoning || next.Name() == stateNameDelivering
   137  }
   138  
   139  func isApproved(md *metaData) bool {
   140  	for _, p := range md.participants {
   141  		if !p.Approve {
   142  			return false
   143  		}
   144  	}
   145  
   146  	return true
   147  }
   148  
   149  func hasOOBMessage(md *metaData) bool {
   150  	for _, p := range md.participants {
   151  		if p.OOBMessage != nil {
   152  			return true
   153  		}
   154  	}
   155  
   156  	return false
   157  }
   158  
   159  func getMetaRecipients(md *metaData) []*Recipient {
   160  	_recipients, ok := md.Msg.Metadata()[metaRecipients].([]interface{})
   161  	if !ok {
   162  		return nil
   163  	}
   164  
   165  	recipients := make([]*Recipient, len(_recipients))
   166  
   167  	for i, _recipient := range _recipients {
   168  		recipient, ok := _recipient.(*Recipient)
   169  		if !ok {
   170  			// should never happen, otherwise, the protocol logic is broken
   171  			panic("recipient type is wrong")
   172  		}
   173  
   174  		recipients[i] = recipient
   175  	}
   176  
   177  	return recipients
   178  }
   179  
   180  // CreateProposal creates a DIDCommMsgMap proposal.
   181  func CreateProposal(r *Recipient) service.DIDCommMsgMap {
   182  	return service.NewDIDCommMsgMap(Proposal{
   183  		ID:       uuid.New().String(),
   184  		Type:     ProposalMsgType,
   185  		To:       r.To,
   186  		GoalCode: r.GoalCode,
   187  		Goal:     r.Goal,
   188  	})
   189  }
   190  
   191  func sendProposals(messenger service.Messenger, md *metaData) error {
   192  	for _, recipient := range getMetaRecipients(md) {
   193  		proposal := CreateProposal(recipient)
   194  		proposal.Metadata()[metaPIID] = md.PIID
   195  		copyMetadata(md.Msg, proposal)
   196  
   197  		var (
   198  			err  error
   199  			thID string
   200  		)
   201  
   202  		if recipient.MyDID == "" && recipient.TheirDID == "" {
   203  			thID, err = md.Msg.ThreadID()
   204  			if err != nil {
   205  				return fmt.Errorf("get threadID: %w", err)
   206  			}
   207  
   208  			if err = md.saveMetadata(proposal, thID); err != nil {
   209  				return fmt.Errorf("save metadata: %w", err)
   210  			}
   211  
   212  			err = messenger.ReplyToMsg(md.Msg, proposal, md.MyDID, md.TheirDID)
   213  		} else {
   214  			if err = md.saveMetadata(proposal, proposal.ID()); err != nil {
   215  				return fmt.Errorf("save metadata: %w", err)
   216  			}
   217  
   218  			err = messenger.Send(proposal, recipient.MyDID, recipient.TheirDID)
   219  		}
   220  
   221  		if err != nil {
   222  			return fmt.Errorf("send proposals: %w", err)
   223  		}
   224  	}
   225  
   226  	return nil
   227  }
   228  
   229  func (s *arranging) ExecuteInbound(messenger service.Messenger, md *metaData) (state, stateAction, error) {
   230  	if md.Msg.Type() == RequestMsgType {
   231  		return &noOp{}, func() error {
   232  			return sendProposals(messenger, md)
   233  		}, nil
   234  	}
   235  
   236  	if isSkipProposal(md) {
   237  		if !isApproved(md) {
   238  			return &abandoning{Code: codeNotApproved}, zeroAction, nil
   239  		}
   240  
   241  		return &delivering{}, zeroAction, nil
   242  	}
   243  
   244  	count := len(md.participants)
   245  	if count != maxIntroducees || md.participants[count-1].TheirDID != md.TheirDID {
   246  		return &noOp{}, zeroAction, nil
   247  	}
   248  
   249  	if !isApproved(md) {
   250  		return &abandoning{Code: codeNotApproved}, zeroAction, nil
   251  	}
   252  
   253  	return &delivering{}, zeroAction, nil
   254  }
   255  
   256  func (s *arranging) ExecuteOutbound(messenger service.Messenger, md *metaData) (state, stateAction, error) {
   257  	return &noOp{}, func() error {
   258  		if md.Msg.ID() == "" {
   259  			md.Msg.SetID(uuid.New().String())
   260  		}
   261  
   262  		err := md.saveMetadata(md.Msg, md.Msg.ID())
   263  		if err != nil {
   264  			return fmt.Errorf("outbound send: %w", err)
   265  		}
   266  
   267  		return messenger.Send(md.Msg, md.MyDID, md.TheirDID)
   268  	}, nil
   269  }
   270  
   271  // delivering state.
   272  type delivering struct{}
   273  
   274  func (s *delivering) Name() string {
   275  	return stateNameDelivering
   276  }
   277  
   278  func (s *delivering) CanTransitionTo(next state) bool {
   279  	return next.Name() == stateNameConfirming || next.Name() == stateNameDone || next.Name() == stateNameAbandoning
   280  }
   281  
   282  func deliveringSkipInvitation(messenger service.Messenger, md *metaData) (state, stateAction, error) {
   283  	thID, err := md.Msg.ThreadID()
   284  	if err != nil {
   285  		return nil, nil, err
   286  	}
   287  
   288  	return &done{}, func() error {
   289  		msg := contextOOBMessage(md.Msg)
   290  
   291  		return messenger.ReplyToNested(msg, &service.NestedReplyOpts{ThreadID: thID, MyDID: md.MyDID, TheirDID: md.TheirDID})
   292  	}, nil
   293  }
   294  
   295  func (s *delivering) ExecuteInbound(messenger service.Messenger, md *metaData) (state, stateAction, error) {
   296  	if isSkipProposal(md) {
   297  		return deliveringSkipInvitation(messenger, md)
   298  	}
   299  
   300  	// edge case: no one shared an oob message
   301  	if !hasOOBMessage(md) {
   302  		return &abandoning{Code: codeNoOOBMessage}, zeroAction, nil
   303  	}
   304  
   305  	var msg service.DIDCommMsgMap
   306  
   307  	var participants []*participant
   308  
   309  	for _, participant := range md.participants {
   310  		if participant.OOBMessage != nil && msg == nil {
   311  			msg = participant.OOBMessage
   312  		} else {
   313  			participants = append(participants, participant)
   314  		}
   315  	}
   316  
   317  	return &confirming{}, func() error {
   318  		for _, p := range participants {
   319  			err := messenger.ReplyToNested(msg,
   320  				&service.NestedReplyOpts{ThreadID: p.ThreadID, MyDID: p.MyDID, TheirDID: p.TheirDID})
   321  			if err != nil {
   322  				return fmt.Errorf("reply to nested: %w", err)
   323  			}
   324  		}
   325  
   326  		return nil
   327  	}, nil
   328  }
   329  
   330  func (s *delivering) ExecuteOutbound(_ service.Messenger, _ *metaData) (state, stateAction, error) {
   331  	return nil, nil, errors.New("delivering: ExecuteOutbound function is not supposed to be used")
   332  }
   333  
   334  // confirming state.
   335  type confirming struct{}
   336  
   337  func (s *confirming) Name() string {
   338  	return stateNameConfirming
   339  }
   340  
   341  func (s *confirming) CanTransitionTo(next state) bool {
   342  	return next.Name() == stateNameDone || next.Name() == stateNameAbandoning
   343  }
   344  
   345  func (s *confirming) ExecuteInbound(messenger service.Messenger, md *metaData) (state, stateAction, error) {
   346  	msgMap := service.NewDIDCommMsgMap(model.Ack{
   347  		Type: AckMsgType,
   348  	})
   349  
   350  	var p *participant
   351  
   352  	for _, participant := range md.participants {
   353  		if participant.OOBMessage == nil {
   354  			continue
   355  		}
   356  
   357  		p = participant
   358  
   359  		break
   360  	}
   361  
   362  	return &done{}, func() error { return messenger.ReplyToMsg(p.Message, msgMap, md.MyDID, p.TheirDID) }, nil
   363  }
   364  
   365  func (s *confirming) ExecuteOutbound(_ service.Messenger, _ *metaData) (state, stateAction, error) {
   366  	return nil, nil, errors.New("confirming: ExecuteOutbound function is not supposed to be used")
   367  }
   368  
   369  // abandoning state.
   370  type abandoning struct {
   371  	Code string
   372  }
   373  
   374  func (s *abandoning) Name() string {
   375  	return stateNameAbandoning
   376  }
   377  
   378  func (s *abandoning) CanTransitionTo(next state) bool {
   379  	return next.Name() == stateNameDone
   380  }
   381  
   382  // nolint: funlen
   383  func (s *abandoning) ExecuteInbound(messenger service.Messenger, md *metaData) (state, stateAction, error) {
   384  	// if code is not provided it means we do not need to notify participants about it.
   385  	// if we received ProblemReport message no need to answer.
   386  	if s.Code == "" || md.Msg.Type() == ProblemReportMsgType {
   387  		return &done{}, zeroAction, nil
   388  	}
   389  
   390  	// In the protocol we might have a custom error.
   391  	// 1. The introducer stop the protocol after receiving a request
   392  	// 2. The introducee stop the protocol after receiving a proposal
   393  	// When introducee stops the protocol we already send a Response with Approve=false. Code is "". Was ignore above.
   394  	// Otherwise, we need to send a ProblemReport message.
   395  	if errors.As(md.err, &customError{}) {
   396  		// It is not possible to receive message without ID or threadID.
   397  		// This error should never happen. If it happens it means that logic is broken.
   398  		thID, err := md.Msg.ThreadID()
   399  		if err != nil {
   400  			return nil, nil, fmt.Errorf("threadID: %w", err)
   401  		}
   402  
   403  		// Sends a ProblemReport to the introducee.
   404  		return &done{}, func() error {
   405  			return messenger.ReplyToNested(service.NewDIDCommMsgMap(model.ProblemReport{
   406  				Type: ProblemReportMsgType,
   407  				Description: model.Code{
   408  					Code: codeRequestDeclined,
   409  				},
   410  			},
   411  			), &service.NestedReplyOpts{ThreadID: thID, MyDID: md.MyDID, TheirDID: md.TheirDID})
   412  		}, nil
   413  	}
   414  
   415  	if len(md.participants) == 0 {
   416  		md.participants = []*participant{{
   417  			MyDID:    md.MyDID,
   418  			TheirDID: md.TheirDID,
   419  		}}
   420  	}
   421  
   422  	return &done{}, func() error {
   423  		// notifies participants about error
   424  		for _, recipient := range md.participants {
   425  			// if code is codeNotApproved we need to ignore sending a ProblemReport
   426  			// to the participant who rejected the introduction
   427  			if s.Code == codeNotApproved && !recipient.Approve {
   428  				continue
   429  			}
   430  
   431  			// sends a ProblemReport to the participant
   432  			problem := service.NewDIDCommMsgMap(model.ProblemReport{
   433  				Type: ProblemReportMsgType,
   434  				Description: model.Code{
   435  					Code: s.Code,
   436  				},
   437  			})
   438  
   439  			if err := messenger.ReplyToNested(problem,
   440  				&service.NestedReplyOpts{
   441  					ThreadID: recipient.ThreadID,
   442  					MyDID:    recipient.MyDID,
   443  					TheirDID: recipient.TheirDID,
   444  				}); err != nil {
   445  				return fmt.Errorf("send problem-report: %w", err)
   446  			}
   447  		}
   448  
   449  		return nil
   450  	}, nil
   451  }
   452  
   453  func (s *abandoning) ExecuteOutbound(_ service.Messenger, _ *metaData) (state, stateAction, error) {
   454  	return nil, nil, errors.New("abandoning: ExecuteOutbound function is not supposed to be used")
   455  }
   456  
   457  // deciding state.
   458  type deciding struct{}
   459  
   460  func (s *deciding) Name() string {
   461  	return stateNameDeciding
   462  }
   463  
   464  func (s *deciding) CanTransitionTo(next state) bool {
   465  	return next.Name() == stateNameWaiting || next.Name() == stateNameDone || next.Name() == stateNameAbandoning
   466  }
   467  
   468  func (s *deciding) ExecuteInbound(messenger service.Messenger, md *metaData) (state, stateAction, error) {
   469  	var st state = &waiting{}
   470  	if md.rejected {
   471  		st = &abandoning{}
   472  	}
   473  
   474  	return st, func() error {
   475  		msg := contextOOBMessage(md.Msg)
   476  
   477  		var attch []*decorator.Attachment
   478  
   479  		if a, found := md.Msg.Metadata()[metaAttachment]; found {
   480  			var ok bool
   481  
   482  			attch, ok = a.([]*decorator.Attachment)
   483  			if !ok {
   484  				return fmt.Errorf(
   485  					"unable to cast metadata key %s to []*decorator.Attachments (this shouldn't happen), found: %+v",
   486  					metaAttachment, a)
   487  			}
   488  		}
   489  
   490  		return messenger.ReplyToMsg(md.Msg, service.NewDIDCommMsgMap(Response{
   491  			Type:        ResponseMsgType,
   492  			OOBMessage:  msg,
   493  			Approve:     !md.rejected,
   494  			Attachments: attch,
   495  		}), md.MyDID, md.TheirDID)
   496  	}, nil
   497  }
   498  
   499  func (s *deciding) ExecuteOutbound(_ service.Messenger, _ *metaData) (state, stateAction, error) {
   500  	return nil, nil, errors.New("deciding: ExecuteOutbound function is not supposed to be used")
   501  }
   502  
   503  // waiting state.
   504  type waiting struct{}
   505  
   506  func (s *waiting) Name() string {
   507  	return stateNameWaiting
   508  }
   509  
   510  func (s *waiting) CanTransitionTo(next state) bool {
   511  	return next.Name() == stateNameDone || next.Name() == stateNameAbandoning
   512  }
   513  
   514  func (s *waiting) ExecuteInbound(_ service.Messenger, _ *metaData) (state, stateAction, error) {
   515  	return &noOp{}, zeroAction, nil
   516  }
   517  
   518  func (s *waiting) ExecuteOutbound(_ service.Messenger, _ *metaData) (state, stateAction, error) {
   519  	return nil, nil, errors.New("waiting: ExecuteOutbound function is not supposed to be used")
   520  }
   521  
   522  // requesting state.
   523  type requesting struct{}
   524  
   525  func (s *requesting) Name() string {
   526  	return stateNameRequesting
   527  }
   528  
   529  func (s *requesting) CanTransitionTo(next state) bool {
   530  	return next.Name() == stateNameDeciding || next.Name() == stateNameAbandoning || next.Name() == stateNameDone
   531  }
   532  
   533  func (s *requesting) ExecuteInbound(_ service.Messenger, _ *metaData) (state, stateAction, error) {
   534  	return nil, nil, errors.New("requesting: ExecuteInbound function is not supposed to be used")
   535  }
   536  
   537  func (s *requesting) ExecuteOutbound(messenger service.Messenger, md *metaData) (state, stateAction, error) {
   538  	return &noOp{}, func() error {
   539  		return messenger.Send(md.Msg, md.MyDID, md.TheirDID)
   540  	}, nil
   541  }