1package wire
2
3import (
4 "errors"
5 "math"
6 "testing"
7
8 "google.golang.org/protobuf/encoding/protowire"
9 "google.golang.org/protobuf/proto"
10)
11
12// TestScanEnvelopeTable covers every scanner family: valid envelopes,
13// unknown fields, wrong wire types, duplicates, truncation, trailing
14// garbage, oversize lengths, and repeated-element abuse.
15func TestScanEnvelopeTable(t *testing.T) {
16 mustMarshal := func(message proto.Message) []byte {
17 raw, err := proto.MarshalOptions{Deterministic: true}.Marshal(message)
18 if err != nil {
19 t.Fatal(err)
20 }
21 return raw
22 }
23 ping := mustMarshal(&ClientEnvelope{Payload: &ClientEnvelope_Ping{Ping: &Ping{}}})
24
25 input := mustMarshal(&ClientEnvelope{Payload: &ClientEnvelope_Input{Input: &Input{InputSeq: 1, Data: []byte("x")}}})
26 snapshot := mustMarshal(&ServerEnvelope{Payload: &ServerEnvelope_PickerSnapshot{PickerSnapshot: &PickerSnapshot{
27 InteractionId: 1, SourceId: "serving", SourceRevision: 1, Status: 1,
28 Recent: &PickerProjection{}, Grouped: &PickerProjection{},
29 }}})
30
31 // Wrong wire type: Hello.version (field 1, varint) as fixed32.
32 wrongType := []byte{0x0D, 0x01, 0x00, 0x00, 0x00}
33 // Truncated varint tag.
34 truncatedTag := []byte{0xFF}
35 // Length-delimited field claiming more than available.
36 overlong := protowire.AppendTag(nil, 2, protowire.BytesType)
37 overlong = protowire.AppendBytes(overlong, make([]byte, 8))
38 overlong = overlong[:len(overlong)-4]
39
40 tests := []struct {
41 name string
42 message proto.Message
43 payload []byte
44 wantErr error
45 }{
46 {name: "valid ping", message: &ClientEnvelope{}, payload: ping},
47 {name: "valid input", message: &ClientEnvelope{}, payload: input},
48 {name: "valid snapshot", message: &ServerEnvelope{}, payload: snapshot},
49 {name: "nil message", message: nil, payload: ping, wantErr: ErrScanNotMessage},
50 {name: "empty payload", message: &ClientEnvelope{}, payload: nil, wantErr: ErrScanEmpty},
51 {name: "empty envelope", message: &ClientEnvelope{}, payload: []byte{}, wantErr: ErrScanEmpty},
52 {name: "truncated", message: &ClientEnvelope{}, payload: ping[:len(ping)-1], wantErr: ErrScanTruncated},
53 {name: "truncated tag", message: &ClientEnvelope{}, payload: truncatedTag, wantErr: ErrScanTruncated},
54 {name: "concatenated envelopes", message: &ClientEnvelope{}, payload: append(append([]byte(nil), ping...), ping...), wantErr: ErrScanDuplicate},
55 {name: "wrong wire type", message: &ClientEnvelope{}, payload: wrongType, wantErr: ErrScanWireType},
56 {name: "overlong bytes", message: &ClientEnvelope{}, payload: overlong, wantErr: ErrScanTruncated},
57 {name: "duplicate variant", message: &ClientEnvelope{}, payload: append(append([]byte(nil), ping...), ping...), wantErr: ErrScanDuplicate},
58 {name: "oversize envelope", message: &ClientEnvelope{}, payload: make([]byte, AbsoluteEnvelopeLimit+1), wantErr: ErrScanLength},
59 }
60 for _, tt := range tests {
61 t.Run(tt.name, func(t *testing.T) {
62 err := ScanEnvelope(tt.message, tt.payload)
63 if tt.wantErr == nil {
64 if err != nil {
65 t.Fatalf("ScanEnvelope() = %v, want nil", err)
66 }
67 return
68 }
69 if !errors.Is(err, tt.wantErr) {
70 t.Fatalf("ScanEnvelope() = %v, want %v", err, tt.wantErr)
71 }
72 })
73 }
74}
75
76// TestScanEnvelopeUnknownField appends an unknown varint field to a valid
77// envelope and requires ErrScanUnknown.
78func TestScanEnvelopeUnknownField(t *testing.T) {
79 valid, err := proto.Marshal(&ClientEnvelope{Payload: &ClientEnvelope_Ping{Ping: &Ping{}}})
80 if err != nil {
81 t.Fatal(err)
82 }
83 unknown := append([]byte(nil), valid...)
84 unknown = protowire.AppendTag(unknown, 99, protowire.VarintType)
85 unknown = protowire.AppendVarint(unknown, 1)
86 if err := ScanEnvelope(&ClientEnvelope{}, unknown); !errors.Is(err, ErrScanUnknown) {
87 t.Fatalf("ScanEnvelope() = %v, want ErrScanUnknown", err)
88 }
89}
90
91// TestScanEnvelopeRejectsUint32Overflow proves a raw varint wider than the
92// declared uint32 field is rejected before generated unmarshal can truncate
93// its high bits into a legitimate value.
94func TestScanEnvelopeRejectsUint32Overflow(t *testing.T) {
95 hello := protowire.AppendTag(nil, 1, protowire.VarintType) // Hello.version
96 hello = protowire.AppendVarint(hello, math.MaxUint32+1)
97 raw := protowire.AppendTag(nil, 1, protowire.BytesType) // ClientEnvelope.hello
98 raw = protowire.AppendBytes(raw, hello)
99 if err := ScanEnvelope(&ClientEnvelope{}, raw); !errors.Is(err, ErrScanVarintRange) {
100 t.Fatalf("ScanEnvelope() = %v, want ErrScanVarintRange", err)
101 }
102}
103
104// TestScanEnvelopeAllowsSignExtendedInt32 proves a canonical negative int32
105// (ten-byte sign-extended varint) still passes: it is a real value, not a
106// truncating overflow.
107func TestScanEnvelopeAllowsSignExtendedInt32(t *testing.T) {
108 for _, value := range []uint64{math.MaxUint64, math.MaxUint64 - uint64(math.MaxInt32)} {
109 style := protowire.AppendTag(nil, 5, protowire.VarintType) // CellStyle.foreground
110 style = protowire.AppendVarint(style, value)
111 if err := ScanEnvelope(&CellStyle{}, style); err != nil {
112 t.Fatalf("ScanEnvelope(value %d) = %v, want nil", value, err)
113 }
114 }
115}
116
117// TestScanEnvelopeRejectsBoolAboveOne proves the strict scanner refuses the
118// non-canonical bool encodings that generated unmarshal would coerce to true.
119func TestScanEnvelopeRejectsBoolAboveOne(t *testing.T) {
120 accepted := protowire.AppendTag(nil, 8, protowire.VarintType) // PreambleResponse.accepted
121 accepted = protowire.AppendVarint(accepted, 2)
122 if err := ScanEnvelope(&PreambleResponse{}, accepted); !errors.Is(err, ErrScanVarintRange) {
123 t.Fatalf("ScanEnvelope() = %v, want ErrScanVarintRange", err)
124 }
125}
126
127// TestScanEnvelopeRejectsInt32Overflow proves a varint that truncates to a
128// different int32 is rejected while canonical sign-extended negatives pass.
129func TestScanEnvelopeRejectsInt32Overflow(t *testing.T) {
130 for _, value := range []uint64{math.MaxUint32, 1 << 32} {
131 style := protowire.AppendTag(nil, 5, protowire.VarintType) // CellStyle.foreground
132 style = protowire.AppendVarint(style, value)
133 if err := ScanEnvelope(&CellStyle{}, style); !errors.Is(err, ErrScanVarintRange) {
134 t.Fatalf("ScanEnvelope(value %d) = %v, want ErrScanVarintRange", value, err)
135 }
136 }
137}
138
139// TestScanEnvelopeRepeatedElements fills a flat repeated field past the
140// scanner budget with minimal one-byte elements.
141func TestScanEnvelopeRepeatedElements(t *testing.T) {
142 hello := &Hello{Version: 1, Intent: 1}
143 for range maxScanRepeated + 1 {
144 hello.Env = append(hello.Env, "x")
145 }
146 raw, err := proto.Marshal(&ClientEnvelope{Payload: &ClientEnvelope_Hello{Hello: hello}})
147 if err != nil {
148 t.Fatal(err)
149 }
150 if err := ScanEnvelope(&ClientEnvelope{}, raw); !errors.Is(err, ErrScanRepeated) {
151 t.Fatalf("ScanEnvelope() = %v, want ErrScanRepeated", err)
152 }
153}
154
155// TestValidatedUnmarshalRoundTrip proves scan + unmarshal accept a valid
156// envelope and reject a mutated one.
157func TestValidatedUnmarshalRoundTrip(t *testing.T) {
158 raw, err := proto.Marshal(&ClientEnvelope{Payload: &ClientEnvelope_Ack{Ack: &Ack{Epoch: 1, State: 2}}})
159 if err != nil {
160 t.Fatal(err)
161 }
162 decoded := &ClientEnvelope{}
163 if err := validatedUnmarshal(decoded, raw); err != nil {
164 t.Fatalf("validatedUnmarshal() = %v", err)
165 }
166 bad := append([]byte(nil), raw...)
167 bad[len(bad)-1] ^= 0xFF
168 if err := validatedUnmarshal(&ClientEnvelope{}, bad); err == nil {
169 t.Fatal("validatedUnmarshal accepted mutated payload")
170 }
171 for size := range len(raw) {
172 decoded := &ClientEnvelope{}
173 if err := validatedUnmarshal(decoded, raw[:size]); err == nil {
174 t.Fatalf("validatedUnmarshal accepted prefix %d", size)
175 }
176 }
177 pong, err := proto.Marshal(&ClientEnvelope{Payload: &ClientEnvelope_Ping{Ping: &Ping{}}})
178 if err != nil {
179 t.Fatal(err)
180 }
181 if err := validatedUnmarshal(&ClientEnvelope{}, append(append([]byte(nil), raw...), pong...)); err == nil {
182 t.Fatal("validatedUnmarshal accepted trailing envelope")
183 }
184}