Skip to content
Snippets Groups Projects
Commit 95ecda94 authored by Jono Wenger's avatar Jono Wenger
Browse files

Move client storage version checking to Load and add function to determine if...

Move client storage version checking to Load and add function to determine if storage update is required
parent b50f6a1e
Branches
Tags
No related merge requests found
...@@ -23,6 +23,7 @@ import ( ...@@ -23,6 +23,7 @@ import (
"gitlab.com/elixxir/comms/client" "gitlab.com/elixxir/comms/client"
"gitlab.com/elixxir/crypto/cyclic" "gitlab.com/elixxir/crypto/cyclic"
"gitlab.com/elixxir/crypto/fastRNG" "gitlab.com/elixxir/crypto/fastRNG"
"gitlab.com/elixxir/primitives/version"
"gitlab.com/xx_network/crypto/csprng" "gitlab.com/xx_network/crypto/csprng"
"gitlab.com/xx_network/crypto/large" "gitlab.com/xx_network/crypto/large"
"gitlab.com/xx_network/crypto/signature/rsa" "gitlab.com/xx_network/crypto/signature/rsa"
...@@ -79,10 +80,16 @@ func NewClient(ndfJSON, storageDir string, password []byte, registrationCode str ...@@ -79,10 +80,16 @@ func NewClient(ndfJSON, storageDir string, password []byte, registrationCode str
protoUser := createNewUser(rngStream, cmixGrp, e2eGrp) protoUser := createNewUser(rngStream, cmixGrp, e2eGrp)
// Get current client version
currentVersion, err := version.ParseVersion(SEMVER)
if err != nil {
return errors.WithMessage(err, "Could not parse version string.")
}
// Create Storage // Create Storage
passwordStr := string(password) passwordStr := string(password)
storageSess, err := storage.New(storageDir, passwordStr, protoUser, storageSess, err := storage.New(storageDir, passwordStr, protoUser,
cmixGrp, e2eGrp, rngStreamGen) currentVersion, cmixGrp, e2eGrp, rngStreamGen)
if err != nil { if err != nil {
return err return err
} }
...@@ -124,10 +131,16 @@ func NewPrecannedClient(precannedID uint, defJSON, storageDir string, password [ ...@@ -124,10 +131,16 @@ func NewPrecannedClient(precannedID uint, defJSON, storageDir string, password [
protoUser := createPrecannedUser(precannedID, rngStream, cmixGrp, e2eGrp) protoUser := createPrecannedUser(precannedID, rngStream, cmixGrp, e2eGrp)
// Get current client version
currentVersion, err := version.ParseVersion(SEMVER)
if err != nil {
return errors.WithMessage(err, "Could not parse version string.")
}
// Create Storage // Create Storage
passwordStr := string(password) passwordStr := string(password)
storageSess, err := storage.New(storageDir, passwordStr, protoUser, storageSess, err := storage.New(storageDir, passwordStr, protoUser,
cmixGrp, e2eGrp, rngStreamGen) currentVersion, cmixGrp, e2eGrp, rngStreamGen)
if err != nil { if err != nil {
return err return err
} }
...@@ -151,12 +164,18 @@ func NewPrecannedClient(precannedID uint, defJSON, storageDir string, password [ ...@@ -151,12 +164,18 @@ func NewPrecannedClient(precannedID uint, defJSON, storageDir string, password [
func OpenClient(storageDir string, password []byte, parameters params.Network) (*Client, error) { func OpenClient(storageDir string, password []byte, parameters params.Network) (*Client, error) {
jww.INFO.Printf("OpenClient()") jww.INFO.Printf("OpenClient()")
// Use fastRNG for RNG ops (AES fortuna based RNG using system RNG) // Use fastRNG for RNG ops (AES fortuna based RNG using system RNG)
rngStreamGen := fastRNG.NewStreamGenerator(12, 3, rngStreamGen := fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG)
csprng.NewSystemRNG)
// Get current client version
currentVersion, err := version.ParseVersion(SEMVER)
if err != nil {
return nil, errors.WithMessage(err, "Could not parse version string.")
}
// Load Storage // Load Storage
passwordStr := string(password) passwordStr := string(password)
storageSess, err := storage.Load(storageDir, passwordStr, rngStreamGen) storageSess, err := storage.Load(storageDir, passwordStr, currentVersion,
rngStreamGen)
if err != nil { if err != nil {
return nil, err return nil, err
} }
......
///////////////////////////////////////////////////////////////////////////////
// Copyright © 2020 xx network SEZC //
// //
// Use of this source code is governed by a license that can be found in the //
// LICENSE file //
///////////////////////////////////////////////////////////////////////////////
package clientVersion
import (
"github.com/pkg/errors"
jww "github.com/spf13/jwalterweatherman"
"gitlab.com/elixxir/client/storage/versioned"
"gitlab.com/elixxir/primitives/version"
"sync"
"time"
)
const (
prefix = "clientVersionStore"
storeKey = "clientVersion"
storeVersion = 0
)
// Store stores the version of the client's storage.
type Store struct {
version version.Version
kv *versioned.KV
sync.RWMutex
}
// NewStore returns a new clientVersion store.
func NewStore(newVersion version.Version, kv *versioned.KV) (*Store, error) {
s := &Store{
version: newVersion,
kv: kv.Prefix(prefix),
}
return s, s.save()
}
// LoadStore loads the clientVersion storage object.
func LoadStore(kv *versioned.KV) (*Store, error) {
s := &Store{
kv: kv.Prefix(prefix),
}
obj, err := s.kv.Get(storeKey)
if err != nil {
return nil, err
}
s.version, err = version.ParseVersion(string(obj.Data))
if err != nil {
return nil, errors.Errorf("failed to parse client version: %+v", err)
}
return s, nil
}
// Get returns the stored version.
func (s *Store) Get() version.Version {
s.RLock()
defer s.RUnlock()
return s.version
}
// CheckUpdateRequired determines if the storage needs to be upgraded to the new
// client version. It returns true if an update is required (new > stored) and
// false otherwise. The old stored version is returned to be used to determine
// how to upgrade storage. If the new version is older than the stored version,
// an error is returned.
func (s *Store) CheckUpdateRequired(newVersion version.Version) (bool, version.Version, error) {
s.Lock()
defer s.Unlock()
oldVersion := s.version
diff := version.Cmp(oldVersion, newVersion)
switch {
case diff < 0:
return true, oldVersion, s.update(newVersion)
case diff > 0:
return false, oldVersion, errors.Errorf("new version (%s) is older "+
"than stored version (%s).", &newVersion, &oldVersion)
default:
return false, oldVersion, nil
}
}
// update replaces the current version with the new version if it is newer. Note
// that this function does not take a lock.
func (s *Store) update(newVersion version.Version) error {
jww.DEBUG.Printf("Updating stored client version from %s to %s.",
&s.version, &newVersion)
// Update version
s.version = newVersion
// Save new version to storage
return s.save()
}
// save stores the clientVersion store. Note that this function does not take
// a lock.
func (s *Store) save() error {
timeNow := time.Now()
obj := versioned.Object{
Version: storeVersion,
Timestamp: timeNow,
Data: []byte(s.version.String()),
}
return s.kv.Set(storeKey, &obj)
}
///////////////////////////////////////////////////////////////////////////////
// Copyright © 2020 xx network SEZC //
// //
// Use of this source code is governed by a license that can be found in the //
// LICENSE file //
///////////////////////////////////////////////////////////////////////////////
package clientVersion
import (
"gitlab.com/elixxir/client/storage/versioned"
"gitlab.com/elixxir/ekv"
"gitlab.com/elixxir/primitives/version"
"reflect"
"strings"
"testing"
"time"
)
// Happy path.
func TestNewStore(t *testing.T) {
kv := versioned.NewKV(make(ekv.Memstore))
expected := &Store{
version: version.New(42, 43, "44"),
kv: kv.Prefix(prefix),
}
test, err := NewStore(expected.version, kv)
if err != nil {
t.Errorf("NewStore() returned an error: %+v", err)
}
if !reflect.DeepEqual(expected, test) {
t.Errorf("NewStore() failed to return the expected Store."+
"\nexpected: %+v\nreceived: %+v", expected, test)
}
}
// Happy path.
func TestLoadStore(t *testing.T) {
kv := versioned.NewKV(make(ekv.Memstore))
ver := version.New(1, 2, "3A")
expected := &Store{
version: ver,
kv: kv.Prefix(prefix),
}
err := expected.save()
if err != nil {
t.Fatalf("Failed to save Store: %+v", err)
}
test, err := LoadStore(kv)
if err != nil {
t.Errorf("LoadStore() returned an error: %+v", err)
}
if !reflect.DeepEqual(expected, test) {
t.Errorf("LoadStore() failed to return the expected Store."+
"\nexpected: %+v\nreceived: %+v", expected, test)
}
}
// Error path: an error is returned when the loaded Store has an invalid version
// that fails to be parsed.
func TestLoadStore_ParseVersionError(t *testing.T) {
kv := versioned.NewKV(make(ekv.Memstore))
obj := versioned.Object{
Version: storeVersion,
Timestamp: time.Now(),
Data: []byte("invalid version"),
}
err := kv.Prefix(prefix).Set(storeKey, &obj)
if err != nil {
t.Fatalf("Failed to save Store: %+v", err)
}
_, err = LoadStore(kv)
if err == nil || !strings.Contains(err.Error(), "failed to parse client version") {
t.Errorf("LoadStore() did not return an error when the client version "+
"is invalid: %+v", err)
}
}
// Happy path.
func TestStore_Get(t *testing.T) {
kv := versioned.NewKV(make(ekv.Memstore))
expected := version.New(1, 2, "3A")
s := &Store{
version: expected,
kv: kv.Prefix(prefix),
}
test := s.Get()
if !reflect.DeepEqual(expected, test) {
t.Errorf("Get() failed to return the expected version."+
"\nexpected: %s\nreceived: %s", &expected, &test)
}
}
// Happy path.
func TestStore_CheckUpdateRequired(t *testing.T) {
kv := versioned.NewKV(make(ekv.Memstore))
storedVersion := version.New(1, 2, "3")
newVersion := version.New(2, 3, "4")
s, err := NewStore(storedVersion, kv)
if err != nil {
t.Fatalf("Failed to generate a new Store: %+v", err)
}
updateRequired, oldVersion, err := s.CheckUpdateRequired(newVersion)
if err != nil {
t.Errorf("CheckUpdateRequired() returned an error: %+v", err)
}
if !updateRequired {
t.Errorf("CheckUpdateRequired() did not indicate that an update is "+
"required when the new Version (%s) is newer than the stored"+
"version (%s)", &newVersion, &storedVersion)
}
if !version.Equal(storedVersion, oldVersion) {
t.Errorf("CheckUpdateRequired() did return the expected old Version."+
"\nexpected: %s\nreceived: %s", &storedVersion, &oldVersion)
}
}
// Happy path: the new version is equal to the stored version.
func TestStore_CheckUpdateRequired_EqualVersions(t *testing.T) {
kv := versioned.NewKV(make(ekv.Memstore))
storedVersion := version.New(2, 3, "3")
newVersion := version.New(2, 3, "4")
s, err := NewStore(storedVersion, kv)
if err != nil {
t.Fatalf("Failed to generate a new Store: %+v", err)
}
updateRequired, oldVersion, err := s.CheckUpdateRequired(newVersion)
if err != nil {
t.Errorf("CheckUpdateRequired() returned an error: %+v", err)
}
if updateRequired {
t.Errorf("CheckUpdateRequired() did not indicate that an update is required "+
"when the new Version (%s) is equal to the stored version (%s)",
&newVersion, &storedVersion)
}
if !version.Equal(storedVersion, oldVersion) {
t.Errorf("CheckUpdateRequired() did return the expected old Version."+
"\nexpected: %s\nreceived: %s", &storedVersion, &oldVersion)
}
}
// Error path: new version is older than stored version.
func TestStore_CheckUpdateRequired_NewVersionTooOldError(t *testing.T) {
kv := versioned.NewKV(make(ekv.Memstore))
storedVersion := version.New(2, 3, "4")
newVersion := version.New(1, 2, "3")
s, err := NewStore(storedVersion, kv)
if err != nil {
t.Fatalf("Failed to generate a new Store: %+v", err)
}
updateRequired, oldVersion, err := s.CheckUpdateRequired(newVersion)
if err == nil || !strings.Contains(err.Error(), "older than stored version") {
t.Errorf("CheckUpdateRequired() did not return an error when the new version "+
"is older than the stored version: %+v", err)
}
if updateRequired {
t.Errorf("CheckUpdateRequired() indicated that an update is required when the "+
"new Version (%s) is older than the stored version (%s)",
&newVersion, &storedVersion)
}
if !version.Equal(storedVersion, oldVersion) {
t.Errorf("CheckUpdateRequired() did return the expected old Version."+
"\nexpected: %s\nreceived: %s", &storedVersion, &oldVersion)
}
}
// Happy path.
func TestStore_update(t *testing.T) {
kv := versioned.NewKV(make(ekv.Memstore))
ver1 := version.New(1, 2, "3A")
ver2 := version.New(1, 5, "patch5")
s := &Store{
version: ver1,
kv: kv.Prefix(prefix),
}
err := s.update(ver2)
if err != nil {
t.Errorf("Update() returned an error: %+v", err)
}
if !reflect.DeepEqual(ver2, s.version) {
t.Errorf("Update() did not set the correct version."+
"\nexpected: %s\nreceived: %s", &ver2, &s.version)
}
}
// Happy path.
func TestStore_save(t *testing.T) {
kv := versioned.NewKV(make(ekv.Memstore))
ver := version.New(1, 2, "3A")
s := &Store{
version: ver,
kv: kv.Prefix(prefix),
}
err := s.save()
if err != nil {
t.Errorf("save() returned an error: %+v", err)
}
obj, err := s.kv.Get(storeKey)
if err != nil {
t.Errorf("Failed to load clientVersion store: %+v", err)
}
if ver.String() != string(obj.Data) {
t.Errorf("Failed to get correct data from stored object."+
"\nexpected: %s\nreceived: %s", ver.String(), obj.Data)
}
if storeVersion != obj.Version {
t.Errorf("Failed to get correct version from stored object."+
"\nexpected: %d\nreceived: %d", storeVersion, obj.Version)
}
}
...@@ -14,6 +14,7 @@ import ( ...@@ -14,6 +14,7 @@ import (
"gitlab.com/elixxir/client/globals" "gitlab.com/elixxir/client/globals"
userInterface "gitlab.com/elixxir/client/interfaces/user" userInterface "gitlab.com/elixxir/client/interfaces/user"
"gitlab.com/elixxir/client/storage/auth" "gitlab.com/elixxir/client/storage/auth"
"gitlab.com/elixxir/client/storage/clientVersion"
"gitlab.com/elixxir/client/storage/cmix" "gitlab.com/elixxir/client/storage/cmix"
"gitlab.com/elixxir/client/storage/conversation" "gitlab.com/elixxir/client/storage/conversation"
"gitlab.com/elixxir/client/storage/e2e" "gitlab.com/elixxir/client/storage/e2e"
...@@ -25,6 +26,7 @@ import ( ...@@ -25,6 +26,7 @@ import (
"gitlab.com/elixxir/crypto/cyclic" "gitlab.com/elixxir/crypto/cyclic"
"gitlab.com/elixxir/crypto/fastRNG" "gitlab.com/elixxir/crypto/fastRNG"
"gitlab.com/elixxir/ekv" "gitlab.com/elixxir/ekv"
"gitlab.com/elixxir/primitives/version"
"gitlab.com/xx_network/crypto/csprng" "gitlab.com/xx_network/crypto/csprng"
"gitlab.com/xx_network/crypto/large" "gitlab.com/xx_network/crypto/large"
"gitlab.com/xx_network/crypto/signature/rsa" "gitlab.com/xx_network/crypto/signature/rsa"
...@@ -57,6 +59,7 @@ type Session struct { ...@@ -57,6 +59,7 @@ type Session struct {
criticalRawMessages *utility.CmixMessageBuffer criticalRawMessages *utility.CmixMessageBuffer
garbledMessages *utility.MeteredCmixMessageBuffer garbledMessages *utility.MeteredCmixMessageBuffer
reception *reception.Store reception *reception.Store
clientVersion *clientVersion.Store
} }
// Initialize a new Session object // Initialize a new Session object
...@@ -76,9 +79,8 @@ func initStore(baseDir, password string) (*Session, error) { ...@@ -76,9 +79,8 @@ func initStore(baseDir, password string) (*Session, error) {
} }
// Creates new UserData in the session // Creates new UserData in the session
func New(baseDir, password string, u userInterface.User, currentVersion version.Version,
func New(baseDir, password string, u userInterface.User, cmixGrp, cmixGrp, e2eGrp *cyclic.Group, rng *fastRNG.StreamGenerator) (*Session, error) {
e2eGrp *cyclic.Group, rng *fastRNG.StreamGenerator) (*Session, error) {
s, err := initStore(baseDir, password) s, err := initStore(baseDir, password)
if err != nil { if err != nil {
...@@ -132,11 +134,18 @@ func New(baseDir, password string, u userInterface.User, cmixGrp, ...@@ -132,11 +134,18 @@ func New(baseDir, password string, u userInterface.User, cmixGrp,
s.reception = reception.NewStore(s.kv) s.reception = reception.NewStore(s.kv)
s.clientVersion, err = clientVersion.NewStore(currentVersion, s.kv)
if err != nil {
return nil, errors.WithMessage(err, "Failed to create client version store.")
}
return s, nil return s, nil
} }
// Loads existing user data into the session // Loads existing user data into the session
func Load(baseDir, password string, rng *fastRNG.StreamGenerator) (*Session, error) { func Load(baseDir, password string, currentVersion version.Version,
rng *fastRNG.StreamGenerator) (*Session, error) {
s, err := initStore(baseDir, password) s, err := initStore(baseDir, password)
if err != nil { if err != nil {
return nil, errors.WithMessage(err, "Failed to load Session") return nil, errors.WithMessage(err, "Failed to load Session")
...@@ -147,6 +156,17 @@ func Load(baseDir, password string, rng *fastRNG.StreamGenerator) (*Session, err ...@@ -147,6 +156,17 @@ func Load(baseDir, password string, rng *fastRNG.StreamGenerator) (*Session, err
return nil, errors.WithMessage(err, "Failed to load Session") return nil, errors.WithMessage(err, "Failed to load Session")
} }
s.clientVersion, err = clientVersion.LoadStore(s.kv)
if err != nil {
return nil, errors.WithMessage(err, "Failed to load client version store.")
}
// Determine if the storage needs to be updated to the current version
_, _, err = s.clientVersion.CheckUpdateRequired(currentVersion)
if err != nil {
return nil, errors.WithMessage(err, "Failed to load client version store.")
}
s.user, err = user.LoadUser(s.kv) s.user, err = user.LoadUser(s.kv)
if err != nil { if err != nil {
return nil, errors.WithMessage(err, "Failed to load Session") return nil, errors.WithMessage(err, "Failed to load Session")
...@@ -241,6 +261,13 @@ func (s *Session) GetGarbledMessages() *utility.MeteredCmixMessageBuffer { ...@@ -241,6 +261,13 @@ func (s *Session) GetGarbledMessages() *utility.MeteredCmixMessageBuffer {
return s.garbledMessages return s.garbledMessages
} }
// GetClientVersion returns the version of the client storage.
func (s *Session) GetClientVersion() version.Version {
s.mux.RLock()
defer s.mux.RUnlock()
return s.clientVersion.Get()
}
func (s *Session) Conversations() *conversation.Store { func (s *Session) Conversations() *conversation.Store {
s.mux.RLock() s.mux.RLock()
defer s.mux.RUnlock() defer s.mux.RUnlock()
...@@ -307,11 +334,11 @@ func InitTestingSession(i interface{}) *Session { ...@@ -307,11 +334,11 @@ func InitTestingSession(i interface{}) *Session {
"3A10B1C4D203CC76A470A33AFDCBDD92959859ABD8B56E1725252D78EAC66E71"+ "3A10B1C4D203CC76A470A33AFDCBDD92959859ABD8B56E1725252D78EAC66E71"+
"BA9AE3F1DD2487199874393CD4D832186800654760E1E34C09E4D155179F9EC0"+ "BA9AE3F1DD2487199874393CD4D832186800654760E1E34C09E4D155179F9EC0"+
"DC4473F996BDCE6EED1CABED8B6F116F7AD9CF505DF0F998E34AB27514B0FFE7", 16)) "DC4473F996BDCE6EED1CABED8B6F116F7AD9CF505DF0F998E34AB27514B0FFE7", 16))
cmix, err := cmix.NewStore(cmixGrp, kv, cmixGrp.NewInt(2)) cmixStore, err := cmix.NewStore(cmixGrp, kv, cmixGrp.NewInt(2))
if err != nil { if err != nil {
globals.Log.FATAL.Panicf("InitTestingSession failed to create dummy cmix session: %+v", err) globals.Log.FATAL.Panicf("InitTestingSession failed to create dummy cmix session: %+v", err)
} }
s.cmix = cmix s.cmix = cmixStore
e2eStore, err := e2e.NewStore(cmixGrp, kv, cmixGrp.NewInt(2), uid, e2eStore, err := e2e.NewStore(cmixGrp, kv, cmixGrp.NewInt(2), uid,
fastRNG.NewStreamGenerator(7, 3, csprng.NewSystemRNG)) fastRNG.NewStreamGenerator(7, 3, csprng.NewSystemRNG))
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment