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 }