1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
|
package packidx_test
import (
"bytes"
"encoding/binary"
"errors"
"os"
"testing"
"lindenii.org/go/furgit/internal/format/packidx"
"lindenii.org/go/furgit/object/id"
)
func TestParseGitIndex(t *testing.T) {
t.Parallel()
for _, objectFormat := range id.SupportedObjectFormats() {
t.Run(objectFormat.String(), func(t *testing.T) {
t.Parallel()
_, prefix, oids := makeGitPack(t, objectFormat)
_, idx := parseGitIdxFile(t, prefix, objectFormat)
if idx.NumObjects() != len(oids) {
t.Fatalf("NumObjects = %d, want %d", idx.NumObjects(), len(oids))
}
for pos := 1; pos < idx.NumObjects(); pos++ {
if bytes.Compare(idx.OIDAt(pos-1), idx.OIDAt(pos)) >= 0 {
t.Fatalf("OIDAt(%d) not sorted after predecessor", pos)
}
}
packData, err := os.ReadFile(prefix + ".pack") //nolint:gosec
if err != nil {
t.Fatalf("ReadFile: %v", err)
}
packTrailer := packData[len(packData)-objectFormat.Size():]
if !bytes.Equal(idx.PackHash(), packTrailer) {
t.Fatalf("PackHash does not match pack trailer")
}
})
}
}
func TestParseMalformed(t *testing.T) {
t.Parallel()
for _, objectFormat := range id.SupportedObjectFormats() {
t.Run(objectFormat.String(), func(t *testing.T) {
t.Parallel()
hashSize := objectFormat.Size()
valid := writeSyntheticIndex(t, objectFormat, syntheticEntries(8))
corrupt := func(mutate func(data []byte) []byte) []byte {
return mutate(bytes.Clone(valid))
}
cases := []struct {
name string
data []byte
}{
{name: "empty", data: []byte{}},
{name: "truncated", data: corrupt(func(d []byte) []byte { return d[:20] })},
{
name: "bad signature",
data: corrupt(func(d []byte) []byte {
d[0] ^= 0xff
return d
}),
},
{
name: "bad version",
data: corrupt(func(d []byte) []byte {
d[7] = 3
return d
}),
},
{
name: "non-monotonic fanout",
data: corrupt(func(d []byte) []byte {
binary.BigEndian.PutUint32(d[8:], 0xffffffff)
return d
}),
},
{
name: "tables exceed index size",
data: corrupt(func(d []byte) []byte {
binary.BigEndian.PutUint32(d[8+255*4:], 0x00ffffff)
return d
}),
},
{
name: "trailing size not 64-bit multiple",
data: corrupt(func(d []byte) []byte { return append(d, 0xde, 0xad, 0xbe, 0xef) }),
},
{
name: "more 64-bit offsets than objects",
data: corrupt(func(d []byte) []byte {
return append(d, bytes.Repeat([]byte{0x00}, 9*8)...)
}),
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
_, err := packidx.Parse(tc.data, hashSize)
if !errors.Is(err, packidx.ErrMalformedPackIndex) {
t.Fatalf("Parse error = %v, want ErrMalformedPackIndex", err)
}
})
}
})
}
}
|