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 }