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  }