func (s *Service) Accept(msgType string) bool {
	switch msgType {
	case ProposePresentationMsgTypeV2, RequestPresentationMsgTypeV2,
		PresentationMsgTypeV2, AckMsgTypeV2, ProblemReportMsgTypeV2,
		ProposePresentationMsgTypeV3, RequestPresentationMsgTypeV3,
		PresentationMsgTypeV3, AckMsgTypeV3, ProblemReportMsgTypeV3:
		return true
	}
	return false
}
Accept msg checks the msg type.
package presentproof
import (
	"encoding/json"
	"errors"
	"fmt"
	"strings"
	"github.com/google/uuid"
	"github.com/hyperledger/aries-framework-go/pkg/common/log"
	"github.com/hyperledger/aries-framework-go/pkg/didcomm/common/service"
	"github.com/hyperledger/aries-framework-go/pkg/doc/verifiable"
	"github.com/hyperledger/aries-framework-go/spi/storage"
)
const (
	Name = "present-proof"
	SpecV2 = "https://didcomm.org/present-proof/2.0/"
	ProposePresentationMsgTypeV2 = SpecV2 + "propose-presentation"
	RequestPresentationMsgTypeV2 = SpecV2 + "request-presentation"
	PresentationMsgTypeV2 = SpecV2 + "presentation"
	AckMsgTypeV2 = SpecV2 + "ack"
	ProblemReportMsgTypeV2 = SpecV2 + "problem-report"
	PresentationPreviewMsgTypeV2 = SpecV2 + "presentation-preview"
	SpecV3 = "https://didcomm.org/present-proof/3.0/"
	ProposePresentationMsgTypeV3 = SpecV3 + "propose-presentation"
	RequestPresentationMsgTypeV3 = SpecV3 + "request-presentation"
	PresentationMsgTypeV3 = SpecV3 + "presentation"
	AckMsgTypeV3 = SpecV3 + "ack"
	ProblemReportMsgTypeV3 = SpecV3 + "problem-report"
	PresentationPreviewMsgTypeV3 = SpecV3 + "presentation-preview"
)
const (
	internalDataKey        = "internal_data_"
	transitionalPayloadKey = "transitionalPayload_%s"
)
var (
	logger         = log.New("aries-framework/presentproof/service")
	initialHandler = HandlerFunc(func(_ Metadata) error {
		return nil
	})
	errProtocolStopped = errors.New("protocol was stopped")
)
type customError struct{ error }
type transitionalPayload struct {
	Action
	StateName   string
	AckRequired bool
	Direction   messageDirection
	Properties  map[string]interface{}
}
type messageDirection string
const (
	inboundMessage  = messageDirection("InboundMessage")
	outboundMessage = messageDirection("OutboundMessage")
)
type metaData struct {
	transitionalPayload
	state                 state
	presentationNames     []string
	properties            map[string]interface{}
	msgClone              service.DIDCommMsg
	presentation          *Presentation
	proposePresentation   *ProposePresentation
	request               *RequestPresentation
	presentationV3        *PresentationV3
	proposePresentationV3 *ProposePresentationV3
	requestV3             *RequestPresentationV3
	addProofFn func(presentation *verifiable.Presentation) error
	err error
}
func (md *metaData) Message() service.DIDCommMsg {
	return md.msgClone
}
func (md *metaData) Presentation() *Presentation {
	return md.presentation
}
func (md *metaData) PresentationV3() *PresentationV3 {
	return md.presentationV3
}
func (md *metaData) ProposePresentation() *ProposePresentation {
	return md.proposePresentation
}
func (md *metaData) ProposePresentationV3() *ProposePresentationV3 {
	return md.proposePresentationV3
}
func (md *metaData) RequestPresentation() *RequestPresentation {
	return md.request
}
func (md *metaData) RequestPresentationV3() *RequestPresentationV3 {
	return md.requestV3
}
func (md *metaData) PresentationNames() []string {
	return md.presentationNames
}
func (md *metaData) StateName() string {
	return md.state.Name()
}
func (md *metaData) Properties() map[string]interface{} {
	return md.properties
}
func (md *metaData) GetAddProofFn() func(presentation *verifiable.Presentation) error {
	return md.addProofFn
}
type Action struct {
	PIID     string
	Msg      service.DIDCommMsgMap
	MyDID    string
	TheirDID string
}
type Opt func(md *metaData)
func WithPresentation(msg *Presentation) Opt {
	return func(md *metaData) {
		md.presentation = msg
	}
}
func WithPresentationV3(msg *PresentationV3) Opt {
	return func(md *metaData) {
		md.presentationV3 = msg
	}
}
func WithAddProofFn(addProof func(presentation *verifiable.Presentation) error) Opt {
	return func(md *metaData) {
		md.addProofFn = addProof
	}
}
func WithMultiOptions(opts ...Opt) Opt {
	return func(md *metaData) {
		for _, opt := range opts {
			opt(md)
		}
	}
}
func WithProposePresentation(msg *ProposePresentation) Opt {
	return func(md *metaData) {
		md.proposePresentation = msg
	}
}
func WithProposePresentationV3(msg *ProposePresentationV3) Opt {
	return func(md *metaData) {
		md.proposePresentationV3 = msg
	}
}
func WithRequestPresentation(msg *RequestPresentation) Opt {
	return func(md *metaData) {
		md.request = msg
	}
}
func WithRequestPresentationV3(msg *RequestPresentationV3) Opt {
	return func(md *metaData) {
		md.requestV3 = msg
	}
}
func WithFriendlyNames(names ...string) Opt {
	return func(md *metaData) {
		md.presentationNames = names
	}
}
func WithProperties(props map[string]interface{}) Opt {
	return func(md *metaData) {
		if len(md.properties) == 0 {
			md.properties = props
			return
		}
		for k, v := range props {
			md.properties[k] = v
		}
	}
}
type Provider interface {
	Messenger() service.Messenger
	StorageProvider() storage.Provider
}
type Service struct {
	service.Action
	service.Message
	store      storage.Store
	callbacks  chan *metaData
	messenger  service.Messenger
	middleware Handler
}
func New(p Provider) (*Service, error) {
	store, err := p.StorageProvider().OpenStore(Name)
	if err != nil {
		return nil, err
	}
	err = p.StorageProvider().SetStoreConfig(Name, storage.StoreConfiguration{TagNames: []string{transitionalPayloadKey}})
	if err != nil {
		return nil, fmt.Errorf("failed to set store configuration: %w", err)
	}
	svc := &Service{
		messenger:  p.Messenger(),
		store:      store,
		callbacks:  make(chan *metaData),
		middleware: initialHandler,
	}
	go svc.startInternalListener()
	return svc, nil
}
func (s *Service) Use(items ...Middleware) {
	var handler Handler = initialHandler
	for i := len(items) - 1; i >= 0; i-- {
		handler = items[i](handler)
	}
	s.middleware = handler
}
func (s *Service) HandleInbound(msg service.DIDCommMsg, ctx service.DIDCommContext) (string, error) {
	logger.Debugf("service.HandleInbound() input: msg=%+v myDID=%s theirDID=%s", msg, ctx.MyDID(), ctx.TheirDID())
	msgMap := msg.Clone()
	aEvent := s.ActionEvent()
	if aEvent == nil {
		return "", errors.New("no clients are registered to handle the message")
	}
	md, err := s.doHandle(msgMap, inboundMessage)
	if err != nil {
		return "", fmt.Errorf("doHandle: %w", err)
	}
	md.MyDID = ctx.MyDID()
	md.TheirDID = ctx.TheirDID()
	if canTriggerActionEvents(msgMap) {
		err = s.saveTransitionalPayload(md.PIID, &(md.transitionalPayload))
		if err != nil {
			return "", fmt.Errorf("save transitional payload: %w", err)
		}
		aEvent <- s.newDIDCommActionMsg(md)
		return "", nil
	}
	thid, err := msgMap.ThreadID()
	if err != nil {
		return "", fmt.Errorf("failed to obtain the message's threadID : %w", err)
	}
	return thid, s.handle(md)
}
func (s *Service) HandleOutbound(msg service.DIDCommMsg, myDID, theirDID string) (string, error) {
	logger.Debugf("service.HandleOutbound() input: msg=%+v myDID=%s theirDID=%s", msg, myDID, theirDID)
	msgMap := msg.Clone()
	md, err := s.doHandle(msgMap, outboundMessage)
	if err != nil {
		return "", fmt.Errorf("doHandle: %w", err)
	}
	md.MyDID = myDID
	md.TheirDID = theirDID
	thid, err := msgMap.ThreadID()
	if err != nil {
		return "", fmt.Errorf("failed to obtain the message's threadID : %w", err)
	}
	return thid, s.handle(md)
}
func (s *Service) getCurrentInternalDataAndPIID(msg service.DIDCommMsg) (string, *internalData, error) {
	piID, err := getPIID(msg)
	if errors.Is(err, service.ErrThreadIDNotFound) {
		msg.SetID(uuid.New().String(), service.WithVersion(getDIDVersion(getVersion(msg.Type()))))
		return msg.ID(), &internalData{StateName: stateNameStart}, nil
	}
	if err != nil {
		return "", nil, fmt.Errorf("piID: %w", err)
	}
	data, err := s.currentInternalData(piID)
	if err != nil {
		return "", nil, fmt.Errorf("current internal data: %w", err)
	}
	return piID, data, nil
}
func (s *Service) doHandle(msg service.DIDCommMsgMap, direction messageDirection) (*metaData, error) {
	piID, data, err := s.getCurrentInternalDataAndPIID(msg)
	if err != nil {
		return nil, fmt.Errorf("current internal data and PIID: %w", err)
	}
	current := stateFromName(data.StateName, getVersion(msg.Type()))
	next, err := nextState(msg, direction)
	if err != nil {
		return nil, fmt.Errorf("nextState: %w", err)
	}
	if !current.CanTransitionTo(next) {
		return nil, fmt.Errorf("invalid state transition: %s -> %s", current.Name(), next.Name())
	}
	return &metaData{
		transitionalPayload: transitionalPayload{
			StateName:   next.Name(),
			AckRequired: data.AckRequired,
			Action: Action{
				Msg:  msg,
				PIID: piID,
			},
			Direction:  direction,
			Properties: next.Properties(),
		},
		properties: next.Properties(),
		state:      next,
		msgClone:   msg.Clone(),
	}, nil
}
func (s *Service) startInternalListener() {
	for msg := range s.callbacks {
		if msg.err == nil {
			msg.err = s.handle(msg)
		}
		if msg.err == nil {
			continue
		}
		logger.Errorf("failed to handle msgID=%s : %s", msg.Msg.ID(), msg.err)
		msg.state = &abandoned{V: getVersion(msg.Msg.Type()), Code: codeInternalError}
		if err := s.handle(msg); err != nil {
			logger.Errorf("listener handle: %s", err)
		}
	}
}
func isNoOp(s state) bool {
	_, ok := s.(*noOp)
	return ok
}
func (s *Service) handle(md *metaData) error {
	current := md.state
	for !isNoOp(current) {
		next, action, err := s.execute(current, md)
		if err != nil {
			return fmt.Errorf("execute: %w", err)
		}
		if !isNoOp(next) && !current.CanTransitionTo(next) {
			return fmt.Errorf("invalid state transition: %s --> %s", current.Name(), next.Name())
		}
		data := &internalData{StateName: current.Name(), AckRequired: md.AckRequired}
		if err := s.saveInternalData(md.PIID, data); err != nil {
			return fmt.Errorf("failed to persist state %s: %w", current.Name(), err)
		}
		if err := action(s.messenger); err != nil {
			return fmt.Errorf("action %s: %w", md.state.Name(), err)
		}
		current = next
	}
	return nil
}
func getPIID(msg service.DIDCommMsg) (string, error) {
	if pthID := msg.ParentThreadID(); pthID != "" {
		return pthID, nil
	}
	return msg.ThreadID()
}
type internalData struct {
	AckRequired bool
	StateName   string
}
func (s *Service) saveInternalData(piID string, data *internalData) error {
	src, err := json.Marshal(data)
	if err != nil {
		return err
	}
	return s.store.Put(internalDataKey+piID, src)
}
func (s *Service) currentInternalData(piID string) (*internalData, error) {
	src, err := s.store.Get(internalDataKey + piID)
	if errors.Is(err, storage.ErrDataNotFound) {
		return &internalData{StateName: stateNameStart}, nil
	}
	if err != nil {
		return nil, err
	}
	var data *internalData
	if err := json.Unmarshal(src, &data); err != nil {
		return nil, err
	}
	return data, nil
}
func stateFromName(name, v string) state {
	switch name {
	case stateNameStart:
		return &start{}
	case StateNameAbandoned:
		return &abandoned{V: v}
	case StateNameDone:
		return &done{V: v}
	case stateNameRequestSent:
		return &requestSent{V: v}
	case stateNamePresentationReceived:
		return &presentationReceived{V: v}
	case stateNameProposalReceived:
		return &proposalReceived{V: v}
	case stateNameRequestReceived:
		return &requestReceived{V: v}
	case stateNamePresentationSent:
		return &presentationSent{V: v}
	case stateNameProposalSent:
		return &proposalSent{V: v}
	default:
		return &noOp{}
	}
}
func nextState(msg service.DIDCommMsgMap, direction messageDirection) (state, error) {
	switch msg.Type() {
	case RequestPresentationMsgTypeV2, RequestPresentationMsgTypeV3:
		switch direction {
		case inboundMessage:
			return &requestReceived{V: getVersion(msg.Type())}, nil
		case outboundMessage:
			return &requestSent{V: getVersion(msg.Type())}, nil
		}
	case ProposePresentationMsgTypeV2, ProposePresentationMsgTypeV3:
		switch direction {
		case inboundMessage:
			return &proposalReceived{V: getVersion(msg.Type())}, nil
		case outboundMessage:
			return &proposalSent{V: getVersion(msg.Type())}, nil
		}
	case PresentationMsgTypeV2, PresentationMsgTypeV3:
		return &presentationReceived{V: getVersion(msg.Type())}, nil
	case ProblemReportMsgTypeV2, ProblemReportMsgTypeV3:
		return &abandoned{V: getVersion(msg.Type()), properties: redirectInfo(msg)}, nil
	case AckMsgTypeV2, AckMsgTypeV3:
		return &done{V: getVersion(msg.Type()), properties: redirectInfo(msg)}, nil
	}
	return nil, fmt.Errorf("unrecognized msgType: %s", msg.Type())
}
func getVersion(t string) string {
	if strings.HasPrefix(t, SpecV2) {
		return SpecV2
	}
	return SpecV3
}
func redirectInfo(msg service.DIDCommMsgMap) map[string]interface{} {
	if redirectInfo, ok := msg[webRedirect].(map[string]interface{}); ok {
		return redirectInfo
	}
	return map[string]interface{}{}
}
func getDIDVersion(v string) service.Version {
	if v == SpecV3 {
		return service.V2
	}
	return service.V1
}
func (s *Service) saveTransitionalPayload(id string, data *transitionalPayload) error {
	src, err := json.Marshal(*data)
	if err != nil {
		return fmt.Errorf("marshal transitional payload: %w", err)
	}
	return s.store.Put(fmt.Sprintf(transitionalPayloadKey, id), src, storage.Tag{Name: transitionalPayloadKey})
}
func canTriggerActionEvents(msg service.DIDCommMsg) bool {
	return msg.Type() == PresentationMsgTypeV2 ||
		msg.Type() == ProposePresentationMsgTypeV2 ||
		msg.Type() == RequestPresentationMsgTypeV2 ||
		msg.Type() == ProblemReportMsgTypeV2 ||
		msg.Type() == PresentationMsgTypeV3 ||
		msg.Type() == ProposePresentationMsgTypeV3 ||
		msg.Type() == RequestPresentationMsgTypeV3 ||
		msg.Type() == ProblemReportMsgTypeV3
}
func (s *Service) getTransitionalPayload(id string) (*transitionalPayload, error) {
	src, err := s.store.Get(fmt.Sprintf(transitionalPayloadKey, id))
	if err != nil {
		return nil, fmt.Errorf("store get: %w", err)
	}
	t := &transitionalPayload{}
	err = json.Unmarshal(src, t)
	if err != nil {
		return nil, fmt.Errorf("unmarshal transitional payload: %w", err)
	}
	return t, err
}
func (s *Service) deleteTransitionalPayload(id string) error {
	return s.store.Delete(fmt.Sprintf(transitionalPayloadKey, id))
}
func (s *Service) Actions() ([]Action, error) {
	records, err := s.store.Query(transitionalPayloadKey)
	if err != nil {
		return nil, fmt.Errorf("failed to query store: %w", err)
	}
	defer storage.Close(records, logger)
	var actions []Action
	more, err := records.Next()
	if err != nil {
		return nil, fmt.Errorf("failed to get next set of data from records: %w", err)
	}
	for more {
		value, err := records.Value()
		if err != nil {
			return nil, fmt.Errorf("failed to get value from records: %w", err)
		}
		var action Action
		if errUnmarshal := json.Unmarshal(value, &action); errUnmarshal != nil {
			return nil, fmt.Errorf("unmarshal: %w", errUnmarshal)
		}
		actions = append(actions, action)
		more, err = records.Next()
		if err != nil {
			return nil, fmt.Errorf("failed to get next set of data from records: %w", err)
		}
	}
	return actions, nil
}
func (s *Service) ActionContinue(piID string, opts ...Opt) error {
	tPayload, err := s.getTransitionalPayload(piID)
	if err != nil {
		return fmt.Errorf("get transitional payload: %w", err)
	}
	md := &metaData{
		transitionalPayload: *tPayload,
		state:               stateFromName(tPayload.StateName, getVersion(tPayload.Msg.Type())),
		msgClone:            tPayload.Msg.Clone(),
		properties:          tPayload.Properties,
	}
	for _, opt := range opts {
		opt(md)
	}
	if err := s.deleteTransitionalPayload(md.PIID); err != nil {
		return fmt.Errorf("delete transitional payload: %w", err)
	}
	s.processCallback(md)
	return nil
}
func (s *Service) ActionStop(piID string, cErr error, opts ...Opt) error {
	tPayload, err := s.getTransitionalPayload(piID)
	if err != nil {
		return fmt.Errorf("get transitional payload: %w", err)
	}
	md := &metaData{
		transitionalPayload: *tPayload,
		state:               stateFromName(tPayload.StateName, tPayload.Msg.Type()),
		msgClone:            tPayload.Msg.Clone(),
		properties:          tPayload.Properties,
	}
	for _, opt := range opts {
		opt(md)
	}
	if err := s.deleteTransitionalPayload(md.PIID); err != nil {
		return fmt.Errorf("delete transitional payload: %w", err)
	}
	if cErr == nil {
		cErr = errProtocolStopped
	}
	md.err = customError{error: cErr}
	s.processCallback(md)
	return nil
}
func (s *Service) processCallback(msg *metaData) {
	s.callbacks <- msg
}
func (s *Service) newDIDCommActionMsg(md *metaData) service.DIDCommAction {
	return service.DIDCommAction{
		ProtocolName: Name,
		Message:      md.msgClone,
		Continue: func(opt interface{}) {
			if fn, ok := opt.(Opt); ok {
				fn(md)
			}
			if err := s.deleteTransitionalPayload(md.PIID); err != nil {
				logger.Errorf("continue: delete transitional payload: %v", err)
			}
			s.processCallback(md)
		},
		Stop: func(cErr error) {
			if err := s.deleteTransitionalPayload(md.PIID); err != nil {
				logger.Errorf("stop: delete transitional payload: %v", err)
			}
			if cErr == nil {
				cErr = errProtocolStopped
			}
			md.err = customError{error: cErr}
			s.processCallback(md)
		},
		Properties: newEventProps(md),
	}
}
func (s *Service) execute(next state, md *metaData) (state, stateAction, error) {
	md.state = next
	s.sendMsgEvents(md, next.Name(), service.PreState)
	defer s.sendMsgEvents(md, next.Name(), service.PostState)
	md.properties = newEventProps(md).All()
	if err := s.middleware.Handle(md); err != nil {
		return nil, nil, fmt.Errorf("middleware: %w", err)
	}
	return next.Execute(md)
}
func (s *Service) sendMsgEvents(md *metaData, stateID string, stateType service.StateMsgType) {
	for _, handler := range s.MsgEvents() {
		handler <- service.StateMsg{
			ProtocolName: Name,
			Type:         stateType,
			Msg:          md.msgClone,
			StateID:      stateID,
			Properties:   newEventProps(md),
		}
	}
}
func (s *Service) Name() string {
	return Name
}