forked from TrueCloudLab/restic
237 lines
6.6 KiB
Go
237 lines
6.6 KiB
Go
package pack
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"io"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/restic/restic/internal/crypto"
|
|
"github.com/restic/restic/internal/restic"
|
|
rtest "github.com/restic/restic/internal/test"
|
|
)
|
|
|
|
func TestParseHeaderEntry(t *testing.T) {
|
|
h := headerEntry{
|
|
Type: 0, // Blob
|
|
Length: 100,
|
|
}
|
|
for i := range h.ID {
|
|
h.ID[i] = byte(i)
|
|
}
|
|
|
|
buf := new(bytes.Buffer)
|
|
_ = binary.Write(buf, binary.LittleEndian, &h)
|
|
|
|
b, size, err := parseHeaderEntry(buf.Bytes())
|
|
rtest.OK(t, err)
|
|
rtest.Equals(t, restic.DataBlob, b.Type)
|
|
rtest.Equals(t, plainEntrySize, size)
|
|
t.Logf("%v %v", h.ID, b.ID)
|
|
rtest.Equals(t, h.ID[:], b.ID[:])
|
|
rtest.Equals(t, uint(h.Length), b.Length)
|
|
rtest.Equals(t, uint(0), b.UncompressedLength)
|
|
|
|
c := compressedHeaderEntry{
|
|
Type: 2, // compressed Blob
|
|
Length: 100,
|
|
UncompressedLength: 200,
|
|
}
|
|
for i := range c.ID {
|
|
c.ID[i] = byte(i)
|
|
}
|
|
|
|
buf = new(bytes.Buffer)
|
|
_ = binary.Write(buf, binary.LittleEndian, &c)
|
|
|
|
b, size, err = parseHeaderEntry(buf.Bytes())
|
|
rtest.OK(t, err)
|
|
rtest.Equals(t, restic.DataBlob, b.Type)
|
|
rtest.Equals(t, entrySize, size)
|
|
t.Logf("%v %v", c.ID, b.ID)
|
|
rtest.Equals(t, c.ID[:], b.ID[:])
|
|
rtest.Equals(t, uint(c.Length), b.Length)
|
|
rtest.Equals(t, uint(c.UncompressedLength), b.UncompressedLength)
|
|
}
|
|
|
|
func TestParseHeaderEntryErrors(t *testing.T) {
|
|
h := headerEntry{
|
|
Type: 0, // Blob
|
|
Length: 100,
|
|
}
|
|
for i := range h.ID {
|
|
h.ID[i] = byte(i)
|
|
}
|
|
|
|
h.Type = 0xae
|
|
buf := new(bytes.Buffer)
|
|
_ = binary.Write(buf, binary.LittleEndian, &h)
|
|
|
|
_, _, err := parseHeaderEntry(buf.Bytes())
|
|
rtest.Assert(t, err != nil, "no error for invalid type")
|
|
|
|
h.Type = 0
|
|
buf.Reset()
|
|
_ = binary.Write(buf, binary.LittleEndian, &h)
|
|
|
|
_, _, err = parseHeaderEntry(buf.Bytes()[:plainEntrySize-1])
|
|
rtest.Assert(t, err != nil, "no error for short input")
|
|
}
|
|
|
|
type countingReaderAt struct {
|
|
delegate io.ReaderAt
|
|
invocationCount int
|
|
}
|
|
|
|
func (rd *countingReaderAt) ReadAt(p []byte, off int64) (n int, err error) {
|
|
rd.invocationCount++
|
|
return rd.delegate.ReadAt(p, off)
|
|
}
|
|
|
|
func TestReadHeaderEagerLoad(t *testing.T) {
|
|
|
|
testReadHeader := func(dataSize, entryCount, expectedReadInvocationCount int) {
|
|
expectedHeader := rtest.Random(0, entryCount*int(entrySize)+crypto.Extension)
|
|
|
|
buf := &bytes.Buffer{}
|
|
buf.Write(rtest.Random(0, dataSize)) // pack blobs data
|
|
buf.Write(expectedHeader) // pack header
|
|
rtest.OK(t, binary.Write(buf, binary.LittleEndian, uint32(len(expectedHeader)))) // pack header length
|
|
|
|
rd := &countingReaderAt{delegate: bytes.NewReader(buf.Bytes())}
|
|
|
|
header, err := readHeader(rd, int64(buf.Len()))
|
|
rtest.OK(t, err)
|
|
|
|
rtest.Equals(t, expectedHeader, header)
|
|
rtest.Equals(t, expectedReadInvocationCount, rd.invocationCount)
|
|
}
|
|
|
|
// basic
|
|
testReadHeader(100, 1, 1)
|
|
|
|
// header entries == eager entries
|
|
testReadHeader(100, eagerEntries-1, 1)
|
|
testReadHeader(100, eagerEntries, 1)
|
|
testReadHeader(100, eagerEntries+1, 2)
|
|
|
|
// file size == eager header load size
|
|
eagerLoadSize := int((eagerEntries * entrySize) + crypto.Extension)
|
|
headerSize := int(1*entrySize) + crypto.Extension
|
|
dataSize := eagerLoadSize - headerSize - binary.Size(uint32(0))
|
|
testReadHeader(dataSize-1, 1, 1)
|
|
testReadHeader(dataSize, 1, 1)
|
|
testReadHeader(dataSize+1, 1, 1)
|
|
testReadHeader(dataSize+2, 1, 1)
|
|
testReadHeader(dataSize+3, 1, 1)
|
|
testReadHeader(dataSize+4, 1, 1)
|
|
}
|
|
|
|
func TestReadRecords(t *testing.T) {
|
|
testReadRecords := func(dataSize, entryCount, totalRecords int) {
|
|
totalHeader := rtest.Random(0, totalRecords*int(entrySize)+crypto.Extension)
|
|
bufSize := entryCount*int(entrySize) + crypto.Extension
|
|
off := len(totalHeader) - bufSize
|
|
if off < 0 {
|
|
off = 0
|
|
}
|
|
expectedHeader := totalHeader[off:]
|
|
|
|
buf := &bytes.Buffer{}
|
|
buf.Write(rtest.Random(0, dataSize)) // pack blobs data
|
|
buf.Write(totalHeader) // pack header
|
|
rtest.OK(t, binary.Write(buf, binary.LittleEndian, uint32(len(totalHeader)))) // pack header length
|
|
|
|
rd := bytes.NewReader(buf.Bytes())
|
|
|
|
header, count, err := readRecords(rd, int64(rd.Len()), bufSize+4)
|
|
rtest.OK(t, err)
|
|
rtest.Equals(t, len(totalHeader)+4, count)
|
|
rtest.Equals(t, expectedHeader, header)
|
|
}
|
|
|
|
// basic
|
|
testReadRecords(100, 1, 1)
|
|
testReadRecords(100, 0, 1)
|
|
testReadRecords(100, 1, 0)
|
|
|
|
// header entries ~ eager entries
|
|
testReadRecords(100, eagerEntries, eagerEntries-1)
|
|
testReadRecords(100, eagerEntries, eagerEntries)
|
|
testReadRecords(100, eagerEntries, eagerEntries+1)
|
|
|
|
// file size == eager header load size
|
|
eagerLoadSize := int((eagerEntries * entrySize) + crypto.Extension)
|
|
headerSize := int(1*entrySize) + crypto.Extension
|
|
dataSize := eagerLoadSize - headerSize - binary.Size(uint32(0))
|
|
testReadRecords(dataSize-1, 1, 1)
|
|
testReadRecords(dataSize, 1, 1)
|
|
testReadRecords(dataSize+1, 1, 1)
|
|
testReadRecords(dataSize+2, 1, 1)
|
|
testReadRecords(dataSize+3, 1, 1)
|
|
testReadRecords(dataSize+4, 1, 1)
|
|
|
|
for i := 0; i < 2; i++ {
|
|
for j := 0; j < 2; j++ {
|
|
testReadRecords(dataSize, i, j)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUnpackedVerification(t *testing.T) {
|
|
// create random keys
|
|
k := crypto.NewRandomKey()
|
|
blobs := []restic.Blob{
|
|
{
|
|
BlobHandle: restic.NewRandomBlobHandle(),
|
|
Length: 42,
|
|
Offset: 0,
|
|
UncompressedLength: 2 * 42,
|
|
},
|
|
}
|
|
|
|
type DamageType string
|
|
const (
|
|
damageData DamageType = "data"
|
|
damageCiphertext DamageType = "ciphertext"
|
|
damageLength DamageType = "length"
|
|
)
|
|
|
|
for _, test := range []struct {
|
|
damage DamageType
|
|
msg string
|
|
}{
|
|
{"", ""},
|
|
{damageData, "pack header entry mismatch"},
|
|
{damageCiphertext, "ciphertext verification failed"},
|
|
{damageLength, "header decoding failed"},
|
|
} {
|
|
header, err := makeHeader(blobs)
|
|
rtest.OK(t, err)
|
|
|
|
if test.damage == damageData {
|
|
header[8] ^= 0x42
|
|
}
|
|
|
|
encryptedHeader := make([]byte, 0, crypto.CiphertextLength(len(header)))
|
|
nonce := crypto.NewRandomNonce()
|
|
encryptedHeader = append(encryptedHeader, nonce...)
|
|
encryptedHeader = k.Seal(encryptedHeader, nonce, header, nil)
|
|
encryptedHeader = binary.LittleEndian.AppendUint32(encryptedHeader, uint32(len(encryptedHeader)))
|
|
|
|
if test.damage == damageCiphertext {
|
|
encryptedHeader[8] ^= 0x42
|
|
}
|
|
if test.damage == damageLength {
|
|
encryptedHeader[len(encryptedHeader)-1] ^= 0x42
|
|
}
|
|
|
|
err = verifyHeader(k, encryptedHeader, blobs)
|
|
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)
|
|
}
|
|
}
|
|
}
|