1// +build go1.12
2
3/*
4 *
5 * Copyright 2020 gRPC authors.
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 *     http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 *
19 */
20
21package xdsclient
22
23import (
24	"fmt"
25	"strings"
26	"testing"
27	"time"
28
29	v1typepb "github.com/cncf/udpa/go/udpa/type/v1"
30	v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
31	"github.com/golang/protobuf/proto"
32	spb "github.com/golang/protobuf/ptypes/struct"
33	"github.com/google/go-cmp/cmp"
34	"google.golang.org/protobuf/types/known/durationpb"
35
36	"google.golang.org/grpc/internal/testutils"
37	"google.golang.org/grpc/xds/internal/httpfilter"
38	"google.golang.org/grpc/xds/internal/version"
39
40	v2xdspb "github.com/envoyproxy/go-control-plane/envoy/api/v2"
41	v2corepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
42	v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
43	v2httppb "github.com/envoyproxy/go-control-plane/envoy/config/filter/network/http_connection_manager/v2"
44	v2listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v2"
45	v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
46	v3httppb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
47	v3tlspb "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
48	anypb "github.com/golang/protobuf/ptypes/any"
49	wrapperspb "github.com/golang/protobuf/ptypes/wrappers"
50)
51
52func (s) TestUnmarshalListener_ClientSide(t *testing.T) {
53	const (
54		v2LDSTarget       = "lds.target.good:2222"
55		v3LDSTarget       = "lds.target.good:3333"
56		v2RouteConfigName = "v2RouteConfig"
57		v3RouteConfigName = "v3RouteConfig"
58		routeName         = "routeName"
59		testVersion       = "test-version-lds-client"
60	)
61
62	var (
63		v2Lis = testutils.MarshalAny(&v2xdspb.Listener{
64			Name: v2LDSTarget,
65			ApiListener: &v2listenerpb.ApiListener{
66				ApiListener: testutils.MarshalAny(&v2httppb.HttpConnectionManager{
67					RouteSpecifier: &v2httppb.HttpConnectionManager_Rds{
68						Rds: &v2httppb.Rds{
69							ConfigSource: &v2corepb.ConfigSource{
70								ConfigSourceSpecifier: &v2corepb.ConfigSource_Ads{Ads: &v2corepb.AggregatedConfigSource{}},
71							},
72							RouteConfigName: v2RouteConfigName,
73						},
74					},
75				}),
76			},
77		})
78		customFilter = &v3httppb.HttpFilter{
79			Name:       "customFilter",
80			ConfigType: &v3httppb.HttpFilter_TypedConfig{TypedConfig: customFilterConfig},
81		}
82		typedStructFilter = &v3httppb.HttpFilter{
83			Name:       "customFilter",
84			ConfigType: &v3httppb.HttpFilter_TypedConfig{TypedConfig: wrappedCustomFilterTypedStructConfig},
85		}
86		customOptionalFilter = &v3httppb.HttpFilter{
87			Name:       "customFilter",
88			ConfigType: &v3httppb.HttpFilter_TypedConfig{TypedConfig: customFilterConfig},
89			IsOptional: true,
90		}
91		customFilter2 = &v3httppb.HttpFilter{
92			Name:       "customFilter2",
93			ConfigType: &v3httppb.HttpFilter_TypedConfig{TypedConfig: customFilterConfig},
94		}
95		errFilter = &v3httppb.HttpFilter{
96			Name:       "errFilter",
97			ConfigType: &v3httppb.HttpFilter_TypedConfig{TypedConfig: errFilterConfig},
98		}
99		errOptionalFilter = &v3httppb.HttpFilter{
100			Name:       "errFilter",
101			ConfigType: &v3httppb.HttpFilter_TypedConfig{TypedConfig: errFilterConfig},
102			IsOptional: true,
103		}
104		clientOnlyCustomFilter = &v3httppb.HttpFilter{
105			Name:       "clientOnlyCustomFilter",
106			ConfigType: &v3httppb.HttpFilter_TypedConfig{TypedConfig: clientOnlyCustomFilterConfig},
107		}
108		serverOnlyCustomFilter = &v3httppb.HttpFilter{
109			Name:       "serverOnlyCustomFilter",
110			ConfigType: &v3httppb.HttpFilter_TypedConfig{TypedConfig: serverOnlyCustomFilterConfig},
111		}
112		serverOnlyOptionalCustomFilter = &v3httppb.HttpFilter{
113			Name:       "serverOnlyOptionalCustomFilter",
114			ConfigType: &v3httppb.HttpFilter_TypedConfig{TypedConfig: serverOnlyCustomFilterConfig},
115			IsOptional: true,
116		}
117		unknownFilter = &v3httppb.HttpFilter{
118			Name:       "unknownFilter",
119			ConfigType: &v3httppb.HttpFilter_TypedConfig{TypedConfig: unknownFilterConfig},
120		}
121		unknownOptionalFilter = &v3httppb.HttpFilter{
122			Name:       "unknownFilter",
123			ConfigType: &v3httppb.HttpFilter_TypedConfig{TypedConfig: unknownFilterConfig},
124			IsOptional: true,
125		}
126		v3LisWithInlineRoute = testutils.MarshalAny(&v3listenerpb.Listener{
127			Name: v3LDSTarget,
128			ApiListener: &v3listenerpb.ApiListener{
129				ApiListener: testutils.MarshalAny(&v3httppb.HttpConnectionManager{
130					RouteSpecifier: &v3httppb.HttpConnectionManager_RouteConfig{
131						RouteConfig: &v3routepb.RouteConfiguration{
132							Name: routeName,
133							VirtualHosts: []*v3routepb.VirtualHost{{
134								Domains: []string{v3LDSTarget},
135								Routes: []*v3routepb.Route{{
136									Match: &v3routepb.RouteMatch{
137										PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/"},
138									},
139									Action: &v3routepb.Route_Route{
140										Route: &v3routepb.RouteAction{
141											ClusterSpecifier: &v3routepb.RouteAction_Cluster{Cluster: clusterName},
142										}}}}}}},
143					},
144					CommonHttpProtocolOptions: &v3corepb.HttpProtocolOptions{
145						MaxStreamDuration: durationpb.New(time.Second),
146					},
147				}),
148			},
149		})
150		v3LisWithFilters = func(fs ...*v3httppb.HttpFilter) *anypb.Any {
151			return testutils.MarshalAny(&v3listenerpb.Listener{
152				Name: v3LDSTarget,
153				ApiListener: &v3listenerpb.ApiListener{
154					ApiListener: testutils.MarshalAny(
155						&v3httppb.HttpConnectionManager{
156							RouteSpecifier: &v3httppb.HttpConnectionManager_Rds{
157								Rds: &v3httppb.Rds{
158									ConfigSource: &v3corepb.ConfigSource{
159										ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{Ads: &v3corepb.AggregatedConfigSource{}},
160									},
161									RouteConfigName: v3RouteConfigName,
162								},
163							},
164							CommonHttpProtocolOptions: &v3corepb.HttpProtocolOptions{
165								MaxStreamDuration: durationpb.New(time.Second),
166							},
167							HttpFilters: fs,
168						}),
169				},
170			})
171		}
172		errMD = UpdateMetadata{
173			Status:  ServiceStatusNACKed,
174			Version: testVersion,
175			ErrState: &UpdateErrorMetadata{
176				Version: testVersion,
177				Err:     errPlaceHolder,
178			},
179		}
180	)
181
182	tests := []struct {
183		name       string
184		resources  []*anypb.Any
185		wantUpdate map[string]ListenerUpdate
186		wantMD     UpdateMetadata
187		wantErr    bool
188	}{
189		{
190			name:      "non-listener resource",
191			resources: []*anypb.Any{{TypeUrl: version.V3HTTPConnManagerURL}},
192			wantMD:    errMD,
193			wantErr:   true,
194		},
195		{
196			name: "badly marshaled listener resource",
197			resources: []*anypb.Any{
198				{
199					TypeUrl: version.V3ListenerURL,
200					Value: func() []byte {
201						lis := &v3listenerpb.Listener{
202							Name: v3LDSTarget,
203							ApiListener: &v3listenerpb.ApiListener{
204								ApiListener: &anypb.Any{
205									TypeUrl: version.V3HTTPConnManagerURL,
206									Value:   []byte{1, 2, 3, 4},
207								},
208							},
209						}
210						mLis, _ := proto.Marshal(lis)
211						return mLis
212					}(),
213				},
214			},
215			wantUpdate: map[string]ListenerUpdate{v3LDSTarget: {}},
216			wantMD:     errMD,
217			wantErr:    true,
218		},
219		{
220			name: "wrong type in apiListener",
221			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
222				Name: v3LDSTarget,
223				ApiListener: &v3listenerpb.ApiListener{
224					ApiListener: testutils.MarshalAny(&v2xdspb.Listener{}),
225				},
226			})},
227			wantUpdate: map[string]ListenerUpdate{v3LDSTarget: {}},
228			wantMD:     errMD,
229			wantErr:    true,
230		},
231		{
232			name: "empty httpConnMgr in apiListener",
233			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
234				Name: v3LDSTarget,
235				ApiListener: &v3listenerpb.ApiListener{
236					ApiListener: testutils.MarshalAny(&v3httppb.HttpConnectionManager{
237						RouteSpecifier: &v3httppb.HttpConnectionManager_Rds{
238							Rds: &v3httppb.Rds{},
239						},
240					}),
241				},
242			})},
243			wantUpdate: map[string]ListenerUpdate{v3LDSTarget: {}},
244			wantMD:     errMD,
245			wantErr:    true,
246		},
247		{
248			name: "scopedRoutes routeConfig in apiListener",
249			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
250				Name: v3LDSTarget,
251				ApiListener: &v3listenerpb.ApiListener{
252					ApiListener: testutils.MarshalAny(&v3httppb.HttpConnectionManager{
253						RouteSpecifier: &v3httppb.HttpConnectionManager_ScopedRoutes{},
254					}),
255				},
256			})},
257			wantUpdate: map[string]ListenerUpdate{v3LDSTarget: {}},
258			wantMD:     errMD,
259			wantErr:    true,
260		},
261		{
262			name: "rds.ConfigSource in apiListener is not ADS",
263			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
264				Name: v3LDSTarget,
265				ApiListener: &v3listenerpb.ApiListener{
266					ApiListener: testutils.MarshalAny(&v3httppb.HttpConnectionManager{
267						RouteSpecifier: &v3httppb.HttpConnectionManager_Rds{
268							Rds: &v3httppb.Rds{
269								ConfigSource: &v3corepb.ConfigSource{
270									ConfigSourceSpecifier: &v3corepb.ConfigSource_Path{
271										Path: "/some/path",
272									},
273								},
274								RouteConfigName: v3RouteConfigName,
275							},
276						},
277					}),
278				},
279			})},
280			wantUpdate: map[string]ListenerUpdate{v3LDSTarget: {}},
281			wantMD:     errMD,
282			wantErr:    true,
283		},
284		{
285			name: "empty resource list",
286			wantMD: UpdateMetadata{
287				Status:  ServiceStatusACKed,
288				Version: testVersion,
289			},
290		},
291		{
292			name:      "v3 with no filters",
293			resources: []*anypb.Any{v3LisWithFilters()},
294			wantUpdate: map[string]ListenerUpdate{
295				v3LDSTarget: {RouteConfigName: v3RouteConfigName, MaxStreamDuration: time.Second, Raw: v3LisWithFilters()},
296			},
297			wantMD: UpdateMetadata{
298				Status:  ServiceStatusACKed,
299				Version: testVersion,
300			},
301		},
302		{
303			name:      "v3 with custom filter",
304			resources: []*anypb.Any{v3LisWithFilters(customFilter)},
305			wantUpdate: map[string]ListenerUpdate{
306				v3LDSTarget: {
307					RouteConfigName: v3RouteConfigName, MaxStreamDuration: time.Second,
308					HTTPFilters: []HTTPFilter{{
309						Name:   "customFilter",
310						Filter: httpFilter{},
311						Config: filterConfig{Cfg: customFilterConfig},
312					}},
313					Raw: v3LisWithFilters(customFilter),
314				},
315			},
316			wantMD: UpdateMetadata{
317				Status:  ServiceStatusACKed,
318				Version: testVersion,
319			},
320		},
321		{
322			name:      "v3 with custom filter in typed struct",
323			resources: []*anypb.Any{v3LisWithFilters(typedStructFilter)},
324			wantUpdate: map[string]ListenerUpdate{
325				v3LDSTarget: {
326					RouteConfigName: v3RouteConfigName, MaxStreamDuration: time.Second,
327					HTTPFilters: []HTTPFilter{{
328						Name:   "customFilter",
329						Filter: httpFilter{},
330						Config: filterConfig{Cfg: customFilterTypedStructConfig},
331					}},
332					Raw: v3LisWithFilters(typedStructFilter),
333				},
334			},
335			wantMD: UpdateMetadata{
336				Status:  ServiceStatusACKed,
337				Version: testVersion,
338			},
339		},
340		{
341			name:      "v3 with optional custom filter",
342			resources: []*anypb.Any{v3LisWithFilters(customOptionalFilter)},
343			wantUpdate: map[string]ListenerUpdate{
344				v3LDSTarget: {
345					RouteConfigName: v3RouteConfigName, MaxStreamDuration: time.Second,
346					HTTPFilters: []HTTPFilter{{
347						Name:   "customFilter",
348						Filter: httpFilter{},
349						Config: filterConfig{Cfg: customFilterConfig},
350					}},
351					Raw: v3LisWithFilters(customOptionalFilter),
352				},
353			},
354			wantMD: UpdateMetadata{
355				Status:  ServiceStatusACKed,
356				Version: testVersion,
357			},
358		},
359		{
360			name:       "v3 with two filters with same name",
361			resources:  []*anypb.Any{v3LisWithFilters(customFilter, customFilter)},
362			wantUpdate: map[string]ListenerUpdate{v3LDSTarget: {}},
363			wantMD:     errMD,
364			wantErr:    true,
365		},
366		{
367			name:      "v3 with two filters - same type different name",
368			resources: []*anypb.Any{v3LisWithFilters(customFilter, customFilter2)},
369			wantUpdate: map[string]ListenerUpdate{
370				v3LDSTarget: {
371					RouteConfigName: v3RouteConfigName, MaxStreamDuration: time.Second,
372					HTTPFilters: []HTTPFilter{{
373						Name:   "customFilter",
374						Filter: httpFilter{},
375						Config: filterConfig{Cfg: customFilterConfig},
376					}, {
377						Name:   "customFilter2",
378						Filter: httpFilter{},
379						Config: filterConfig{Cfg: customFilterConfig},
380					}},
381					Raw: v3LisWithFilters(customFilter, customFilter2),
382				},
383			},
384			wantMD: UpdateMetadata{
385				Status:  ServiceStatusACKed,
386				Version: testVersion,
387			},
388		},
389		{
390			name:       "v3 with server-only filter",
391			resources:  []*anypb.Any{v3LisWithFilters(serverOnlyCustomFilter)},
392			wantUpdate: map[string]ListenerUpdate{v3LDSTarget: {}},
393			wantMD:     errMD,
394			wantErr:    true,
395		},
396		{
397			name:      "v3 with optional server-only filter",
398			resources: []*anypb.Any{v3LisWithFilters(serverOnlyOptionalCustomFilter)},
399			wantUpdate: map[string]ListenerUpdate{
400				v3LDSTarget: {
401					RouteConfigName:   v3RouteConfigName,
402					MaxStreamDuration: time.Second,
403					Raw:               v3LisWithFilters(serverOnlyOptionalCustomFilter),
404				},
405			},
406			wantMD: UpdateMetadata{
407				Status:  ServiceStatusACKed,
408				Version: testVersion,
409			},
410		},
411		{
412			name:      "v3 with client-only filter",
413			resources: []*anypb.Any{v3LisWithFilters(clientOnlyCustomFilter)},
414			wantUpdate: map[string]ListenerUpdate{
415				v3LDSTarget: {
416					RouteConfigName: v3RouteConfigName, MaxStreamDuration: time.Second,
417					HTTPFilters: []HTTPFilter{{
418						Name:   "clientOnlyCustomFilter",
419						Filter: clientOnlyHTTPFilter{},
420						Config: filterConfig{Cfg: clientOnlyCustomFilterConfig},
421					}},
422					Raw: v3LisWithFilters(clientOnlyCustomFilter),
423				},
424			},
425			wantMD: UpdateMetadata{
426				Status:  ServiceStatusACKed,
427				Version: testVersion,
428			},
429		},
430		{
431			name:       "v3 with err filter",
432			resources:  []*anypb.Any{v3LisWithFilters(errFilter)},
433			wantUpdate: map[string]ListenerUpdate{v3LDSTarget: {}},
434			wantMD:     errMD,
435			wantErr:    true,
436		},
437		{
438			name:       "v3 with optional err filter",
439			resources:  []*anypb.Any{v3LisWithFilters(errOptionalFilter)},
440			wantUpdate: map[string]ListenerUpdate{v3LDSTarget: {}},
441			wantMD:     errMD,
442			wantErr:    true,
443		},
444		{
445			name:       "v3 with unknown filter",
446			resources:  []*anypb.Any{v3LisWithFilters(unknownFilter)},
447			wantUpdate: map[string]ListenerUpdate{v3LDSTarget: {}},
448			wantMD:     errMD,
449			wantErr:    true,
450		},
451		{
452			name:      "v3 with unknown filter (optional)",
453			resources: []*anypb.Any{v3LisWithFilters(unknownOptionalFilter)},
454			wantUpdate: map[string]ListenerUpdate{
455				v3LDSTarget: {
456					RouteConfigName:   v3RouteConfigName,
457					MaxStreamDuration: time.Second,
458					Raw:               v3LisWithFilters(unknownOptionalFilter),
459				},
460			},
461			wantMD: UpdateMetadata{
462				Status:  ServiceStatusACKed,
463				Version: testVersion,
464			},
465		},
466		{
467			name:      "v2 listener resource",
468			resources: []*anypb.Any{v2Lis},
469			wantUpdate: map[string]ListenerUpdate{
470				v2LDSTarget: {RouteConfigName: v2RouteConfigName, Raw: v2Lis},
471			},
472			wantMD: UpdateMetadata{
473				Status:  ServiceStatusACKed,
474				Version: testVersion,
475			},
476		},
477		{
478			name:      "v3 listener resource",
479			resources: []*anypb.Any{v3LisWithFilters()},
480			wantUpdate: map[string]ListenerUpdate{
481				v3LDSTarget: {RouteConfigName: v3RouteConfigName, MaxStreamDuration: time.Second, Raw: v3LisWithFilters()},
482			},
483			wantMD: UpdateMetadata{
484				Status:  ServiceStatusACKed,
485				Version: testVersion,
486			},
487		},
488		{
489			name:      "v3 listener with inline route configuration",
490			resources: []*anypb.Any{v3LisWithInlineRoute},
491			wantUpdate: map[string]ListenerUpdate{
492				v3LDSTarget: {
493					InlineRouteConfig: &RouteConfigUpdate{
494						VirtualHosts: []*VirtualHost{{
495							Domains: []string{v3LDSTarget},
496							Routes:  []*Route{{Prefix: newStringP("/"), WeightedClusters: map[string]WeightedCluster{clusterName: {Weight: 1}}}},
497						}}},
498					MaxStreamDuration: time.Second,
499					Raw:               v3LisWithInlineRoute,
500				},
501			},
502			wantMD: UpdateMetadata{
503				Status:  ServiceStatusACKed,
504				Version: testVersion,
505			},
506		},
507		{
508			name:      "multiple listener resources",
509			resources: []*anypb.Any{v2Lis, v3LisWithFilters()},
510			wantUpdate: map[string]ListenerUpdate{
511				v2LDSTarget: {RouteConfigName: v2RouteConfigName, Raw: v2Lis},
512				v3LDSTarget: {RouteConfigName: v3RouteConfigName, MaxStreamDuration: time.Second, Raw: v3LisWithFilters()},
513			},
514			wantMD: UpdateMetadata{
515				Status:  ServiceStatusACKed,
516				Version: testVersion,
517			},
518		},
519		{
520			// To test that unmarshal keeps processing on errors.
521			name: "good and bad listener resources",
522			resources: []*anypb.Any{
523				v2Lis,
524				testutils.MarshalAny(&v3listenerpb.Listener{
525					Name: "bad",
526					ApiListener: &v3listenerpb.ApiListener{
527						ApiListener: testutils.MarshalAny(&v3httppb.HttpConnectionManager{
528							RouteSpecifier: &v3httppb.HttpConnectionManager_ScopedRoutes{},
529						}),
530					}}),
531				v3LisWithFilters(),
532			},
533			wantUpdate: map[string]ListenerUpdate{
534				v2LDSTarget: {RouteConfigName: v2RouteConfigName, Raw: v2Lis},
535				v3LDSTarget: {RouteConfigName: v3RouteConfigName, MaxStreamDuration: time.Second, Raw: v3LisWithFilters()},
536				"bad":       {},
537			},
538			wantMD:  errMD,
539			wantErr: true,
540		},
541	}
542
543	for _, test := range tests {
544		t.Run(test.name, func(t *testing.T) {
545			update, md, err := UnmarshalListener(testVersion, test.resources, nil)
546			if (err != nil) != test.wantErr {
547				t.Fatalf("UnmarshalListener(), got err: %v, wantErr: %v", err, test.wantErr)
548			}
549			if diff := cmp.Diff(update, test.wantUpdate, cmpOpts); diff != "" {
550				t.Errorf("got unexpected update, diff (-got +want): %v", diff)
551			}
552			if diff := cmp.Diff(md, test.wantMD, cmpOptsIgnoreDetails); diff != "" {
553				t.Errorf("got unexpected metadata, diff (-got +want): %v", diff)
554			}
555		})
556	}
557}
558
559func (s) TestUnmarshalListener_ServerSide(t *testing.T) {
560	const (
561		v3LDSTarget = "grpc/server?xds.resource.listening_address=0.0.0.0:9999"
562		testVersion = "test-version-lds-server"
563	)
564
565	var (
566		emptyValidNetworkFilters = []*v3listenerpb.Filter{
567			{
568				Name: "filter-1",
569				ConfigType: &v3listenerpb.Filter_TypedConfig{
570					TypedConfig: testutils.MarshalAny(&v3httppb.HttpConnectionManager{}),
571				},
572			},
573		}
574		localSocketAddress = &v3corepb.Address{
575			Address: &v3corepb.Address_SocketAddress{
576				SocketAddress: &v3corepb.SocketAddress{
577					Address: "0.0.0.0",
578					PortSpecifier: &v3corepb.SocketAddress_PortValue{
579						PortValue: 9999,
580					},
581				},
582			},
583		}
584		listenerEmptyTransportSocket = testutils.MarshalAny(&v3listenerpb.Listener{
585			Name:    v3LDSTarget,
586			Address: localSocketAddress,
587			FilterChains: []*v3listenerpb.FilterChain{
588				{
589					Name:    "filter-chain-1",
590					Filters: emptyValidNetworkFilters,
591				},
592			},
593		})
594		listenerNoValidationContext = testutils.MarshalAny(&v3listenerpb.Listener{
595			Name:    v3LDSTarget,
596			Address: localSocketAddress,
597			FilterChains: []*v3listenerpb.FilterChain{
598				{
599					Name:    "filter-chain-1",
600					Filters: emptyValidNetworkFilters,
601					TransportSocket: &v3corepb.TransportSocket{
602						Name: "envoy.transport_sockets.tls",
603						ConfigType: &v3corepb.TransportSocket_TypedConfig{
604							TypedConfig: testutils.MarshalAny(&v3tlspb.DownstreamTlsContext{
605								CommonTlsContext: &v3tlspb.CommonTlsContext{
606									TlsCertificateCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
607										InstanceName:    "identityPluginInstance",
608										CertificateName: "identityCertName",
609									},
610								},
611							}),
612						},
613					},
614				},
615			},
616			DefaultFilterChain: &v3listenerpb.FilterChain{
617				Name:    "default-filter-chain-1",
618				Filters: emptyValidNetworkFilters,
619				TransportSocket: &v3corepb.TransportSocket{
620					Name: "envoy.transport_sockets.tls",
621					ConfigType: &v3corepb.TransportSocket_TypedConfig{
622						TypedConfig: testutils.MarshalAny(&v3tlspb.DownstreamTlsContext{
623							CommonTlsContext: &v3tlspb.CommonTlsContext{
624								TlsCertificateCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
625									InstanceName:    "defaultIdentityPluginInstance",
626									CertificateName: "defaultIdentityCertName",
627								},
628							},
629						}),
630					},
631				},
632			},
633		})
634		listenerWithValidationContext = testutils.MarshalAny(&v3listenerpb.Listener{
635			Name:    v3LDSTarget,
636			Address: localSocketAddress,
637			FilterChains: []*v3listenerpb.FilterChain{
638				{
639					Name:    "filter-chain-1",
640					Filters: emptyValidNetworkFilters,
641					TransportSocket: &v3corepb.TransportSocket{
642						Name: "envoy.transport_sockets.tls",
643						ConfigType: &v3corepb.TransportSocket_TypedConfig{
644							TypedConfig: testutils.MarshalAny(&v3tlspb.DownstreamTlsContext{
645								RequireClientCertificate: &wrapperspb.BoolValue{Value: true},
646								CommonTlsContext: &v3tlspb.CommonTlsContext{
647									TlsCertificateCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
648										InstanceName:    "identityPluginInstance",
649										CertificateName: "identityCertName",
650									},
651									ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContextCertificateProviderInstance{
652										ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
653											InstanceName:    "rootPluginInstance",
654											CertificateName: "rootCertName",
655										},
656									},
657								},
658							}),
659						},
660					},
661				},
662			},
663			DefaultFilterChain: &v3listenerpb.FilterChain{
664				Name:    "default-filter-chain-1",
665				Filters: emptyValidNetworkFilters,
666				TransportSocket: &v3corepb.TransportSocket{
667					Name: "envoy.transport_sockets.tls",
668					ConfigType: &v3corepb.TransportSocket_TypedConfig{
669						TypedConfig: testutils.MarshalAny(&v3tlspb.DownstreamTlsContext{
670							RequireClientCertificate: &wrapperspb.BoolValue{Value: true},
671							CommonTlsContext: &v3tlspb.CommonTlsContext{
672								TlsCertificateCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
673									InstanceName:    "defaultIdentityPluginInstance",
674									CertificateName: "defaultIdentityCertName",
675								},
676								ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContextCertificateProviderInstance{
677									ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
678										InstanceName:    "defaultRootPluginInstance",
679										CertificateName: "defaultRootCertName",
680									},
681								},
682							},
683						}),
684					},
685				},
686			},
687		})
688		errMD = UpdateMetadata{
689			Status:  ServiceStatusNACKed,
690			Version: testVersion,
691			ErrState: &UpdateErrorMetadata{
692				Version: testVersion,
693				Err:     errPlaceHolder,
694			},
695		}
696	)
697
698	tests := []struct {
699		name       string
700		resources  []*anypb.Any
701		wantUpdate map[string]ListenerUpdate
702		wantMD     UpdateMetadata
703		wantErr    string
704	}{
705		{
706			name: "non-empty listener filters",
707			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
708				Name: v3LDSTarget,
709				ListenerFilters: []*v3listenerpb.ListenerFilter{
710					{Name: "listener-filter-1"},
711				},
712			})},
713			wantUpdate: map[string]ListenerUpdate{v3LDSTarget: {}},
714			wantMD:     errMD,
715			wantErr:    "unsupported field 'listener_filters'",
716		},
717		{
718			name: "use_original_dst is set",
719			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
720				Name:           v3LDSTarget,
721				UseOriginalDst: &wrapperspb.BoolValue{Value: true},
722			})},
723			wantUpdate: map[string]ListenerUpdate{v3LDSTarget: {}},
724			wantMD:     errMD,
725			wantErr:    "unsupported field 'use_original_dst'",
726		},
727		{
728			name:       "no address field",
729			resources:  []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{Name: v3LDSTarget})},
730			wantUpdate: map[string]ListenerUpdate{v3LDSTarget: {}},
731			wantMD:     errMD,
732			wantErr:    "no address field in LDS response",
733		},
734		{
735			name: "no socket address field",
736			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
737				Name:    v3LDSTarget,
738				Address: &v3corepb.Address{},
739			})},
740			wantUpdate: map[string]ListenerUpdate{v3LDSTarget: {}},
741			wantMD:     errMD,
742			wantErr:    "no socket_address field in LDS response",
743		},
744		{
745			name: "no filter chains and no default filter chain",
746			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
747				Name:    v3LDSTarget,
748				Address: localSocketAddress,
749				FilterChains: []*v3listenerpb.FilterChain{
750					{
751						FilterChainMatch: &v3listenerpb.FilterChainMatch{DestinationPort: &wrapperspb.UInt32Value{Value: 666}},
752						Filters:          emptyValidNetworkFilters,
753					},
754				},
755			})},
756			wantUpdate: map[string]ListenerUpdate{v3LDSTarget: {}},
757			wantMD:     errMD,
758			wantErr:    "no supported filter chains and no default filter chain",
759		},
760		{
761			name: "missing http connection manager network filter",
762			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
763				Name:    v3LDSTarget,
764				Address: localSocketAddress,
765				FilterChains: []*v3listenerpb.FilterChain{
766					{
767						Name: "filter-chain-1",
768					},
769				},
770			})},
771			wantUpdate: map[string]ListenerUpdate{v3LDSTarget: {}},
772			wantMD:     errMD,
773			wantErr:    "missing HttpConnectionManager filter",
774		},
775		{
776			name: "missing filter name in http filter",
777			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
778				Name:    v3LDSTarget,
779				Address: localSocketAddress,
780				FilterChains: []*v3listenerpb.FilterChain{
781					{
782						Name: "filter-chain-1",
783						Filters: []*v3listenerpb.Filter{
784							{
785								ConfigType: &v3listenerpb.Filter_TypedConfig{
786									TypedConfig: testutils.MarshalAny(&v3httppb.HttpConnectionManager{}),
787								},
788							},
789						},
790					},
791				},
792			})},
793			wantUpdate: map[string]ListenerUpdate{v3LDSTarget: {}},
794			wantMD:     errMD,
795			wantErr:    "missing name field in filter",
796		},
797		{
798			name: "duplicate filter names in http filter",
799			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
800				Name:    v3LDSTarget,
801				Address: localSocketAddress,
802				FilterChains: []*v3listenerpb.FilterChain{
803					{
804						Name: "filter-chain-1",
805						Filters: []*v3listenerpb.Filter{
806							{
807								Name: "name",
808								ConfigType: &v3listenerpb.Filter_TypedConfig{
809									TypedConfig: testutils.MarshalAny(&v3httppb.HttpConnectionManager{}),
810								},
811							},
812							{
813								Name: "name",
814								ConfigType: &v3listenerpb.Filter_TypedConfig{
815									TypedConfig: testutils.MarshalAny(&v3httppb.HttpConnectionManager{}),
816								},
817							},
818						},
819					},
820				},
821			})},
822			wantUpdate: map[string]ListenerUpdate{v3LDSTarget: {}},
823			wantMD:     errMD,
824			wantErr:    "duplicate filter name",
825		},
826		{
827			name: "unsupported oneof in typed config of http filter",
828			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
829				Name:    v3LDSTarget,
830				Address: localSocketAddress,
831				FilterChains: []*v3listenerpb.FilterChain{
832					{
833						Name: "filter-chain-1",
834						Filters: []*v3listenerpb.Filter{
835							{
836								Name:       "name",
837								ConfigType: &v3listenerpb.Filter_ConfigDiscovery{},
838							},
839						},
840					},
841				},
842			})},
843			wantUpdate: map[string]ListenerUpdate{v3LDSTarget: {}},
844			wantMD:     errMD,
845			wantErr:    "unsupported config_type",
846		},
847		{
848			name: "overlapping filter chain match criteria",
849			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
850				Name:    v3LDSTarget,
851				Address: localSocketAddress,
852				FilterChains: []*v3listenerpb.FilterChain{
853					{
854						FilterChainMatch: &v3listenerpb.FilterChainMatch{SourcePorts: []uint32{1, 2, 3, 4, 5}},
855						Filters:          emptyValidNetworkFilters,
856					},
857					{
858						FilterChainMatch: &v3listenerpb.FilterChainMatch{},
859						Filters:          emptyValidNetworkFilters,
860					},
861					{
862						FilterChainMatch: &v3listenerpb.FilterChainMatch{SourcePorts: []uint32{5, 6, 7}},
863						Filters:          emptyValidNetworkFilters,
864					},
865				},
866			})},
867			wantUpdate: map[string]ListenerUpdate{v3LDSTarget: {}},
868			wantMD:     errMD,
869			wantErr:    "multiple filter chains with overlapping matching rules are defined",
870		},
871		{
872			name: "unsupported network filter",
873			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
874				Name:    v3LDSTarget,
875				Address: localSocketAddress,
876				FilterChains: []*v3listenerpb.FilterChain{
877					{
878						Name: "filter-chain-1",
879						Filters: []*v3listenerpb.Filter{
880							{
881								Name: "name",
882								ConfigType: &v3listenerpb.Filter_TypedConfig{
883									TypedConfig: testutils.MarshalAny(&v3httppb.LocalReplyConfig{}),
884								},
885							},
886						},
887					},
888				},
889			})},
890			wantUpdate: map[string]ListenerUpdate{v3LDSTarget: {}},
891			wantMD:     errMD,
892			wantErr:    "unsupported network filter",
893		},
894		{
895			name: "badly marshaled network filter",
896			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
897				Name:    v3LDSTarget,
898				Address: localSocketAddress,
899				FilterChains: []*v3listenerpb.FilterChain{
900					{
901						Name: "filter-chain-1",
902						Filters: []*v3listenerpb.Filter{
903							{
904								Name: "name",
905								ConfigType: &v3listenerpb.Filter_TypedConfig{
906									TypedConfig: &anypb.Any{
907										TypeUrl: version.V3HTTPConnManagerURL,
908										Value:   []byte{1, 2, 3, 4},
909									},
910								},
911							},
912						},
913					},
914				},
915			})},
916			wantUpdate: map[string]ListenerUpdate{v3LDSTarget: {}},
917			wantMD:     errMD,
918			wantErr:    "failed unmarshaling of network filter",
919		},
920		{
921			name: "unexpected transport socket name",
922			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
923				Name:    v3LDSTarget,
924				Address: localSocketAddress,
925				FilterChains: []*v3listenerpb.FilterChain{
926					{
927						Name:    "filter-chain-1",
928						Filters: emptyValidNetworkFilters,
929						TransportSocket: &v3corepb.TransportSocket{
930							Name: "unsupported-transport-socket-name",
931						},
932					},
933				},
934			})},
935			wantUpdate: map[string]ListenerUpdate{v3LDSTarget: {}},
936			wantMD:     errMD,
937			wantErr:    "transport_socket field has unexpected name",
938		},
939		{
940			name: "unexpected transport socket typedConfig URL",
941			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
942				Name:    v3LDSTarget,
943				Address: localSocketAddress,
944				FilterChains: []*v3listenerpb.FilterChain{
945					{
946						Name:    "filter-chain-1",
947						Filters: emptyValidNetworkFilters,
948						TransportSocket: &v3corepb.TransportSocket{
949							Name: "envoy.transport_sockets.tls",
950							ConfigType: &v3corepb.TransportSocket_TypedConfig{
951								TypedConfig: testutils.MarshalAny(&v3tlspb.UpstreamTlsContext{}),
952							},
953						},
954					},
955				},
956			})},
957			wantUpdate: map[string]ListenerUpdate{v3LDSTarget: {}},
958			wantMD:     errMD,
959			wantErr:    "transport_socket field has unexpected typeURL",
960		},
961		{
962			name: "badly marshaled transport socket",
963			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
964				Name:    v3LDSTarget,
965				Address: localSocketAddress,
966				FilterChains: []*v3listenerpb.FilterChain{
967					{
968						Name:    "filter-chain-1",
969						Filters: emptyValidNetworkFilters,
970						TransportSocket: &v3corepb.TransportSocket{
971							Name: "envoy.transport_sockets.tls",
972							ConfigType: &v3corepb.TransportSocket_TypedConfig{
973								TypedConfig: &anypb.Any{
974									TypeUrl: version.V3DownstreamTLSContextURL,
975									Value:   []byte{1, 2, 3, 4},
976								},
977							},
978						},
979					},
980				},
981			})},
982			wantUpdate: map[string]ListenerUpdate{v3LDSTarget: {}},
983			wantMD:     errMD,
984			wantErr:    "failed to unmarshal DownstreamTlsContext in LDS response",
985		},
986		{
987			name: "missing CommonTlsContext",
988			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
989				Name:    v3LDSTarget,
990				Address: localSocketAddress,
991				FilterChains: []*v3listenerpb.FilterChain{
992					{
993						Name:    "filter-chain-1",
994						Filters: emptyValidNetworkFilters,
995						TransportSocket: &v3corepb.TransportSocket{
996							Name: "envoy.transport_sockets.tls",
997							ConfigType: &v3corepb.TransportSocket_TypedConfig{
998								TypedConfig: testutils.MarshalAny(&v3tlspb.DownstreamTlsContext{}),
999							},
1000						},
1001					},
1002				},
1003			})},
1004			wantUpdate: map[string]ListenerUpdate{v3LDSTarget: {}},
1005			wantMD:     errMD,
1006			wantErr:    "DownstreamTlsContext in LDS response does not contain a CommonTlsContext",
1007		},
1008		{
1009			name: "unsupported validation context in transport socket",
1010			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
1011				Name:    v3LDSTarget,
1012				Address: localSocketAddress,
1013				FilterChains: []*v3listenerpb.FilterChain{
1014					{
1015						Name:    "filter-chain-1",
1016						Filters: emptyValidNetworkFilters,
1017						TransportSocket: &v3corepb.TransportSocket{
1018							Name: "envoy.transport_sockets.tls",
1019							ConfigType: &v3corepb.TransportSocket_TypedConfig{
1020								TypedConfig: testutils.MarshalAny(&v3tlspb.DownstreamTlsContext{
1021									CommonTlsContext: &v3tlspb.CommonTlsContext{
1022										ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContextSdsSecretConfig{
1023											ValidationContextSdsSecretConfig: &v3tlspb.SdsSecretConfig{
1024												Name: "foo-sds-secret",
1025											},
1026										},
1027									},
1028								}),
1029							},
1030						},
1031					},
1032				},
1033			})},
1034			wantUpdate: map[string]ListenerUpdate{v3LDSTarget: {}},
1035			wantMD:     errMD,
1036			wantErr:    "validation context contains unexpected type",
1037		},
1038		{
1039			name:      "empty transport socket",
1040			resources: []*anypb.Any{listenerEmptyTransportSocket},
1041			wantUpdate: map[string]ListenerUpdate{
1042				v3LDSTarget: {
1043					InboundListenerCfg: &InboundListenerConfig{
1044						Address: "0.0.0.0",
1045						Port:    "9999",
1046						FilterChains: &FilterChainManager{
1047							dstPrefixMap: map[string]*destPrefixEntry{
1048								unspecifiedPrefixMapKey: {
1049									srcTypeArr: [3]*sourcePrefixes{
1050										{
1051											srcPrefixMap: map[string]*sourcePrefixEntry{
1052												unspecifiedPrefixMapKey: {
1053													srcPortMap: map[int]*FilterChain{
1054														0: {},
1055													},
1056												},
1057											},
1058										},
1059									},
1060								},
1061							},
1062						},
1063					},
1064					Raw: listenerEmptyTransportSocket,
1065				},
1066			},
1067			wantMD: UpdateMetadata{
1068				Status:  ServiceStatusACKed,
1069				Version: testVersion,
1070			},
1071		},
1072		{
1073			name: "no identity and root certificate providers",
1074			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
1075				Name:    v3LDSTarget,
1076				Address: localSocketAddress,
1077				FilterChains: []*v3listenerpb.FilterChain{
1078					{
1079						Name:    "filter-chain-1",
1080						Filters: emptyValidNetworkFilters,
1081						TransportSocket: &v3corepb.TransportSocket{
1082							Name: "envoy.transport_sockets.tls",
1083							ConfigType: &v3corepb.TransportSocket_TypedConfig{
1084								TypedConfig: testutils.MarshalAny(&v3tlspb.DownstreamTlsContext{
1085									RequireClientCertificate: &wrapperspb.BoolValue{Value: true},
1086									CommonTlsContext: &v3tlspb.CommonTlsContext{
1087										TlsCertificateCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
1088											InstanceName:    "identityPluginInstance",
1089											CertificateName: "identityCertName",
1090										},
1091									},
1092								}),
1093							},
1094						},
1095					},
1096				},
1097			})},
1098			wantUpdate: map[string]ListenerUpdate{v3LDSTarget: {}},
1099			wantMD:     errMD,
1100			wantErr:    "security configuration on the server-side does not contain root certificate provider instance name, but require_client_cert field is set",
1101		},
1102		{
1103			name: "no identity certificate provider with require_client_cert",
1104			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
1105				Name:    v3LDSTarget,
1106				Address: localSocketAddress,
1107				FilterChains: []*v3listenerpb.FilterChain{
1108					{
1109						Name:    "filter-chain-1",
1110						Filters: emptyValidNetworkFilters,
1111						TransportSocket: &v3corepb.TransportSocket{
1112							Name: "envoy.transport_sockets.tls",
1113							ConfigType: &v3corepb.TransportSocket_TypedConfig{
1114								TypedConfig: testutils.MarshalAny(&v3tlspb.DownstreamTlsContext{
1115									CommonTlsContext: &v3tlspb.CommonTlsContext{},
1116								}),
1117							},
1118						},
1119					},
1120				},
1121			})},
1122			wantUpdate: map[string]ListenerUpdate{v3LDSTarget: {}},
1123			wantMD:     errMD,
1124			wantErr:    "security configuration on the server-side does not contain identity certificate provider instance name",
1125		},
1126		{
1127			name:      "happy case with no validation context",
1128			resources: []*anypb.Any{listenerNoValidationContext},
1129			wantUpdate: map[string]ListenerUpdate{
1130				v3LDSTarget: {
1131					InboundListenerCfg: &InboundListenerConfig{
1132						Address: "0.0.0.0",
1133						Port:    "9999",
1134						FilterChains: &FilterChainManager{
1135							dstPrefixMap: map[string]*destPrefixEntry{
1136								unspecifiedPrefixMapKey: {
1137									srcTypeArr: [3]*sourcePrefixes{
1138										{
1139											srcPrefixMap: map[string]*sourcePrefixEntry{
1140												unspecifiedPrefixMapKey: {
1141													srcPortMap: map[int]*FilterChain{
1142														0: {
1143															SecurityCfg: &SecurityConfig{
1144																IdentityInstanceName: "identityPluginInstance",
1145																IdentityCertName:     "identityCertName",
1146															},
1147														},
1148													},
1149												},
1150											},
1151										},
1152									},
1153								},
1154							},
1155							def: &FilterChain{
1156								SecurityCfg: &SecurityConfig{
1157									IdentityInstanceName: "defaultIdentityPluginInstance",
1158									IdentityCertName:     "defaultIdentityCertName",
1159								},
1160							},
1161						},
1162					},
1163					Raw: listenerNoValidationContext,
1164				},
1165			},
1166			wantMD: UpdateMetadata{
1167				Status:  ServiceStatusACKed,
1168				Version: testVersion,
1169			},
1170		},
1171		{
1172			name:      "happy case with validation context provider instance",
1173			resources: []*anypb.Any{listenerWithValidationContext},
1174			wantUpdate: map[string]ListenerUpdate{
1175				v3LDSTarget: {
1176					InboundListenerCfg: &InboundListenerConfig{
1177						Address: "0.0.0.0",
1178						Port:    "9999",
1179						FilterChains: &FilterChainManager{
1180							dstPrefixMap: map[string]*destPrefixEntry{
1181								unspecifiedPrefixMapKey: {
1182									srcTypeArr: [3]*sourcePrefixes{
1183										{
1184											srcPrefixMap: map[string]*sourcePrefixEntry{
1185												unspecifiedPrefixMapKey: {
1186													srcPortMap: map[int]*FilterChain{
1187														0: {
1188															SecurityCfg: &SecurityConfig{
1189																RootInstanceName:     "rootPluginInstance",
1190																RootCertName:         "rootCertName",
1191																IdentityInstanceName: "identityPluginInstance",
1192																IdentityCertName:     "identityCertName",
1193																RequireClientCert:    true,
1194															},
1195														},
1196													},
1197												},
1198											},
1199										},
1200									},
1201								},
1202							},
1203							def: &FilterChain{
1204								SecurityCfg: &SecurityConfig{
1205									RootInstanceName:     "defaultRootPluginInstance",
1206									RootCertName:         "defaultRootCertName",
1207									IdentityInstanceName: "defaultIdentityPluginInstance",
1208									IdentityCertName:     "defaultIdentityCertName",
1209									RequireClientCert:    true,
1210								},
1211							},
1212						},
1213					},
1214					Raw: listenerWithValidationContext,
1215				},
1216			},
1217			wantMD: UpdateMetadata{
1218				Status:  ServiceStatusACKed,
1219				Version: testVersion,
1220			},
1221		},
1222	}
1223
1224	for _, test := range tests {
1225		t.Run(test.name, func(t *testing.T) {
1226			gotUpdate, md, err := UnmarshalListener(testVersion, test.resources, nil)
1227			if (err != nil) != (test.wantErr != "") {
1228				t.Fatalf("UnmarshalListener(), got err: %v, wantErr: %v", err, test.wantErr)
1229			}
1230			if err != nil && !strings.Contains(err.Error(), test.wantErr) {
1231				t.Fatalf("UnmarshalListener() = %v wantErr: %q", err, test.wantErr)
1232			}
1233			if diff := cmp.Diff(gotUpdate, test.wantUpdate, cmpOpts); diff != "" {
1234				t.Errorf("got unexpected update, diff (-got +want): %v", diff)
1235			}
1236			if diff := cmp.Diff(md, test.wantMD, cmpOptsIgnoreDetails); diff != "" {
1237				t.Errorf("got unexpected metadata, diff (-got +want): %v", diff)
1238			}
1239		})
1240	}
1241}
1242
1243type filterConfig struct {
1244	httpfilter.FilterConfig
1245	Cfg      proto.Message
1246	Override proto.Message
1247}
1248
1249// httpFilter allows testing the http filter registry and parsing functionality.
1250type httpFilter struct {
1251	httpfilter.ClientInterceptorBuilder
1252	httpfilter.ServerInterceptorBuilder
1253}
1254
1255func (httpFilter) TypeURLs() []string { return []string{"custom.filter"} }
1256
1257func (httpFilter) ParseFilterConfig(cfg proto.Message) (httpfilter.FilterConfig, error) {
1258	return filterConfig{Cfg: cfg}, nil
1259}
1260
1261func (httpFilter) ParseFilterConfigOverride(override proto.Message) (httpfilter.FilterConfig, error) {
1262	return filterConfig{Override: override}, nil
1263}
1264
1265// errHTTPFilter returns errors no matter what is passed to ParseFilterConfig.
1266type errHTTPFilter struct {
1267	httpfilter.ClientInterceptorBuilder
1268}
1269
1270func (errHTTPFilter) TypeURLs() []string { return []string{"err.custom.filter"} }
1271
1272func (errHTTPFilter) ParseFilterConfig(cfg proto.Message) (httpfilter.FilterConfig, error) {
1273	return nil, fmt.Errorf("error from ParseFilterConfig")
1274}
1275
1276func (errHTTPFilter) ParseFilterConfigOverride(override proto.Message) (httpfilter.FilterConfig, error) {
1277	return nil, fmt.Errorf("error from ParseFilterConfigOverride")
1278}
1279
1280func init() {
1281	httpfilter.Register(httpFilter{})
1282	httpfilter.Register(errHTTPFilter{})
1283	httpfilter.Register(serverOnlyHTTPFilter{})
1284	httpfilter.Register(clientOnlyHTTPFilter{})
1285}
1286
1287// serverOnlyHTTPFilter does not implement ClientInterceptorBuilder
1288type serverOnlyHTTPFilter struct {
1289	httpfilter.ServerInterceptorBuilder
1290}
1291
1292func (serverOnlyHTTPFilter) TypeURLs() []string { return []string{"serverOnly.custom.filter"} }
1293
1294func (serverOnlyHTTPFilter) ParseFilterConfig(cfg proto.Message) (httpfilter.FilterConfig, error) {
1295	return filterConfig{Cfg: cfg}, nil
1296}
1297
1298func (serverOnlyHTTPFilter) ParseFilterConfigOverride(override proto.Message) (httpfilter.FilterConfig, error) {
1299	return filterConfig{Override: override}, nil
1300}
1301
1302// clientOnlyHTTPFilter does not implement ServerInterceptorBuilder
1303type clientOnlyHTTPFilter struct {
1304	httpfilter.ClientInterceptorBuilder
1305}
1306
1307func (clientOnlyHTTPFilter) TypeURLs() []string { return []string{"clientOnly.custom.filter"} }
1308
1309func (clientOnlyHTTPFilter) ParseFilterConfig(cfg proto.Message) (httpfilter.FilterConfig, error) {
1310	return filterConfig{Cfg: cfg}, nil
1311}
1312
1313func (clientOnlyHTTPFilter) ParseFilterConfigOverride(override proto.Message) (httpfilter.FilterConfig, error) {
1314	return filterConfig{Override: override}, nil
1315}
1316
1317var customFilterConfig = &anypb.Any{
1318	TypeUrl: "custom.filter",
1319	Value:   []byte{1, 2, 3},
1320}
1321
1322var errFilterConfig = &anypb.Any{
1323	TypeUrl: "err.custom.filter",
1324	Value:   []byte{1, 2, 3},
1325}
1326
1327var serverOnlyCustomFilterConfig = &anypb.Any{
1328	TypeUrl: "serverOnly.custom.filter",
1329	Value:   []byte{1, 2, 3},
1330}
1331
1332var clientOnlyCustomFilterConfig = &anypb.Any{
1333	TypeUrl: "clientOnly.custom.filter",
1334	Value:   []byte{1, 2, 3},
1335}
1336
1337var customFilterTypedStructConfig = &v1typepb.TypedStruct{
1338	TypeUrl: "custom.filter",
1339	Value: &spb.Struct{
1340		Fields: map[string]*spb.Value{
1341			"foo": {Kind: &spb.Value_StringValue{StringValue: "bar"}},
1342		},
1343	},
1344}
1345var wrappedCustomFilterTypedStructConfig *anypb.Any
1346
1347func init() {
1348	wrappedCustomFilterTypedStructConfig = testutils.MarshalAny(customFilterTypedStructConfig)
1349}
1350
1351var unknownFilterConfig = &anypb.Any{
1352	TypeUrl: "unknown.custom.filter",
1353	Value:   []byte{1, 2, 3},
1354}
1355
1356func wrappedOptionalFilter(name string) *anypb.Any {
1357	return testutils.MarshalAny(&v3routepb.FilterConfig{
1358		IsOptional: true,
1359		Config: &anypb.Any{
1360			TypeUrl: name,
1361			Value:   []byte{1, 2, 3},
1362		},
1363	})
1364}
1365