From 8feef4be951f73ba270d7a8d893632ab407e8188 Mon Sep 17 00:00:00 2001 From: PRIYA RANJAN NANDA <151918045+priyaranjannanda@users.noreply.github.com> Date: Fri, 19 Jan 2024 05:22:12 +0530 Subject: [PATCH] RT-9: Disable IPv6 ND Router Advertisement (#2485) * Initial commit for RT5.6 * Updated test plan id * Corrected typo and resolved the merge conflict RT-8 * Resolving merge conflict * Resolving merge conflict * Resolving merge conflict * Resolving merge conflict * Resolving failure * Resolving merge conflict * Update metadata.pb.go Addressed static failure issue --------- Co-authored-by: Arul Kumar Sekar <128646204+arulkumarsekar@users.noreply.github.com> Co-authored-by: KandukuriSudheer --- .../disable_ipv6_nd_ra_test/README.md | 2 +- .../disable_ipv6_nd_ra_test.go | 236 ++++++++++++++++++ .../metadata.textproto | 14 +- internal/deviations/deviations.go | 5 + proto/metadata.proto | 3 + proto/metadata_go_proto/metadata.pb.go | 90 ++++--- 6 files changed, 309 insertions(+), 41 deletions(-) create mode 100644 feature/interface/ip/ipv6_ND/otg_tests/disable_ipv6_nd_ra_test/disable_ipv6_nd_ra_test.go diff --git a/feature/interface/ip/ipv6_ND/otg_tests/disable_ipv6_nd_ra_test/README.md b/feature/interface/ip/ipv6_ND/otg_tests/disable_ipv6_nd_ra_test/README.md index c4f46be1e6d..1039076c28b 100644 --- a/feature/interface/ip/ipv6_ND/otg_tests/disable_ipv6_nd_ra_test/README.md +++ b/feature/interface/ip/ipv6_ND/otg_tests/disable_ipv6_nd_ra_test/README.md @@ -1,4 +1,4 @@ -# RT-5.6: Disable IPv6 ND Router Arvetisment +# RT-9: Disable IPv6 ND Router Arvetisment ## Summary diff --git a/feature/interface/ip/ipv6_ND/otg_tests/disable_ipv6_nd_ra_test/disable_ipv6_nd_ra_test.go b/feature/interface/ip/ipv6_ND/otg_tests/disable_ipv6_nd_ra_test/disable_ipv6_nd_ra_test.go new file mode 100644 index 00000000000..eabcadb33c5 --- /dev/null +++ b/feature/interface/ip/ipv6_ND/otg_tests/disable_ipv6_nd_ra_test/disable_ipv6_nd_ra_test.go @@ -0,0 +1,236 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package topology_test configures just the ports on DUT and ATE, +// assuming that DUT port i is connected to ATE i. It detects the +// number of ports in the testbed and can be used with the 2, 4, 12 +// port variants of the atedut testbed. + +package disable_ipv6_nd_ra_test + +import ( + "os" + "testing" + "time" + + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + "github.com/google/gopacket/pcap" + "github.com/open-traffic-generator/snappi/gosnappi" + "github.com/openconfig/featureprofiles/internal/attrs" + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/otgutils" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ygot/ygot" +) + +// Reserving the testbed and running tests +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +const ( + plen6 = 126 + ipv6 = "IPv6" + routerAdvertisementTimeInterval = 5 + frameSize = 512 + pps = 100 + routerAdvertisementDisabled = true +) + +var ( + dutSrc = attrs.Attributes{ + Desc: "dutsrc", + IPv6: "2001:db8::1", + IPv6Len: plen6, + MAC: "02:11:01:00:00:04", + } + + ateSrc = attrs.Attributes{ + Name: "atesrc", + MAC: "02:11:01:00:00:01", + IPv6: "2001:db8::2", + IPv6Len: plen6, + } + + dutDst = attrs.Attributes{ + Desc: "dutdst", + IPv6: "2001:db8::5", + IPv6Len: plen6, + MAC: "02:11:01:00:00:05", + } + ateDst = attrs.Attributes{ + Name: "atedst", + MAC: "02:12:01:00:00:01", + IPv6: "2001:db8::6", + IPv6Len: plen6, + } +) + +// configureDUT configures port1 and port2 of the DUT. +func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { + d := gnmi.OC() + p1 := dut.Port(t, "port1") + i1 := &oc.Interface{Name: ygot.String(p1.Name())} + gnmi.Replace(t, dut, d.Interface(p1.Name()).Config(), configInterfaceDUT(i1, &dutSrc, dut)) + p2 := dut.Port(t, "port2") + i2 := &oc.Interface{Name: ygot.String(p2.Name())} + gnmi.Replace(t, dut, d.Interface(p2.Name()).Config(), configInterfaceDUT(i2, &dutDst, dut)) +} + +// configInterfaceDUT configures the given DUT interface. +func configInterfaceDUT(i *oc.Interface, a *attrs.Attributes, dut *ondatra.DUTDevice) *oc.Interface { + i.Description = ygot.String(a.Desc) + i.Type = oc.IETFInterfaces_InterfaceType_ethernetCsmacd + if deviations.InterfaceEnabled(dut) { + i.Enabled = ygot.Bool(true) + } + s := i.GetOrCreateSubinterface(0) + s6 := s.GetOrCreateIpv6() + if deviations.InterfaceEnabled(dut) { + s6.Enabled = ygot.Bool(true) + } + s6a := s6.GetOrCreateAddress(a.IPv6) + s6a.PrefixLength = ygot.Uint8(plen6) + routerAdvert := s6.GetOrCreateRouterAdvertisement() + routerAdvert.SetInterval(*ygot.Uint32(routerAdvertisementTimeInterval)) + if deviations.Ipv6RouterAdvertisementConfigUnsupported(dut) { + routerAdvert.SetSuppress(*ygot.Bool(routerAdvertisementDisabled)) + } else { + routerAdvert.SetEnable(*ygot.Bool(false)) + routerAdvert.SetMode(oc.RouterAdvertisement_Mode_ALL) + } + return i +} + +// configureOTG configures OTG interfaces to send and recieve ipv6 packets +func configureOTG(t *testing.T, ate *ondatra.ATEDevice) gosnappi.Config { + topo := gosnappi.NewConfig() + t.Logf("Configuring OTG port1") + srcPort := topo.Ports().Add().SetName("port1") + srcDev := topo.Devices().Add().SetName(ateSrc.Name) + srcEth := srcDev.Ethernets().Add().SetName(ateSrc.Name + ".Eth").SetMac(ateSrc.MAC) + srcEth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(srcPort.Name()) + srcIpv6 := srcEth.Ipv6Addresses().Add().SetName(ateSrc.Name + ".IPv6") + srcIpv6.SetAddress(ateSrc.IPv6).SetGateway(dutSrc.IPv6).SetPrefix(uint32(ateSrc.IPv6Len)) + t.Logf("Configuring OTG port2") + dstPort := topo.Ports().Add().SetName("port2") + dstDev := topo.Devices().Add().SetName(ateDst.Name) + dstEth := dstDev.Ethernets().Add().SetName(ateDst.Name + ".Eth").SetMac(ateDst.MAC) + dstEth.Connection().SetChoice(gosnappi.EthernetConnectionChoice.PORT_NAME).SetPortName(dstPort.Name()) + dstIpv6 := dstEth.Ipv6Addresses().Add().SetName(ateDst.Name + ".IPv6") + dstIpv6.SetAddress(ateDst.IPv6).SetGateway(dutDst.IPv6).SetPrefix(uint32(ateDst.IPv6Len)) + topo.Captures().Add().SetName("raCapture").SetPortNames([]string{dstPort.Name()}).SetFormat(gosnappi.CaptureFormat.PCAP) + t.Logf("OTG configuration completed!") + topo.Flows().Clear().Items() + ate.OTG().PushConfig(t, topo) + t.Logf("starting protocols... ") + ate.OTG().StartProtocols(t) + return topo +} + +// Verifies that desired parameters are set with required value on the device - Change the function Name verifyRATelemetry +func verifyRATelemetry(t *testing.T, dut *ondatra.DUTDevice) { + txPort := dut.Port(t, "port1") + telemetryTimeIntervalQuery := gnmi.OC().Interface(txPort.Name()).Subinterface(0).Ipv6().RouterAdvertisement().Interval().State() + timeIntervalOnTelemetry := gnmi.Get(t, dut, telemetryTimeIntervalQuery) + t.Logf("Required RA time interval = %v, RA Time interval observed on telemetry = %v ", routerAdvertisementTimeInterval, timeIntervalOnTelemetry) + if timeIntervalOnTelemetry != routerAdvertisementTimeInterval { + t.Fatalf("Inconsistent Time interval!\nRequired RA time interval = %v and Configured RA Time Interval = %v are not same!", routerAdvertisementTimeInterval, timeIntervalOnTelemetry) + } + + if deviations.Ipv6RouterAdvertisementConfigUnsupported(dut) { + deviceRAConfigQuery := gnmi.OC().Interface(txPort.Name()).Subinterface(0).Ipv6().RouterAdvertisement().Suppress().Config() + raConfigOnDevice := gnmi.Get(t, dut, deviceRAConfigQuery) + t.Logf("Router Advertisement State = %v", raConfigOnDevice) + } else { + deviceRAConfigQuery := gnmi.OC().Interface(txPort.Name()).Subinterface(0).Ipv6().RouterAdvertisement().Enable().Config() + deviceRAModeQuery := gnmi.OC().Interface(txPort.Name()).Subinterface(0).Ipv6().RouterAdvertisement().Enable().Config() + raModeOnDevice := gnmi.Get(t, dut, deviceRAModeQuery) + raConfigOnDevice := gnmi.Get(t, dut, deviceRAConfigQuery) + t.Logf("Router Advertisement mode = %v", raModeOnDevice) + t.Logf("Router Advertisement State = %v", raConfigOnDevice) + } +} + +// captureTrafficStats Captures traffic statistics and verifies for the loss +func verifyOTGPacketCaptureForRA(t *testing.T, ate *ondatra.ATEDevice, config gosnappi.Config, ipv6Solicitation bool, waitTime uint8) { + otg := ate.OTG() + otg.StartProtocols(t) + + cs := gosnappi.NewControlState() + cs.Port().Capture().SetState(gosnappi.StatePortCaptureState.START) + otg.SetControlState(t, cs) + if ipv6Solicitation { + otgutils.WaitForARP(t, ate.OTG(), config, "IPv6") + } + + time.Sleep(time.Duration(waitTime) * time.Second) + bytes := otg.GetCapture(t, gosnappi.NewCaptureRequest().SetPortName(config.Ports().Items()[1].Name())) + t.Logf("Config Ports %v", config.Ports().Items()) + f, err := os.CreateTemp("", "pcap") + if err != nil { + t.Fatalf("ERROR: Could not create temporary pcap file: %v\n", err) + } + if _, err := f.Write(bytes); err != nil { + t.Fatalf("ERROR: Could not write bytes to pcap file: %v\n", err) + } + f.Close() + validatePackets(t, f.Name()) +} + +// To detect if the routerAdvertisement packet is found in the captured packets +func validatePackets(t *testing.T, fileName string) { + t.Logf("Reading pcap file from : %v", fileName) + handle, err := pcap.OpenOffline(fileName) + if err != nil { + t.Logf("No Packets found in the file = %v !", fileName) + return + } + defer handle.Close() + packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) + for packet := range packetSource.Packets() { + ipv6Layer := packet.Layer(layers.LayerTypeIPv6) + if ipv6Layer != nil { + + icmpv6Layer := packet.Layer(layers.LayerTypeICMPv6) + if icmpv6Layer != nil { + routerAdvert := packet.Layer(layers.LayerTypeICMPv6RouterAdvertisement) + if routerAdvert != nil { + t.Fatalf("Error:Found a router advertisement packet!") + } + + } + } + } + t.Logf("No Router advertisement packets found!") +} + +func TestIpv6NDRA(t *testing.T) { + dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") + configureDUT(t, dut) + otgConfig := configureOTG(t, ate) + t.Run("TestCase-1: No periodical Router Advertisement", func(t *testing.T) { + verifyRATelemetry(t, dut) + verifyOTGPacketCaptureForRA(t, ate, otgConfig, false, 10) + }) + t.Run("TestCase-2: No Router Advertisement in response to Router Solicitation", func(t *testing.T) { + verifyOTGPacketCaptureForRA(t, ate, otgConfig, true, 1) + }) + +} diff --git a/feature/interface/ip/ipv6_ND/otg_tests/disable_ipv6_nd_ra_test/metadata.textproto b/feature/interface/ip/ipv6_ND/otg_tests/disable_ipv6_nd_ra_test/metadata.textproto index 7c5a2c4a727..96b04c598b8 100644 --- a/feature/interface/ip/ipv6_ND/otg_tests/disable_ipv6_nd_ra_test/metadata.textproto +++ b/feature/interface/ip/ipv6_ND/otg_tests/disable_ipv6_nd_ra_test/metadata.textproto @@ -1,7 +1,15 @@ # proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto # proto-message: Metadata -plan_id: "RT-5.6" -description: "IPv6 ND RA disable" +uuid: "43776caf-2340-4f35-ab30-8c763feaadae" +plan_id: "RT-9" +description: "Disable IPv6 ND Router Arvetisment" testbed: TESTBED_DUT_ATE_2LINKS - +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + ipv6_router_advertisement_config_unsupported: true + } +} diff --git a/internal/deviations/deviations.go b/internal/deviations/deviations.go index e7702770b1f..16abdd4ba97 100644 --- a/internal/deviations/deviations.go +++ b/internal/deviations/deviations.go @@ -735,3 +735,8 @@ func SkipStaticNexthopCheck(dut *ondatra.DUTDevice) bool { func EnableFlowctrlFlag(dut *ondatra.DUTDevice) bool { return lookupDUTDeviations(dut).GetEnableFlowctrlFlag() } + +// Ipv6RouterAdvertisementConfigUnsupported returns true for devices which don't support Ipv6 RouterAdvertisement configuration +func Ipv6RouterAdvertisementConfigUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetIpv6RouterAdvertisementConfigUnsupported() +} diff --git a/proto/metadata.proto b/proto/metadata.proto index 73ebbef6c07..b6d95cd8de0 100644 --- a/proto/metadata.proto +++ b/proto/metadata.proto @@ -392,6 +392,9 @@ message Metadata { // Devices which needs to enable leaf specific flag // Juniper: b/319202763 bool enable_flowctrl_flag = 137; + // Device doesn't support router advertisement enable and mode config + // Juniper: b/316173974 + bool ipv6_router_advertisement_config_unsupported = 138; // Reserved field numbers and identifiers. reserved 84, 9, 28, 20, 90, 97, 55, 89, 19; diff --git a/proto/metadata_go_proto/metadata.pb.go b/proto/metadata_go_proto/metadata.pb.go index 4c8ac4fb085..f0c2c7684d7 100644 --- a/proto/metadata_go_proto/metadata.pb.go +++ b/proto/metadata_go_proto/metadata.pb.go @@ -659,6 +659,9 @@ type Metadata_Deviations struct { // Devices which needs to enable leaf specific flag // Juniper: b/319202763 EnableFlowctrlFlag bool `protobuf:"varint,137,opt,name=enable_flowctrl_flag,json=enableFlowctrlFlag,proto3" json:"enable_flowctrl_flag,omitempty"` + // Device doesn't support router advertisement enable and mode config + // Juniper: b/316173974 + Ipv6RouterAdvertisementConfigUnsupported bool `protobuf:"varint,138,opt,name=ipv6_router_advertisement_config_unsupported,json=ipv6RouterAdvertisementConfigUnsupported,proto3" json:"ipv6_router_advertisement_config_unsupported,omitempty"` } func (x *Metadata_Deviations) Reset() { @@ -1540,6 +1543,13 @@ func (x *Metadata_Deviations) GetEnableFlowctrlFlag() bool { return false } +func (x *Metadata_Deviations) GetIpv6RouterAdvertisementConfigUnsupported() bool { + if x != nil { + return x.Ipv6RouterAdvertisementConfigUnsupported + } + return false +} + type Metadata_PlatformExceptions struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1603,7 +1613,7 @@ var file_metadata_proto_rawDesc = []byte{ 0x74, 0x69, 0x6e, 0x67, 0x1a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x6f, 0x6e, 0x64, 0x61, 0x74, 0x72, 0x61, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x62, 0x65, - 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe5, 0x4b, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, + 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc6, 0x4c, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x75, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6c, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6c, 0x61, 0x6e, 0x49, @@ -1634,7 +1644,7 @@ var file_metadata_proto_rawDesc = []byte{ 0x72, 0x65, 0x67, 0x65, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x67, 0x65, 0x78, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x52, 0x0e, 0x68, 0x61, 0x72, 0x64, 0x77, 0x61, 0x72, - 0x65, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x1a, 0x99, 0x44, 0x0a, 0x0a, 0x44, 0x65, 0x76, 0x69, + 0x65, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x1a, 0xfa, 0x44, 0x0a, 0x0a, 0x44, 0x65, 0x76, 0x69, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x30, 0x0a, 0x14, 0x69, 0x70, 0x76, 0x34, 0x5f, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x69, 0x70, 0x76, 0x34, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6e, @@ -2176,41 +2186,47 @@ var file_metadata_proto_rawDesc = []byte{ 0x78, 0x74, 0x68, 0x6f, 0x70, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x31, 0x0a, 0x14, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x74, 0x72, 0x6c, 0x5f, 0x66, 0x6c, 0x61, 0x67, 0x18, 0x89, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x65, 0x6e, 0x61, 0x62, 0x6c, - 0x65, 0x46, 0x6c, 0x6f, 0x77, 0x63, 0x74, 0x72, 0x6c, 0x46, 0x6c, 0x61, 0x67, 0x4a, 0x04, 0x08, - 0x54, 0x10, 0x55, 0x4a, 0x04, 0x08, 0x09, 0x10, 0x0a, 0x4a, 0x04, 0x08, 0x1c, 0x10, 0x1d, 0x4a, - 0x04, 0x08, 0x14, 0x10, 0x15, 0x4a, 0x04, 0x08, 0x5a, 0x10, 0x5b, 0x4a, 0x04, 0x08, 0x61, 0x10, - 0x62, 0x4a, 0x04, 0x08, 0x37, 0x10, 0x38, 0x4a, 0x04, 0x08, 0x59, 0x10, 0x5a, 0x4a, 0x04, 0x08, - 0x13, 0x10, 0x14, 0x1a, 0xa0, 0x01, 0x0a, 0x12, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, - 0x45, 0x78, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x41, 0x0a, 0x08, 0x70, 0x6c, - 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x6f, - 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, - 0x67, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x50, 0x6c, 0x61, 0x74, 0x66, - 0x6f, 0x72, 0x6d, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x47, 0x0a, - 0x0a, 0x64, 0x65, 0x76, 0x69, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x27, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x74, - 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, - 0x44, 0x65, 0x76, 0x69, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x0a, 0x64, 0x65, 0x76, 0x69, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xc7, 0x01, 0x0a, 0x07, 0x54, 0x65, 0x73, 0x74, 0x62, - 0x65, 0x64, 0x12, 0x17, 0x0a, 0x13, 0x54, 0x45, 0x53, 0x54, 0x42, 0x45, 0x44, 0x5f, 0x55, 0x4e, - 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x54, - 0x45, 0x53, 0x54, 0x42, 0x45, 0x44, 0x5f, 0x44, 0x55, 0x54, 0x10, 0x01, 0x12, 0x1a, 0x0a, 0x16, - 0x54, 0x45, 0x53, 0x54, 0x42, 0x45, 0x44, 0x5f, 0x44, 0x55, 0x54, 0x5f, 0x44, 0x55, 0x54, 0x5f, - 0x34, 0x4c, 0x49, 0x4e, 0x4b, 0x53, 0x10, 0x02, 0x12, 0x1a, 0x0a, 0x16, 0x54, 0x45, 0x53, 0x54, - 0x42, 0x45, 0x44, 0x5f, 0x44, 0x55, 0x54, 0x5f, 0x41, 0x54, 0x45, 0x5f, 0x32, 0x4c, 0x49, 0x4e, - 0x4b, 0x53, 0x10, 0x03, 0x12, 0x1a, 0x0a, 0x16, 0x54, 0x45, 0x53, 0x54, 0x42, 0x45, 0x44, 0x5f, - 0x44, 0x55, 0x54, 0x5f, 0x41, 0x54, 0x45, 0x5f, 0x34, 0x4c, 0x49, 0x4e, 0x4b, 0x53, 0x10, 0x04, - 0x12, 0x1e, 0x0a, 0x1a, 0x54, 0x45, 0x53, 0x54, 0x42, 0x45, 0x44, 0x5f, 0x44, 0x55, 0x54, 0x5f, - 0x41, 0x54, 0x45, 0x5f, 0x39, 0x4c, 0x49, 0x4e, 0x4b, 0x53, 0x5f, 0x4c, 0x41, 0x47, 0x10, 0x05, - 0x12, 0x1e, 0x0a, 0x1a, 0x54, 0x45, 0x53, 0x54, 0x42, 0x45, 0x44, 0x5f, 0x44, 0x55, 0x54, 0x5f, - 0x44, 0x55, 0x54, 0x5f, 0x41, 0x54, 0x45, 0x5f, 0x32, 0x4c, 0x49, 0x4e, 0x4b, 0x53, 0x10, 0x06, - 0x22, 0x6d, 0x0a, 0x04, 0x54, 0x61, 0x67, 0x73, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x41, 0x47, 0x53, - 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x14, - 0x0a, 0x10, 0x54, 0x41, 0x47, 0x53, 0x5f, 0x41, 0x47, 0x47, 0x52, 0x45, 0x47, 0x41, 0x54, 0x49, - 0x4f, 0x4e, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x54, 0x41, 0x47, 0x53, 0x5f, 0x44, 0x41, 0x54, - 0x41, 0x43, 0x45, 0x4e, 0x54, 0x45, 0x52, 0x5f, 0x45, 0x44, 0x47, 0x45, 0x10, 0x02, 0x12, 0x0d, - 0x0a, 0x09, 0x54, 0x41, 0x47, 0x53, 0x5f, 0x45, 0x44, 0x47, 0x45, 0x10, 0x03, 0x12, 0x10, 0x0a, - 0x0c, 0x54, 0x41, 0x47, 0x53, 0x5f, 0x54, 0x52, 0x41, 0x4e, 0x53, 0x49, 0x54, 0x10, 0x04, 0x62, - 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x46, 0x6c, 0x6f, 0x77, 0x63, 0x74, 0x72, 0x6c, 0x46, 0x6c, 0x61, 0x67, 0x12, 0x5f, 0x0a, + 0x2c, 0x69, 0x70, 0x76, 0x36, 0x5f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x5f, 0x61, 0x64, 0x76, + 0x65, 0x72, 0x74, 0x69, 0x73, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x8a, 0x01, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x28, 0x69, 0x70, 0x76, 0x36, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72, + 0x41, 0x64, 0x76, 0x65, 0x72, 0x74, 0x69, 0x73, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x4a, 0x04, + 0x08, 0x54, 0x10, 0x55, 0x4a, 0x04, 0x08, 0x09, 0x10, 0x0a, 0x4a, 0x04, 0x08, 0x1c, 0x10, 0x1d, + 0x4a, 0x04, 0x08, 0x14, 0x10, 0x15, 0x4a, 0x04, 0x08, 0x5a, 0x10, 0x5b, 0x4a, 0x04, 0x08, 0x61, + 0x10, 0x62, 0x4a, 0x04, 0x08, 0x37, 0x10, 0x38, 0x4a, 0x04, 0x08, 0x59, 0x10, 0x5a, 0x4a, 0x04, + 0x08, 0x13, 0x10, 0x14, 0x1a, 0xa0, 0x01, 0x0a, 0x12, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, + 0x6d, 0x45, 0x78, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x41, 0x0a, 0x08, 0x70, + 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, + 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, + 0x6e, 0x67, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x50, 0x6c, 0x61, 0x74, + 0x66, 0x6f, 0x72, 0x6d, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x47, + 0x0a, 0x0a, 0x64, 0x65, 0x76, 0x69, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, + 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x2e, 0x44, 0x65, 0x76, 0x69, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x0a, 0x64, 0x65, 0x76, + 0x69, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xc7, 0x01, 0x0a, 0x07, 0x54, 0x65, 0x73, 0x74, + 0x62, 0x65, 0x64, 0x12, 0x17, 0x0a, 0x13, 0x54, 0x45, 0x53, 0x54, 0x42, 0x45, 0x44, 0x5f, 0x55, + 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0f, 0x0a, 0x0b, + 0x54, 0x45, 0x53, 0x54, 0x42, 0x45, 0x44, 0x5f, 0x44, 0x55, 0x54, 0x10, 0x01, 0x12, 0x1a, 0x0a, + 0x16, 0x54, 0x45, 0x53, 0x54, 0x42, 0x45, 0x44, 0x5f, 0x44, 0x55, 0x54, 0x5f, 0x44, 0x55, 0x54, + 0x5f, 0x34, 0x4c, 0x49, 0x4e, 0x4b, 0x53, 0x10, 0x02, 0x12, 0x1a, 0x0a, 0x16, 0x54, 0x45, 0x53, + 0x54, 0x42, 0x45, 0x44, 0x5f, 0x44, 0x55, 0x54, 0x5f, 0x41, 0x54, 0x45, 0x5f, 0x32, 0x4c, 0x49, + 0x4e, 0x4b, 0x53, 0x10, 0x03, 0x12, 0x1a, 0x0a, 0x16, 0x54, 0x45, 0x53, 0x54, 0x42, 0x45, 0x44, + 0x5f, 0x44, 0x55, 0x54, 0x5f, 0x41, 0x54, 0x45, 0x5f, 0x34, 0x4c, 0x49, 0x4e, 0x4b, 0x53, 0x10, + 0x04, 0x12, 0x1e, 0x0a, 0x1a, 0x54, 0x45, 0x53, 0x54, 0x42, 0x45, 0x44, 0x5f, 0x44, 0x55, 0x54, + 0x5f, 0x41, 0x54, 0x45, 0x5f, 0x39, 0x4c, 0x49, 0x4e, 0x4b, 0x53, 0x5f, 0x4c, 0x41, 0x47, 0x10, + 0x05, 0x12, 0x1e, 0x0a, 0x1a, 0x54, 0x45, 0x53, 0x54, 0x42, 0x45, 0x44, 0x5f, 0x44, 0x55, 0x54, + 0x5f, 0x44, 0x55, 0x54, 0x5f, 0x41, 0x54, 0x45, 0x5f, 0x32, 0x4c, 0x49, 0x4e, 0x4b, 0x53, 0x10, + 0x06, 0x22, 0x6d, 0x0a, 0x04, 0x54, 0x61, 0x67, 0x73, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x41, 0x47, + 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, + 0x14, 0x0a, 0x10, 0x54, 0x41, 0x47, 0x53, 0x5f, 0x41, 0x47, 0x47, 0x52, 0x45, 0x47, 0x41, 0x54, + 0x49, 0x4f, 0x4e, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x54, 0x41, 0x47, 0x53, 0x5f, 0x44, 0x41, + 0x54, 0x41, 0x43, 0x45, 0x4e, 0x54, 0x45, 0x52, 0x5f, 0x45, 0x44, 0x47, 0x45, 0x10, 0x02, 0x12, + 0x0d, 0x0a, 0x09, 0x54, 0x41, 0x47, 0x53, 0x5f, 0x45, 0x44, 0x47, 0x45, 0x10, 0x03, 0x12, 0x10, + 0x0a, 0x0c, 0x54, 0x41, 0x47, 0x53, 0x5f, 0x54, 0x52, 0x41, 0x4e, 0x53, 0x49, 0x54, 0x10, 0x04, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var (