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

     1  /*
     2  Copyright SecureKey Technologies Inc. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package messenger
     8  
     9  import (
    10  	"encoding/json"
    11  	"errors"
    12  	"fmt"
    13  
    14  	"github.com/google/uuid"
    15  
    16  	"github.com/hyperledger/aries-framework-go/pkg/common/log"
    17  	"github.com/hyperledger/aries-framework-go/pkg/didcomm/common/service"
    18  	"github.com/hyperledger/aries-framework-go/pkg/didcomm/dispatcher"
    19  	"github.com/hyperledger/aries-framework-go/spi/storage"
    20  )
    21  
    22  // MessengerStore is messenger store name.
    23  const MessengerStore = "messenger_store"
    24  
    25  // record is an internal structure and keeps payload about inbound message.
    26  type record struct {
    27  	MyDID          string `json:"my_did,omitempty"`
    28  	TheirDID       string `json:"their_did,omitempty"`
    29  	ThreadID       string `json:"thread_id,omitempty"`
    30  	ParentThreadID string `json:"parent_thread_id,omitempty"`
    31  }
    32  
    33  // Provider contains dependencies for the Messenger.
    34  type Provider interface {
    35  	OutboundDispatcher() dispatcher.Outbound
    36  	StorageProvider() storage.Provider
    37  }
    38  
    39  // Messenger describes the messenger structure.
    40  type Messenger struct {
    41  	store      storage.Store
    42  	dispatcher dispatcher.Outbound
    43  }
    44  
    45  var logger = log.New("aries-framework/pkg/didcomm/messenger")
    46  
    47  // NewMessenger returns a new instance of the Messenger.
    48  func NewMessenger(ctx Provider) (*Messenger, error) {
    49  	store, err := ctx.StorageProvider().OpenStore(MessengerStore)
    50  	if err != nil {
    51  		return nil, fmt.Errorf("open store: %w", err)
    52  	}
    53  
    54  	return &Messenger{
    55  		store:      store,
    56  		dispatcher: ctx.OutboundDispatcher(),
    57  	}, nil
    58  }
    59  
    60  // HandleInbound handles all inbound messages.
    61  func (m *Messenger) HandleInbound(msg service.DIDCommMsgMap, ctx service.DIDCommContext) error {
    62  	// an incoming message cannot be without id
    63  	if msg.ID() == "" {
    64  		return errors.New("message-id is absent and can't be processed")
    65  	}
    66  
    67  	// get message threadID
    68  	thID, err := msg.ThreadID()
    69  	if err != nil {
    70  		// since we are checking ID above this should never happen
    71  		// even if ~thread decorator is absent the message ID should be returned as a threadID
    72  		return fmt.Errorf("threadID: %w", err)
    73  	}
    74  
    75  	// saves message payload
    76  	return m.saveRecord(msg.ID(), record{
    77  		ParentThreadID: msg.ParentThreadID(),
    78  		MyDID:          ctx.MyDID(),
    79  		TheirDID:       ctx.TheirDID(),
    80  		ThreadID:       thID,
    81  	})
    82  }
    83  
    84  // Send sends the message by starting a new thread.
    85  // Do not provide a message with ~thread decorator. It will be removed.
    86  // Use ReplyTo function instead. It will keep ~thread decorator automatically.
    87  func (m *Messenger) Send(msg service.DIDCommMsgMap, myDID, theirDID string, opts ...service.Opt) error {
    88  	// fills missing fields
    89  	fillIfMissing(msg, opts...)
    90  
    91  	msg.UnsetThread()
    92  	msg.SetThread(msg.ID(), "", opts...)
    93  
    94  	return m.dispatcher.SendToDID(msg, myDID, theirDID)
    95  }
    96  
    97  // SendToDestination sends the message to given destination by starting a new thread.
    98  // Do not provide a message with ~thread decorator. It will be removed.
    99  // Use ReplyTo function instead. It will keep ~thread decorator automatically.
   100  func (m *Messenger) SendToDestination(msg service.DIDCommMsgMap, sender string,
   101  	destination *service.Destination, opts ...service.Opt) error {
   102  	// fills missing fields
   103  	fillIfMissing(msg, opts...)
   104  
   105  	msg.UnsetThread()
   106  
   107  	return m.dispatcher.Send(msg, sender, destination)
   108  }
   109  
   110  // ReplyTo replies to the message by given msgID.
   111  // The function adds ~thread decorator to the message according to the given msgID.
   112  // Do not provide a message with ~thread decorator. It will be rewritten.
   113  func (m *Messenger) ReplyTo(msgID string, msg service.DIDCommMsgMap, opts ...service.Opt) error {
   114  	// fills missing fields
   115  	fillIfMissing(msg, opts...)
   116  
   117  	rec, err := m.getRecord(msgID)
   118  	if err != nil {
   119  		return fmt.Errorf("get record: %w", err)
   120  	}
   121  
   122  	msg.UnsetThread()
   123  	// sets thread
   124  	msg.SetThread(rec.ThreadID, rec.ParentThreadID, opts...)
   125  
   126  	return m.dispatcher.SendToDID(msg, rec.MyDID, rec.TheirDID)
   127  }
   128  
   129  // ReplyToMsg replies to the given message.
   130  // The function adds ~thread decorator to the message according to the given msgID.
   131  // Do not provide a message with ~thread decorator. It will be rewritten.
   132  func (m *Messenger) ReplyToMsg(in, out service.DIDCommMsgMap, myDID, theirDID string, opts ...service.Opt) error {
   133  	// fills missing fields
   134  	fillIfMissing(out, opts...)
   135  
   136  	thID, err := in.ThreadID()
   137  	if err != nil {
   138  		return fmt.Errorf("get threadID: %w", err)
   139  	}
   140  
   141  	out.UnsetThread()
   142  	// sets thread
   143  	out.SetThread(thID, in.ParentThreadID(), opts...)
   144  
   145  	return m.dispatcher.SendToDID(out, myDID, theirDID)
   146  }
   147  
   148  // ReplyToNested sends the message by starting a new thread.
   149  // Do not provide a message with ~thread decorator. It will be rewritten.
   150  // The function adds ~thread decorator to the message according to the given threadID.
   151  // NOTE: Given threadID (from opts or from message record) becomes parent threadID.
   152  func (m *Messenger) ReplyToNested(msg service.DIDCommMsgMap, opts *service.NestedReplyOpts) error {
   153  	// fills missing fields
   154  	fillIfMissing(msg, service.WithVersion(opts.V))
   155  
   156  	if err := m.fillNestedReplyOption(opts); err != nil {
   157  		return fmt.Errorf("failed to prepare nested reply options: %w", err)
   158  	}
   159  
   160  	msg.UnsetThread()
   161  	// sets parent thread id
   162  	msg.SetThread("", opts.ThreadID, service.WithVersion(opts.V))
   163  
   164  	return m.dispatcher.SendToDID(msg, opts.MyDID, opts.TheirDID)
   165  }
   166  
   167  // fillIfMissing populates message with common fields such as ID.
   168  func fillIfMissing(msg service.DIDCommMsgMap, opts ...service.Opt) {
   169  	// if ID is empty we will create a new one
   170  	if msg.ID() == "" {
   171  		msg.SetID(uuid.New().String(), opts...)
   172  	}
   173  }
   174  
   175  // getRecord returns message payload by msgID.
   176  func (m *Messenger) getRecord(msgID string) (*record, error) {
   177  	src, err := m.store.Get(msgID)
   178  	if err != nil {
   179  		return nil, fmt.Errorf("store get: %w", err)
   180  	}
   181  
   182  	var r *record
   183  	if err = json.Unmarshal(src, &r); err != nil {
   184  		return nil, fmt.Errorf("unmarshal record: %w", err)
   185  	}
   186  
   187  	return r, nil
   188  }
   189  
   190  // saveRecord saves incoming message payload.
   191  func (m *Messenger) saveRecord(msgID string, rec record) error {
   192  	src, err := json.Marshal(rec)
   193  	if err != nil {
   194  		return fmt.Errorf("marshal record: %w", err)
   195  	}
   196  
   197  	return m.store.Put(msgID, src)
   198  }
   199  
   200  // fillNestedReplyOption prefills missing nested reply options from record.
   201  func (m *Messenger) fillNestedReplyOption(opts *service.NestedReplyOpts) error {
   202  	if opts.ThreadID != "" && opts.TheirDID != "" && opts.MyDID != "" {
   203  		return nil
   204  	}
   205  
   206  	if opts.MsgID == "" { // nolint: staticcheck
   207  		logger.Debugf("failed to prepare fill nested reply options, missing message ID")
   208  		return nil
   209  	}
   210  
   211  	rec, err := m.getRecord(opts.MsgID) // nolint: staticcheck
   212  	if err != nil {
   213  		return err
   214  	}
   215  
   216  	if opts.ThreadID == "" {
   217  		opts.ThreadID = rec.ThreadID
   218  	}
   219  
   220  	if opts.TheirDID == "" {
   221  		opts.TheirDID = rec.TheirDID
   222  	}
   223  
   224  	if opts.MyDID == "" {
   225  		opts.MyDID = rec.MyDID
   226  	}
   227  
   228  	return nil
   229  }