1// SPDX-License-Identifier: MIT
2
3package git
4
5import (
6 "bytes"
7 "compress/zlib"
8 "crypto/sha1"
9 "encoding/binary"
10 "errors"
11 "fmt"
12 "strings"
13 "testing"
14)
15
16func TestObjectID(t *testing.T) {
17 const hex = "0123456789abcdef0123456789abcdef01234567"
18 id, err := ParseID(hex)
19 if err != nil || id.String() != hex || id.IsZero() {
20 t.Fatalf("ParseID = %s %v", id, err)
21 }
22 if up, err := ParseID(strings.ToUpper(hex)); err != nil || up != id {
23 t.Fatalf("upper case: %v", err)
24 }
25 for _, bad := range []string{"", "abc", hex[:39], hex + "0", strings.Repeat("g", 40), hex[:39] + "\x00"} {
26 if _, err := ParseID(bad); !errors.Is(err, ErrInvalidID) {
27 t.Errorf("ParseID(%q) = %v", bad, err)
28 }
29 }
30 if !(ObjectID{}).IsZero() {
31 t.Error("zero id not zero")
32 }
33 buf := make([]byte, 0, 64)
34 out := id.AppendHex(append(buf, "x:"...))
35 if string(out) != "x:"+hex {
36 t.Errorf("AppendHex = %s", out)
37 }
38 if n := testing.AllocsPerRun(100, func() { _ = id.AppendHex(buf[:0]) }); n != 0 {
39 t.Errorf("AppendHex allocates %v", n)
40 }
41}
42
43// varint encodes a delta header size.
44func varint(v uint64) []byte {
45 var b []byte
46 for {
47 c := byte(v & 0x7f)
48 v >>= 7
49 if v != 0 {
50 b = append(b, c|0x80)
51 } else {
52 return append(b, c)
53 }
54 }
55}
56
57func copyOp(off, size uint32) []byte {
58 op := byte(0x80)
59 var args []byte
60 for i := 0; i < 4; i++ {
61 if b := byte(off >> (8 * i)); b != 0 {
62 op |= 1 << i
63 args = append(args, b)
64 }
65 }
66 for i := 0; i < 3; i++ {
67 if b := byte(size >> (8 * i)); b != 0 {
68 op |= 0x10 << i
69 args = append(args, b)
70 }
71 }
72 return append([]byte{op}, args...)
73}
74
75func TestApplyDelta(t *testing.T) {
76 base := []byte("hello, delta world")
77 mk := func(baseSize, resSize uint64, ops ...[]byte) []byte {
78 d := append(varint(baseSize), varint(resSize)...)
79 for _, o := range ops {
80 d = append(d, o...)
81 }
82 return d
83 }
84 ins := func(s string) []byte { return append([]byte{byte(len(s))}, s...) }
85 good := mk(uint64(len(base)), 12, copyOp(0, 5), ins(" Go"), copyOp(11, 4))
86 got, err := patch(base, good, 1<<20)
87 if err != nil || string(got) != "hello Go"+string(base[11:15]) {
88 t.Fatalf("patch = %q, %v", got, err)
89 }
90 // copy size 0 means 0x10000
91 bigBase := bytes.Repeat([]byte{'x'}, 0x10000)
92 got, err = patch(bigBase, mk(0x10000, 0x10000, []byte{0x80}), 1<<20)
93 if err != nil || !bytes.Equal(got, bigBase) {
94 t.Fatalf("copy 0x10000: %v", err)
95 }
96 bad := map[string][]byte{
97 "empty": nil,
98 "truncated header": {0x80},
99 "base size": mk(3, 1, ins("a")),
100 "copy outside": mk(uint64(len(base)), 4, copyOp(15, 4)),
101 "copy huge": mk(uint64(len(base)), 4, copyOp(0xffffffff, 0xffffff)),
102 "insert past end": mk(uint64(len(base)), 9, []byte{9, 'a'}),
103 "result too long": mk(uint64(len(base)), 1, ins("ab")),
104 "result too short": mk(uint64(len(base)), 5, ins("ab")),
105 "opcode zero": mk(uint64(len(base)), 1, []byte{0}),
106 "truncated copy": mk(uint64(len(base)), 1, []byte{0x81}),
107 "huge varint": append(bytes.Repeat([]byte{0xff}, 12), 0x01),
108 }
109 for name, d := range bad {
110 if _, err := patch(base, d, 1<<20); !errors.Is(err, ErrCorrupt) {
111 t.Errorf("%s: %v, want ErrCorrupt", name, err)
112 }
113 }
114 if _, err := patch(base, mk(uint64(len(base)), 1<<40, ins("a")), 1<<20); !errors.Is(err, ErrTooLarge) {
115 t.Errorf("huge result: %v, want ErrTooLarge", err)
116 }
117}
118
119func TestParseEntryHeader(t *testing.T) {
120 mk := func(body ...byte) []byte {
121 d := append([]byte("PACK\x00\x00\x00\x02\x00\x00\x00\x01"), body...)
122 return append(d, make([]byte, 20)...)
123 }
124 // blob, size 5
125 h, err := parseEntryHeader(mk(0x35, 0x78, 0x9c), 12)
126 if err != nil || h.typ != 3 || h.size != 5 || h.data != 13 {
127 t.Fatalf("blob header = %+v %v", h, err)
128 }
129 // size 0x1234 : 4 low bits 4, then 7 bits ...
130 h, err = parseEntryHeader(mk(0xb4, 0x23, 0x78), 12) // type 3 continued
131 if err != nil || h.size != 4|0x23<<4 {
132 t.Fatalf("multi byte size = %+v %v", h, err)
133 }
134 for name, d := range map[string][]byte{
135 "unterminated": mk(bytes.Repeat([]byte{0xbf}, 20)...),
136 "bad type 0": mk(0x05, 0x78),
137 "bad type 5": mk(0x55, 0x78),
138 "ofs no data": mk(0x65),
139 "ofs outside": mk(0x65, 0x7f, 0x78),
140 "ref truncated": mk(0x75, 1, 2, 3),
141 } {
142 off := uint64(12)
143 if _, err := parseEntryHeader(d, off); !errors.Is(err, ErrCorrupt) {
144 t.Errorf("%s: %v", name, err)
145 }
146 }
147 if _, err := parseEntryHeader(mk(0x35, 0x78), 5); !errors.Is(err, ErrCorrupt) {
148 t.Error("offset inside the pack header")
149 }
150 if _, err := parseEntryHeader(mk(0x35, 0x78), 1<<40); !errors.Is(err, ErrCorrupt) {
151 t.Error("offset far outside")
152 }
153 if _, err := parseEntryHeader(nil, 12); !errors.Is(err, ErrCorrupt) {
154 t.Error("nil pack")
155 }
156}
157
158func TestParseTreeErrors(t *testing.T) {
159 good := append([]byte("100644 a\x00"), bytes.Repeat([]byte{1}, 20)...)
160 good = append(good, append([]byte("40000 d\x00"), bytes.Repeat([]byte{2}, 20)...)...)
161 n := 0
162 if err := ParseTree(good, func(e Entry) bool { n++; return true }); err != nil || n != 2 {
163 t.Fatalf("good tree: %d %v", n, err)
164 }
165 n = 0
166 if err := ParseTree(good, func(e Entry) bool { n++; return false }); err != nil || n != 1 {
167 t.Fatalf("early stop: %d %v", n, err)
168 }
169 for name, d := range map[string][]byte{
170 "no space": []byte("100644"),
171 "mode letters": []byte("10x644 a\x00" + strings.Repeat("a", 20)),
172 "empty mode": []byte(" a\x00" + strings.Repeat("a", 20)),
173 "long mode": []byte("10000000 a\x00" + strings.Repeat("a", 20)),
174 "no nul": []byte("100644 abc"),
175 "short id": []byte("100644 a\x00" + strings.Repeat("a", 19)),
176 "empty name": []byte("100644 \x00" + strings.Repeat("a", 20)),
177 } {
178 if err := ParseTree(d, func(Entry) bool { return true }); !errors.Is(err, ErrCorrupt) {
179 t.Errorf("%s: %v", name, err)
180 }
181 }
182 if err := ParseTree(nil, func(Entry) bool { t.Fatal("called"); return true }); err != nil {
183 t.Errorf("empty tree: %v", err)
184 }
185}
186
187func TestParseCommitEdgeCases(t *testing.T) {
188 tree := strings.Repeat("a", 40)
189 par := strings.Repeat("b", 40)
190 var c Commit
191 // No message at all, no trailing newline.
192 if err := ParseCommit([]byte("tree "+tree+"\nauthor A <a@x> 1 +0000\ncommitter B <b@x> 2 -0130"), &c); err != nil {
193 t.Fatal(err)
194 }
195 if c.Message != nil || c.Committer.Offset != -(3600+30*60) || len(c.Parents) != 0 {
196 t.Errorf("commit = %+v", c)
197 }
198 // Two parents then reuse with zero parents keeps no stale state.
199 if err := ParseCommit([]byte("tree "+tree+"\nparent "+par+"\nparent "+tree+"\nauthor A <a@x> 1 +0000\n\nmsg\n"), &c); err != nil || len(c.Parents) != 2 || string(c.Message) != "msg\n" {
200 t.Fatalf("two parents: %+v %v", c, err)
201 }
202 if err := ParseCommit([]byte("tree "+tree+"\n\n"), &c); err != nil || len(c.Parents) != 0 || c.Author.Name != nil || len(c.Message) != 0 {
203 t.Fatalf("reuse: %+v %v", c, err)
204 }
205 // Multi-line header continuation containing something that looks like a header.
206 raw := "tree " + tree + "\nmergetag object x\n tag y\n parent " + par + "\n\n \nauthor A <a@x> 1 +0000\ncommitter C <c@x> 5 +0100\n\nbody"
207 if err := ParseCommit([]byte(raw), &c); err != nil || len(c.Parents) != 0 {
208 t.Fatalf("continuation: %+v %v", c, err)
209 }
210 // Odd signatures.
211 for in, want := range map[string]Signature{
212 "Name <e> 12 +0200": {Name: []byte("Name"), Email: []byte("e"), Time: 12, Offset: 7200},
213 "<> 0 +0000": {Name: []byte{}, Email: []byte{}},
214 "No Email 1 +0000": {Name: []byte("No Email 1 +0000")},
215 "A <b> notatime +0100": {Name: []byte("A"), Email: []byte("b")},
216 "A <b> 5 +01": {Name: []byte("A"), Email: []byte("b"), Time: 5},
217 "A <b <c> 7 -0000": {Name: []byte("A"), Email: []byte("b <c"), Time: 7},
218 "A <b> 99999999999999999999 +0000": {Name: []byte("A"), Email: []byte("b")},
219 } {
220 got := parseSignature([]byte(in))
221 if !bytes.Equal(got.Name, want.Name) || !bytes.Equal(got.Email, want.Email) || got.Time != want.Time || got.Offset != want.Offset {
222 t.Errorf("parseSignature(%q) = %+v %q %q, want %+v", in, got, got.Name, got.Email, want)
223 }
224 }
225 for name, d := range map[string]string{
226 "empty": "",
227 "no tree": "author A <a@x> 1 +0000\n\nmsg",
228 "bad tree": "tree xyz\n\n",
229 "short tree": "tree " + tree[:39] + "\n\n",
230 "bad parent": "tree " + tree + "\nparent zz\n\n",
231 "tree second": "author A <a@x> 1 +0000\ntree " + tree + "\n\n",
232 "parent first": "parent " + tree + "\ntree " + tree + "\n\n",
233 "blank first": "\ntree " + tree + "\n",
234 "bad parent after": "tree " + tree + "\nparent " + tree + "\nparent x\n\n",
235 } {
236 if err := ParseCommit([]byte(d), &c); !errors.Is(err, ErrCorrupt) {
237 t.Errorf("%s: %v", name, err)
238 }
239 }
240}
241
242func TestParseTagErrors(t *testing.T) {
243 obj := strings.Repeat("c", 40)
244 var tg Tag
245 if err := ParseTag([]byte("object "+obj+"\ntype commit\ntag v1\ntagger T <t@x> 3 +0000\n\nmsg"), &tg); err != nil || tg.Type != TypeCommit || string(tg.Name) != "v1" || string(tg.Message) != "msg" {
246 t.Fatalf("tag = %+v %v", tg, err)
247 }
248 for name, d := range map[string]string{
249 "empty": "",
250 "no type": "object " + obj + "\n",
251 "no obj": "type commit\n",
252 "bad type": "object " + obj + "\ntype thing\n",
253 "bad obj": "object zz\ntype commit\n",
254 } {
255 if err := ParseTag([]byte(d), &tg); !errors.Is(err, ErrCorrupt) {
256 t.Errorf("%s: %v", name, err)
257 }
258 }
259}
260
261func TestParsePackedRefsFormats(t *testing.T) {
262 a, b, c := strings.Repeat("a", 40), strings.Repeat("b", 40), strings.Repeat("c", 40)
263 p, err := parsePackedRefs("# pack-refs with: peeled fully-peeled sorted \n" + a + " refs/heads/main\n" + b + " refs/tags/v1\n^" + c + "\n" + c + " refs/tags/v2\n")
264 if err != nil {
265 t.Fatal(err)
266 }
267 if e, _ := p.find("refs/tags/v1"); !e.peeledKnown || e.peeled.String() != c {
268 t.Errorf("v1 = %+v", e)
269 }
270 if e, _ := p.find("refs/heads/main"); !e.peeledKnown || e.peeled.String() != a {
271 t.Errorf("fully peeled: main = %+v", e)
272 }
273 // Without the header nothing is known.
274 p, err = parsePackedRefs(a + " refs/tags/v1\n")
275 if err != nil {
276 t.Fatal(err)
277 }
278 if e, _ := p.find("refs/tags/v1"); e.peeledKnown {
279 t.Errorf("unknown peel claimed known")
280 }
281 // Old "peeled" trait: tags known, branches not.
282 p, _ = parsePackedRefs("# pack-refs with: peeled\n" + a + " refs/heads/x\n" + a + " refs/tags/t\n")
283 if e, _ := p.find("refs/heads/x"); e.peeledKnown {
284 t.Error("branch peel known with the peeled trait")
285 }
286 if e, _ := p.find("refs/tags/t"); !e.peeledKnown {
287 t.Error("tag peel unknown with the peeled trait")
288 }
289 for _, bad := range []string{"^" + a + "\n", a + "\n", "zz refs/x\n", a + " \n", a + " refs/x\n^zz\n"} {
290 if _, err := parsePackedRefs(bad); !errors.Is(err, ErrCorrupt) {
291 t.Errorf("parsePackedRefs(%q) = %v", bad, err)
292 }
293 }
294}
295
296func TestValidRefName(t *testing.T) {
297 for _, ok := range []string{"refs/heads/main", "refs/heads/a/b", "refs/tags/v1.0", "refs/heads/é"} {
298 if !validRefName(ok) {
299 t.Errorf("%q rejected", ok)
300 }
301 }
302 for _, bad := range []string{"", "HEAD", "refs", "refs/", "/refs/heads/x", "refs/heads/x/", "refs/heads/../x", "refs/heads/a..b", "refs/heads/.x", "refs/heads/x.lock",
303 "refs/heads//x", "refs/heads/a b", "refs/heads/a~1", "refs/heads/a^", "refs/heads/a:b", "refs/heads/a?", "refs/heads/a*", "refs/heads/a[", "refs/heads/a\\b", "refs/heads/a@{1}", "refs/heads/\x01"} {
304 if validRefName(bad) {
305 t.Errorf("%q accepted", bad)
306 }
307 }
308}
309
310// zlibBytes compresses b.
311func zlibBytes(b []byte) []byte {
312 var out bytes.Buffer
313 w := zlib.NewWriter(&out)
314 _, _ = w.Write(b)
315 _ = w.Close()
316 return out.Bytes()
317}
318
319// entryBytes encodes a pack entry header followed by data.
320func entryBytes(typ int, size int, extra []byte, data []byte) []byte {
321 c := byte(typ<<4) | byte(size&0x0f)
322 size >>= 4
323 var out []byte
324 for size > 0 {
325 out = append(out, c|0x80)
326 c = byte(size & 0x7f)
327 size >>= 7
328 }
329 out = append(out, c)
330 out = append(out, extra...)
331 return append(out, zlibBytes(data)...)
332}
333
334// buildPack assembles a pack file and its v2 index from entries (whose ids
335// are given) and returns both.
336func buildPack(entries [][]byte, ids []ObjectID, offs *[]uint64) (pack, idx []byte) {
337 pack = append([]byte("PACK"), 0, 0, 0, 2, 0, 0, 0, byte(len(entries)))
338 for _, e := range entries {
339 *offs = append(*offs, uint64(len(pack)))
340 pack = append(pack, e...)
341 }
342 sum := sha1.Sum(pack)
343 pack = append(pack, sum[:]...)
344
345 order := make([]int, len(ids))
346 for i := range order {
347 order[i] = i
348 }
349 for i := range order { // insertion sort by id
350 for j := i; j > 0 && bytes.Compare(ids[order[j]][:], ids[order[j-1]][:]) < 0; j-- {
351 order[j], order[j-1] = order[j-1], order[j]
352 }
353 }
354 idx = append([]byte{0xff, 't', 'O', 'c', 0, 0, 0, 2}, make([]byte, 1024)...)
355 for i, k := range order {
356 for b := int(ids[k][0]); b < 256; b++ {
357 cur := binary.BigEndian.Uint32(idx[8+4*b:])
358 if cur < uint32(i+1) {
359 binary.BigEndian.PutUint32(idx[8+4*b:], uint32(i+1))
360 }
361 }
362 }
363 for _, k := range order {
364 idx = append(idx, ids[k][:]...)
365 }
366 idx = append(idx, make([]byte, 4*len(order))...) // crc
367 for _, k := range order {
368 idx = binary.BigEndian.AppendUint32(idx, uint32((*offs)[k]))
369 }
370 idx = append(idx, pack[len(pack)-20:]...)
371 isum := sha1.Sum(idx)
372 return pack, append(idx, isum[:]...)
373}
374
375func objectIDOf(typ string, data []byte) ObjectID {
376 h := sha1.New()
377 _, _ = fmt.Fprintf(h, "%s %d\x00", typ, len(data))
378 _, _ = h.Write(data)
379 var id ObjectID
380 copy(id[:], h.Sum(nil))
381 return id
382}
383
384func TestParseIdxErrors(t *testing.T) {
385 var offs []uint64
386 blob := []byte("hello")
387 _, idx := buildPack([][]byte{entryBytes(3, len(blob), nil, blob)}, []ObjectID{objectIDOf("blob", blob)}, &offs)
388 x, err := parseIdx(idx)
389 if err != nil || x.n != 1 {
390 t.Fatalf("good idx: %v", err)
391 }
392 if _, ok := x.find(&ObjectID{}); ok {
393 t.Error("found a missing id")
394 }
395 id := objectIDOf("blob", blob)
396 if pos, ok := x.find(&id); !ok || pos != 0 {
397 t.Error("id not found")
398 }
399 trunc := func(n int) []byte { return idx[:n] }
400 mut := func(f func(b []byte)) []byte { b := bytes.Clone(idx); f(b); return b }
401 for name, d := range map[string][]byte{
402 "empty": nil,
403 "short": trunc(100),
404 "no trailer": trunc(len(idx) - 41),
405 "magic": mut(func(b []byte) { b[0] = 0 }),
406 "version 3": mut(func(b []byte) { b[7] = 3 }),
407 "fanout down": mut(func(b []byte) { binary.BigEndian.PutUint32(b[8+4*10:], 5) }),
408 "n too big": mut(func(b []byte) { binary.BigEndian.PutUint32(b[8+4*255:], 1<<31) }),
409 "odd large part": append(bytes.Clone(idx), 1, 2, 3),
410 } {
411 if _, err := parseIdx(d); err == nil {
412 t.Errorf("%s: accepted", name)
413 }
414 }
415 // A large offset entry pointing outside of the table.
416 d := mut(func(b []byte) {
417 off := len(b) - 40 - 4
418 binary.BigEndian.PutUint32(b[off:], 0x80000005)
419 })
420 x, err = parseIdx(d)
421 if err != nil {
422 t.Fatal(err)
423 }
424 if _, err := x.offset(0); !errors.Is(err, ErrCorrupt) {
425 t.Errorf("large offset outside table: %v", err)
426 }
427}
428
429// TestCorruptPacks mutates a real pack on disk and checks that every read
430// either succeeds with the right content or fails with ErrCorrupt, never
431// panics.
432func TestCorruptPacks(t *testing.T) {
433 // A small hand built pack: a blob, a delta against it, a ref-delta.
434 base := bytes.Repeat([]byte("0123456789abcdef"), 50)
435 res := append(bytes.Clone(base[:400]), []byte("tail")...)
436 delta := append(varint(uint64(len(base))), varint(uint64(len(res)))...)
437 delta = append(delta, copyOp(0, 400)...)
438 delta = append(delta, 4, 't', 'a', 'i', 'l')
439 baseID, resID := objectIDOf("blob", base), objectIDOf("blob", res)
440
441 var offs []uint64
442 e0 := entryBytes(3, len(base), nil, base)
443 // ofs-delta distance to entry 0 is len(e0)
444 ofs := []byte{byte(len(e0))}
445 if len(e0) >= 128 {
446 t.Fatal("fixture: e0 too long for a one byte offset")
447 }
448 e1 := entryBytes(objOfsDelta, len(delta), ofs, delta)
449 res2 := append(bytes.Clone(base[:400]), []byte("TAIL")...)
450 delta2 := append(varint(uint64(len(base))), varint(uint64(len(res2)))...)
451 delta2 = append(delta2, copyOp(0, 400)...)
452 delta2 = append(delta2, 4, 'T', 'A', 'I', 'L')
453 e2 := entryBytes(objRefDelta, len(delta2), baseID[:], delta2)
454 res2ID := objectIDOf("blob", res2)
455 pk, idx := buildPack([][]byte{e0, e1, e2}, []ObjectID{baseID, resID, res2ID}, &offs)
456
457 dir := t.TempDir()
458 write := func(pack, idxd []byte) *Repo {
459 t.Helper()
460 g := dir + "/g" + fmt.Sprint(len(pack)) + fmt.Sprint(len(idxd)) + fmt.Sprint(bytes.Count(pack, []byte{0}))
461 writeFile(t, g+"/HEAD", "ref: refs/heads/main\n")
462 writeFile(t, g+"/refs/.keep", "")
463 name := fmt.Sprintf("pack-%x", pk[len(pk)-20:])
464 writeFile(t, g+"/objects/pack/"+name+".pack", string(pack))
465 writeFile(t, g+"/objects/pack/"+name+".idx", string(idxd))
466 r, err := Open(g)
467 if err != nil {
468 return nil
469 }
470 t.Cleanup(func() { _ = r.Close() })
471 return r
472 }
473 // Intact.
474 r := write(pk, idx)
475 if r == nil {
476 t.Fatal("intact pack rejected")
477 }
478 for id, want := range map[ObjectID][]byte{baseID: base, resID: res, res2ID: res2} {
479 _, got, err := r.ReadObject(id, nil)
480 if err != nil || !bytes.Equal(got, want) {
481 t.Fatalf("intact %s: %v", id, err)
482 }
483 if _, n, err := r.ObjectHeader(id); err != nil || n != int64(len(want)) {
484 t.Fatalf("intact header %s: %d %v", id, n, err)
485 }
486 }
487 // Every single byte flipped in the pack body and truncations.
488 cases := 0
489 check := func(pack []byte) {
490 cases++
491 r := write(pack, idx)
492 if r == nil {
493 return // rejected at Open: fine
494 }
495 for _, id := range []ObjectID{baseID, resID, res2ID} {
496 _, _, err := r.ReadObject(id, nil)
497 if err != nil && !errors.Is(err, ErrCorrupt) && !errors.Is(err, ErrTooLarge) {
498 t.Fatalf("ReadObject: %v is not ErrCorrupt", err)
499 }
500 _, _, err = r.ObjectHeader(id)
501 if err != nil && !errors.Is(err, ErrCorrupt) {
502 t.Fatalf("ObjectHeader: %v is not ErrCorrupt", err)
503 }
504 if _, rc, err := r.OpenBlob(id); err == nil {
505 _, rerr := readAll(rc)
506 _ = rc.Close()
507 if rerr != nil && !errors.Is(rerr, ErrCorrupt) {
508 t.Fatalf("blob read: %v is not ErrCorrupt", rerr)
509 }
510 } else if !errors.Is(err, ErrCorrupt) && !errors.Is(err, ErrTooLarge) && !errors.Is(err, ErrWrongType) {
511 t.Fatalf("OpenBlob: %v", err)
512 }
513 }
514 }
515 for i := 12; i < len(pk)-20; i++ {
516 for _, x := range []byte{0x01, 0x80, 0xff} {
517 m := bytes.Clone(pk)
518 m[i] ^= x
519 check(m)
520 }
521 }
522 for n := 12; n < len(pk); n += 7 {
523 check(pk[:n])
524 }
525 t.Logf("%d corrupted packs", cases)
526}
527
528func readAll(rc interface{ Read([]byte) (int, error) }) ([]byte, error) {
529 var out []byte
530 buf := make([]byte, 4096)
531 for {
532 n, err := rc.Read(buf)
533 out = append(out, buf[:n]...)
534 if err != nil {
535 if err.Error() == "EOF" {
536 return out, nil
537 }
538 return out, err
539 }
540 }
541}