xref: /aosp_15_r20/external/cronet/net/dns/opt_record_rdata_unittest.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2022 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "net/dns/opt_record_rdata.h"
6 
7 #include <algorithm>
8 #include <memory>
9 #include <optional>
10 #include <string_view>
11 #include <utility>
12 
13 #include "base/big_endian.h"
14 #include "net/dns/dns_response.h"
15 #include "net/dns/dns_test_util.h"
16 #include "net/test/gtest_util.h"
17 #include "testing/gmock/include/gmock/gmock.h"
18 #include "testing/gtest/include/gtest/gtest.h"
19 
20 namespace net {
21 namespace {
22 
23 using ::testing::ElementsAreArray;
24 using ::testing::IsNull;
25 using ::testing::NotNull;
26 using ::testing::SizeIs;
27 
MakeStringPiece(const uint8_t * data,unsigned size)28 std::string_view MakeStringPiece(const uint8_t* data, unsigned size) {
29   const char* data_cc = reinterpret_cast<const char*>(data);
30   return std::string_view(data_cc, size);
31 }
32 
TEST(OptRecordRdataTest,ParseOptRecord)33 TEST(OptRecordRdataTest, ParseOptRecord) {
34   // This is just the rdata portion of an OPT record, rather than a complete
35   // record.
36   const uint8_t rdata[] = {
37       // First OPT
38       0x00, 0x01,  // OPT code
39       0x00, 0x02,  // OPT data size
40       0xDE, 0xAD,  // OPT data
41       // Second OPT
42       0x00, 0xFF,             // OPT code
43       0x00, 0x04,             // OPT data size
44       0xDE, 0xAD, 0xBE, 0xEF  // OPT data
45   };
46 
47   std::string_view rdata_strpiece = MakeStringPiece(rdata, sizeof(rdata));
48   std::unique_ptr<OptRecordRdata> rdata_obj =
49       OptRecordRdata::Create(rdata_strpiece);
50 
51   ASSERT_THAT(rdata_obj, NotNull());
52   ASSERT_EQ(rdata_obj->OptCount(), 2u);
53 
54   // Check contains
55   ASSERT_TRUE(rdata_obj->ContainsOptCode(1));
56   ASSERT_FALSE(rdata_obj->ContainsOptCode(30));
57 
58   // Check elements
59 
60   // Note: When passing string or StringPiece as argument, make sure to
61   // construct arguments with length. Otherwise, strings containing a '\0'
62   // character will be truncated.
63   // https://crbug.com/1348679
64 
65   std::unique_ptr<OptRecordRdata::UnknownOpt> opt0 =
66       OptRecordRdata::UnknownOpt::CreateForTesting(1,
67                                                    std::string("\xde\xad", 2));
68   std::unique_ptr<OptRecordRdata::UnknownOpt> opt1 =
69       OptRecordRdata::UnknownOpt::CreateForTesting(
70           255, std::string("\xde\xad\xbe\xef", 4));
71 
72   ASSERT_EQ(*(rdata_obj->GetOpts()[0]), *(opt0.get()));
73   ASSERT_EQ(*(rdata_obj->GetOpts()[1]), *(opt1.get()));
74 }
75 
TEST(OptRecordRdataTest,ParseOptRecordWithShorterSizeThanData)76 TEST(OptRecordRdataTest, ParseOptRecordWithShorterSizeThanData) {
77   // This is just the rdata portion of an OPT record, rather than a complete
78   // record.
79   const uint8_t rdata[] = {
80       0x00, 0xFF,             // OPT code
81       0x00, 0x02,             // OPT data size (incorrect, should be 4)
82       0xDE, 0xAD, 0xBE, 0xEF  // OPT data
83   };
84 
85   DnsRecordParser parser(rdata, sizeof(rdata), 0, /*num_records=*/0);
86   std::string_view rdata_strpiece = MakeStringPiece(rdata, sizeof(rdata));
87 
88   std::unique_ptr<OptRecordRdata> rdata_obj =
89       OptRecordRdata::Create(rdata_strpiece);
90   ASSERT_THAT(rdata_obj, IsNull());
91 }
92 
TEST(OptRecordRdataTest,ParseOptRecordWithLongerSizeThanData)93 TEST(OptRecordRdataTest, ParseOptRecordWithLongerSizeThanData) {
94   // This is just the rdata portion of an OPT record, rather than a complete
95   // record.
96   const uint8_t rdata[] = {
97       0x00, 0xFF,  // OPT code
98       0x00, 0x04,  // OPT data size (incorrect, should be 4)
99       0xDE, 0xAD   // OPT data
100   };
101 
102   DnsRecordParser parser(rdata, sizeof(rdata), 0, /*num_records=*/0);
103   std::string_view rdata_strpiece = MakeStringPiece(rdata, sizeof(rdata));
104 
105   std::unique_ptr<OptRecordRdata> rdata_obj =
106       OptRecordRdata::Create(rdata_strpiece);
107   ASSERT_THAT(rdata_obj, IsNull());
108 }
109 
TEST(OptRecordRdataTest,CreateEdeOpt)110 TEST(OptRecordRdataTest, CreateEdeOpt) {
111   OptRecordRdata::EdeOpt opt0(22, std::string("Don Quixote"));
112 
113   ASSERT_EQ(opt0.data(), std::string("\x00\x16"
114                                      "Don Quixote",
115                                      13));
116   ASSERT_EQ(opt0.info_code(), 22u);
117   ASSERT_EQ(opt0.extra_text(), std::string("Don Quixote"));
118 
119   std::unique_ptr<OptRecordRdata::EdeOpt> opt1 =
120       OptRecordRdata::EdeOpt::Create(std::string("\x00\x08"
121                                                  "Manhattan",
122                                                  11));
123 
124   ASSERT_EQ(opt1->data(), std::string("\x00\x08"
125                                       "Manhattan",
126                                       11));
127   ASSERT_EQ(opt1->info_code(), 8u);
128   ASSERT_EQ(opt1->extra_text(), std::string("Manhattan"));
129 }
130 
TEST(OptRecordRdataTest,TestEdeInfoCode)131 TEST(OptRecordRdataTest, TestEdeInfoCode) {
132   std::unique_ptr<OptRecordRdata::EdeOpt> edeOpt0 =
133       std::make_unique<OptRecordRdata::EdeOpt>(0, "bullettrain");
134   std::unique_ptr<OptRecordRdata::EdeOpt> edeOpt1 =
135       std::make_unique<OptRecordRdata::EdeOpt>(27, "ferrari");
136   std::unique_ptr<OptRecordRdata::EdeOpt> edeOpt2 =
137       std::make_unique<OptRecordRdata::EdeOpt>(28, "sukrit ganesh");
138   ASSERT_EQ(edeOpt0->GetEnumFromInfoCode(),
139             OptRecordRdata::EdeOpt::EdeInfoCode::kOtherError);
140   ASSERT_EQ(
141       edeOpt1->GetEnumFromInfoCode(),
142       OptRecordRdata::EdeOpt::EdeInfoCode::kUnsupportedNsec3IterationsValue);
143   ASSERT_EQ(edeOpt2->GetEnumFromInfoCode(),
144             OptRecordRdata::EdeOpt::EdeInfoCode::kUnrecognizedErrorCode);
145   ASSERT_EQ(OptRecordRdata::EdeOpt::GetEnumFromInfoCode(15),
146             OptRecordRdata::EdeOpt::kBlocked);
147 }
148 
149 // Test that an Opt EDE record is parsed correctly
TEST(OptRecordRdataTest,ParseEdeOptRecords)150 TEST(OptRecordRdataTest, ParseEdeOptRecords) {
151   const uint8_t rdata[] = {
152       // First OPT (non-EDE record)
153       0x00, 0x06,              // OPT code (6)
154       0x00, 0x04,              // OPT data size (4)
155       0xB0, 0xBA, 0xFE, 0x77,  // OPT data (Boba Fett)
156 
157       // Second OPT (EDE record)
158       0x00, 0x0F,     // OPT code (15 for EDE)
159       0x00, 0x05,     // OPT data size (info code + extra text)
160       0x00, 0x0D,     // EDE info code (13 for Cached Error)
161       'M', 'T', 'A',  // UTF-8 EDE extra text ("MTA")
162 
163       // Third OPT (EDE record)
164       0x00, 0x0F,         // OPT code (15 for EDE)
165       0x00, 0x06,         // OPT data size (info code + extra text)
166       0x00, 0x10,         // EDE info code (16 for Censored)
167       'M', 'B', 'T', 'A'  // UTF-8 EDE extra text ("MBTA")
168   };
169 
170   std::string_view rdata_strpiece = MakeStringPiece(rdata, sizeof(rdata));
171   std::unique_ptr<OptRecordRdata> rdata_obj =
172       OptRecordRdata::Create(rdata_strpiece);
173 
174   // Test Size of Query
175   ASSERT_THAT(rdata_obj, NotNull());
176   ASSERT_EQ(rdata_obj->OptCount(), 3u);
177 
178   // Test Unknown Opt
179   std::unique_ptr<OptRecordRdata::UnknownOpt> opt0 =
180       OptRecordRdata::UnknownOpt::CreateForTesting(
181           6, std::string("\xb0\xba\xfe\x77", 4));
182 
183   ASSERT_THAT(rdata_obj->GetOpts(), SizeIs(3));
184   ASSERT_EQ(*rdata_obj->GetOpts()[0], *opt0.get());
185 
186   // Test EDE
187   OptRecordRdata::EdeOpt edeOpt0(13, std::string("MTA", 3));
188   OptRecordRdata::EdeOpt edeOpt1(16, std::string("MBTA", 4));
189 
190   ASSERT_THAT(rdata_obj->GetEdeOpts(), SizeIs(2));
191   ASSERT_EQ(*rdata_obj->GetEdeOpts()[0], edeOpt0);
192   ASSERT_EQ(*rdata_obj->GetEdeOpts()[1], edeOpt1);
193 
194   // Check that member variables are alright
195   ASSERT_EQ(rdata_obj->GetEdeOpts()[0]->data(), edeOpt0.data());
196   ASSERT_EQ(rdata_obj->GetEdeOpts()[1]->data(), edeOpt1.data());
197 
198   ASSERT_EQ(rdata_obj->GetEdeOpts()[0]->extra_text(), std::string("MTA", 3));
199   ASSERT_EQ(rdata_obj->GetEdeOpts()[1]->extra_text(), std::string("MBTA", 4));
200 
201   ASSERT_EQ(rdata_obj->GetEdeOpts()[0]->info_code(), edeOpt0.info_code());
202   ASSERT_EQ(rdata_obj->GetEdeOpts()[1]->info_code(), edeOpt1.info_code());
203 }
204 
205 // Test the Opt equality operator (and its subclasses as well)
TEST(OptRecordRdataTest,OptEquality)206 TEST(OptRecordRdataTest, OptEquality) {
207   // `rdata_obj0` second opt has extra text "BIOS"
208   // `rdata_obj1` second opt has extra text "BIOO"
209   // Note: rdata_obj0 and rdata_obj1 have 2 common Opts and 1 different one.
210   OptRecordRdata rdata_obj0;
211   rdata_obj0.AddOpt(OptRecordRdata::UnknownOpt::CreateForTesting(
212       6, std::string("\xb0\xba\xfe\x77", 4)));
213   rdata_obj0.AddOpt(
214       std::make_unique<OptRecordRdata::EdeOpt>(13, std::string("USA", 3)));
215   rdata_obj0.AddOpt(
216       std::make_unique<OptRecordRdata::EdeOpt>(16, std::string("BIOS", 4)));
217   ASSERT_EQ(rdata_obj0.OptCount(), 3u);
218 
219   OptRecordRdata rdata_obj1;
220   rdata_obj1.AddOpt(OptRecordRdata::UnknownOpt::CreateForTesting(
221       6, std::string("\xb0\xba\xfe\x77", 4)));
222   rdata_obj1.AddOpt(
223       std::make_unique<OptRecordRdata::EdeOpt>(13, std::string("USA", 3)));
224   rdata_obj1.AddOpt(
225       std::make_unique<OptRecordRdata::EdeOpt>(16, std::string("BIOO", 4)));
226   ASSERT_EQ(rdata_obj1.OptCount(), 3u);
227 
228   auto opts0 = rdata_obj0.GetOpts();
229   auto opts1 = rdata_obj1.GetOpts();
230   auto edeOpts0 = rdata_obj0.GetEdeOpts();
231   auto edeOpts1 = rdata_obj1.GetEdeOpts();
232   ASSERT_THAT(opts0, SizeIs(3));
233   ASSERT_THAT(opts1, SizeIs(3));
234   ASSERT_THAT(edeOpts0, SizeIs(2));
235   ASSERT_THAT(edeOpts1, SizeIs(2));
236 
237   // Opt equality
238   ASSERT_EQ(*opts0[0], *opts1[0]);
239   ASSERT_EQ(*opts0[1], *opts1[1]);
240   ASSERT_NE(*opts0[0], *opts1[1]);
241 
242   // EdeOpt equality
243   ASSERT_EQ(*edeOpts0[0], *edeOpts1[0]);
244   ASSERT_NE(*edeOpts0[1], *edeOpts1[1]);
245 
246   // EdeOpt equality with Opt
247   ASSERT_EQ(*edeOpts0[0], *opts1[1]);
248   ASSERT_NE(*edeOpts0[1], *opts1[2]);
249 
250   // Opt equality with EdeOpt
251   // Should work if raw data matches
252   ASSERT_EQ(*opts1[1], *edeOpts0[0]);
253   ASSERT_NE(*opts1[2], *edeOpts0[1]);
254 }
255 
256 // Check that rdata is null if the data section of an EDE record is too small
257 // (<2 bytes)
TEST(OptRecordRdataTest,EdeRecordTooSmall)258 TEST(OptRecordRdataTest, EdeRecordTooSmall) {
259   const uint8_t rdata[] = {
260       0x00, 0x0F,  // OPT code (15 for EDE)
261       0x00, 0x01,  // OPT data size (info code + extra text)
262       0x00         // Fragment of Info Code
263   };
264 
265   std::string_view rdata_strpiece = MakeStringPiece(rdata, sizeof(rdata));
266   std::unique_ptr<OptRecordRdata> rdata_obj =
267       OptRecordRdata::Create(rdata_strpiece);
268   ASSERT_THAT(rdata_obj, IsNull());
269 }
270 
271 // Check that an EDE record with no extra text is parsed correctly.
TEST(OptRecordRdataTest,EdeRecordNoExtraText)272 TEST(OptRecordRdataTest, EdeRecordNoExtraText) {
273   const uint8_t rdata[] = {
274       0x00, 0x0F,  // OPT code (15 for EDE)
275       0x00, 0x02,  // OPT data size (info code + extra text)
276       0x00, 0x05   // Info Code
277   };
278 
279   std::string_view rdata_strpiece = MakeStringPiece(rdata, sizeof(rdata));
280   std::unique_ptr<OptRecordRdata> rdata_obj =
281       OptRecordRdata::Create(rdata_strpiece);
282   ASSERT_THAT(rdata_obj, NotNull());
283   ASSERT_THAT(rdata_obj->GetEdeOpts(), SizeIs(1));
284   ASSERT_EQ(rdata_obj->GetEdeOpts()[0]->data(), std::string("\x00\x05", 2));
285   ASSERT_EQ(rdata_obj->GetEdeOpts()[0]->info_code(), 5u);
286   ASSERT_EQ(rdata_obj->GetEdeOpts()[0]->extra_text(), "");
287 }
288 
289 // Check that an EDE record with non-UTF-8 fails to parse.
TEST(OptRecordRdataTest,EdeRecordExtraTextNonUTF8)290 TEST(OptRecordRdataTest, EdeRecordExtraTextNonUTF8) {
291   const uint8_t rdata[] = {
292       0x00, 0x0F,             // OPT code (15 for EDE)
293       0x00, 0x06,             // OPT data size (info code + extra text)
294       0x00, 0x05,             // Info Code
295       0xB1, 0x05, 0xF0, 0x0D  // Extra Text (non-UTF-8)
296   };
297 
298   ASSERT_FALSE(base::IsStringUTF8(std::string("\xb1\x05\xf0\x0d", 4)));
299 
300   std::string_view rdata_strpiece = MakeStringPiece(rdata, sizeof(rdata));
301   std::unique_ptr<OptRecordRdata> rdata_obj =
302       OptRecordRdata::Create(rdata_strpiece);
303   ASSERT_THAT(rdata_obj, IsNull());
304 }
305 
306 // Check that an EDE record with an unknown info code is parsed correctly.
TEST(OptRecordRdataTest,EdeRecordUnknownInfoCode)307 TEST(OptRecordRdataTest, EdeRecordUnknownInfoCode) {
308   const uint8_t rdata[] = {
309       0x00, 0x0F,                     // OPT code (15 for EDE)
310       0x00, 0x08,                     // OPT data size (info code + extra text)
311       0x00, 0x44,                     // Info Code (68 doesn't exist)
312       'B',  'O',  'S', 'T', 'O', 'N'  // Extra Text ("BOSTON")
313   };
314 
315   std::string_view rdata_strpiece = MakeStringPiece(rdata, sizeof(rdata));
316   std::unique_ptr<OptRecordRdata> rdata_obj =
317       OptRecordRdata::Create(rdata_strpiece);
318   ASSERT_THAT(rdata_obj, NotNull());
319   ASSERT_THAT(rdata_obj->GetEdeOpts(), SizeIs(1));
320   auto* opt = rdata_obj->GetEdeOpts()[0];
321   ASSERT_EQ(opt->data(), std::string("\x00\x44"
322                                      "BOSTON",
323                                      8));
324   ASSERT_EQ(opt->info_code(), 68u);
325   ASSERT_EQ(opt->extra_text(), std::string("BOSTON", 6));
326   ASSERT_EQ(opt->GetEnumFromInfoCode(),
327             OptRecordRdata::EdeOpt::EdeInfoCode::kUnrecognizedErrorCode);
328 }
329 
TEST(OptRecordRdataTest,CreatePaddingOpt)330 TEST(OptRecordRdataTest, CreatePaddingOpt) {
331   std::unique_ptr<OptRecordRdata::PaddingOpt> opt0 =
332       std::make_unique<OptRecordRdata::PaddingOpt>(12);
333 
334   ASSERT_EQ(opt0->data(), std::string(12, '\0'));
335   ASSERT_THAT(opt0->data(), SizeIs(12u));
336 
337   std::unique_ptr<OptRecordRdata::PaddingOpt> opt1 =
338       std::make_unique<OptRecordRdata::PaddingOpt>("MASSACHUSETTS");
339 
340   ASSERT_EQ(opt1->data(), std::string("MASSACHUSETTS"));
341   ASSERT_THAT(opt1->data(), SizeIs(13u));
342 }
343 
TEST(OptRecordRdataTest,ParsePaddingOpt)344 TEST(OptRecordRdataTest, ParsePaddingOpt) {
345   const uint8_t rdata[] = {
346       // First OPT
347       0x00, 0x0C,  // OPT code
348       0x00, 0x07,  // OPT data size
349       0xB0, 0x03,  // OPT data padding (Book of Boba Fett)
350       0x0F, 0xB0, 0xBA, 0xFE, 0x77,
351   };
352 
353   std::string_view rdata_strpiece = MakeStringPiece(rdata, sizeof(rdata));
354   std::unique_ptr<OptRecordRdata> rdata_obj =
355       OptRecordRdata::Create(rdata_strpiece);
356 
357   ASSERT_THAT(rdata_obj, NotNull());
358   ASSERT_EQ(rdata_obj->OptCount(), 1u);
359   ASSERT_THAT(rdata_obj->GetOpts(), SizeIs(1));
360   ASSERT_THAT(rdata_obj->GetPaddingOpts(), SizeIs(1));
361 
362   // Check elements
363   OptRecordRdata::PaddingOpt opt0(
364       std::string("\xb0\x03\x0f\xb0\xba\xfe\x77", 7));
365 
366   ASSERT_EQ(*(rdata_obj->GetOpts()[0]), opt0);
367   ASSERT_EQ(*(rdata_obj->GetPaddingOpts()[0]), opt0);
368   ASSERT_THAT(opt0.data(), SizeIs(7u));
369 }
370 
TEST(OptRecordRdataTest,AddOptToOptRecord)371 TEST(OptRecordRdataTest, AddOptToOptRecord) {
372   // This is just the rdata portion of an OPT record, rather than a complete
373   // record.
374   const uint8_t expected_rdata[] = {
375       0x00, 0xFF,             // OPT code
376       0x00, 0x04,             // OPT data size
377       0xDE, 0xAD, 0xBE, 0xEF  // OPT data
378   };
379 
380   OptRecordRdata rdata;
381   rdata.AddOpt(OptRecordRdata::UnknownOpt::CreateForTesting(
382       255, std::string("\xde\xad\xbe\xef", 4)));
383   EXPECT_THAT(rdata.buf(), ElementsAreArray(expected_rdata));
384 }
385 
386 // Test the OptRecordRdata equality operator.
387 // Equality must be order sensitive. If Opts are same but inserted in different
388 // order, test will fail epically.
TEST(OptRecordRdataTest,EqualityIsOptOrderSensitive)389 TEST(OptRecordRdataTest, EqualityIsOptOrderSensitive) {
390   // Control rdata
391   OptRecordRdata rdata_obj0;
392   rdata_obj0.AddOpt(OptRecordRdata::UnknownOpt::CreateForTesting(
393       1, std::string("\xb0\xba\xfe\x77", 4)));
394   rdata_obj0.AddOpt(OptRecordRdata::UnknownOpt::CreateForTesting(
395       2, std::string("\xb1\x05\xf0\x0d", 4)));
396   ASSERT_EQ(rdata_obj0.OptCount(), 2u);
397 
398   // Same as `rdata_obj0`
399   OptRecordRdata rdata_obj1;
400   rdata_obj1.AddOpt(OptRecordRdata::UnknownOpt::CreateForTesting(
401       1, std::string("\xb0\xba\xfe\x77", 4)));
402   rdata_obj1.AddOpt(OptRecordRdata::UnknownOpt::CreateForTesting(
403       2, std::string("\xb1\x05\xf0\x0d", 4)));
404   ASSERT_EQ(rdata_obj1.OptCount(), 2u);
405 
406   ASSERT_EQ(rdata_obj0, rdata_obj1);
407 
408   // Same contents as `rdata_obj0` & `rdata_obj1`, but different order
409   OptRecordRdata rdata_obj2;
410   rdata_obj2.AddOpt(OptRecordRdata::UnknownOpt::CreateForTesting(
411       2, std::string("\xb1\x05\xf0\x0d", 4)));
412   rdata_obj2.AddOpt(OptRecordRdata::UnknownOpt::CreateForTesting(
413       1, std::string("\xb0\xba\xfe\x77", 4)));
414   ASSERT_EQ(rdata_obj2.OptCount(), 2u);
415 
416   // Order matters! obj0 and obj2 contain same Opts but in different order.
417   ASSERT_FALSE(rdata_obj0.IsEqual(&rdata_obj2));
418 
419   // Contains only `rdata_obj0` first opt
420   // 2nd opt is added later
421   OptRecordRdata rdata_obj3;
422   rdata_obj3.AddOpt(OptRecordRdata::UnknownOpt::CreateForTesting(
423       1, std::string("\xb0\xba\xfe\x77", 4)));
424   ASSERT_EQ(rdata_obj3.OptCount(), 1u);
425 
426   ASSERT_FALSE(rdata_obj0.IsEqual(&rdata_obj3));
427 
428   rdata_obj3.AddOpt(OptRecordRdata::UnknownOpt::CreateForTesting(
429       2, std::string("\xb1\x05\xf0\x0d", 4)));
430 
431   ASSERT_TRUE(rdata_obj0.IsEqual(&rdata_obj3));
432 
433   // Test == operator
434   ASSERT_TRUE(rdata_obj0 == rdata_obj1);
435   ASSERT_EQ(rdata_obj0, rdata_obj1);
436   ASSERT_NE(rdata_obj0, rdata_obj2);
437 }
438 
439 // Test that GetOpts() follows specified order.
440 // Sort by key, then by insertion order.
TEST(OptRecordRdataTest,TestGetOptsOrder)441 TEST(OptRecordRdataTest, TestGetOptsOrder) {
442   OptRecordRdata rdata_obj0;
443   rdata_obj0.AddOpt(OptRecordRdata::UnknownOpt::CreateForTesting(
444       10, std::string("\x33\x33", 2)));
445   rdata_obj0.AddOpt(OptRecordRdata::UnknownOpt::CreateForTesting(
446       5, std::string("\x11\x11", 2)));
447   rdata_obj0.AddOpt(OptRecordRdata::UnknownOpt::CreateForTesting(
448       5, std::string("\x22\x22", 2)));
449   ASSERT_EQ(rdata_obj0.OptCount(), 3u);
450 
451   auto opts = rdata_obj0.GetOpts();
452   ASSERT_EQ(opts[0]->data(),
453             std::string("\x11\x11", 2));  // opt code 5 (inserted first)
454   ASSERT_EQ(opts[1]->data(),
455             std::string("\x22\x22", 2));  // opt code 5 (inserted second)
456   ASSERT_EQ(opts[2]->data(), std::string("\x33\x33", 2));  // opt code 10
457 }
458 
459 }  // namespace
460 }  // namespace net
461