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 }