Skip to content
Snippets Groups Projects
Select Git revision
  • 9c9f70f6502ed0c0ca6cfc91db768734780f8907
  • release default protected
  • 11-22-implement-kv-interface-defined-in-collectiveversionedkvgo
  • hotfix/TestHostPool_UpdateNdf_AddFilter
  • XX-4719/announcementChannels
  • xx-4717/logLevel
  • jonah/noob-channel
  • master protected
  • XX-4707/tagDiskJson
  • xx-4698/notification-retry
  • hotfix/notifylockup
  • syncNodes
  • hotfix/localCB
  • XX-4677/NewChanManagerMobile
  • XX-4689/DmSync
  • duplicatePrefix
  • XX-4601/HavenInvites
  • finalizedUICallbacks
  • XX-4673/AdminKeySync
  • debugNotifID
  • anne/test
  • v4.7.5
  • v4.7.4
  • v4.7.3
  • v4.7.2
  • v4.7.1
  • v4.6.3
  • v4.6.1
  • v4.5.0
  • v4.4.4
  • v4.3.11
  • v4.3.8
  • v4.3.7
  • v4.3.6
  • v4.3.5
  • v4.2.0
  • v4.3.0
  • v4.3.4
  • v4.3.3
  • v4.3.2
  • v4.3.1
41 results

README.md

Blame
  • facts.go 7.07 KiB
    ///////////////////////////////////////////////////////////////////////////////
    // Copyright © 2020 xx network SEZC                                          //
    //                                                                           //
    // Use of this source code is governed by a license that can be found in the //
    // LICENSE file                                                              //
    ///////////////////////////////////////////////////////////////////////////////
    
    package ud
    
    import (
    	"fmt"
    	"github.com/pkg/errors"
    	"gitlab.com/elixxir/client/storage/versioned"
    	"gitlab.com/elixxir/primitives/fact"
    	"sync"
    )
    
    const (
    	factTypeExistsErr               = "Fact %v cannot be added as fact type %s has already been stored. Cancelling backup operation!"
    	backupMissingInvalidFactTypeErr = "BackUpMissingFacts expects input in the order (email, phone). " +
    		"%s (%s) is non-empty but not an email. Cancelling backup operation"
    	backupMissingAllZeroesFactErr = "Cannot backup missing facts: Both email and phone facts are empty!"
    	factNotInStoreErr             = "Fact %v does not exist in store"
    )
    
    // Store is the storage object for the higher level ud.Manager object.
    // This storage implementation is written for client side.
    type Store struct {
    	// confirmedFacts contains facts that have been confirmed
    	confirmedFacts map[fact.Fact]struct{}
    	// Stores facts that have been added by UDB but unconfirmed facts.
    	// Maps confirmID to fact
    	unconfirmedFacts map[string]fact.Fact
    	kv               *versioned.KV
    	mux              sync.RWMutex
    }
    
    // NewStore creates a new, empty Store object.
    func NewStore(kv *versioned.KV) (*Store, error) {
    	kv = kv.Prefix(prefix)
    
    	s := &Store{
    		confirmedFacts:   make(map[fact.Fact]struct{}, 0),
    		unconfirmedFacts: make(map[string]fact.Fact, 0),
    		kv:               kv,
    	}
    
    	return s, s.save()
    }
    
    // StoreUnconfirmedFact stores a fact that has been added to UD but has not been
    // confirmed by the user. It is keyed on the confirmation ID given by UD.
    func (s *Store) StoreUnconfirmedFact(confirmationId string, f fact.Fact) error {
    	s.mux.Lock()
    	defer s.mux.Unlock()
    
    	s.unconfirmedFacts[confirmationId] = f
    	return s.saveUnconfirmedFacts()
    }
    
    // ConfirmFact will delete the fact from the unconfirmed store and
    // add it to the confirmed fact store. The Store will then be saved
    func (s *Store) ConfirmFact(confirmationId string) error {
    	s.mux.Lock()
    	defer s.mux.Unlock()
    
    	f, exists := s.unconfirmedFacts[confirmationId]
    	if !exists {
    		return errors.New(fmt.Sprintf("No fact exists in store "+
    			"with confirmation ID %q", confirmationId))
    	}
    
    	delete(s.unconfirmedFacts, confirmationId)
    	s.confirmedFacts[f] = struct{}{}
    	return s.save()
    }
    
    // BackUpMissingFacts adds a registered fact to the Store object. It can take in both an
    // email and a phone number. One or the other may be an empty string, however both is considered
    // an error. It checks for each whether that fact type already exists in the structure. If a fact
    // type already exists, an error is returned.
    // ************************************************************************
    // NOTE: This is done since BackUpMissingFacts is exposed to the
    // bindings layer. This prevents front end from using this as the method
    // to store facts on their end, which is not its intended use case. It's intended use
    // case is to store already registered facts, prior to the creation of this function.
    // We handle storage of newly registered internally using Store.ConfirmFact.
    // ************************************************************************
    // Any other fact.FactType is not accepted and returns an error and nothing is backed up.
    // If you attempt to back up a fact type that has already been backed up,
    // an error will be returned and nothing will be backed up.
    // Otherwise, it adds the fact and returns whether the Store saved successfully.
    func (s *Store) BackUpMissingFacts(email, phone fact.Fact) error {
    	s.mux.Lock()
    	defer s.mux.Unlock()
    
    	if isFactZero(email) && isFactZero(phone) {
    		return errors.New(backupMissingAllZeroesFactErr)
    	}
    
    	modifiedEmail, modifiedPhone := false, false
    
    	// Handle email if it is not zero (empty string)
    	if !isFactZero(email) {
    		// check if fact is expected type
    		if email.T != fact.Email {
    			return errors.New(fmt.Sprintf(backupMissingInvalidFactTypeErr, fact.Email, email.Fact))
    		}
    
    		// Check if fact type is already in map. See docstring NOTE for explanation
    		if isFactTypeInMap(fact.Email, s.confirmedFacts) {
    			// If an email exists in memory, return an error
    			return errors.Errorf(factTypeExistsErr, email, fact.Email)
    		} else {
    			modifiedEmail = true
    		}
    	}
    
    	if !isFactZero(phone) {
    		// check if fact is expected type
    		if phone.T != fact.Phone {
    			return errors.New(fmt.Sprintf(backupMissingInvalidFactTypeErr, fact.Phone, phone.Fact))
    		}
    
    		// Check if fact type is already in map. See docstring NOTE for explanation
    		if isFactTypeInMap(fact.Phone, s.confirmedFacts) {
    			// If a phone exists in memory, return an error
    			return errors.Errorf(factTypeExistsErr, phone, fact.Phone)
    		} else {
    			modifiedPhone = true
    		}
    	}
    
    	if modifiedPhone || modifiedEmail {
    		if modifiedEmail {
    			s.confirmedFacts[email] = struct{}{}
    		}
    
    		if modifiedPhone {
    			s.confirmedFacts[phone] = struct{}{}
    		}
    
    		return s.saveConfirmedFacts()
    	}
    
    	return nil
    
    }
    
    // DeleteFact is our internal use function which will delete the registered fact
    // from memory and storage. An error is returned if the fact does not exist in
    // memory.
    func (s *Store) DeleteFact(f fact.Fact) error {
    	s.mux.Lock()
    	defer s.mux.Unlock()
    
    	if _, exists := s.confirmedFacts[f]; !exists {
    		return errors.Errorf(factNotInStoreErr, f)
    	}
    
    	delete(s.confirmedFacts, f)
    	return s.saveConfirmedFacts()
    }
    
    // GetStringifiedFacts returns a list of stringified facts from the Store's
    // confirmedFacts map.
    func (s *Store) GetStringifiedFacts() []string {
    	s.mux.RLock()
    	defer s.mux.RUnlock()
    
    	return s.serializeConfirmedFacts()
    }
    
    // GetFacts returns a list of fact.Fact objects that exist within the
    // Store's confirmedFacts map.
    func (s *Store) GetFacts() []fact.Fact {
    	s.mux.RLock()
    	defer s.mux.RUnlock()
    
    	// Flatten the facts into a slice
    	facts := make([]fact.Fact, 0, len(s.confirmedFacts))
    	for f := range s.confirmedFacts {
    		facts = append(facts, f)
    	}
    
    	return facts
    }
    
    // serializeConfirmedFacts is a helper function which serializes Store's confirmedFacts
    // map into a list of strings. Each string in the list represents
    // a fact.Fact that has been Stringified.
    func (s *Store) serializeConfirmedFacts() []string {
    	fStrings := make([]string, 0, len(s.confirmedFacts))
    	for f := range s.confirmedFacts {
    		fStrings = append(fStrings, f.Stringify())
    	}
    
    	return fStrings
    }
    
    // fixme: consider this being a method on the fact.Fact object?
    // isFactZero tests whether a fact has been uninitialized.
    func isFactZero(f fact.Fact) bool {
    	return f.T == fact.Username && f.Fact == ""
    }
    
    // isFactTypeInMap is a helper function which determines whether a fact type exists within
    // the data structure.
    func isFactTypeInMap(factType fact.FactType, facts map[fact.Fact]struct{}) bool {
    	for f := range facts {
    		if f.T == factType {
    			return true
    		}
    	}
    
    	return false
    }