github.com/hyperledger/aries-framework-go@v0.3.2/pkg/didcomm/protocol/issuecredential/states.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 "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 stateNameAbandoning = "abandoning" 21 stateNameDone = "done" 22 stateNameNoop = "noop" 23 24 // states for Issuer. 25 stateNameProposalReceived = "proposal-received" 26 stateNameOfferSent = "offer-sent" 27 stateNameRequestReceived = "request-received" 28 stateNameCredentialIssued = "credential-issued" 29 30 // states for Holder. 31 stateNameProposalSent = "proposal-sent" 32 stateNameOfferReceived = "offer-received" 33 stateNameRequestSent = "request-sent" 34 stateNameCredentialReceived = "credential-received" 35 36 // web redirect decorator V2. 37 webRedirect = "~web-redirect" 38 ) 39 40 const ( 41 codeRejectedError = "rejected" 42 codeInternalError = "internal" 43 ) 44 45 // state action for network call. 46 type stateAction func(messenger service.Messenger) error 47 48 // the protocol's state. 49 type state interface { 50 // Name of this state. 51 Name() string 52 // CanTransitionTo Whether this state allows transitioning into the next state. 53 CanTransitionTo(next state) bool 54 // ExecuteInbound 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(msg *MetaData) (state, stateAction, error) 57 ExecuteOutbound(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 // noOp state. 66 type noOp struct{} 67 68 func (s *noOp) Name() string { 69 return stateNameNoop 70 } 71 72 func (s *noOp) CanTransitionTo(_ state) bool { 73 return false 74 } 75 76 func (s *noOp) ExecuteInbound(_ *MetaData) (state, stateAction, error) { 77 return nil, nil, errors.New("cannot execute no-op") 78 } 79 80 func (s *noOp) ExecuteOutbound(_ *MetaData) (state, stateAction, error) { 81 return nil, nil, errors.New("cannot execute no-op") 82 } 83 84 func (s *noOp) Properties() map[string]interface{} { 85 return map[string]interface{}{} 86 } 87 88 // start state. 89 type start struct{} 90 91 func (s *start) Name() string { 92 return stateNameStart 93 } 94 95 func (s *start) CanTransitionTo(st state) bool { 96 switch st.Name() { 97 // Issuer. 98 case stateNameProposalReceived, stateNameOfferSent, stateNameRequestReceived: 99 return true 100 // Holder. 101 case stateNameProposalSent, stateNameOfferReceived, stateNameRequestSent: 102 return true 103 } 104 105 return false 106 } 107 108 func (s *start) ExecuteInbound(_ *MetaData) (state, stateAction, error) { 109 return nil, nil, fmt.Errorf("%s: ExecuteInbound is not implemented yet", s.Name()) 110 } 111 112 func (s *start) ExecuteOutbound(_ *MetaData) (state, stateAction, error) { 113 return nil, nil, fmt.Errorf("%s: ExecuteOutbound is not implemented yet", s.Name()) 114 } 115 116 func (s *start) Properties() map[string]interface{} { 117 return map[string]interface{}{} 118 } 119 120 // abandoning state. 121 type abandoning struct { 122 V string 123 Code string 124 properties map[string]interface{} 125 } 126 127 func (s *abandoning) Name() string { 128 return stateNameAbandoning 129 } 130 131 func (s *abandoning) CanTransitionTo(st state) bool { 132 return st.Name() == stateNameDone 133 } 134 135 func (s *abandoning) ExecuteInbound(md *MetaData) (state, stateAction, error) { 136 // if code is not provided it means we do not need to notify the another agent. 137 // if we received ProblemReport message no need to answer. 138 if s.Code == "" || md.Msg.Type() == ProblemReportMsgTypeV2 || md.Msg.Type() == ProblemReportMsgTypeV3 { 139 return &done{}, zeroAction, nil 140 } 141 142 code := model.Code{Code: s.Code} 143 144 // if the protocol was stopped by the user we will set the rejected error code. 145 if errors.As(md.err, &customError{}) { 146 code = model.Code{Code: codeRejectedError} 147 } 148 149 thID, err := md.Msg.ThreadID() 150 if err != nil { 151 return nil, nil, fmt.Errorf("threadID: %w", err) 152 } 153 154 return &done{}, func(messenger service.Messenger) error { 155 if s.V == SpecV3 { 156 return messenger.ReplyToNested(service.NewDIDCommMsgMap(&model.ProblemReportV2{ 157 Type: ProblemReportMsgTypeV3, 158 Body: model.ProblemReportV2Body{Code: code.Code, WebRedirect: md.properties[webRedirect]}, 159 }), &service.NestedReplyOpts{ThreadID: thID, MyDID: md.MyDID, TheirDID: md.TheirDID, V: getDIDVersion(s.V)}) 160 } 161 162 return messenger.ReplyToNested(service.NewDIDCommMsgMap(&model.ProblemReport{ 163 Type: ProblemReportMsgTypeV2, 164 Description: code, 165 WebRedirect: md.properties[webRedirect], 166 }), &service.NestedReplyOpts{ThreadID: thID, MyDID: md.MyDID, TheirDID: md.TheirDID, V: getDIDVersion(s.V)}) 167 }, nil 168 } 169 170 func (s *abandoning) ExecuteOutbound(_ *MetaData) (state, stateAction, error) { 171 return nil, nil, fmt.Errorf("%s: ExecuteOutbound is not implemented yet", s.Name()) 172 } 173 174 func (s *abandoning) Properties() map[string]interface{} { 175 return s.properties 176 } 177 178 // done state. 179 type done struct { 180 V string 181 properties map[string]interface{} 182 } 183 184 func (s *done) Name() string { 185 return stateNameDone 186 } 187 188 func (s *done) CanTransitionTo(_ state) bool { 189 return false 190 } 191 192 func (s *done) ExecuteInbound(_ *MetaData) (state, stateAction, error) { 193 return &noOp{}, zeroAction, nil 194 } 195 196 func (s *done) ExecuteOutbound(_ *MetaData) (state, stateAction, error) { 197 return nil, nil, fmt.Errorf("%s: ExecuteOutbound is not implemented yet", s.Name()) 198 } 199 200 func (s *done) Properties() map[string]interface{} { 201 return s.properties 202 } 203 204 // proposalReceived the Issuer's state. 205 type proposalReceived struct { 206 V string 207 properties map[string]interface{} 208 } 209 210 func (s *proposalReceived) Name() string { 211 return stateNameProposalReceived 212 } 213 214 func (s *proposalReceived) CanTransitionTo(st state) bool { 215 return st.Name() == stateNameOfferSent || st.Name() == stateNameAbandoning 216 } 217 218 func (s *proposalReceived) ExecuteInbound(_ *MetaData) (state, stateAction, error) { 219 return &offerSent{V: s.V}, zeroAction, nil 220 } 221 222 func (s *proposalReceived) ExecuteOutbound(_ *MetaData) (state, stateAction, error) { 223 return nil, nil, fmt.Errorf("%s: ExecuteOutbound is not implemented yet", s.Name()) 224 } 225 226 func (s *proposalReceived) Properties() map[string]interface{} { 227 return s.properties 228 } 229 230 // offerSent the Issuer's state. 231 type offerSent struct { 232 V string 233 properties map[string]interface{} 234 } 235 236 func (s *offerSent) Name() string { 237 return stateNameOfferSent 238 } 239 240 func (s *offerSent) CanTransitionTo(st state) bool { 241 return st.Name() == stateNameProposalReceived || 242 st.Name() == stateNameRequestReceived || 243 st.Name() == stateNameAbandoning 244 } 245 246 func (s *offerSent) ExecuteInbound(md *MetaData) (state, stateAction, error) { 247 if md.offerCredentialV2 == nil && md.offerCredentialV3 == nil { 248 return nil, nil, errors.New("offer credential was not provided") 249 } 250 251 // creates the state's action. 252 action := func(messenger service.Messenger) error { 253 if s.V == SpecV3 { 254 // sets message type 255 md.offerCredentialV3.Type = OfferCredentialMsgTypeV3 256 257 return messenger.ReplyToMsg(md.Msg, service.NewDIDCommMsgMap(md.offerCredentialV3), md.MyDID, md.TheirDID, 258 service.WithVersion(getDIDVersion(s.V))) 259 } 260 261 // sets message type. 262 md.offerCredentialV2.Type = OfferCredentialMsgTypeV2 263 264 return messenger.ReplyToMsg(md.Msg, service.NewDIDCommMsgMap(md.offerCredentialV2), md.MyDID, md.TheirDID, 265 service.WithVersion(getDIDVersion(s.V))) 266 } 267 268 return &noOp{}, action, nil 269 } 270 271 func (s *offerSent) ExecuteOutbound(md *MetaData) (state, stateAction, error) { 272 // creates the state's action. 273 action := func(messenger service.Messenger) error { 274 return messenger.Send(md.Msg, md.MyDID, md.TheirDID, service.WithVersion(getDIDVersion(s.V))) 275 } 276 277 return &noOp{}, action, nil 278 } 279 280 func (s *offerSent) Properties() map[string]interface{} { 281 return s.properties 282 } 283 284 // requestReceived the Issuer's state. 285 type requestReceived struct { 286 V string 287 properties map[string]interface{} 288 } 289 290 func (s *requestReceived) Name() string { 291 return stateNameRequestReceived 292 } 293 294 func (s *requestReceived) CanTransitionTo(st state) bool { 295 return st.Name() == stateNameCredentialIssued || st.Name() == stateNameAbandoning 296 } 297 298 func (s *requestReceived) ExecuteInbound(md *MetaData) (state, stateAction, error) { 299 if md.issueCredentialV2 == nil && md.issueCredentialV3 == nil { 300 return nil, nil, errors.New("issue credential was not provided") 301 } 302 303 // creates the state's action 304 action := func(messenger service.Messenger) error { 305 if s.V == SpecV3 { 306 // sets message type 307 md.issueCredentialV3.Type = IssueCredentialMsgTypeV3 308 309 return messenger.ReplyToMsg(md.Msg, service.NewDIDCommMsgMap(md.issueCredentialV3), md.MyDID, md.TheirDID, 310 service.WithVersion(getDIDVersion(s.V))) 311 } 312 313 // sets message type 314 md.issueCredentialV2.Type = IssueCredentialMsgTypeV2 315 316 return messenger.ReplyToMsg(md.Msg, service.NewDIDCommMsgMap(md.issueCredentialV2), md.MyDID, md.TheirDID, 317 service.WithVersion(getDIDVersion(s.V))) 318 } 319 320 return &credentialIssued{}, action, nil 321 } 322 323 func (s *requestReceived) ExecuteOutbound(_ *MetaData) (state, stateAction, error) { 324 return nil, nil, fmt.Errorf("%s: ExecuteOutbound is not implemented yet", s.Name()) 325 } 326 327 func (s *requestReceived) Properties() map[string]interface{} { 328 return s.properties 329 } 330 331 // credentialIssued the Issuer's state. 332 type credentialIssued struct { 333 V string 334 properties map[string]interface{} 335 } 336 337 func (s *credentialIssued) Name() string { 338 return stateNameCredentialIssued 339 } 340 341 func (s *credentialIssued) CanTransitionTo(st state) bool { 342 return st.Name() == stateNameDone || st.Name() == stateNameAbandoning 343 } 344 345 func (s *credentialIssued) ExecuteInbound(_ *MetaData) (state, stateAction, error) { 346 return &noOp{}, zeroAction, nil 347 } 348 349 func (s *credentialIssued) ExecuteOutbound(_ *MetaData) (state, stateAction, error) { 350 return nil, nil, fmt.Errorf("%s: ExecuteOutbound is not implemented yet", s.Name()) 351 } 352 353 func (s *credentialIssued) Properties() map[string]interface{} { 354 return s.properties 355 } 356 357 // proposalSent the Holder's state. 358 type proposalSent struct { 359 V string 360 properties map[string]interface{} 361 } 362 363 func (s *proposalSent) Name() string { 364 return stateNameProposalSent 365 } 366 367 func (s *proposalSent) CanTransitionTo(st state) bool { 368 return st.Name() == stateNameOfferReceived || st.Name() == stateNameAbandoning 369 } 370 371 func (s *proposalSent) ExecuteInbound(md *MetaData) (state, stateAction, error) { 372 if md.proposeCredentialV2 == nil && md.proposeCredentialV3 == nil { 373 return nil, nil, errors.New("propose credential was not provided") 374 } 375 376 // creates the state's action 377 action := func(messenger service.Messenger) error { 378 if s.V == SpecV3 { 379 // sets message type 380 md.proposeCredentialV3.Type = ProposeCredentialMsgTypeV3 381 382 return messenger.ReplyToMsg(md.Msg, service.NewDIDCommMsgMap(md.proposeCredentialV3), md.MyDID, md.TheirDID, 383 service.WithVersion(getDIDVersion(s.V))) 384 } 385 386 // sets message type 387 md.proposeCredentialV2.Type = ProposeCredentialMsgTypeV2 388 389 return messenger.ReplyToMsg(md.Msg, service.NewDIDCommMsgMap(md.proposeCredentialV2), md.MyDID, md.TheirDID, 390 service.WithVersion(getDIDVersion(s.V))) 391 } 392 393 return &noOp{}, action, nil 394 } 395 396 func (s *proposalSent) ExecuteOutbound(md *MetaData) (state, stateAction, error) { 397 // creates the state's action 398 action := func(messenger service.Messenger) error { 399 return messenger.Send(md.Msg, md.MyDID, md.TheirDID, service.WithVersion(getDIDVersion(s.V))) 400 } 401 402 return &noOp{}, action, nil 403 } 404 405 func (s *proposalSent) Properties() map[string]interface{} { 406 return s.properties 407 } 408 409 // offerReceived the Holder's state. 410 type offerReceived struct { 411 V string 412 properties map[string]interface{} 413 } 414 415 func (s *offerReceived) Name() string { 416 return stateNameOfferReceived 417 } 418 419 func (s *offerReceived) CanTransitionTo(st state) bool { 420 return st.Name() == stateNameProposalSent || 421 st.Name() == stateNameRequestSent || 422 st.Name() == stateNameAbandoning 423 } 424 425 func (s *offerReceived) ExecuteInbound(md *MetaData) (state, stateAction, error) { 426 // sends propose credential if it was provided 427 if md.proposeCredentialV2 != nil || md.proposeCredentialV3 != nil { 428 return &proposalSent{V: s.V}, zeroAction, nil 429 } 430 431 var action func(messenger service.Messenger) error 432 433 if s.V == SpecV3 { //nolint: nestif 434 offer := OfferCredentialV3{} 435 if err := md.Msg.Decode(&offer); err != nil { 436 return nil, nil, fmt.Errorf("decode: %w", err) 437 } 438 439 response := &RequestCredentialV3{ 440 Type: RequestCredentialMsgTypeV3, 441 ID: offer.ID, 442 Body: RequestCredentialV3Body{ 443 GoalCode: offer.Body.GoalCode, 444 Comment: offer.Body.Comment, 445 }, 446 Attachments: offer.Attachments, 447 } 448 449 req := md.RequestCredentialV3() 450 if req != nil && req.notEmpty() { 451 response = md.RequestCredentialV3() 452 response.Type = RequestCredentialMsgTypeV3 453 } 454 455 // creates the state's action 456 action = func(messenger service.Messenger) error { 457 return messenger.ReplyToMsg(md.Msg, service.NewDIDCommMsgMap(response), md.MyDID, md.TheirDID, 458 service.WithVersion(getDIDVersion(s.V))) 459 } 460 } else { 461 offer := OfferCredentialV2{} 462 if err := md.Msg.Decode(&offer); err != nil { 463 return nil, nil, fmt.Errorf("decode: %w", err) 464 } 465 466 response := &RequestCredentialV2{ 467 Type: RequestCredentialMsgTypeV2, 468 Formats: offer.Formats, 469 RequestsAttach: offer.OffersAttach, 470 } 471 472 req := md.RequestCredentialV2() 473 if req != nil && req.notEmpty() { 474 response = md.RequestCredentialV2() 475 response.Type = RequestCredentialMsgTypeV2 476 } 477 478 // creates the state's action 479 action = func(messenger service.Messenger) error { 480 return messenger.ReplyToMsg(md.Msg, service.NewDIDCommMsgMap(response), md.MyDID, md.TheirDID, 481 service.WithVersion(getDIDVersion(s.V))) 482 } 483 } 484 485 return &requestSent{}, action, nil 486 } 487 488 func (s *offerReceived) ExecuteOutbound(_ *MetaData) (state, stateAction, error) { 489 return nil, nil, fmt.Errorf("%s: ExecuteOutbound is not implemented yet", s.Name()) 490 } 491 492 func (s *offerReceived) Properties() map[string]interface{} { 493 return s.properties 494 } 495 496 // requestSent the Holder's state. 497 type requestSent struct { 498 V string 499 properties map[string]interface{} 500 } 501 502 func (s *requestSent) Name() string { 503 return stateNameRequestSent 504 } 505 506 func (s *requestSent) CanTransitionTo(st state) bool { 507 return st.Name() == stateNameCredentialReceived || st.Name() == stateNameAbandoning 508 } 509 510 func (s *requestSent) ExecuteInbound(_ *MetaData) (state, stateAction, error) { 511 return &noOp{}, zeroAction, nil 512 } 513 514 func (s *requestSent) ExecuteOutbound(md *MetaData) (state, stateAction, error) { 515 // creates the state's action 516 action := func(messenger service.Messenger) error { 517 return messenger.Send(md.Msg, md.MyDID, md.TheirDID, service.WithVersion(getDIDVersion(s.V))) 518 } 519 520 return &noOp{}, action, nil 521 } 522 523 func (s *requestSent) Properties() map[string]interface{} { 524 return s.properties 525 } 526 527 // credentialReceived state. 528 type credentialReceived struct { 529 V string 530 properties map[string]interface{} 531 } 532 533 func (s *credentialReceived) Name() string { 534 return stateNameCredentialReceived 535 } 536 537 func (s *credentialReceived) CanTransitionTo(st state) bool { 538 return st.Name() == stateNameDone || st.Name() == stateNameAbandoning 539 } 540 541 func (s *credentialReceived) ExecuteInbound(md *MetaData) (state, stateAction, error) { 542 // creates the state's action 543 action := func(messenger service.Messenger) error { 544 if s.V == SpecV3 { 545 return messenger.ReplyToMsg(md.Msg, service.NewDIDCommMsgMap(model.AckV2{ 546 Type: AckMsgTypeV3, 547 Body: model.AckV2Body{Status: "OK"}, 548 }), md.MyDID, md.TheirDID, service.WithVersion(getDIDVersion(s.V))) 549 } 550 551 return messenger.ReplyToMsg(md.Msg, service.NewDIDCommMsgMap(model.Ack{ 552 Type: AckMsgTypeV2, 553 Status: "OK", 554 }), md.MyDID, md.TheirDID, service.WithVersion(getDIDVersion(s.V))) 555 } 556 557 return &done{properties: s.properties}, action, nil 558 } 559 560 func (s *credentialReceived) ExecuteOutbound(_ *MetaData) (state, stateAction, error) { 561 return nil, nil, fmt.Errorf("%s: ExecuteOutbound is not implemented yet", s.Name()) 562 } 563 564 func (s *credentialReceived) Properties() map[string]interface{} { 565 return s.properties 566 }