OLD | NEW |
| (Empty) |
1 // Copyright 2014 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/spdy/hpack/hpack_huffman_table.h" | |
6 | |
7 #include <stdint.h> | |
8 | |
9 #include <bitset> | |
10 #include <utility> | |
11 | |
12 #include "base/logging.h" | |
13 #include "base/macros.h" | |
14 #include "net/spdy/hpack/hpack_constants.h" | |
15 #include "net/spdy/hpack/hpack_huffman_decoder.h" | |
16 #include "net/spdy/hpack/hpack_input_stream.h" | |
17 #include "net/spdy/hpack/hpack_output_stream.h" | |
18 #include "net/spdy/spdy_test_utils.h" | |
19 #include "testing/gmock/include/gmock/gmock.h" | |
20 #include "testing/gtest/include/gtest/gtest.h" | |
21 | |
22 using testing::ElementsAreArray; | |
23 using testing::Pointwise; | |
24 | |
25 namespace net { | |
26 | |
27 namespace test { | |
28 | |
29 typedef HpackHuffmanTable::DecodeEntry DecodeEntry; | |
30 typedef HpackHuffmanTable::DecodeTable DecodeTable; | |
31 | |
32 class HpackHuffmanTablePeer { | |
33 public: | |
34 explicit HpackHuffmanTablePeer(const HpackHuffmanTable& table) | |
35 : table_(table) {} | |
36 | |
37 const std::vector<uint32_t>& code_by_id() const { return table_.code_by_id_; } | |
38 const std::vector<uint8_t>& length_by_id() const { | |
39 return table_.length_by_id_; | |
40 } | |
41 const std::vector<DecodeTable>& decode_tables() const { | |
42 return table_.decode_tables_; | |
43 } | |
44 char pad_bits() const { | |
45 // Cast to match signed-ness of bits8(). | |
46 return static_cast<char>(table_.pad_bits_); | |
47 } | |
48 uint16_t failed_symbol_id() const { return table_.failed_symbol_id_; } | |
49 std::vector<DecodeEntry> decode_entries(const DecodeTable& decode_table) { | |
50 std::vector<DecodeEntry>::const_iterator begin = | |
51 table_.decode_entries_.begin() + decode_table.entries_offset; | |
52 return std::vector<DecodeEntry>(begin, begin + decode_table.size()); | |
53 } | |
54 | |
55 private: | |
56 const HpackHuffmanTable& table_; | |
57 }; | |
58 | |
59 namespace { | |
60 | |
61 // Tests of the ability to decode some canonical Huffman code, | |
62 // not just the one defined in the RFC 7541. | |
63 class GenericHuffmanTableTest : public ::testing::TestWithParam<bool> { | |
64 protected: | |
65 GenericHuffmanTableTest() : table_(), peer_(table_) {} | |
66 | |
67 SpdyString EncodeString(SpdyStringPiece input) { | |
68 SpdyString result; | |
69 HpackOutputStream output_stream; | |
70 table_.EncodeString(input, &output_stream); | |
71 | |
72 output_stream.TakeString(&result); | |
73 // Verify EncodedSize() agrees with EncodeString(). | |
74 EXPECT_EQ(result.size(), table_.EncodedSize(input)); | |
75 return result; | |
76 } | |
77 | |
78 HpackHuffmanTable table_; | |
79 HpackHuffmanTablePeer peer_; | |
80 }; | |
81 | |
82 MATCHER(DecodeEntryEq, "") { | |
83 const DecodeEntry& lhs = std::tr1::get<0>(arg); | |
84 const DecodeEntry& rhs = std::tr1::get<1>(arg); | |
85 return lhs.next_table_index == rhs.next_table_index && | |
86 lhs.length == rhs.length && lhs.symbol_id == rhs.symbol_id; | |
87 } | |
88 | |
89 uint32_t bits32(const SpdyString& bitstring) { | |
90 return std::bitset<32>(bitstring).to_ulong(); | |
91 } | |
92 char bits8(const SpdyString& bitstring) { | |
93 return static_cast<char>(std::bitset<8>(bitstring).to_ulong()); | |
94 } | |
95 | |
96 TEST_F(GenericHuffmanTableTest, InitializeEdgeCases) { | |
97 { | |
98 // Verify eight symbols can be encoded with 3 bits per symbol. | |
99 HpackHuffmanSymbol code[] = { | |
100 {bits32("00000000000000000000000000000000"), 3, 0}, | |
101 {bits32("00100000000000000000000000000000"), 3, 1}, | |
102 {bits32("01000000000000000000000000000000"), 3, 2}, | |
103 {bits32("01100000000000000000000000000000"), 3, 3}, | |
104 {bits32("10000000000000000000000000000000"), 3, 4}, | |
105 {bits32("10100000000000000000000000000000"), 3, 5}, | |
106 {bits32("11000000000000000000000000000000"), 3, 6}, | |
107 {bits32("11100000000000000000000000000000"), 8, 7}}; | |
108 HpackHuffmanTable table; | |
109 EXPECT_TRUE(table.Initialize(code, arraysize(code))); | |
110 } | |
111 { | |
112 // But using 2 bits with one symbol overflows the code. | |
113 HpackHuffmanSymbol code[] = { | |
114 {bits32("01000000000000000000000000000000"), 3, 0}, | |
115 {bits32("01100000000000000000000000000000"), 3, 1}, | |
116 {bits32("00000000000000000000000000000000"), 2, 2}, | |
117 {bits32("10000000000000000000000000000000"), 3, 3}, | |
118 {bits32("10100000000000000000000000000000"), 3, 4}, | |
119 {bits32("11000000000000000000000000000000"), 3, 5}, | |
120 {bits32("11100000000000000000000000000000"), 3, 6}, | |
121 {bits32("00000000000000000000000000000000"), 8, 7}}; // Overflow. | |
122 HpackHuffmanTable table; | |
123 EXPECT_FALSE(table.Initialize(code, arraysize(code))); | |
124 EXPECT_EQ(7, HpackHuffmanTablePeer(table).failed_symbol_id()); | |
125 } | |
126 { | |
127 // Verify four symbols can be encoded with incremental bits per symbol. | |
128 HpackHuffmanSymbol code[] = { | |
129 {bits32("00000000000000000000000000000000"), 1, 0}, | |
130 {bits32("10000000000000000000000000000000"), 2, 1}, | |
131 {bits32("11000000000000000000000000000000"), 3, 2}, | |
132 {bits32("11100000000000000000000000000000"), 8, 3}}; | |
133 HpackHuffmanTable table; | |
134 EXPECT_TRUE(table.Initialize(code, arraysize(code))); | |
135 } | |
136 { | |
137 // But repeating a length overflows the code. | |
138 HpackHuffmanSymbol code[] = { | |
139 {bits32("00000000000000000000000000000000"), 1, 0}, | |
140 {bits32("10000000000000000000000000000000"), 2, 1}, | |
141 {bits32("11000000000000000000000000000000"), 2, 2}, | |
142 {bits32("00000000000000000000000000000000"), 8, 3}}; // Overflow. | |
143 HpackHuffmanTable table; | |
144 EXPECT_FALSE(table.Initialize(code, arraysize(code))); | |
145 EXPECT_EQ(3, HpackHuffmanTablePeer(table).failed_symbol_id()); | |
146 } | |
147 { | |
148 // Symbol IDs must be assigned sequentially with no gaps. | |
149 HpackHuffmanSymbol code[] = { | |
150 {bits32("00000000000000000000000000000000"), 1, 0}, | |
151 {bits32("10000000000000000000000000000000"), 2, 1}, | |
152 {bits32("11000000000000000000000000000000"), 3, 1}, // Repeat. | |
153 {bits32("11100000000000000000000000000000"), 8, 3}}; | |
154 HpackHuffmanTable table; | |
155 EXPECT_FALSE(table.Initialize(code, arraysize(code))); | |
156 EXPECT_EQ(2, HpackHuffmanTablePeer(table).failed_symbol_id()); | |
157 } | |
158 { | |
159 // Canonical codes must begin with zero. | |
160 HpackHuffmanSymbol code[] = { | |
161 {bits32("10000000000000000000000000000000"), 4, 0}, | |
162 {bits32("10010000000000000000000000000000"), 4, 1}, | |
163 {bits32("10100000000000000000000000000000"), 4, 2}, | |
164 {bits32("10110000000000000000000000000000"), 8, 3}}; | |
165 HpackHuffmanTable table; | |
166 EXPECT_FALSE(table.Initialize(code, arraysize(code))); | |
167 EXPECT_EQ(0, HpackHuffmanTablePeer(table).failed_symbol_id()); | |
168 } | |
169 { | |
170 // Codes must match the expected canonical sequence. | |
171 HpackHuffmanSymbol code[] = { | |
172 {bits32("00000000000000000000000000000000"), 2, 0}, | |
173 {bits32("01000000000000000000000000000000"), 2, 1}, | |
174 {bits32("11000000000000000000000000000000"), 2, 2}, // Not canonical. | |
175 {bits32("10000000000000000000000000000000"), 8, 3}}; | |
176 HpackHuffmanTable table; | |
177 EXPECT_FALSE(table.Initialize(code, arraysize(code))); | |
178 EXPECT_EQ(2, HpackHuffmanTablePeer(table).failed_symbol_id()); | |
179 } | |
180 { | |
181 // At least one code must have a length of 8 bits (to ensure pad-ability). | |
182 HpackHuffmanSymbol code[] = { | |
183 {bits32("00000000000000000000000000000000"), 1, 0}, | |
184 {bits32("10000000000000000000000000000000"), 2, 1}, | |
185 {bits32("11000000000000000000000000000000"), 3, 2}, | |
186 {bits32("11100000000000000000000000000000"), 7, 3}}; | |
187 HpackHuffmanTable table; | |
188 EXPECT_FALSE(table.Initialize(code, arraysize(code))); | |
189 } | |
190 } | |
191 | |
192 TEST_F(GenericHuffmanTableTest, ValidateInternalsWithSmallCode) { | |
193 HpackHuffmanSymbol code[] = { | |
194 {bits32("01100000000000000000000000000000"), 4, 0}, // 3rd. | |
195 {bits32("01110000000000000000000000000000"), 4, 1}, // 4th. | |
196 {bits32("00000000000000000000000000000000"), 2, 2}, // 1st assigned code. | |
197 {bits32("01000000000000000000000000000000"), 3, 3}, // 2nd. | |
198 {bits32("10000000000000000000000000000000"), 5, 4}, // 5th. | |
199 {bits32("10001000000000000000000000000000"), 5, 5}, // 6th. | |
200 {bits32("10011000000000000000000000000000"), 8, 6}, // 8th. | |
201 {bits32("10010000000000000000000000000000"), 5, 7}}; // 7th. | |
202 EXPECT_TRUE(table_.Initialize(code, arraysize(code))); | |
203 ASSERT_EQ(arraysize(code), peer_.code_by_id().size()); | |
204 ASSERT_EQ(arraysize(code), peer_.length_by_id().size()); | |
205 for (size_t i = 0; i < arraysize(code); ++i) { | |
206 EXPECT_EQ(code[i].code, peer_.code_by_id()[i]); | |
207 EXPECT_EQ(code[i].length, peer_.length_by_id()[i]); | |
208 } | |
209 | |
210 EXPECT_EQ(1u, peer_.decode_tables().size()); | |
211 { | |
212 std::vector<DecodeEntry> expected; | |
213 expected.resize(128, DecodeEntry(0, 2, 2)); // Fills 128. | |
214 expected.resize(192, DecodeEntry(0, 3, 3)); // Fills 64. | |
215 expected.resize(224, DecodeEntry(0, 4, 0)); // Fills 32. | |
216 expected.resize(256, DecodeEntry(0, 4, 1)); // Fills 32. | |
217 expected.resize(272, DecodeEntry(0, 5, 4)); // Fills 16. | |
218 expected.resize(288, DecodeEntry(0, 5, 5)); // Fills 16. | |
219 expected.resize(304, DecodeEntry(0, 5, 7)); // Fills 16. | |
220 expected.resize(306, DecodeEntry(0, 8, 6)); // Fills 2. | |
221 expected.resize(512, DecodeEntry()); // Remainder is empty. | |
222 | |
223 EXPECT_THAT(peer_.decode_entries(peer_.decode_tables()[0]), | |
224 Pointwise(DecodeEntryEq(), expected)); | |
225 } | |
226 EXPECT_EQ(bits8("10011000"), peer_.pad_bits()); | |
227 | |
228 char input_storage[] = {2, 3, 2, 7, 4}; | |
229 SpdyStringPiece input(input_storage, arraysize(input_storage)); | |
230 // By symbol: (2) 00 (3) 010 (2) 00 (7) 10010 (4) 10000 (6 as pad) 1001100. | |
231 char expect_storage[] = {bits8("00010001"), bits8("00101000"), | |
232 bits8("01001100")}; | |
233 SpdyStringPiece expect(expect_storage, arraysize(expect_storage)); | |
234 | |
235 SpdyString buffer_in = EncodeString(input); | |
236 EXPECT_EQ(expect, buffer_in); | |
237 | |
238 SpdyString buffer_out; | |
239 HpackInputStream input_stream(buffer_in); | |
240 EXPECT_TRUE(table_.GenericDecodeString(&input_stream, &buffer_out)); | |
241 EXPECT_EQ(buffer_out, input); | |
242 } | |
243 | |
244 TEST_F(GenericHuffmanTableTest, ValidateMultiLevelDecodeTables) { | |
245 HpackHuffmanSymbol code[] = { | |
246 {bits32("00000000000000000000000000000000"), 6, 0}, | |
247 {bits32("00000100000000000000000000000000"), 6, 1}, | |
248 {bits32("00001000000000000000000000000000"), 11, 2}, | |
249 {bits32("00001000001000000000000000000000"), 11, 3}, | |
250 {bits32("00001000010000000000000000000000"), 12, 4}, | |
251 }; | |
252 EXPECT_TRUE(table_.Initialize(code, arraysize(code))); | |
253 | |
254 EXPECT_EQ(2u, peer_.decode_tables().size()); | |
255 { | |
256 std::vector<DecodeEntry> expected; | |
257 expected.resize(8, DecodeEntry(0, 6, 0)); // Fills 8. | |
258 expected.resize(16, DecodeEntry(0, 6, 1)); // Fills 8. | |
259 expected.resize(17, DecodeEntry(1, 12, 0)); // Pointer. Fills 1. | |
260 expected.resize(512, DecodeEntry()); // Remainder is empty. | |
261 | |
262 const DecodeTable& decode_table = peer_.decode_tables()[0]; | |
263 EXPECT_EQ(decode_table.prefix_length, 0); | |
264 EXPECT_EQ(decode_table.indexed_length, 9); | |
265 EXPECT_THAT(peer_.decode_entries(decode_table), | |
266 Pointwise(DecodeEntryEq(), expected)); | |
267 } | |
268 { | |
269 std::vector<DecodeEntry> expected; | |
270 expected.resize(2, DecodeEntry(1, 11, 2)); // Fills 2. | |
271 expected.resize(4, DecodeEntry(1, 11, 3)); // Fills 2. | |
272 expected.resize(5, DecodeEntry(1, 12, 4)); // Fills 1. | |
273 expected.resize(8, DecodeEntry()); // Remainder is empty. | |
274 | |
275 const DecodeTable& decode_table = peer_.decode_tables()[1]; | |
276 EXPECT_EQ(decode_table.prefix_length, 9); | |
277 EXPECT_EQ(decode_table.indexed_length, 3); | |
278 EXPECT_THAT(peer_.decode_entries(decode_table), | |
279 Pointwise(DecodeEntryEq(), expected)); | |
280 } | |
281 EXPECT_EQ(bits8("00001000"), peer_.pad_bits()); | |
282 } | |
283 | |
284 TEST_F(GenericHuffmanTableTest, DecodeWithBadInput) { | |
285 HpackHuffmanSymbol code[] = { | |
286 {bits32("01100000000000000000000000000000"), 4, 0}, | |
287 {bits32("01110000000000000000000000000000"), 4, 1}, | |
288 {bits32("00000000000000000000000000000000"), 2, 2}, | |
289 {bits32("01000000000000000000000000000000"), 3, 3}, | |
290 {bits32("10000000000000000000000000000000"), 5, 4}, | |
291 {bits32("10001000000000000000000000000000"), 5, 5}, | |
292 {bits32("10011000000000000000000000000000"), 6, 6}, | |
293 {bits32("10010000000000000000000000000000"), 5, 7}, | |
294 {bits32("10011100000000000000000000000000"), 16, 8}}; | |
295 EXPECT_TRUE(table_.Initialize(code, arraysize(code))); | |
296 | |
297 SpdyString buffer; | |
298 { | |
299 // This example works: (2) 00 (3) 010 (2) 00 (6) 100110 (pad) 100. | |
300 char input_storage[] = {bits8("00010001"), bits8("00110100")}; | |
301 SpdyStringPiece input(input_storage, arraysize(input_storage)); | |
302 | |
303 HpackInputStream input_stream(input); | |
304 EXPECT_TRUE(table_.GenericDecodeString(&input_stream, &buffer)); | |
305 EXPECT_EQ(buffer, "\x02\x03\x02\x06"); | |
306 } | |
307 { | |
308 // Expect to fail on an invalid code prefix. | |
309 // (2) 00 (3) 010 (2) 00 (too-large) 101000 (pad) 100. | |
310 char input_storage[] = {bits8("00010001"), bits8("01000111")}; | |
311 SpdyStringPiece input(input_storage, arraysize(input_storage)); | |
312 | |
313 HpackInputStream input_stream(input); | |
314 EXPECT_FALSE(table_.GenericDecodeString(&input_stream, &buffer)); | |
315 EXPECT_EQ(buffer, "\x02\x03\x02"); | |
316 } | |
317 { | |
318 // Expect to fail if more than a byte of unconsumed input remains. | |
319 // (6) 100110 (8 truncated) 1001110000 | |
320 char input_storage[] = {bits8("10011010"), bits8("01110000")}; | |
321 SpdyStringPiece input(input_storage, arraysize(input_storage)); | |
322 | |
323 HpackInputStream input_stream(input); | |
324 EXPECT_FALSE(table_.GenericDecodeString(&input_stream, &buffer)); | |
325 EXPECT_EQ(buffer, "\x06"); | |
326 } | |
327 } | |
328 | |
329 // Tests of the ability to decode the HPACK Huffman Code, defined in: | |
330 // https://httpwg.github.io/specs/rfc7541.html#huffman.code | |
331 class HpackHuffmanTableTest : public GenericHuffmanTableTest { | |
332 protected: | |
333 void SetUp() override { | |
334 std::vector<HpackHuffmanSymbol> code = HpackHuffmanCode(); | |
335 EXPECT_TRUE(table_.Initialize(&code[0], code.size())); | |
336 EXPECT_TRUE(table_.IsInitialized()); | |
337 } | |
338 | |
339 void DecodeStringTwice(const SpdyString& encoded, SpdyString* out) { | |
340 // First decode with HpackHuffmanTable. | |
341 { | |
342 HpackInputStream input_stream(encoded); | |
343 EXPECT_TRUE(table_.GenericDecodeString(&input_stream, out)); | |
344 } | |
345 // And decode again with the fixed decoder, confirming that the result is | |
346 // the same. | |
347 { | |
348 HpackInputStream input_stream(encoded); | |
349 SpdyString buf; | |
350 EXPECT_TRUE(HpackHuffmanDecoder::DecodeString(&input_stream, &buf)); | |
351 EXPECT_EQ(*out, buf); | |
352 } | |
353 } | |
354 }; | |
355 | |
356 TEST_F(HpackHuffmanTableTest, InitializeHpackCode) { | |
357 EXPECT_EQ(peer_.pad_bits(), '\xFF'); // First 8 bits of EOS. | |
358 } | |
359 | |
360 TEST_F(HpackHuffmanTableTest, SpecRequestExamples) { | |
361 SpdyString buffer; | |
362 SpdyString test_table[] = { | |
363 a2b_hex("f1e3c2e5f23a6ba0ab90f4ff"), | |
364 "www.example.com", | |
365 a2b_hex("a8eb10649cbf"), | |
366 "no-cache", | |
367 a2b_hex("25a849e95ba97d7f"), | |
368 "custom-key", | |
369 a2b_hex("25a849e95bb8e8b4bf"), | |
370 "custom-value", | |
371 }; | |
372 // Round-trip each test example. | |
373 for (size_t i = 0; i != arraysize(test_table); i += 2) { | |
374 const SpdyString& encodedFixture(test_table[i]); | |
375 const SpdyString& decodedFixture(test_table[i + 1]); | |
376 DecodeStringTwice(encodedFixture, &buffer); | |
377 EXPECT_EQ(decodedFixture, buffer); | |
378 buffer = EncodeString(decodedFixture); | |
379 EXPECT_EQ(encodedFixture, buffer); | |
380 } | |
381 } | |
382 | |
383 TEST_F(HpackHuffmanTableTest, SpecResponseExamples) { | |
384 SpdyString buffer; | |
385 SpdyString test_table[] = { | |
386 a2b_hex("6402"), | |
387 "302", | |
388 a2b_hex("aec3771a4b"), | |
389 "private", | |
390 a2b_hex("d07abe941054d444a8200595040b8166" | |
391 "e082a62d1bff"), | |
392 "Mon, 21 Oct 2013 20:13:21 GMT", | |
393 a2b_hex("9d29ad171863c78f0b97c8e9ae82ae43" | |
394 "d3"), | |
395 "https://www.example.com", | |
396 a2b_hex("94e7821dd7f2e6c7b335dfdfcd5b3960" | |
397 "d5af27087f3672c1ab270fb5291f9587" | |
398 "316065c003ed4ee5b1063d5007"), | |
399 "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", | |
400 }; | |
401 // Round-trip each test example. | |
402 for (size_t i = 0; i != arraysize(test_table); i += 2) { | |
403 const SpdyString& encodedFixture(test_table[i]); | |
404 const SpdyString& decodedFixture(test_table[i + 1]); | |
405 DecodeStringTwice(encodedFixture, &buffer); | |
406 EXPECT_EQ(decodedFixture, buffer); | |
407 buffer = EncodeString(decodedFixture); | |
408 EXPECT_EQ(encodedFixture, buffer); | |
409 } | |
410 } | |
411 | |
412 TEST_F(HpackHuffmanTableTest, RoundTripIndividualSymbols) { | |
413 for (size_t i = 0; i != 256; i++) { | |
414 char c = static_cast<char>(i); | |
415 char storage[3] = {c, c, c}; | |
416 SpdyStringPiece input(storage, arraysize(storage)); | |
417 SpdyString buffer_in = EncodeString(input); | |
418 SpdyString buffer_out; | |
419 DecodeStringTwice(buffer_in, &buffer_out); | |
420 EXPECT_EQ(input, buffer_out); | |
421 } | |
422 } | |
423 | |
424 TEST_F(HpackHuffmanTableTest, RoundTripSymbolSequence) { | |
425 char storage[512]; | |
426 for (size_t i = 0; i != 256; i++) { | |
427 storage[i] = static_cast<char>(i); | |
428 storage[511 - i] = static_cast<char>(i); | |
429 } | |
430 SpdyStringPiece input(storage, arraysize(storage)); | |
431 | |
432 SpdyString buffer_in = EncodeString(input); | |
433 SpdyString buffer_out; | |
434 DecodeStringTwice(buffer_in, &buffer_out); | |
435 EXPECT_EQ(input, buffer_out); | |
436 } | |
437 | |
438 TEST_F(HpackHuffmanTableTest, EncodedSizeAgreesWithEncodeString) { | |
439 SpdyString test_table[] = { | |
440 "", | |
441 "Mon, 21 Oct 2013 20:13:21 GMT", | |
442 "https://www.example.com", | |
443 "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", | |
444 SpdyString(1, '\0'), | |
445 SpdyString("foo\0bar", 7), | |
446 SpdyString(256, '\0'), | |
447 }; | |
448 for (size_t i = 0; i != 256; ++i) { | |
449 // Expand last |test_table| entry to cover all codes. | |
450 test_table[arraysize(test_table) - 1][i] = static_cast<char>(i); | |
451 } | |
452 | |
453 HpackOutputStream output_stream; | |
454 SpdyString encoding; | |
455 for (size_t i = 0; i != arraysize(test_table); ++i) { | |
456 table_.EncodeString(test_table[i], &output_stream); | |
457 output_stream.TakeString(&encoding); | |
458 EXPECT_EQ(encoding.size(), table_.EncodedSize(test_table[i])); | |
459 } | |
460 } | |
461 | |
462 } // namespace | |
463 | |
464 } // namespace test | |
465 | |
466 } // namespace net | |
OLD | NEW |