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