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

     1  /*
     2  Copyright SecureKey Technologies Inc. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package presentproof
     8  
     9  import (
    10  	"errors"
    11  	"fmt"
    12  
    13  	"github.com/hyperledger/aries-framework-go/pkg/didcomm/common/model"
    14  	"github.com/hyperledger/aries-framework-go/pkg/didcomm/common/service"
    15  )
    16  
    17  const (
    18  	// common states.
    19  	stateNameStart = "start"
    20  	stateNameNoop  = "noop"
    21  
    22  	// StateNameAbandoned is present proof protocol state 'abandoned'.
    23  	StateNameAbandoned = "abandoned"
    24  	// StateNameDone is present proof protocol state 'done'.
    25  	StateNameDone = "done"
    26  
    27  	// states for Verifier.
    28  	stateNameRequestSent          = "request-sent"
    29  	stateNamePresentationReceived = "presentation-received"
    30  	stateNameProposalReceived     = "proposal-received"
    31  
    32  	// states for Prover.
    33  	stateNameRequestReceived  = "request-received"
    34  	stateNamePresentationSent = "presentation-sent"
    35  	stateNameProposalSent     = "proposal-sent"
    36  )
    37  
    38  const (
    39  	// error codes.
    40  	codeInternalError = "internal"
    41  	codeRejectedError = "rejected"
    42  	webRedirect       = "~web-redirect"
    43  	webRedirectV2     = "web_redirect"
    44  )
    45  
    46  // state action for network call.
    47  type stateAction func(messenger service.Messenger) error
    48  
    49  // the protocol's state.
    50  type state interface {
    51  	// Name of this state.
    52  	Name() string
    53  	// Whether this state allows transitioning into the next state.
    54  	CanTransitionTo(next state) bool
    55  	// Executes this state, returning a followup state to be immediately executed as well.
    56  	// The 'noOp' state should be returned if the state has no followup.
    57  	Execute(msg *metaData) (state, stateAction, error)
    58  	// Message properties required for further steps or next state transition.
    59  	Properties() map[string]interface{}
    60  }
    61  
    62  // represents zero state's action.
    63  func zeroAction(service.Messenger) error { return nil }
    64  
    65  // start state.
    66  type start struct{}
    67  
    68  func (s *start) Name() string {
    69  	return stateNameStart
    70  }
    71  
    72  func (s *start) CanTransitionTo(st state) bool {
    73  	switch st.Name() {
    74  	// Verifier.
    75  	case stateNameRequestSent, stateNameProposalReceived:
    76  		return true
    77  	// Prover.
    78  	case stateNameProposalSent, stateNameRequestReceived:
    79  		return true
    80  	}
    81  
    82  	return false
    83  }
    84  
    85  func (s *start) Execute(_ *metaData) (state, stateAction, error) {
    86  	return nil, nil, fmt.Errorf("%s: is not implemented yet", s.Name())
    87  }
    88  
    89  func (s *start) Properties() map[string]interface{} {
    90  	return map[string]interface{}{}
    91  }
    92  
    93  // abandoned state.
    94  type abandoned struct {
    95  	V          string
    96  	Code       string
    97  	properties map[string]interface{}
    98  }
    99  
   100  func (s *abandoned) Name() string {
   101  	return StateNameAbandoned
   102  }
   103  
   104  func (s *abandoned) CanTransitionTo(st state) bool {
   105  	return false
   106  }
   107  
   108  func (s *abandoned) Execute(md *metaData) (state, stateAction, error) {
   109  	// if code is not provided it means we do not need to notify the another agent.
   110  	// if we received ProblemReport message no need to answer.
   111  	if s.Code == "" || md.Msg.Type() == ProblemReportMsgTypeV2 || md.Msg.Type() == ProblemReportMsgTypeV3 {
   112  		return &noOp{}, zeroAction, nil
   113  	}
   114  
   115  	code := model.Code{Code: s.Code}
   116  
   117  	// if the protocol was stopped by the user we will set the rejected error code
   118  	if errors.As(md.err, &customError{}) {
   119  		code = model.Code{Code: codeRejectedError}
   120  	}
   121  
   122  	thID, err := md.Msg.ThreadID()
   123  	if err != nil {
   124  		return nil, nil, fmt.Errorf("threadID: %w", err)
   125  	}
   126  
   127  	return &noOp{}, func(messenger service.Messenger) error {
   128  		if s.V == SpecV3 {
   129  			return messenger.ReplyToNested(service.NewDIDCommMsgMap(&model.ProblemReportV2{
   130  				Type: ProblemReportMsgTypeV3,
   131  				Body: model.ProblemReportV2Body{Code: code.Code, WebRedirect: md.properties[webRedirect]},
   132  			}), &service.NestedReplyOpts{ThreadID: thID, MyDID: md.MyDID, TheirDID: md.TheirDID, V: getDIDVersion(s.V)})
   133  		}
   134  
   135  		return messenger.ReplyToNested(service.NewDIDCommMsgMap(&model.ProblemReport{
   136  			Type:        ProblemReportMsgTypeV2,
   137  			Description: code,
   138  			WebRedirect: md.properties[webRedirect],
   139  		}), &service.NestedReplyOpts{ThreadID: thID, MyDID: md.MyDID, TheirDID: md.TheirDID, V: getDIDVersion(s.V)})
   140  	}, nil
   141  }
   142  
   143  func (s *abandoned) Properties() map[string]interface{} {
   144  	return s.properties
   145  }
   146  
   147  // done state.
   148  type done struct {
   149  	V          string
   150  	properties map[string]interface{}
   151  }
   152  
   153  func (s *done) Name() string {
   154  	return StateNameDone
   155  }
   156  
   157  func (s *done) CanTransitionTo(_ state) bool {
   158  	return false
   159  }
   160  
   161  func (s *done) Execute(_ *metaData) (state, stateAction, error) {
   162  	return &noOp{}, zeroAction, nil
   163  }
   164  
   165  func (s *done) Properties() map[string]interface{} {
   166  	return s.properties
   167  }
   168  
   169  // noOp state.
   170  type noOp struct{}
   171  
   172  func (s *noOp) Name() string {
   173  	return stateNameNoop
   174  }
   175  
   176  func (s *noOp) CanTransitionTo(_ state) bool {
   177  	return false
   178  }
   179  
   180  func (s *noOp) Execute(_ *metaData) (state, stateAction, error) {
   181  	return nil, nil, errors.New("cannot execute no-op")
   182  }
   183  
   184  func (s *noOp) Properties() map[string]interface{} {
   185  	return map[string]interface{}{}
   186  }
   187  
   188  // requestReceived the Prover's state.
   189  type requestReceived struct {
   190  	V string
   191  }
   192  
   193  func (s *requestReceived) Name() string {
   194  	return stateNameRequestReceived
   195  }
   196  
   197  func (s *requestReceived) CanTransitionTo(st state) bool {
   198  	return st.Name() == stateNamePresentationSent ||
   199  		st.Name() == stateNameProposalSent ||
   200  		st.Name() == StateNameAbandoned
   201  }
   202  
   203  func (s *requestReceived) Execute(md *metaData) (state, stateAction, error) {
   204  	if md.presentation == nil && md.presentationV3 == nil {
   205  		return &proposalSent{V: s.V}, zeroAction, nil
   206  	}
   207  
   208  	if s.V == SpecV3 {
   209  		var req *RequestPresentationV3
   210  
   211  		if err := md.Msg.Decode(&req); err != nil {
   212  			return nil, nil, err
   213  		}
   214  
   215  		return &presentationSent{V: s.V, WillConfirm: req.Body.WillConfirm}, zeroAction, nil
   216  	}
   217  
   218  	var req *RequestPresentationV2
   219  
   220  	if err := md.Msg.Decode(&req); err != nil {
   221  		return nil, nil, err
   222  	}
   223  
   224  	return &presentationSent{V: s.V, WillConfirm: req.WillConfirm}, zeroAction, nil
   225  }
   226  
   227  func (s *requestReceived) Properties() map[string]interface{} {
   228  	return map[string]interface{}{}
   229  }
   230  
   231  // requestSent the Verifier's state.
   232  type requestSent struct {
   233  	V string
   234  }
   235  
   236  func (s *requestSent) Name() string {
   237  	return stateNameRequestSent
   238  }
   239  
   240  func (s *requestSent) CanTransitionTo(st state) bool {
   241  	return st.Name() == stateNamePresentationReceived ||
   242  		st.Name() == stateNameProposalReceived ||
   243  		st.Name() == StateNameAbandoned
   244  }
   245  
   246  func forwardInitial(md *metaData, v service.Version) stateAction {
   247  	return func(messenger service.Messenger) error {
   248  		return messenger.Send(md.Msg, md.MyDID, md.TheirDID, service.WithVersion(v))
   249  	}
   250  }
   251  
   252  func (s *requestSent) Execute(md *metaData) (state, stateAction, error) {
   253  	if md.Direction == outboundMessage {
   254  		if s.V == SpecV3 {
   255  			var req *RequestPresentationV3
   256  
   257  			if err := md.Msg.Decode(&req); err != nil {
   258  				return nil, nil, err
   259  			}
   260  
   261  			md.AckRequired = req.Body.WillConfirm
   262  
   263  			return &noOp{}, forwardInitial(md, getDIDVersion(s.V)), nil
   264  		}
   265  
   266  		var req *RequestPresentationV2
   267  
   268  		if err := md.Msg.Decode(&req); err != nil {
   269  			return nil, nil, err
   270  		}
   271  
   272  		md.AckRequired = req.WillConfirm
   273  
   274  		return &noOp{}, forwardInitial(md, getDIDVersion(s.V)), nil
   275  	}
   276  
   277  	if md.request == nil && md.requestV3 == nil {
   278  		return nil, nil, errors.New("request was not provided")
   279  	}
   280  
   281  	if s.V == SpecV3 {
   282  		md.AckRequired = md.requestV3.Body.WillConfirm
   283  	} else {
   284  		md.AckRequired = md.request.WillConfirm
   285  	}
   286  
   287  	return &noOp{}, func(messenger service.Messenger) error {
   288  		if s.V == SpecV3 {
   289  			md.requestV3.Type = RequestPresentationMsgTypeV3
   290  
   291  			return messenger.ReplyToMsg(md.Msg, service.NewDIDCommMsgMap(md.requestV3), md.MyDID, md.TheirDID,
   292  				service.WithVersion(getDIDVersion(s.V)),
   293  			)
   294  		}
   295  
   296  		md.request.Type = RequestPresentationMsgTypeV2
   297  
   298  		return messenger.ReplyToMsg(md.Msg, service.NewDIDCommMsgMap(md.request), md.MyDID, md.TheirDID,
   299  			service.WithVersion(getDIDVersion(s.V)),
   300  		)
   301  	}, nil
   302  }
   303  
   304  func (s *requestSent) Properties() map[string]interface{} {
   305  	return map[string]interface{}{}
   306  }
   307  
   308  // presentationSent the Prover's state.
   309  type presentationSent struct {
   310  	V           string
   311  	WillConfirm bool
   312  }
   313  
   314  func (s *presentationSent) Name() string {
   315  	return stateNamePresentationSent
   316  }
   317  
   318  func (s *presentationSent) CanTransitionTo(st state) bool {
   319  	return st.Name() == StateNameAbandoned ||
   320  		st.Name() == StateNameDone
   321  }
   322  
   323  func (s *presentationSent) Execute(md *metaData) (state, stateAction, error) {
   324  	if md.presentation == nil && md.presentationV3 == nil {
   325  		return nil, nil, errors.New("presentation was not provided")
   326  	}
   327  
   328  	// creates the state's action
   329  	action := func(messenger service.Messenger) error {
   330  		if s.V == SpecV3 {
   331  			// sets message type
   332  			md.presentationV3.Type = PresentationMsgTypeV3
   333  
   334  			return messenger.ReplyToMsg(md.Msg, service.NewDIDCommMsgMap(md.presentationV3), md.MyDID, md.TheirDID,
   335  				service.WithVersion(getDIDVersion(s.V)),
   336  			)
   337  		}
   338  
   339  		// sets message type
   340  		md.presentation.Type = PresentationMsgTypeV2
   341  
   342  		return messenger.ReplyToMsg(md.Msg, service.NewDIDCommMsgMap(md.presentation), md.MyDID, md.TheirDID,
   343  			service.WithVersion(getDIDVersion(s.V)),
   344  		)
   345  	}
   346  
   347  	if !s.WillConfirm {
   348  		return &done{V: s.V}, action, nil
   349  	}
   350  
   351  	return &noOp{}, action, nil
   352  }
   353  
   354  func (s *presentationSent) Properties() map[string]interface{} {
   355  	return map[string]interface{}{}
   356  }
   357  
   358  // presentationReceived the Verifier's state.
   359  type presentationReceived struct {
   360  	V string
   361  }
   362  
   363  func (s *presentationReceived) Name() string {
   364  	return stateNamePresentationReceived
   365  }
   366  
   367  func (s *presentationReceived) CanTransitionTo(st state) bool {
   368  	return st.Name() == StateNameAbandoned ||
   369  		st.Name() == StateNameDone
   370  }
   371  
   372  func (s *presentationReceived) Execute(md *metaData) (state, stateAction, error) {
   373  	if !md.AckRequired {
   374  		return &done{V: s.V}, zeroAction, nil
   375  	}
   376  
   377  	// creates the state's action
   378  	action := func(messenger service.Messenger) error {
   379  		if s.V == SpecV3 {
   380  			return messenger.ReplyToMsg(md.Msg, service.NewDIDCommMsgMap(model.AckV2{
   381  				Type:        AckMsgTypeV3,
   382  				WebRedirect: md.properties[webRedirect],
   383  				Body:        model.AckV2Body{},
   384  			}), md.MyDID, md.TheirDID, service.WithVersion(getDIDVersion(s.V)))
   385  		}
   386  
   387  		return messenger.ReplyToMsg(md.Msg, service.NewDIDCommMsgMap(model.Ack{
   388  			Type:        AckMsgTypeV2,
   389  			Status:      "OK",
   390  			WebRedirect: md.properties[webRedirect],
   391  		}), md.MyDID, md.TheirDID, service.WithVersion(getDIDVersion(s.V)))
   392  	}
   393  
   394  	return &done{V: s.V}, action, nil
   395  }
   396  
   397  func (s *presentationReceived) Properties() map[string]interface{} {
   398  	return map[string]interface{}{}
   399  }
   400  
   401  // proposalSent the Prover's state.
   402  type proposalSent struct {
   403  	V string
   404  }
   405  
   406  func (s *proposalSent) Name() string {
   407  	return stateNameProposalSent
   408  }
   409  
   410  func (s *proposalSent) CanTransitionTo(st state) bool {
   411  	return st.Name() == stateNameRequestReceived ||
   412  		st.Name() == StateNameAbandoned
   413  }
   414  
   415  func (s *proposalSent) Execute(md *metaData) (state, stateAction, error) {
   416  	if md.Direction == outboundMessage {
   417  		return &noOp{}, forwardInitial(md, getDIDVersion(s.V)), nil
   418  	}
   419  
   420  	if md.proposePresentation == nil && md.proposePresentationV3 == nil {
   421  		return nil, nil, errors.New("propose-presentation was not provided")
   422  	}
   423  
   424  	return &noOp{}, func(messenger service.Messenger) error {
   425  		if s.V == SpecV3 {
   426  			md.proposePresentationV3.Type = ProposePresentationMsgTypeV3
   427  
   428  			return messenger.ReplyToMsg(md.Msg, service.NewDIDCommMsgMap(md.proposePresentationV3), md.MyDID, md.TheirDID,
   429  				service.WithVersion(getDIDVersion(s.V)),
   430  			)
   431  		}
   432  
   433  		md.proposePresentation.Type = ProposePresentationMsgTypeV2
   434  
   435  		return messenger.ReplyToMsg(md.Msg, service.NewDIDCommMsgMap(md.proposePresentation), md.MyDID, md.TheirDID,
   436  			service.WithVersion(getDIDVersion(s.V)),
   437  		)
   438  	}, nil
   439  }
   440  
   441  func (s *proposalSent) Properties() map[string]interface{} {
   442  	return map[string]interface{}{}
   443  }
   444  
   445  // proposalReceived the Verifier's state.
   446  type proposalReceived struct {
   447  	V string
   448  }
   449  
   450  func (s *proposalReceived) Name() string {
   451  	return stateNameProposalReceived
   452  }
   453  
   454  func (s *proposalReceived) CanTransitionTo(st state) bool {
   455  	return st.Name() == stateNameRequestSent ||
   456  		st.Name() == StateNameAbandoned
   457  }
   458  
   459  func (s *proposalReceived) Execute(_ *metaData) (state, stateAction, error) {
   460  	return &requestSent{V: s.V}, zeroAction, nil
   461  }
   462  
   463  func (s *proposalReceived) Properties() map[string]interface{} {
   464  	return map[string]interface{}{}
   465  }