github.com/hyperledger/aries-framework-go@v0.3.2/pkg/store/connection/connection_lookup.go (about) 1 /* 2 * 3 * Copyright SecureKey Technologies Inc. All Rights Reserved. 4 * 5 * SPDX-License-Identifier: Apache-2.0 6 * / 7 * 8 */ 9 10 package connection 11 12 import ( 13 "encoding/json" 14 "errors" 15 "fmt" 16 "strings" 17 18 "github.com/hyperledger/aries-framework-go/pkg/common/log" 19 "github.com/hyperledger/aries-framework-go/pkg/common/model" 20 didcomm "github.com/hyperledger/aries-framework-go/pkg/didcomm/common/service" 21 "github.com/hyperledger/aries-framework-go/spi/storage" 22 ) 23 24 const ( 25 // Namespace is namespace of connection store name. 26 Namespace = "didexchange" 27 keyPattern = "%s_%s" 28 connIDKeyPrefix = "conn" 29 connStateKeyPrefix = "connstate" 30 bothDIDsTagName = "bothDIDs" 31 theirDIDTagName = "theirDID" 32 invKeyPrefix = "inv" 33 oobV2InvKeyPrefix = "oob2" 34 eventDataKeyPrefix = "connevent" 35 keySeparator = "_" 36 stateIDEmptyErr = "stateID can't be empty" 37 ) 38 39 var logger = log.New("aries-framework/store/connection") 40 41 // KeyPrefix is prefix builder for storage keys. 42 type KeyPrefix func(...string) string 43 44 type provider interface { 45 ProtocolStateStorageProvider() storage.Provider 46 StorageProvider() storage.Provider 47 } 48 49 // DIDRotationRecord holds information about a DID Rotation. 50 type DIDRotationRecord struct { 51 OldDID string `json:"oldDID,omitempty"` 52 NewDID string `json:"newDID,omitempty"` 53 FromPrior string `json:"fromPrior,omitempty"` 54 } 55 56 // Record contain info about did exchange connection. 57 // nolint:lll 58 type Record struct { 59 ConnectionID string 60 State string 61 ThreadID string 62 ParentThreadID string 63 TheirLabel string 64 TheirDID string 65 MyDID string 66 ServiceEndPoint model.Endpoint // ServiceEndPoint is 'their' DIDComm service endpoint. 67 RecipientKeys []string // RecipientKeys holds 'their' DIDComm recipient keys. 68 RoutingKeys []string // RoutingKeys holds 'their' DIDComm routing keys. 69 InvitationID string 70 InvitationDID string 71 InvitationRecipientKeys []string `json:"invitationRecipientKeys,omitempty"` // InvitationRecipientKeys holds keys generated for invitation 72 Implicit bool 73 Namespace string 74 MediaTypeProfiles []string 75 DIDCommVersion didcomm.Version 76 PeerDIDInitialState string 77 MyDIDRotation *DIDRotationRecord `json:"myDIDRotation,omitempty"` 78 } 79 80 // NewLookup returns new connection lookup instance. 81 // Lookup is read only connection store. It provides connection record related query features. 82 func NewLookup(p provider) (*Lookup, error) { 83 store, err := p.StorageProvider().OpenStore(Namespace) 84 if err != nil { 85 return nil, fmt.Errorf("failed to open permanent store to create new connection recorder: %w", err) 86 } 87 88 err = p.StorageProvider().SetStoreConfig(Namespace, storage.StoreConfiguration{TagNames: []string{ 89 connIDKeyPrefix, 90 bothDIDsTagName, 91 theirDIDTagName, 92 }}) 93 if err != nil { 94 return nil, fmt.Errorf("failed to set store config in permanent store: %w", err) 95 } 96 97 protocolStateStore, err := p.ProtocolStateStorageProvider().OpenStore(Namespace) 98 if err != nil { 99 return nil, fmt.Errorf("failed to open protocol state store to create new connection recorder: %w", err) 100 } 101 102 err = p.ProtocolStateStorageProvider().SetStoreConfig(Namespace, 103 storage.StoreConfiguration{TagNames: []string{connIDKeyPrefix, connStateKeyPrefix}}) 104 if err != nil { 105 return nil, fmt.Errorf("failed to set store config in protocol state store: %w", err) 106 } 107 108 return &Lookup{protocolStateStore: protocolStateStore, store: store}, nil 109 } 110 111 // Lookup takes care of connection related persistence features. 112 type Lookup struct { 113 protocolStateStore storage.Store 114 store storage.Store 115 } 116 117 // GetConnectionRecord return connection record based on the connection ID. 118 func (c *Lookup) GetConnectionRecord(connectionID string) (*Record, error) { 119 var rec Record 120 121 err := getAndUnmarshal(getConnectionKeyPrefix()(connectionID), &rec, c.store) 122 if err != nil { 123 if errors.Is(err, storage.ErrDataNotFound) { 124 err = getAndUnmarshal(getConnectionKeyPrefix()(connectionID), &rec, c.protocolStateStore) 125 if err != nil { 126 return nil, err 127 } 128 } else { 129 return nil, err 130 } 131 } 132 133 return &rec, nil 134 } 135 136 // GetConnectionRecordByDIDs return connection record for completed connection based on the DIDs of the participants. 137 func (c *Lookup) GetConnectionRecordByDIDs(myDID, theirDID string) (*Record, error) { 138 return c.queryExpectingOne(bothDIDsTagName+":"+tagValueFromDIDs(myDID, theirDID), c.store) 139 } 140 141 // GetConnectionRecordByTheirDID return connection record for completed connection based on the DID of the other party. 142 func (c *Lookup) GetConnectionRecordByTheirDID(theirDID string) (*Record, error) { 143 return c.queryExpectingOne(theirDIDTagName+":"+tagValueFromDIDs(theirDID), c.store) 144 } 145 146 func (c *Lookup) queryExpectingOne(query string, store storage.Store) (*Record, error) { 147 records, err := queryRecordsFromStore(query, store, nil, nil) 148 if err != nil { 149 return nil, fmt.Errorf("failed to get data from persistent store: %w", err) 150 } 151 152 if len(records) == 0 { 153 return nil, storage.ErrDataNotFound 154 } 155 156 logger.Debugf("query '%s' expected 1 result, got %d", query, len(records)) 157 158 return records[0], nil 159 } 160 161 // QueryConnectionRecords returns connection records found in underlying store 162 // for given query criteria. 163 func (c *Lookup) QueryConnectionRecords() ([]*Record, error) { 164 // TODO https://github.com/hyperledger/aries-framework-go/issues/655 query criteria to be added as part of issue 165 searchKey := getConnectionKeyPrefix()("") 166 167 var ( 168 records []*Record 169 keys = make(map[string]struct{}) 170 err error 171 ) 172 173 records, err = queryRecordsFromStore(searchKey, c.store, keys, records) 174 if err != nil { 175 return nil, fmt.Errorf("failed to get data from persistent store: %w", err) 176 } 177 178 records, err = queryRecordsFromStore(searchKey, c.protocolStateStore, keys, records) 179 if err != nil { 180 return nil, fmt.Errorf("failed to augment records from persistent store with records "+ 181 "from the protocol state store: %w", err) 182 } 183 184 return records, nil 185 } 186 187 func queryRecordsFromStore(searchKey string, store storage.Store, usedKeys map[string]struct{}, appendTo []*Record) ( 188 []*Record, error) { 189 if usedKeys == nil { 190 usedKeys = make(map[string]struct{}) 191 } 192 193 itr, err := store.Query(searchKey) 194 if err != nil { 195 return nil, fmt.Errorf("failed to query store: %w", err) 196 } 197 198 defer func() { 199 errClose := itr.Close() 200 if errClose != nil { 201 logger.Errorf("failed to close records iterator: %s", errClose.Error()) 202 } 203 }() 204 205 appendTo, err = readRecordIterator(itr, usedKeys, appendTo) 206 if err != nil { 207 return nil, fmt.Errorf("failed to read records: %w", err) 208 } 209 210 return appendTo, nil 211 } 212 213 // GetConnectionRecordAtState return connection record based on the connection ID and state. 214 func (c *Lookup) GetConnectionRecordAtState(connectionID, stateID string) (*Record, error) { 215 if stateID == "" { 216 return nil, errors.New(stateIDEmptyErr) 217 } 218 219 var rec Record 220 221 err := getAndUnmarshal(getConnectionStateKeyPrefix()(connectionID, stateID), &rec, c.protocolStateStore) 222 if err != nil { 223 return nil, fmt.Errorf("faild to get connection record by state : %s, cause : %w", stateID, err) 224 } 225 226 return &rec, nil 227 } 228 229 // GetConnectionRecordByNSThreadID return connection record via namespaced threadID. 230 func (c *Lookup) GetConnectionRecordByNSThreadID(nsThreadID string) (*Record, error) { 231 connectionIDBytes, err := c.protocolStateStore.Get(nsThreadID) 232 if err != nil { 233 return nil, fmt.Errorf("get connectionID by namespaced threadID: %w", err) 234 } 235 236 var rec Record 237 238 err = getAndUnmarshal(getConnectionKeyPrefix()(string(connectionIDBytes)), &rec, c.protocolStateStore) 239 if err != nil { 240 return nil, fmt.Errorf("faild to get connection record by NS thread ID : %s, cause : %w", nsThreadID, err) 241 } 242 243 return &rec, nil 244 } 245 246 // GetConnectionIDByDIDs return connection id based on dids (my or their did) metadata. 247 func (c *Lookup) GetConnectionIDByDIDs(myDID, theirDID string) (string, error) { 248 record, err := c.GetConnectionRecordByDIDs(myDID, theirDID) 249 if err != nil { 250 return "", fmt.Errorf("get connection record by DIDs: %w", err) 251 } 252 253 return record.ConnectionID, nil 254 } 255 256 // GetInvitation finds and parses stored invitation to target type. 257 // TODO should avoid using target of type `interface{}` [Issue #1030]. 258 func (c *Lookup) GetInvitation(id string, target interface{}) error { 259 if id == "" { 260 return fmt.Errorf(errMsgInvalidKey) 261 } 262 263 return getAndUnmarshal(getInvitationKeyPrefix()(id), target, c.store) 264 } 265 266 // GetOOBv2Invitation finds and parses stored OOBv2 invitation to target type. 267 // TODO should avoid using target of type `interface{}` [Issue #1030]. 268 func (c *Lookup) GetOOBv2Invitation(myDID string, target interface{}) error { 269 if myDID == "" { 270 return fmt.Errorf(errMsgInvalidKey) 271 } 272 273 return getAndUnmarshal(getOOBInvitationV2KeyPrefix()(tagValueFromDIDs(myDID)), target, c.store) 274 } 275 276 // GetEvent returns persisted event data for given connection ID. 277 // TODO connection event data shouldn't be transient [Issues #1029]. 278 func (c *Recorder) GetEvent(connectionID string) ([]byte, error) { 279 if connectionID == "" { 280 return nil, fmt.Errorf(errMsgInvalidKey) 281 } 282 283 return c.protocolStateStore.Get(getEventDataKeyPrefix()(connectionID)) 284 } 285 286 func readRecordIterator(itr storage.Iterator, usedKeys map[string]struct{}, appendTo []*Record) ([]*Record, error) { 287 var ( 288 more bool 289 errNext error 290 ) 291 292 for more, errNext = itr.Next(); more && errNext == nil; more, errNext = itr.Next() { 293 key, err := itr.Key() 294 if err != nil { 295 return nil, fmt.Errorf("failed to get key from iterator: %w", err) 296 } 297 298 // skip elements that were already found in a previous store 299 if _, ok := usedKeys[key]; ok { 300 continue 301 } 302 303 value, err := itr.Value() 304 if err != nil { 305 return nil, fmt.Errorf("failed to get value from iterator: %w", err) 306 } 307 308 var record Record 309 310 err = json.Unmarshal(value, &record) 311 if err != nil { 312 return nil, fmt.Errorf("failed to unmarshal connection record: %w", err) 313 } 314 315 appendTo = append(appendTo, &record) 316 usedKeys[key] = struct{}{} 317 } 318 319 if errNext != nil { 320 return nil, fmt.Errorf("failed to get next set of data from iterator: %w", errNext) 321 } 322 323 return appendTo, nil 324 } 325 326 func getAndUnmarshal(key string, target interface{}, store storage.Store) error { 327 bytes, err := store.Get(key) 328 if err != nil { 329 return err 330 } 331 332 err = json.Unmarshal(bytes, target) 333 if err != nil { 334 return err 335 } 336 337 return nil 338 } 339 340 // getConnectionKeyPrefix key prefix for connection record persisted. 341 func getConnectionKeyPrefix() KeyPrefix { 342 return func(key ...string) string { 343 return fmt.Sprintf(keyPattern, connIDKeyPrefix, strings.Join(key, keySeparator)) 344 } 345 } 346 347 // getConnectionStateKeyPrefix key prefix for state based connection record persisted. 348 func getConnectionStateKeyPrefix() KeyPrefix { 349 return func(key ...string) string { 350 return fmt.Sprintf(keyPattern, connStateKeyPrefix, strings.Join(key, keySeparator)) 351 } 352 } 353 354 // getInvitationKeyPrefix key prefix for saving invitations. 355 func getInvitationKeyPrefix() KeyPrefix { 356 return func(key ...string) string { 357 return fmt.Sprintf(keyPattern, invKeyPrefix, strings.Join(key, keySeparator)) 358 } 359 } 360 361 // getOOBInvitationV2KeyPrefix key prefix for saving OOBv2 invitations. 362 func getOOBInvitationV2KeyPrefix() KeyPrefix { 363 return func(key ...string) string { 364 return fmt.Sprintf(keyPattern, oobV2InvKeyPrefix, strings.Join(key, keySeparator)) 365 } 366 } 367 368 // getNamespaceKeyPrefix key prefix for saving connections records with mappings. 369 func getNamespaceKeyPrefix(prefix string) KeyPrefix { 370 return func(key ...string) string { 371 return fmt.Sprintf(keyPattern, prefix, strings.Join(key, keySeparator)) 372 } 373 } 374 375 // getEventDataKeyPrefix key prefix for saving event data. 376 func getEventDataKeyPrefix() KeyPrefix { 377 return func(key ...string) string { 378 return fmt.Sprintf(keyPattern, eventDataKeyPrefix, strings.Join(key, keySeparator)) 379 } 380 } 381 382 // CreateNamespaceKey creates key prefix for namespace related data. 383 func CreateNamespaceKey(prefix, thID string) (string, error) { 384 key, err := computeHash([]byte(thID)) 385 if err != nil { 386 return "", err 387 } 388 389 return getNamespaceKeyPrefix(prefix)(key), nil 390 } 391 392 func tagValueFromDIDs(dids ...string) string { 393 // DIDs have colons, but tag values can't, so we replace each colon with a $ 394 for i, did := range dids { 395 dids[i] = strings.ReplaceAll(did, ":", "$") 396 } 397 398 return strings.Join(dids, "|") 399 }