2020-03-06 08:17:33 +00:00
|
|
|
package repository
|
|
|
|
|
|
|
|
import (
|
|
|
|
"math/rand"
|
2020-03-10 14:56:08 +00:00
|
|
|
"sort"
|
2024-02-03 17:13:34 +00:00
|
|
|
"strings"
|
2020-03-06 08:17:33 +00:00
|
|
|
"testing"
|
|
|
|
|
2024-02-03 17:13:34 +00:00
|
|
|
"github.com/restic/restic/internal/crypto"
|
2020-03-06 08:17:33 +00:00
|
|
|
"github.com/restic/restic/internal/restic"
|
|
|
|
rtest "github.com/restic/restic/internal/test"
|
|
|
|
)
|
|
|
|
|
2020-03-10 14:56:08 +00:00
|
|
|
type mapcache map[restic.Handle]bool
|
2020-03-06 08:17:33 +00:00
|
|
|
|
2020-03-10 14:56:08 +00:00
|
|
|
func (c mapcache) Has(h restic.Handle) bool { return c[h] }
|
2020-03-06 08:17:33 +00:00
|
|
|
|
|
|
|
func TestSortCachedPacksFirst(t *testing.T) {
|
|
|
|
var (
|
2020-03-10 14:56:08 +00:00
|
|
|
blobs, sorted [100]restic.PackedBlob
|
|
|
|
|
|
|
|
cache = make(mapcache)
|
|
|
|
r = rand.New(rand.NewSource(1261))
|
2020-03-06 08:17:33 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
for i := 0; i < len(blobs); i++ {
|
|
|
|
var id restic.ID
|
|
|
|
r.Read(id[:])
|
|
|
|
blobs[i] = restic.PackedBlob{PackID: id}
|
|
|
|
|
|
|
|
if i%3 == 0 {
|
2020-08-16 09:16:38 +00:00
|
|
|
h := restic.Handle{Name: id.String(), Type: restic.PackFile}
|
2020-03-10 14:56:08 +00:00
|
|
|
cache[h] = true
|
2020-03-06 08:17:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-10 14:56:08 +00:00
|
|
|
copy(sorted[:], blobs[:])
|
|
|
|
sort.SliceStable(sorted[:], func(i, j int) bool {
|
2020-08-16 09:16:38 +00:00
|
|
|
hi := restic.Handle{Type: restic.PackFile, Name: sorted[i].PackID.String()}
|
|
|
|
hj := restic.Handle{Type: restic.PackFile, Name: sorted[j].PackID.String()}
|
2020-03-10 14:56:08 +00:00
|
|
|
return cache.Has(hi) && !cache.Has(hj)
|
|
|
|
})
|
2020-03-06 08:17:33 +00:00
|
|
|
|
2020-03-10 14:56:08 +00:00
|
|
|
sortCachedPacksFirst(cache, blobs[:])
|
|
|
|
rtest.Equals(t, sorted, blobs)
|
2020-03-06 08:17:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func BenchmarkSortCachedPacksFirst(b *testing.B) {
|
|
|
|
const nblobs = 512 // Corresponds to a file of ca. 2GB.
|
|
|
|
|
|
|
|
var (
|
|
|
|
blobs [nblobs]restic.PackedBlob
|
|
|
|
cache = make(mapcache)
|
|
|
|
r = rand.New(rand.NewSource(1261))
|
|
|
|
)
|
|
|
|
|
|
|
|
for i := 0; i < nblobs; i++ {
|
|
|
|
var id restic.ID
|
|
|
|
r.Read(id[:])
|
|
|
|
blobs[i] = restic.PackedBlob{PackID: id}
|
|
|
|
|
|
|
|
if i%3 == 0 {
|
2020-08-16 09:16:38 +00:00
|
|
|
h := restic.Handle{Name: id.String(), Type: restic.PackFile}
|
2020-03-10 14:56:08 +00:00
|
|
|
cache[h] = true
|
2020-03-06 08:17:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var cpy [nblobs]restic.PackedBlob
|
|
|
|
b.ReportAllocs()
|
|
|
|
b.ResetTimer()
|
|
|
|
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
copy(cpy[:], blobs[:])
|
|
|
|
sortCachedPacksFirst(cache, cpy[:])
|
|
|
|
}
|
|
|
|
}
|
2024-02-03 17:13:34 +00:00
|
|
|
|
|
|
|
func TestBlobVerification(t *testing.T) {
|
|
|
|
repo := TestRepository(t).(*Repository)
|
|
|
|
|
|
|
|
type DamageType string
|
|
|
|
const (
|
|
|
|
damageData DamageType = "data"
|
|
|
|
damageCompressed DamageType = "compressed"
|
|
|
|
damageCiphertext DamageType = "ciphertext"
|
|
|
|
)
|
|
|
|
|
|
|
|
for _, test := range []struct {
|
|
|
|
damage DamageType
|
|
|
|
msg string
|
|
|
|
}{
|
|
|
|
{"", ""},
|
|
|
|
{damageData, "hash mismatch"},
|
|
|
|
{damageCompressed, "decompression failed"},
|
|
|
|
{damageCiphertext, "ciphertext verification failed"},
|
|
|
|
} {
|
|
|
|
plaintext := rtest.Random(800, 1234)
|
|
|
|
id := restic.Hash(plaintext)
|
|
|
|
if test.damage == damageData {
|
|
|
|
plaintext[42] ^= 0x42
|
|
|
|
}
|
|
|
|
|
|
|
|
uncompressedLength := uint(len(plaintext))
|
|
|
|
plaintext = repo.getZstdEncoder().EncodeAll(plaintext, nil)
|
|
|
|
|
|
|
|
if test.damage == damageCompressed {
|
|
|
|
plaintext = plaintext[:len(plaintext)-8]
|
|
|
|
}
|
|
|
|
|
|
|
|
nonce := crypto.NewRandomNonce()
|
|
|
|
ciphertext := append([]byte{}, nonce...)
|
|
|
|
ciphertext = repo.Key().Seal(ciphertext, nonce, plaintext, nil)
|
|
|
|
|
|
|
|
if test.damage == damageCiphertext {
|
|
|
|
ciphertext[42] ^= 0x42
|
|
|
|
}
|
|
|
|
|
|
|
|
err := repo.verifyCiphertext(ciphertext, int(uncompressedLength), id)
|
|
|
|
if test.msg == "" {
|
|
|
|
rtest.Assert(t, err == nil, "expected no error, got %v", err)
|
|
|
|
} else {
|
|
|
|
rtest.Assert(t, strings.Contains(err.Error(), test.msg), "expected error to contain %q, got %q", test.msg, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestUnpackedVerification(t *testing.T) {
|
|
|
|
repo := TestRepository(t).(*Repository)
|
|
|
|
|
|
|
|
type DamageType string
|
|
|
|
const (
|
|
|
|
damageData DamageType = "data"
|
|
|
|
damageCompressed DamageType = "compressed"
|
|
|
|
damageCiphertext DamageType = "ciphertext"
|
|
|
|
)
|
|
|
|
|
|
|
|
for _, test := range []struct {
|
|
|
|
damage DamageType
|
|
|
|
msg string
|
|
|
|
}{
|
|
|
|
{"", ""},
|
|
|
|
{damageData, "data mismatch"},
|
|
|
|
{damageCompressed, "decompression failed"},
|
|
|
|
{damageCiphertext, "ciphertext verification failed"},
|
|
|
|
} {
|
|
|
|
plaintext := rtest.Random(800, 1234)
|
|
|
|
orig := append([]byte{}, plaintext...)
|
|
|
|
if test.damage == damageData {
|
|
|
|
plaintext[42] ^= 0x42
|
|
|
|
}
|
|
|
|
|
|
|
|
compressed := []byte{2}
|
|
|
|
compressed = repo.getZstdEncoder().EncodeAll(plaintext, compressed)
|
|
|
|
|
|
|
|
if test.damage == damageCompressed {
|
|
|
|
compressed = compressed[:len(compressed)-8]
|
|
|
|
}
|
|
|
|
|
|
|
|
nonce := crypto.NewRandomNonce()
|
|
|
|
ciphertext := append([]byte{}, nonce...)
|
|
|
|
ciphertext = repo.Key().Seal(ciphertext, nonce, compressed, nil)
|
|
|
|
|
|
|
|
if test.damage == damageCiphertext {
|
|
|
|
ciphertext[42] ^= 0x42
|
|
|
|
}
|
|
|
|
|
|
|
|
err := repo.verifyUnpacked(ciphertext, restic.IndexFile, orig)
|
|
|
|
if test.msg == "" {
|
|
|
|
rtest.Assert(t, err == nil, "expected no error, got %v", err)
|
|
|
|
} else {
|
|
|
|
rtest.Assert(t, strings.Contains(err.Error(), test.msg), "expected error to contain %q, got %q", test.msg, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|