package packfile_test
import (
"bytes"
"errors"
"math"
"testing"
"lindenii.org/go/furgit/internal/format/packfile"
"lindenii.org/go/furgit/object/id"
)
func TestParseEntryHeaderBase(t *testing.T) {
t.Parallel()
// Commit entry of size 85:
// 0x95 has the continuation bit set, type 1, and size low bits 0x5;
// 0x05 contributes 5<<4.
header, err := packfile.ParseEntryHeader([]byte{0x95, 0x05}, 20)
if err != nil {
t.Fatalf("ParseEntryHeader: %v", err)
}
if header.Type != packfile.EntryTypeCommit {
t.Fatalf("ParseEntryHeader type = %d, want %d", header.Type, packfile.EntryTypeCommit)
}
if header.Size != 85 {
t.Fatalf("ParseEntryHeader size = %d, want 85", header.Size)
}
if header.HeaderLen != 2 {
t.Fatalf("ParseEntryHeader header len = %d, want 2", header.HeaderLen)
}
}
func TestParseEntryHeaderRefDelta(t *testing.T) {
t.Parallel()
for _, objectFormat := range id.SupportedObjectFormats() {
t.Run(objectFormat.String(), func(t *testing.T) {
t.Parallel()
hashSize := objectFormat.Size()
base := bytes.Repeat([]byte{0xab}, hashSize)
data := append([]byte{0x73}, base...)
header, err := packfile.ParseEntryHeader(data, hashSize)
if err != nil {
t.Fatalf("ParseEntryHeader: %v", err)
}
if header.Type != packfile.EntryTypeRefDelta {
t.Fatalf("ParseEntryHeader type = %d, want %d", header.Type, packfile.EntryTypeRefDelta)
}
if header.Size != 3 {
t.Fatalf("ParseEntryHeader size = %d, want 3", header.Size)
}
if header.HeaderLen != 1+hashSize {
t.Fatalf("ParseEntryHeader header len = %d, want %d", header.HeaderLen, 1+hashSize)
}
if !bytes.Equal(header.RefBase[:hashSize], base) {
t.Fatalf("ParseEntryHeader ref base mismatch")
}
})
}
}
func TestParseEntryHeaderOfsDelta(t *testing.T) {
t.Parallel()
header, err := packfile.ParseEntryHeader([]byte{0x65, 0x80, 0x00}, 20)
if err != nil {
t.Fatalf("ParseEntryHeader: %v", err)
}
if header.Type != packfile.EntryTypeOfsDelta {
t.Fatalf("ParseEntryHeader type = %d, want %d", header.Type, packfile.EntryTypeOfsDelta)
}
if header.OfsDistance != 128 {
t.Fatalf("ParseEntryHeader distance = %d, want 128", header.OfsDistance)
}
if header.HeaderLen != 3 {
t.Fatalf("ParseEntryHeader header len = %d, want 3", header.HeaderLen)
}
}
func TestParseEntryHeaderMalformed(t *testing.T) {
t.Parallel()
cases := []struct {
name string
data []byte
}{
{name: "empty", data: []byte{}},
{name: "truncated type/size", data: []byte{0x95}},
{
name: "size overflow",
data: append([]byte{0x9f}, bytes.Repeat([]byte{0xff}, 9)...),
},
{
name: "overlong type/size",
data: append([]byte{0x95}, append(bytes.Repeat([]byte{0x80}, 9), 0x00)...),
},
{name: "truncated ref-delta base", data: []byte{0x73, 0xab, 0xab}},
{name: "truncated ofs distance", data: []byte{0x65, 0x80}},
{name: "type invalid", data: []byte{0x05}},
{name: "type future", data: []byte{0x55}},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
_, err := packfile.ParseEntryHeader(tc.data, 20)
if !errors.Is(err, packfile.ErrMalformedEntryHeader) {
t.Fatalf("ParseEntryHeader error = %v, want ErrMalformedEntryHeader", err)
}
})
}
}
func TestParseEntryHeaderBadHashSize(t *testing.T) {
t.Parallel()
for _, hashSize := range []int{-1, 0, id.MaxObjectIDSize + 1} {
func() {
defer func() {
if recover() == nil {
t.Fatalf("ParseEntryHeader hash size %d: expected panic", hashSize)
}
}()
_, _ = packfile.ParseEntryHeader([]byte{0x95, 0x05}, hashSize)
}()
}
}
func TestTypeSizeRoundTrip(t *testing.T) {
t.Parallel()
baseTypes := []packfile.EntryType{
packfile.EntryTypeCommit,
packfile.EntryTypeTree,
packfile.EntryTypeBlob,
packfile.EntryTypeTag,
}
sizes := []uint64{
0, 1, 15, 16, 127, 128, 1 << 20, 1 << 57, math.MaxUint64,
}
for _, entryType := range baseTypes {
for _, size := range sizes {
data := packfile.AppendTypeSize(nil, entryType, size)
if len(data) > packfile.MaxTypeSizeLen {
t.Fatalf("AppendTypeSize(%d, %d) length = %d, want <= %d",
entryType, size, len(data), packfile.MaxTypeSizeLen)
}
header, err := packfile.ParseEntryHeader(data, 20)
if err != nil {
t.Fatalf("ParseEntryHeader: %v", err)
}
if header.Type != entryType {
t.Fatalf("round trip type = %d, want %d", header.Type, entryType)
}
if header.Size != size {
t.Fatalf("round trip size = %d, want %d", header.Size, size)
}
if header.HeaderLen != len(data) {
t.Fatalf("round trip header len = %d, want %d", header.HeaderLen, len(data))
}
}
}
}
func TestRefDeltaHeaderRoundTrip(t *testing.T) {
t.Parallel()
for _, objectFormat := range id.SupportedObjectFormats() {
t.Run(objectFormat.String(), func(t *testing.T) {
t.Parallel()
hashSize := objectFormat.Size()
base := bytes.Repeat([]byte{0xcd}, hashSize)
data := packfile.AppendTypeSize(nil, packfile.EntryTypeRefDelta, 42)
data = append(data, base...)
header, err := packfile.ParseEntryHeader(data, hashSize)
if err != nil {
t.Fatalf("ParseEntryHeader: %v", err)
}
if !bytes.Equal(header.RefBase[:hashSize], base) {
t.Fatalf("round trip ref base mismatch")
}
if header.HeaderLen != len(data) {
t.Fatalf("round trip header len = %d, want %d", header.HeaderLen, len(data))
}
})
}
}
func TestOfsDeltaHeaderRoundTrip(t *testing.T) {
t.Parallel()
data := packfile.AppendTypeSize(nil, packfile.EntryTypeOfsDelta, 7)
data = packfile.AppendOfsDeltaDistance(data, 123456)
header, err := packfile.ParseEntryHeader(data, 20)
if err != nil {
t.Fatalf("ParseEntryHeader: %v", err)
}
if header.OfsDistance != 123456 {
t.Fatalf("round trip distance = %d, want 123456", header.OfsDistance)
}
if header.HeaderLen != len(data) {
t.Fatalf("round trip header len = %d, want %d", header.HeaderLen, len(data))
}
}
func TestMaxEntryHeaderLenBounds(t *testing.T) {
t.Parallel()
for _, objectFormat := range id.SupportedObjectFormats() {
hashSize := objectFormat.Size()
maxLen := packfile.MaxEntryHeaderLen(hashSize)
if maxLen < packfile.MaxTypeSizeLen+hashSize {
t.Fatalf("MaxEntryHeaderLen(%d) = %d cannot fit a ref-delta header", hashSize, maxLen)
}
if maxLen < packfile.MaxTypeSizeLen+packfile.MaxOfsDeltaDistanceLen {
t.Fatalf("MaxEntryHeaderLen(%d) = %d cannot fit an ofs-delta header", hashSize, maxLen)
}
}
}