diff --git a/go.mod b/go.mod index 857f474..d6ce5f0 100644 --- a/go.mod +++ b/go.mod @@ -6,8 +6,8 @@ require ( github.com/stretchr/testify v1.8.4 go.dedis.ch/dela v0.0.0-20231011144949-4677467c030c go.dedis.ch/kyber/v3 v3.1.1-0.20231024084410-31ea167adbbb - go.dedis.ch/libpurb v0.0.0-20231107160354-ffba29c17caf - golang.org/x/exp v0.0.0-20231006140011-7918f672742d + go.dedis.ch/libpurb v0.0.0-20231108133532-c70e1b84b632 + golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 ) @@ -32,7 +32,7 @@ require ( go.dedis.ch/fixbuf v1.0.3 // indirect go.etcd.io/bbolt v1.3.5 // indirect golang.org/x/crypto v0.14.0 // indirect - golang.org/x/sys v0.13.0 // indirect + golang.org/x/sys v0.14.0 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 61f85ad..fc97be0 100644 --- a/go.sum +++ b/go.sum @@ -108,16 +108,16 @@ go.dedis.ch/fixbuf v1.0.3 h1:hGcV9Cd/znUxlusJ64eAlExS+5cJDIyTyEG+otu5wQs= go.dedis.ch/fixbuf v1.0.3/go.mod h1:yzJMt34Wa5xD37V5RTdmp38cz3QhMagdGoem9anUalw= go.dedis.ch/kyber/v3 v3.1.1-0.20231024084410-31ea167adbbb h1:G7GmitWgVMlkBaSzHJJo05bydYhLFCSPzKwCHTwf3NM= go.dedis.ch/kyber/v3 v3.1.1-0.20231024084410-31ea167adbbb/go.mod h1:nL0a5f3E//lnOCKumtzMmb8qzykxOVsihqp8C1Eb5rc= -go.dedis.ch/libpurb v0.0.0-20231107160354-ffba29c17caf h1:kNjCdcUbtc4+ANLZWApokLVzkHuW1NPxb4fNpGzFjBY= -go.dedis.ch/libpurb v0.0.0-20231107160354-ffba29c17caf/go.mod h1:XCi40g75txGSLusqJOanJytpkxiQLJzRb9RRV9UN2p4= +go.dedis.ch/libpurb v0.0.0-20231108133532-c70e1b84b632 h1:e3rOQfoyJm6ywIbuBcRwoKdWSUsPmcW9qdHO47dOmyc= +go.dedis.ch/libpurb v0.0.0-20231108133532-c70e1b84b632/go.mod h1:XCi40g75txGSLusqJOanJytpkxiQLJzRb9RRV9UN2p4= go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= -golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -134,8 +134,8 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= diff --git a/store/kv/blob.go b/store/kv/blob.go index 78ee15b..af08f7f 100644 --- a/store/kv/blob.go +++ b/store/kv/blob.go @@ -1,3 +1,4 @@ +// Blob is a wrapper around libpurb.Purb package kv import ( @@ -9,39 +10,31 @@ import ( "go.dedis.ch/kyber/v3/util/random" ) -type Blob struct { - purb *libpurb.Purb -} +const numberOfRecipients = 1 // NewBlob creates a new blob -func NewBlob() *Blob { - suitesInfo := getSuiteInfo() - simplified := true - +func NewBlob(keypair []key.Pair) *libpurb.Purb { p := libpurb.NewPurb( - suitesInfo, - simplified, + getSuiteInfo(), + false, random.New(), ) + p.Recipients = createRecipients(keypair) - p.Recipients = createRecipients(1) - - return &Blob{ - purb: p, - } + return p } // Encode encodes a slice of bytes into a blob -func (b *Blob) Encode(data []byte) ([]byte, error) { - err := b.purb.Encode(data) - blob := b.purb.ToBytes() +func Encode(purb *libpurb.Purb, data []byte) ([]byte, error) { + err := purb.Encode(data) + blob := purb.ToBytes() return blob, err } // Decode decodes a blob into a slice of bytes -func (b *Blob) Decode(blob []byte) ([]byte, error) { - success, decrypted, err := b.purb.Decode(blob) +func Decode(purb *libpurb.Purb, blob []byte) ([]byte, error) { + success, decrypted, err := purb.Decode(blob) if !success { err = xerrors.Errorf("Failed to decrypt blob: %v", err) @@ -71,19 +64,27 @@ func getSuiteInfo() libpurb.SuiteInfoMap { } // see example in libpurb -func createRecipients(n int) []libpurb.Recipient { - decs := make([]libpurb.Recipient, 0) +func createRecipients(keypair []key.Pair) []libpurb.Recipient { + r := make([]libpurb.Recipient, 0) suites := []libpurb.Suite{curve25519.NewBlakeSHA256Curve25519(true)} + + if len(keypair) < numberOfRecipients { + keypair = make([]key.Pair, 0) + } + for _, suite := range suites { - for i := 0; i < n; i++ { - pair := key.NewKeyPair(suite) - decs = append(decs, libpurb.Recipient{ + for i := 0; i < numberOfRecipients; i++ { + if len(keypair) < numberOfRecipients { + keypair = append(keypair, *key.NewKeyPair(suite)) + } + + r = append(r, libpurb.Recipient{ SuiteName: suite.String(), Suite: suite, - PublicKey: pair.Public, - PrivateKey: pair.Private, + PublicKey: keypair[i].Public, + PrivateKey: keypair[i].Private, }) } } - return decs + return r } diff --git a/store/kv/db.go b/store/kv/db.go index 99fdef5..dfac17f 100644 --- a/store/kv/db.go +++ b/store/kv/db.go @@ -3,10 +3,13 @@ package kv import ( "bytes" "encoding/gob" - "fmt" + "errors" + "io" "os" "sync" + "go.dedis.ch/kyber/v3/util/key" + "go.dedis.ch/libpurb/libpurb" "golang.org/x/xerrors" ) @@ -27,47 +30,70 @@ func newBucketDb() bucketDb { // // - implements kv.DB type purbDB struct { - dbFilePath string - db bucketDb - purbIsOn bool - blob *Blob + dbFile string + bucketDb bucketDb + blob *libpurb.Purb + purbIsOn bool } // NewDB opens a new database to the given file. -func NewDB(path string, purbIsOn bool) (DB, error) { +func NewDB(path string, purbIsOn bool) (DB, []key.Pair, error) { f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0755) if err != nil { - return nil, xerrors.Errorf("failed to open DB file: %v", err) + return nil, nil, xerrors.Errorf("failed to open DB file: %v", err) } defer f.Close() stats, _ := f.Stat() s := stats.Size() + if s > 0 { + return nil, nil, xerrors.New("failed to create DB file: file already exists") + } + var data = make([]byte, s) l, err := f.Read(data) - if int64(l) < s || err != nil { - return nil, xerrors.Errorf("failed to read DB file: %v", err) + if int64(l) != 0 || err != nil { + return nil, nil, xerrors.Errorf("failed to read DB file: %v", err) + } + + var b *libpurb.Purb = nil + keypair := make([]key.Pair, 0) + if purbIsOn { + b = NewBlob(nil) + pair := key.Pair{ + Public: b.Recipients[0].PublicKey, + Private: b.Recipients[0].PrivateKey, + } + keypair = append(keypair, pair) } p := &purbDB{ - dbFilePath: path, - db: newBucketDb(), - purbIsOn: purbIsOn, - blob: NewBlob(), + dbFile: path, + bucketDb: newBucketDb(), + purbIsOn: purbIsOn, + blob: b, } - buffer := bytes.NewBuffer(data) + return p, keypair, nil +} + +// LoadDB opens a database from a given file. +func LoadDB(path string, purbIsOn bool, keypair []key.Pair) (DB, error) { + var b *libpurb.Purb = nil if purbIsOn { - decrypted, err := p.blob.Decode(data) - if err != nil { - return nil, err - } - buffer.Write(decrypted) + b = NewBlob(keypair) } - err = p.deserialize(buffer) - if fmt.Sprint(err) != "EOF" { - return nil, xerrors.Errorf("failed to initialize new DB file: %v", err) + p := &purbDB{ + dbFile: path, + bucketDb: newBucketDb(), + purbIsOn: purbIsOn, + blob: b, + } + + err := p.load() + if err != nil { + return nil, err } return p, nil @@ -76,7 +102,7 @@ func NewDB(path string, purbIsOn bool) (DB, error) { // View implements kv.DB. It executes the read-only transaction in the context // of the database. func (p *purbDB) View(fn func(ReadableTx) error) error { - tx := &dpTx{db: p.db, new: newBucketDb()} + tx := &dpTx{db: p.bucketDb, new: newBucketDb()} err := fn(tx) @@ -90,21 +116,20 @@ func (p *purbDB) View(fn func(ReadableTx) error) error { // Update implements kv.DB. It executes the writable transaction in the context // of the database. func (p *purbDB) Update(fn func(WritableTx) error) error { - tx := &dpTx{db: p.db, new: newBucketDb()} + tx := &dpTx{db: p.bucketDb, new: newBucketDb()} err := fn(tx) if err != nil { return err } - p.db.Lock() + p.bucketDb.Lock() for k, v := range tx.new.Db { - p.db.Db[k] = v + p.bucketDb.Db[k] = v } + p.bucketDb.Unlock() - p.db.Unlock() - - err = p.savePurbified() + err = p.save() if err != nil { return err } @@ -125,46 +150,67 @@ func (p *purbDB) Close() error { // --------------------------------------------------------------------------- // helper functions -func (p *purbDB) serialize() (bytes.Buffer, error) { +func (p *purbDB) serialize() (*bytes.Buffer, error) { var data bytes.Buffer encoder := gob.NewEncoder(&data) - p.db.RLock() - defer p.db.RUnlock() - err := encoder.Encode(p.db) - return data, err + p.bucketDb.RLock() + defer p.bucketDb.RUnlock() + err := encoder.Encode(p.bucketDb.Db) + return &data, err } func (p *purbDB) deserialize(input *bytes.Buffer) error { decoder := gob.NewDecoder(input) - err := decoder.Decode(&p.db) + err := decoder.Decode(&p.bucketDb.Db) - for _, x := range p.db.Db { + for _, x := range p.bucketDb.Db { x.updateIndex() } return err } -func (p *purbDB) savePurbified() error { +func (p *purbDB) save() error { data, err := p.serialize() if err != nil { return xerrors.Errorf("failed to serialize DB file: %v", err) } if p.purbIsOn { - blob, err := p.blob.Encode(data.Bytes()) + blob, err := Encode(p.blob, data.Bytes()) if err != nil { return xerrors.Errorf("failed to purbify DB file: %v", err) } - data.Write(blob) + data = bytes.NewBuffer(blob) } - err = os.WriteFile(p.dbFilePath, data.Bytes(), 0755) + err = os.WriteFile(p.dbFile, data.Bytes(), 0755) if err != nil { return xerrors.Errorf("failed to save DB file: %v", err) } return nil } + +func (p *purbDB) load() error { + data, err := os.ReadFile(p.dbFile) + if err != nil { + return xerrors.Errorf("failed to load DB from file: %v", err) + } + + if p.purbIsOn && len(data) > 0 { + data, err = Decode(p.blob, data) + if err != nil { + return xerrors.Errorf("failed to decode purbified DB file: %v", err) + } + } + + buffer := bytes.NewBuffer(data) + err = p.deserialize(buffer) + if err != nil && errors.Is(err, io.EOF) { + return nil + } + return err +} diff --git a/store/kv/db_test.go b/store/kv/db_test.go index 4b1de3c..715a0eb 100644 --- a/store/kv/db_test.go +++ b/store/kv/db_test.go @@ -10,28 +10,46 @@ import ( "golang.org/x/xerrors" ) -const purbDbTestDir = "purb-Db-kv" +const dbTestDir = "db-kv" -func TestPurbDB_OpenClose(t *testing.T) { - dir, err := os.MkdirTemp(os.TempDir(), purbDbTestDir) +func TestDb_OpenClose(t *testing.T) { + dir, err := os.MkdirTemp(os.TempDir(), dbTestDir) + require.NoError(t, err) + defer os.RemoveAll(dir) + + db, _, err := NewDB(filepath.Join(dir, "test.Db"), false) + require.NoError(t, err) + + err = db.Close() require.NoError(t, err) +} +func TestDb_OpenCloseReopen(t *testing.T) { + dir, err := os.MkdirTemp(os.TempDir(), dbTestDir) + require.NoError(t, err) defer os.RemoveAll(dir) - db, err := NewDB(filepath.Join(dir, "test.Db"), false) + db, _, err := NewDB(filepath.Join(dir, "test.Db"), false) + require.NoError(t, err) + + err = db.Close() + require.NoError(t, err) + + //reopen + db, err = LoadDB(filepath.Join(dir, "test.Db"), false, nil) require.NoError(t, err) err = db.Close() require.NoError(t, err) } -func TestPurbDB_UpdateAndView(t *testing.T) { - dir, err := os.MkdirTemp(os.TempDir(), purbDbTestDir) +func TestDb_UpdateAndView(t *testing.T) { + dir, err := os.MkdirTemp(os.TempDir(), dbTestDir) require.NoError(t, err) defer os.RemoveAll(dir) - db, err := NewDB(filepath.Join(dir, "test.Db"), false) + db, _, err := NewDB(filepath.Join(dir, "test.Db"), false) require.NoError(t, err) ch := make(chan struct{}) @@ -63,13 +81,13 @@ func TestPurbDB_UpdateAndView(t *testing.T) { require.NoError(t, err) } -func TestPurbTx_GetBucket(t *testing.T) { - dir, err := os.MkdirTemp(os.TempDir(), purbDbTestDir) +func TestDb_GetBucket(t *testing.T) { + dir, err := os.MkdirTemp(os.TempDir(), dbTestDir) require.NoError(t, err) defer os.RemoveAll(dir) - db, err := NewDB(filepath.Join(dir, "test.Db"), false) + db, _, err := NewDB(filepath.Join(dir, "test.Db"), false) require.NoError(t, err) err = db.Update(func(tx WritableTx) error { @@ -87,13 +105,13 @@ func TestPurbTx_GetBucket(t *testing.T) { require.NoError(t, err) } -func TestPurbBucket_Get_Set_Delete(t *testing.T) { - dir, err := os.MkdirTemp(os.TempDir(), purbDbTestDir) +func TestDb_GetSetDelete(t *testing.T) { + dir, err := os.MkdirTemp(os.TempDir(), dbTestDir) require.NoError(t, err) defer os.RemoveAll(dir) - db, err := NewDB(filepath.Join(dir, "test.Db"), false) + db, _, err := NewDB(filepath.Join(dir, "test.Db"), false) require.NoError(t, err) err = db.Update(func(txn WritableTx) error { @@ -119,13 +137,57 @@ func TestPurbBucket_Get_Set_Delete(t *testing.T) { require.NoError(t, err) } -func TestPurbBucket_ForEach(t *testing.T) { +func TestDb_SetReopenGet(t *testing.T) { dir, err := os.MkdirTemp(os.TempDir(), purbDbTestDir) require.NoError(t, err) defer os.RemoveAll(dir) - db, err := NewDB(filepath.Join(dir, "test.Db"), false) + db, keypair, err := NewDB(filepath.Join(dir, "test.Db"), false) + require.NoError(t, err) + + err = db.Update(func(txn WritableTx) error { + b, err := txn.GetBucketOrCreate([]byte("bucket")) + require.NoError(t, err) + + err = b.Set([]byte("ping"), []byte("pong")) + require.NoError(t, err) + + return nil + }) + require.NoError(t, err) + + err = db.Close() + require.NoError(t, err) + + //reopen + db, err = LoadDB(filepath.Join(dir, "test.Db"), false, keypair) + require.NoError(t, err) + + err = db.Update(func(txn WritableTx) error { + b := txn.GetBucket([]byte("bucket")) + require.NotNil(t, b) + + value := b.Get([]byte("ping")) + require.Equal(t, []byte("pong"), value) + + return nil + }) + require.NoError(t, err) + + err = db.Close() + require.NoError(t, err) + + require.NoError(t, err) +} + +func TestDb_ForEach(t *testing.T) { + dir, err := os.MkdirTemp(os.TempDir(), dbTestDir) + require.NoError(t, err) + + defer os.RemoveAll(dir) + + db, _, err := NewDB(filepath.Join(dir, "test.Db"), false) require.NoError(t, err) err = db.Update(func(txn WritableTx) error { @@ -147,13 +209,13 @@ func TestPurbBucket_ForEach(t *testing.T) { require.NoError(t, err) } -func TestPurbBucket_AbortedForEach(t *testing.T) { - dir, err := os.MkdirTemp(os.TempDir(), purbDbTestDir) +func TestDb_ForEachAborted(t *testing.T) { + dir, err := os.MkdirTemp(os.TempDir(), dbTestDir) require.NoError(t, err) defer os.RemoveAll(dir) - db, err := NewDB(filepath.Join(dir, "test.Db"), false) + db, _, err := NewDB(filepath.Join(dir, "test.Db"), false) require.NoError(t, err) // set some values in the DB @@ -197,13 +259,13 @@ func TestPurbBucket_AbortedForEach(t *testing.T) { require.NoError(t, err) } -func TestPurbBucket_ReOpenClosedDb(t *testing.T) { - dir, err := os.MkdirTemp(os.TempDir(), purbDbTestDir) +func TestDb_ReOpenClosedDb(t *testing.T) { + dir, err := os.MkdirTemp(os.TempDir(), dbTestDir) require.NoError(t, err) defer os.RemoveAll(dir) - db, err := NewDB(filepath.Join(dir, "test.Db"), false) + db, _, err := NewDB(filepath.Join(dir, "test.Db"), false) require.NoError(t, err) // set some values in the DB @@ -223,7 +285,7 @@ func TestPurbBucket_ReOpenClosedDb(t *testing.T) { require.NoError(t, err) // re-open DB file - newdb, err := NewDB(filepath.Join(dir, "test.Db"), false) + newdb, err := LoadDB(filepath.Join(dir, "test.Db"), false, nil) require.NoError(t, err) // checks that the DB values are still ok @@ -245,13 +307,13 @@ func TestPurbBucket_ReOpenClosedDb(t *testing.T) { require.NoError(t, err) } -func TestPurbBucket_Scan(t *testing.T) { - dir, err := os.MkdirTemp(os.TempDir(), purbDbTestDir) +func TestDb_Scan(t *testing.T) { + dir, err := os.MkdirTemp(os.TempDir(), dbTestDir) require.NoError(t, err) defer os.RemoveAll(dir) - db, err := NewDB(filepath.Join(dir, "test.Db"), false) + db, _, err := NewDB(filepath.Join(dir, "test.Db"), false) require.NoError(t, err) err = db.Update(func(txn WritableTx) error { diff --git a/store/kv/example_test.go b/store/kv/example_test.go index 8935fad..fb3354e 100644 --- a/store/kv/example_test.go +++ b/store/kv/example_test.go @@ -14,7 +14,7 @@ func ExampleBucket_Scan() { defer os.RemoveAll(dir) - db, err := NewDB(filepath.Join(dir, "example.Db"), false) + db, _, err := NewDB(filepath.Join(dir, "example.Db"), false) if err != nil { panic("failed to open Db: " + err.Error()) } diff --git a/store/kv/purbdb_test.go b/store/kv/purbdb_test.go new file mode 100644 index 0000000..37ece42 --- /dev/null +++ b/store/kv/purbdb_test.go @@ -0,0 +1,349 @@ +package kv + +import ( + "os" + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/require" + "golang.org/x/xerrors" +) + +const purbDbTestDir = "purb-db-kv" + +func TestPurbDb_OpenClose(t *testing.T) { + dir, err := os.MkdirTemp(os.TempDir(), purbDbTestDir) + require.NoError(t, err) + defer os.RemoveAll(dir) + + db, _, err := NewDB(filepath.Join(dir, "test.Db"), true) + require.NoError(t, err) + + err = db.Close() + require.NoError(t, err) +} + +func TestPurbDb_OpenCloseReopen(t *testing.T) { + dir, err := os.MkdirTemp(os.TempDir(), purbDbTestDir) + require.NoError(t, err) + defer os.RemoveAll(dir) + + db, keypair, err := NewDB(filepath.Join(dir, "test.Db"), true) + require.NoError(t, err) + + err = db.Close() + require.NoError(t, err) + + //reopen + db, err = LoadDB(filepath.Join(dir, "test.Db"), true, keypair) + require.NoError(t, err) + + err = db.Close() + require.NoError(t, err) +} + +func TestPurbDb_UpdateAndView(t *testing.T) { + dir, err := os.MkdirTemp(os.TempDir(), purbDbTestDir) + require.NoError(t, err) + + defer os.RemoveAll(dir) + + db, _, err := NewDB(filepath.Join(dir, "test.Db"), true) + require.NoError(t, err) + + ch := make(chan struct{}) + err = db.Update(func(txn WritableTx) error { + txn.OnCommit(func() { close(ch) }) + + bucket, err := txn.GetBucketOrCreate([]byte("bucket")) + require.NoError(t, err) + + return bucket.Set([]byte("ping"), []byte("pong")) + }) + require.NoError(t, err) + + select { + case <-ch: + case <-time.After(time.Second): + t.Fatal("timeout") + } + + err = db.View(func(txn ReadableTx) error { + bucket := txn.GetBucket([]byte("bucket")) + require.NotNil(t, bucket) + + value := bucket.Get([]byte("ping")) + require.Equal(t, []byte("pong"), value) + + return nil + }) + require.NoError(t, err) +} + +func TestPurbDb_GetBucket(t *testing.T) { + dir, err := os.MkdirTemp(os.TempDir(), purbDbTestDir) + require.NoError(t, err) + + defer os.RemoveAll(dir) + + db, _, err := NewDB(filepath.Join(dir, "test.Db"), true) + require.NoError(t, err) + + err = db.Update(func(tx WritableTx) error { + require.Nil(t, tx.GetBucket([]byte("unknown"))) + + _, err := tx.GetBucketOrCreate([]byte("A")) + require.NoError(t, err) + require.NotNil(t, tx.GetBucket([]byte("A"))) + + _, err = tx.GetBucketOrCreate(nil) + require.EqualError(t, err, "create bucket failed: bucket name required") + + return nil + }) + require.NoError(t, err) +} + +func TestPurbDb_GetSetDelete(t *testing.T) { + dir, err := os.MkdirTemp(os.TempDir(), purbDbTestDir) + require.NoError(t, err) + + defer os.RemoveAll(dir) + + db, _, err := NewDB(filepath.Join(dir, "test.Db"), true) + require.NoError(t, err) + + err = db.Update(func(txn WritableTx) error { + b, err := txn.GetBucketOrCreate([]byte("bucket")) + require.NoError(t, err) + + require.NoError(t, b.Set([]byte("ping"), []byte("pong"))) + + value := b.Get([]byte("ping")) + require.Equal(t, []byte("pong"), value) + + value = b.Get([]byte("pong")) + require.Nil(t, value) + + require.NoError(t, b.Delete([]byte("ping"))) + + value = b.Get([]byte("ping")) + require.Nil(t, value) + + return nil + }) + + require.NoError(t, err) +} + +func TestPurbDb_SetReopenGet(t *testing.T) { + dir, err := os.MkdirTemp(os.TempDir(), purbDbTestDir) + require.NoError(t, err) + + defer os.RemoveAll(dir) + + db, keypair, err := NewDB(filepath.Join(dir, "test.Db"), true) + require.NoError(t, err) + + err = db.Update(func(txn WritableTx) error { + b, err := txn.GetBucketOrCreate([]byte("bucket")) + require.NoError(t, err) + + err = b.Set([]byte("ping"), []byte("pong")) + require.NoError(t, err) + + return nil + }) + require.NoError(t, err) + + err = db.Close() + require.NoError(t, err) + + //reopen + db, err = LoadDB(filepath.Join(dir, "test.Db"), true, keypair) + require.NoError(t, err) + + err = db.Update(func(txn WritableTx) error { + b := txn.GetBucket([]byte("bucket")) + require.NotNil(t, b) + + value := b.Get([]byte("ping")) + require.Equal(t, []byte("pong"), value) + + return nil + }) + require.NoError(t, err) + + err = db.Close() + require.NoError(t, err) + + require.NoError(t, err) +} + +func TestPurbDb_ForEach(t *testing.T) { + dir, err := os.MkdirTemp(os.TempDir(), purbDbTestDir) + require.NoError(t, err) + + defer os.RemoveAll(dir) + + db, _, err := NewDB(filepath.Join(dir, "test.Db"), true) + require.NoError(t, err) + + err = db.Update(func(txn WritableTx) error { + b, err := txn.GetBucketOrCreate([]byte("test")) + require.NoError(t, err) + + require.NoError(t, b.Set([]byte{2}, []byte{2})) + require.NoError(t, b.Set([]byte{1}, []byte{1})) + require.NoError(t, b.Set([]byte{0}, []byte{0})) + + var i byte = 0 + return b.ForEach(func(k, v []byte) error { + require.Equal(t, []byte{i}, k) + require.Equal(t, []byte{i}, v) + i++ + return nil + }) + }) + require.NoError(t, err) +} + +func TestPurbDb_ForEachAborted(t *testing.T) { + dir, err := os.MkdirTemp(os.TempDir(), purbDbTestDir) + require.NoError(t, err) + + defer os.RemoveAll(dir) + + db, _, err := NewDB(filepath.Join(dir, "test.Db"), true) + require.NoError(t, err) + + // set some values in the DB + err = db.Update(func(txn WritableTx) error { + b, err := txn.GetBucketOrCreate([]byte("test")) + require.NoError(t, err) + + require.NoError(t, b.Set([]byte{2}, []byte{2})) + require.NoError(t, b.Set([]byte{1}, []byte{1})) + require.NoError(t, b.Set([]byte{0}, []byte{0})) + + return nil + }) + require.NoError(t, err) + + // try to alter the DB with an interrupted transaction + err = db.Update(func(txn WritableTx) error { + b, err := txn.GetBucketOrCreate([]byte("test")) + require.NoError(t, err) + + err = b.Set([]byte{0}, []byte{7}) + require.NoError(t, err) + + return xerrors.New("testing error") + }) + require.Error(t, err) + + // checks that the DB values are still ok + err = db.Update(func(txn WritableTx) error { + b, err := txn.GetBucketOrCreate([]byte("test")) + require.NoError(t, err) + + var i byte = 0 + return b.ForEach(func(k, v []byte) error { + require.Equal(t, []byte{i}, k) + require.Equal(t, []byte{i}, v) + i++ + return nil + }) + }) + require.NoError(t, err) +} + +func TestPurbDb_ReOpenClosedDb(t *testing.T) { + dir, err := os.MkdirTemp(os.TempDir(), purbDbTestDir) + require.NoError(t, err) + + defer os.RemoveAll(dir) + + db, keypair, err := NewDB(filepath.Join(dir, "test.Db"), true) + require.NoError(t, err) + + // set some values in the DB + err = db.Update(func(txn WritableTx) error { + b, err := txn.GetBucketOrCreate([]byte("test")) + require.NoError(t, err) + + require.NoError(t, b.Set([]byte{2}, []byte{2})) + require.NoError(t, b.Set([]byte{1}, []byte{1})) + require.NoError(t, b.Set([]byte{0}, []byte{0})) + + return nil + }) + require.NoError(t, err) + + err = db.Close() + require.NoError(t, err) + + // re-open DB file + newdb, err := LoadDB(filepath.Join(dir, "test.Db"), true, keypair) + require.NoError(t, err) + + // checks that the DB values are still ok + err = newdb.Update(func(txn WritableTx) error { + b, err := txn.GetBucketOrCreate([]byte("test")) + require.NoError(t, err) + + var i byte = 0 + return b.ForEach(func(k, v []byte) error { + require.Equal(t, []byte{i}, k) + require.Equal(t, []byte{i}, v) + i++ + return nil + }) + }) + require.NoError(t, err) + + err = db.Close() + require.NoError(t, err) +} + +func TestPurbDb_Scan(t *testing.T) { + dir, err := os.MkdirTemp(os.TempDir(), purbDbTestDir) + require.NoError(t, err) + + defer os.RemoveAll(dir) + + db, _, err := NewDB(filepath.Join(dir, "test.Db"), true) + require.NoError(t, err) + + err = db.Update(func(txn WritableTx) error { + b, err := txn.GetBucketOrCreate([]byte("bucket")) + require.NoError(t, err) + + require.NoError(t, b.Set([]byte{7}, []byte{7})) + require.NoError(t, b.Set([]byte{0}, []byte{0})) + + var i byte = 0 + err = b.Scan(nil, func(k, v []byte) error { + require.Equal(t, []byte{i}, k) + require.Equal(t, []byte{i}, v) + i += 7 + return nil + }) + require.NoError(t, err) + require.Equal(t, byte(14), i) + + err = b.Scan([]byte{1}, func(k, v []byte) error { + return xerrors.New("callback error") + }) + require.NoError(t, err) + + err = b.Scan([]byte{}, func(k, v []byte) error { + return xerrors.New("callback error") + }) + require.ErrorContains(t, err, "callback error") + + return nil + }) + require.NoError(t, err) +}