1package snapshotcodec
2
3import (
4 "bytes"
5 "encoding/binary"
6 "errors"
7 "hash/crc32"
8 "testing"
9)
10
11// reframeObject rewrites the VEVO body and fixes length and CRC so a test
12// reaches the payload checks instead of failing on framing.
13func reframeObject(t *testing.T, encoded []byte, mutate func(body []byte) []byte) []byte {
14 t.Helper()
15 body := mutate(append([]byte(nil), encoded[manifestHeaderSize:]...))
16 out := append([]byte(nil), encoded[:manifestHeaderSize]...)
17 binary.BigEndian.PutUint32(out[8:12], uint32(len(body)))
18 binary.BigEndian.PutUint32(out[12:16], crc32.ChecksumIEEE(body))
19 return append(out, body...)
20}
21
22func TestObjectCompressionRejectsMalformedPayloads(t *testing.T) {
23 payload := bytes.Repeat([]byte("history row "), 512)
24 object, err := MarshalObject(HistoryChunk, payload)
25 if err != nil {
26 t.Fatal(err)
27 }
28 if len(object.Data) >= len(payload)/4 {
29 t.Fatalf("object = %d bytes, want well under %d raw bytes", len(object.Data), len(payload))
30 }
31 again, err := MarshalObject(HistoryChunk, payload)
32 if err != nil || !bytes.Equal(again.Data, object.Data) || again.Digest != object.Digest {
33 t.Fatal("equal payloads must share one deterministic content address")
34 }
35
36 for _, tc := range []struct {
37 name string
38 mutate func(body []byte) []byte
39 want error
40 }{
41 {name: "declared shorter", mutate: func(b []byte) []byte {
42 binary.BigEndian.PutUint32(b[1:5], uint32(len(payload)-1))
43 return b
44 }, want: ErrInvalidData},
45 {name: "declared longer", mutate: func(b []byte) []byte {
46 binary.BigEndian.PutUint32(b[1:5], uint32(len(payload)+1))
47 return b
48 }, want: ErrInvalidData},
49 {name: "declared maximum on a small stream", mutate: func(b []byte) []byte {
50 binary.BigEndian.PutUint32(b[1:5], maxObjectPayloadSize)
51 return b
52 }, want: ErrInvalidData},
53 {name: "truncated stream", mutate: func(b []byte) []byte { return b[:len(b)-1] }, want: ErrInvalidData},
54 {name: "corrupt stream", mutate: func(b []byte) []byte { b[len(b)-6] ^= 0xff; return b }, want: ErrInvalidData},
55 {name: "trailing garbage", mutate: func(b []byte) []byte { return append(b, 0) }, want: ErrTrailingBytes},
56 } {
57 t.Run(tc.name, func(t *testing.T) {
58 bad := reframeObject(t, object.Data, tc.mutate)
59 if _, err := PreflightObject(bad); err != nil {
60 t.Fatalf("preflight must accept valid framing, got %v", err)
61 }
62 if _, _, err := UnmarshalObject(bad); !errors.Is(err, tc.want) {
63 t.Fatalf("UnmarshalObject() error = %v, want %v", err, tc.want)
64 }
65 })
66 }
67}