OLD | NEW |
| (Empty) |
1 // Copyright 2016 The Chromium Authors. All rights reserved. | |
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/http2/decoder/http2_structure_decoder.h" | |
6 | |
7 // Tests decoding all of the fixed size HTTP/2 structures (i.e. those defined in | |
8 // net/http2/http2_structures.h) using Http2StructureDecoder, which handles | |
9 // buffering of structures split across input buffer boundaries, and in turn | |
10 // uses DoDecode when it has all of a structure in a contiguous buffer. | |
11 | |
12 // NOTE: This tests the first pair of Start and Resume, which don't take | |
13 // a remaining_payload parameter. The other pair are well tested via the | |
14 // payload decoder tests, though... | |
15 // TODO(jamessynge): Create type parameterized tests for Http2StructureDecoder | |
16 // where the type is the type of structure, and with testing of both pairs of | |
17 // Start and Resume methods; note that it appears that the first pair will be | |
18 // used only for Http2FrameHeader, and the other pair only for structures in the | |
19 // frame payload. | |
20 | |
21 #include <stddef.h> | |
22 #include <string> | |
23 | |
24 #include "base/bind.h" | |
25 #include "base/bind_helpers.h" | |
26 #include "base/logging.h" | |
27 #include "base/strings/string_piece.h" | |
28 #include "net/http2/decoder/decode_buffer.h" | |
29 #include "net/http2/decoder/decode_status.h" | |
30 #include "net/http2/http2_constants.h" | |
31 #include "net/http2/http2_structures_test_util.h" | |
32 #include "net/http2/tools/failure.h" | |
33 #include "net/http2/tools/http2_frame_builder.h" | |
34 #include "net/http2/tools/random_decoder_test.h" | |
35 #include "testing/gtest/include/gtest/gtest.h" | |
36 | |
37 using ::testing::AssertionFailure; | |
38 using ::testing::AssertionResult; | |
39 using ::testing::AssertionSuccess; | |
40 using base::StringPiece; | |
41 using std::string; | |
42 | |
43 namespace net { | |
44 namespace test { | |
45 namespace { | |
46 const bool kMayReturnZeroOnFirst = false; | |
47 | |
48 template <class S> | |
49 string SerializeStructure(const S& s) { | |
50 Http2FrameBuilder fb; | |
51 fb.Append(s); | |
52 EXPECT_EQ(S::EncodedSize(), fb.size()); | |
53 return fb.buffer(); | |
54 } | |
55 | |
56 template <class S> | |
57 class Http2StructureDecoderTest : public RandomDecoderTest { | |
58 protected: | |
59 typedef S Structure; | |
60 | |
61 Http2StructureDecoderTest() { | |
62 // IF the test adds more data after the encoded structure, stop as | |
63 // soon as the structure is decoded. | |
64 stop_decode_on_done_ = true; | |
65 } | |
66 | |
67 DecodeStatus StartDecoding(DecodeBuffer* b) override { | |
68 // Overwrite the current contents of |structure_|, in to which we'll | |
69 // decode the buffer, so that we can be confident that we really decoded | |
70 // the structure every time. | |
71 structure_.~S(); | |
72 new (&structure_) S; | |
73 uint32_t old_remaining = b->Remaining(); | |
74 if (structure_decoder_.Start(&structure_, b)) { | |
75 EXPECT_EQ(old_remaining - S::EncodedSize(), b->Remaining()); | |
76 ++fast_decode_count_; | |
77 return DecodeStatus::kDecodeDone; | |
78 } else { | |
79 EXPECT_LT(structure_decoder_.offset(), S::EncodedSize()); | |
80 EXPECT_EQ(0u, b->Remaining()); | |
81 EXPECT_EQ(old_remaining - structure_decoder_.offset(), b->Remaining()); | |
82 ++incomplete_start_count_; | |
83 return DecodeStatus::kDecodeInProgress; | |
84 } | |
85 } | |
86 | |
87 DecodeStatus ResumeDecoding(DecodeBuffer* b) override { | |
88 uint32_t old_offset = structure_decoder_.offset(); | |
89 EXPECT_LT(old_offset, S::EncodedSize()); | |
90 uint32_t avail = b->Remaining(); | |
91 if (structure_decoder_.Resume(&structure_, b)) { | |
92 EXPECT_LE(S::EncodedSize(), old_offset + avail); | |
93 EXPECT_EQ(b->Remaining(), avail - (S::EncodedSize() - old_offset)); | |
94 ++slow_decode_count_; | |
95 return DecodeStatus::kDecodeDone; | |
96 } else { | |
97 EXPECT_LT(structure_decoder_.offset(), S::EncodedSize()); | |
98 EXPECT_EQ(0u, b->Remaining()); | |
99 EXPECT_GT(S::EncodedSize(), old_offset + avail); | |
100 ++incomplete_resume_count_; | |
101 return DecodeStatus::kDecodeInProgress; | |
102 } | |
103 } | |
104 | |
105 AssertionResult ValidatorForDecodeLeadingStructure(const S* expected, | |
106 const DecodeBuffer& db, | |
107 DecodeStatus status) { | |
108 VERIFY_EQ(*expected, structure_); | |
109 return AssertionSuccess(); | |
110 } | |
111 | |
112 // Fully decodes the Structure at the start of data, and confirms it matches | |
113 // *expected (if provided). | |
114 AssertionResult DecodeLeadingStructure(const S* expected, StringPiece data) { | |
115 VERIFY_LE(S::EncodedSize(), data.size()); | |
116 DecodeBuffer original(data); | |
117 | |
118 // The validator is called after each of the several times that the input | |
119 // DecodeBuffer is decoded, each with a different segmentation of the input. | |
120 // Validate that structure_ matches the expected value, if provided. | |
121 Validator validator = | |
122 (expected == nullptr) | |
123 ? base::Bind(&SucceedingValidator) | |
124 : base::Bind(&Http2StructureDecoderTest:: | |
125 ValidatorForDecodeLeadingStructure, | |
126 base::Unretained(this), expected); | |
127 | |
128 // Before that, validate that decoding is done and that we've advanced | |
129 // the cursor the expected amount. | |
130 Validator wrapped_validator = | |
131 ValidateDoneAndOffset(S::EncodedSize(), validator); | |
132 | |
133 // Decode several times, with several segmentations of the input buffer. | |
134 fast_decode_count_ = 0; | |
135 slow_decode_count_ = 0; | |
136 incomplete_start_count_ = 0; | |
137 incomplete_resume_count_ = 0; | |
138 VERIFY_SUCCESS(DecodeAndValidateSeveralWays( | |
139 &original, kMayReturnZeroOnFirst, wrapped_validator)); | |
140 VERIFY_FALSE(HasFailure()); | |
141 VERIFY_EQ(S::EncodedSize(), structure_decoder_.offset()); | |
142 VERIFY_EQ(S::EncodedSize(), original.Offset()); | |
143 VERIFY_LT(0u, fast_decode_count_); | |
144 VERIFY_LT(0u, slow_decode_count_); | |
145 VERIFY_LT(0u, incomplete_start_count_); | |
146 | |
147 // If the structure is large enough so that SelectZeroOrOne will have | |
148 // caused Resume to return false, check that occurred. | |
149 if (S::EncodedSize() >= 2) { | |
150 VERIFY_LE(0u, incomplete_resume_count_); | |
151 } else { | |
152 VERIFY_EQ(0u, incomplete_resume_count_); | |
153 } | |
154 if (expected != nullptr) { | |
155 DVLOG(1) << "DecodeLeadingStructure expected: " << *expected; | |
156 DVLOG(1) << "DecodeLeadingStructure actual: " << structure_; | |
157 VERIFY_EQ(*expected, structure_); | |
158 } | |
159 return AssertionSuccess(); | |
160 } | |
161 | |
162 template <size_t N> | |
163 AssertionResult DecodeLeadingStructure(const char (&data)[N]) { | |
164 VERIFY_AND_RETURN_SUCCESS( | |
165 DecodeLeadingStructure(nullptr, StringPiece(data, N))); | |
166 } | |
167 | |
168 // Encode the structure |in_s| into bytes, then decode the bytes | |
169 // and validate that the decoder produced the same field values. | |
170 AssertionResult EncodeThenDecode(const S& in_s) { | |
171 string bytes = SerializeStructure(in_s); | |
172 VERIFY_EQ(S::EncodedSize(), bytes.size()); | |
173 VERIFY_AND_RETURN_SUCCESS(DecodeLeadingStructure(&in_s, bytes)); | |
174 } | |
175 | |
176 // Repeatedly fill a structure with random but valid contents, encode it, then | |
177 // decode it, and finally validate that the decoded structure matches the | |
178 // random input. Lather-rinse-and-repeat. | |
179 AssertionResult TestDecodingRandomizedStructures(size_t count) { | |
180 for (size_t i = 0; i < count; ++i) { | |
181 Structure input; | |
182 Randomize(&input, RandomPtr()); | |
183 VERIFY_SUCCESS(EncodeThenDecode(input)); | |
184 } | |
185 return AssertionSuccess(); | |
186 } | |
187 | |
188 AssertionResult TestDecodingRandomizedStructures() { | |
189 VERIFY_SUCCESS(TestDecodingRandomizedStructures(100)); | |
190 return AssertionSuccess(); | |
191 } | |
192 | |
193 uint32_t decode_offset_ = 0; | |
194 S structure_; | |
195 Http2StructureDecoder structure_decoder_; | |
196 size_t fast_decode_count_ = 0; | |
197 size_t slow_decode_count_ = 0; | |
198 size_t incomplete_start_count_ = 0; | |
199 size_t incomplete_resume_count_ = 0; | |
200 }; | |
201 | |
202 class Http2FrameHeaderDecoderTest | |
203 : public Http2StructureDecoderTest<Http2FrameHeader> {}; | |
204 | |
205 TEST_F(Http2FrameHeaderDecoderTest, DecodesLiteral) { | |
206 { | |
207 // Realistic input. | |
208 const char kData[] = { | |
209 0x00, 0x00, 0x05, // Payload length: 5 | |
210 0x01, // Frame type: HEADERS | |
211 0x08, // Flags: PADDED | |
212 0x00, 0x00, 0x00, 0x01, // Stream ID: 1 | |
213 0x04, // Padding length: 4 | |
214 0x00, 0x00, 0x00, 0x00, // Padding bytes | |
215 }; | |
216 ASSERT_TRUE(DecodeLeadingStructure(kData)); | |
217 EXPECT_EQ(5u, structure_.payload_length); | |
218 EXPECT_EQ(Http2FrameType::HEADERS, structure_.type); | |
219 EXPECT_EQ(Http2FrameFlag::FLAG_PADDED, structure_.flags); | |
220 EXPECT_EQ(1u, structure_.stream_id); | |
221 } | |
222 { | |
223 // Unlikely input. | |
224 const char kData[] = { | |
225 0xffu, 0xffu, 0xffu, // Payload length: uint24 max | |
226 0xffu, // Frame type: Unknown | |
227 0xffu, // Flags: Unknown/All | |
228 0xffu, 0xffu, 0xffu, 0xffu, // Stream ID: uint31 max, plus R-bit | |
229 }; | |
230 ASSERT_TRUE(DecodeLeadingStructure(kData)); | |
231 EXPECT_EQ((1u << 24) - 1u, structure_.payload_length); | |
232 EXPECT_EQ(static_cast<Http2FrameType>(255), structure_.type); | |
233 EXPECT_EQ(255, structure_.flags); | |
234 EXPECT_EQ(0x7FFFFFFFu, structure_.stream_id); | |
235 } | |
236 } | |
237 | |
238 TEST_F(Http2FrameHeaderDecoderTest, DecodesRandomized) { | |
239 TestDecodingRandomizedStructures(); | |
240 } | |
241 | |
242 //------------------------------------------------------------------------------ | |
243 | |
244 class Http2PriorityFieldsDecoderTest | |
245 : public Http2StructureDecoderTest<Http2PriorityFields> {}; | |
246 | |
247 TEST_F(Http2PriorityFieldsDecoderTest, DecodesLiteral) { | |
248 { | |
249 const char kData[] = { | |
250 0x80u, 0x00, 0x00, 0x05, // Exclusive (yes) and Dependency (5) | |
251 0xffu, // Weight: 256 (after adding 1) | |
252 }; | |
253 ASSERT_TRUE(DecodeLeadingStructure(kData)); | |
254 EXPECT_EQ(5u, structure_.stream_dependency); | |
255 EXPECT_EQ(256u, structure_.weight); | |
256 EXPECT_EQ(true, structure_.is_exclusive); | |
257 } | |
258 { | |
259 const char kData[] = { | |
260 0x7fu, 0xffu, | |
261 0xffu, 0xffu, // Exclusive (no) and Dependency (0x7fffffff) | |
262 0x00u, // Weight: 1 (after adding 1) | |
263 }; | |
264 ASSERT_TRUE(DecodeLeadingStructure(kData)); | |
265 EXPECT_EQ(StreamIdMask(), structure_.stream_dependency); | |
266 EXPECT_EQ(1u, structure_.weight); | |
267 EXPECT_FALSE(structure_.is_exclusive); | |
268 } | |
269 } | |
270 | |
271 TEST_F(Http2PriorityFieldsDecoderTest, DecodesRandomized) { | |
272 TestDecodingRandomizedStructures(); | |
273 } | |
274 | |
275 //------------------------------------------------------------------------------ | |
276 | |
277 class Http2RstStreamFieldsDecoderTest | |
278 : public Http2StructureDecoderTest<Http2RstStreamFields> {}; | |
279 | |
280 TEST_F(Http2RstStreamFieldsDecoderTest, DecodesLiteral) { | |
281 { | |
282 const char kData[] = { | |
283 0x00, 0x00, 0x00, 0x01, // Error: PROTOCOL_ERROR | |
284 }; | |
285 ASSERT_TRUE(DecodeLeadingStructure(kData)); | |
286 EXPECT_TRUE(structure_.IsSupportedErrorCode()); | |
287 EXPECT_EQ(Http2ErrorCode::PROTOCOL_ERROR, structure_.error_code); | |
288 } | |
289 { | |
290 const char kData[] = { | |
291 0xffu, 0xffu, 0xffu, 0xffu, // Error: max uint32 (Unknown error code) | |
292 }; | |
293 ASSERT_TRUE(DecodeLeadingStructure(kData)); | |
294 EXPECT_FALSE(structure_.IsSupportedErrorCode()); | |
295 EXPECT_EQ(static_cast<Http2ErrorCode>(0xffffffff), structure_.error_code); | |
296 } | |
297 } | |
298 | |
299 TEST_F(Http2RstStreamFieldsDecoderTest, DecodesRandomized) { | |
300 TestDecodingRandomizedStructures(); | |
301 } | |
302 | |
303 //------------------------------------------------------------------------------ | |
304 | |
305 class Http2SettingFieldsDecoderTest | |
306 : public Http2StructureDecoderTest<Http2SettingFields> {}; | |
307 | |
308 TEST_F(Http2SettingFieldsDecoderTest, DecodesLiteral) { | |
309 { | |
310 const char kData[] = { | |
311 0x00, 0x01, // Setting: HEADER_TABLE_SIZE | |
312 0x00, 0x00, 0x40, 0x00, // Value: 16K | |
313 }; | |
314 ASSERT_TRUE(DecodeLeadingStructure(kData)); | |
315 EXPECT_TRUE(structure_.IsSupportedParameter()); | |
316 EXPECT_EQ(Http2SettingsParameter::HEADER_TABLE_SIZE, structure_.parameter); | |
317 EXPECT_EQ(1u << 14, structure_.value); | |
318 } | |
319 { | |
320 const char kData[] = { | |
321 0x00, 0x00, // Setting: Unknown (0) | |
322 0xffu, 0xffu, 0xffu, 0xffu, // Value: max uint32 | |
323 }; | |
324 ASSERT_TRUE(DecodeLeadingStructure(kData)); | |
325 EXPECT_FALSE(structure_.IsSupportedParameter()); | |
326 EXPECT_EQ(static_cast<Http2SettingsParameter>(0), structure_.parameter); | |
327 } | |
328 } | |
329 | |
330 TEST_F(Http2SettingFieldsDecoderTest, DecodesRandomized) { | |
331 TestDecodingRandomizedStructures(); | |
332 } | |
333 | |
334 //------------------------------------------------------------------------------ | |
335 | |
336 class Http2PushPromiseFieldsDecoderTest | |
337 : public Http2StructureDecoderTest<Http2PushPromiseFields> {}; | |
338 | |
339 TEST_F(Http2PushPromiseFieldsDecoderTest, DecodesLiteral) { | |
340 { | |
341 const char kData[] = { | |
342 0x00, 0x01, 0x8au, 0x92u, // Promised Stream ID: 101010 | |
343 }; | |
344 ASSERT_TRUE(DecodeLeadingStructure(kData)); | |
345 EXPECT_EQ(101010u, structure_.promised_stream_id); | |
346 } | |
347 { | |
348 // Promised stream id has R-bit (reserved for future use) set, which | |
349 // should be cleared by the decoder. | |
350 const char kData[] = { | |
351 0xffu, 0xffu, 0xffu, 0xffu, // Promised Stream ID: max uint31 and R-bit | |
352 }; | |
353 ASSERT_TRUE(DecodeLeadingStructure(kData)); | |
354 EXPECT_EQ(StreamIdMask(), structure_.promised_stream_id); | |
355 } | |
356 } | |
357 | |
358 TEST_F(Http2PushPromiseFieldsDecoderTest, DecodesRandomized) { | |
359 TestDecodingRandomizedStructures(); | |
360 } | |
361 | |
362 //------------------------------------------------------------------------------ | |
363 | |
364 class Http2PingFieldsDecoderTest | |
365 : public Http2StructureDecoderTest<Http2PingFields> {}; | |
366 | |
367 TEST_F(Http2PingFieldsDecoderTest, DecodesLiteral) { | |
368 { | |
369 // Each byte is different, so can detect if order changed. | |
370 const char kData[] = { | |
371 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, | |
372 }; | |
373 ASSERT_TRUE(DecodeLeadingStructure(kData)); | |
374 EXPECT_EQ(StringPiece(kData, 8), ToStringPiece(structure_.opaque_data)); | |
375 } | |
376 { | |
377 // All zeros, detect problems handling NULs. | |
378 const char kData[] = { | |
379 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
380 }; | |
381 ASSERT_TRUE(DecodeLeadingStructure(kData)); | |
382 EXPECT_EQ(StringPiece(kData, 8), ToStringPiece(structure_.opaque_data)); | |
383 } | |
384 { | |
385 const char kData[] = { | |
386 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, | |
387 }; | |
388 ASSERT_TRUE(DecodeLeadingStructure(kData)); | |
389 EXPECT_EQ(StringPiece(kData, 8), ToStringPiece(structure_.opaque_data)); | |
390 } | |
391 } | |
392 | |
393 TEST_F(Http2PingFieldsDecoderTest, DecodesRandomized) { | |
394 TestDecodingRandomizedStructures(); | |
395 } | |
396 | |
397 //------------------------------------------------------------------------------ | |
398 | |
399 class Http2GoAwayFieldsDecoderTest | |
400 : public Http2StructureDecoderTest<Http2GoAwayFields> {}; | |
401 | |
402 TEST_F(Http2GoAwayFieldsDecoderTest, DecodesLiteral) { | |
403 { | |
404 const char kData[] = { | |
405 0x00, 0x00, 0x00, 0x00, // Last Stream ID: 0 | |
406 0x00, 0x00, 0x00, 0x00, // Error: NO_ERROR (0) | |
407 }; | |
408 ASSERT_TRUE(DecodeLeadingStructure(kData)); | |
409 EXPECT_EQ(0u, structure_.last_stream_id); | |
410 EXPECT_TRUE(structure_.IsSupportedErrorCode()); | |
411 EXPECT_EQ(Http2ErrorCode::HTTP2_NO_ERROR, structure_.error_code); | |
412 } | |
413 { | |
414 const char kData[] = { | |
415 0x00, 0x00, 0x00, 0x01, // Last Stream ID: 1 | |
416 0x00, 0x00, 0x00, 0x0d, // Error: HTTP_1_1_REQUIRED | |
417 }; | |
418 ASSERT_TRUE(DecodeLeadingStructure(kData)); | |
419 EXPECT_EQ(1u, structure_.last_stream_id); | |
420 EXPECT_TRUE(structure_.IsSupportedErrorCode()); | |
421 EXPECT_EQ(Http2ErrorCode::HTTP_1_1_REQUIRED, structure_.error_code); | |
422 } | |
423 { | |
424 const char kData[] = { | |
425 0xffu, 0xffu, 0xffu, 0xffu, // Last Stream ID: max uint31 and R-bit | |
426 0xffu, 0xffu, 0xffu, 0xffu, // Error: max uint32 (Unknown error code) | |
427 }; | |
428 ASSERT_TRUE(DecodeLeadingStructure(kData)); | |
429 EXPECT_EQ(StreamIdMask(), structure_.last_stream_id); // No high-bit. | |
430 EXPECT_FALSE(structure_.IsSupportedErrorCode()); | |
431 EXPECT_EQ(static_cast<Http2ErrorCode>(0xffffffff), structure_.error_code); | |
432 } | |
433 } | |
434 | |
435 TEST_F(Http2GoAwayFieldsDecoderTest, DecodesRandomized) { | |
436 TestDecodingRandomizedStructures(); | |
437 } | |
438 | |
439 //------------------------------------------------------------------------------ | |
440 | |
441 class Http2WindowUpdateFieldsDecoderTest | |
442 : public Http2StructureDecoderTest<Http2WindowUpdateFields> {}; | |
443 | |
444 TEST_F(Http2WindowUpdateFieldsDecoderTest, DecodesLiteral) { | |
445 { | |
446 const char kData[] = { | |
447 0x00, 0x01, 0x00, 0x00, // Window Size Increment: 2 ^ 16 | |
448 }; | |
449 ASSERT_TRUE(DecodeLeadingStructure(kData)); | |
450 EXPECT_EQ(1u << 16, structure_.window_size_increment); | |
451 } | |
452 { | |
453 // Increment must be non-zero, but we need to be able to decode the invalid | |
454 // zero to detect it. | |
455 const char kData[] = { | |
456 0x00, 0x00, 0x00, 0x00, // Window Size Increment: 0 | |
457 }; | |
458 ASSERT_TRUE(DecodeLeadingStructure(kData)); | |
459 EXPECT_EQ(0u, structure_.window_size_increment); | |
460 } | |
461 { | |
462 // Increment has R-bit (reserved for future use) set, which | |
463 // should be cleared by the decoder. | |
464 const char kData[] = { | |
465 0xffu, 0xffu, 0xffu, | |
466 0xffu, // Window Size Increment: max uint31 and R-bit | |
467 }; | |
468 ASSERT_TRUE(DecodeLeadingStructure(kData)); | |
469 EXPECT_EQ(StreamIdMask(), structure_.window_size_increment); | |
470 } | |
471 } | |
472 | |
473 TEST_F(Http2WindowUpdateFieldsDecoderTest, DecodesRandomized) { | |
474 TestDecodingRandomizedStructures(); | |
475 } | |
476 | |
477 //------------------------------------------------------------------------------ | |
478 | |
479 class Http2AltSvcFieldsDecoderTest | |
480 : public Http2StructureDecoderTest<Http2AltSvcFields> {}; | |
481 | |
482 TEST_F(Http2AltSvcFieldsDecoderTest, DecodesLiteral) { | |
483 { | |
484 const char kData[] = { | |
485 0x00, 0x00, // Origin Length: 0 | |
486 }; | |
487 ASSERT_TRUE(DecodeLeadingStructure(kData)); | |
488 EXPECT_EQ(0, structure_.origin_length); | |
489 } | |
490 { | |
491 const char kData[] = { | |
492 0x00, 0x14, // Origin Length: 20 | |
493 }; | |
494 ASSERT_TRUE(DecodeLeadingStructure(kData)); | |
495 EXPECT_EQ(20, structure_.origin_length); | |
496 } | |
497 { | |
498 const char kData[] = { | |
499 0xffu, 0xffu, // Origin Length: uint16 max | |
500 }; | |
501 ASSERT_TRUE(DecodeLeadingStructure(kData)); | |
502 EXPECT_EQ(65535, structure_.origin_length); | |
503 } | |
504 } | |
505 | |
506 TEST_F(Http2AltSvcFieldsDecoderTest, DecodesRandomized) { | |
507 TestDecodingRandomizedStructures(); | |
508 } | |
509 | |
510 } // namespace | |
511 } // namespace test | |
512 } // namespace net | |
OLD | NEW |