| OLD | NEW |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "net/spdy/hpack/hpack_decoder.h" | 5 #include "net/spdy/hpack/hpack_decoder.h" |
| 6 | 6 |
| 7 #include <map> | 7 #include <map> |
| 8 #include <string> | 8 #include <string> |
| 9 | 9 |
| 10 #include "base/logging.h" | 10 #include "base/logging.h" |
| (...skipping 19 matching lines...) Expand all Loading... |
| 30 void HandleHeaderRepresentation(StringPiece name, StringPiece value) { | 30 void HandleHeaderRepresentation(StringPiece name, StringPiece value) { |
| 31 decoder_->HandleHeaderRepresentation(name, value); | 31 decoder_->HandleHeaderRepresentation(name, value); |
| 32 } | 32 } |
| 33 bool DecodeNextName(HpackInputStream* in, StringPiece* out) { | 33 bool DecodeNextName(HpackInputStream* in, StringPiece* out) { |
| 34 return decoder_->DecodeNextName(in, out); | 34 return decoder_->DecodeNextName(in, out); |
| 35 } | 35 } |
| 36 HpackHeaderTable* header_table() { return &decoder_->header_table_; } | 36 HpackHeaderTable* header_table() { return &decoder_->header_table_; } |
| 37 const SpdyHeaderBlock& decoded_block() const { | 37 const SpdyHeaderBlock& decoded_block() const { |
| 38 return decoder_->decoded_block_; | 38 return decoder_->decoded_block_; |
| 39 } | 39 } |
| 40 |
| 41 bool DecodeNextStringLiteral(HpackInputStream* in, |
| 42 bool is_header_key, |
| 43 StringPiece* str) { |
| 44 return decoder_->DecodeNextStringLiteral(in, is_header_key, str); |
| 45 } |
| 46 |
| 40 const string& headers_block_buffer() const { | 47 const string& headers_block_buffer() const { |
| 41 return decoder_->headers_block_buffer_; | 48 return decoder_->headers_block_buffer_; |
| 42 } | 49 } |
| 43 | 50 |
| 44 private: | 51 private: |
| 45 HpackDecoder* decoder_; | 52 HpackDecoder* decoder_; |
| 46 }; | 53 }; |
| 47 | 54 |
| 48 namespace { | 55 namespace { |
| 49 | 56 |
| 50 using base::StringPiece; | 57 using base::StringPiece; |
| 51 using std::string; | 58 using std::string; |
| 52 using test::a2b_hex; | 59 using test::a2b_hex; |
| 53 | 60 |
| 54 using testing::ElementsAre; | 61 using testing::ElementsAre; |
| 55 using testing::Pair; | 62 using testing::Pair; |
| 56 | 63 |
| 57 const size_t kLiteralBound = 1024; | 64 const size_t kLiteralBound = 1024; |
| 58 | 65 |
| 59 class HpackDecoderTest : public ::testing::TestWithParam<bool> { | 66 class HpackDecoderTest : public ::testing::TestWithParam<bool> { |
| 60 protected: | 67 protected: |
| 61 HpackDecoderTest() : decoder_(), decoder_peer_(&decoder_) {} | 68 HpackDecoderTest() : decoder_(), decoder_peer_(&decoder_) {} |
| 62 | 69 |
| 70 void SetUp() override { handler_exists_ = GetParam(); } |
| 71 |
| 63 bool DecodeHeaderBlock(StringPiece str) { | 72 bool DecodeHeaderBlock(StringPiece str) { |
| 64 if (GetParam()) { | 73 if (handler_exists_) { |
| 65 decoder_.HandleControlFrameHeadersStart(&handler_); | 74 decoder_.HandleControlFrameHeadersStart(&handler_); |
| 66 } | 75 } |
| 67 return decoder_.HandleControlFrameHeadersData(str.data(), str.size()) && | 76 return decoder_.HandleControlFrameHeadersData(str.data(), str.size()) && |
| 68 decoder_.HandleControlFrameHeadersComplete(nullptr); | 77 decoder_.HandleControlFrameHeadersComplete(nullptr); |
| 69 } | 78 } |
| 70 | 79 |
| 80 bool HandleControlFrameHeadersData(StringPiece str) { |
| 81 return decoder_.HandleControlFrameHeadersData(str.data(), str.size()); |
| 82 } |
| 83 |
| 84 bool HandleControlFrameHeadersComplete(size_t* size) { |
| 85 return decoder_.HandleControlFrameHeadersComplete(size); |
| 86 } |
| 87 |
| 71 const SpdyHeaderBlock& decoded_block() const { | 88 const SpdyHeaderBlock& decoded_block() const { |
| 72 if (GetParam()) { | 89 if (handler_exists_) { |
| 73 return handler_.decoded_block(); | 90 return handler_.decoded_block(); |
| 74 } else { | 91 } else { |
| 75 return decoder_peer_.decoded_block(); | 92 return decoder_peer_.decoded_block(); |
| 76 } | 93 } |
| 77 } | 94 } |
| 78 | 95 |
| 79 const SpdyHeaderBlock& DecodeBlockExpectingSuccess(StringPiece str) { | 96 const SpdyHeaderBlock& DecodeBlockExpectingSuccess(StringPiece str) { |
| 80 EXPECT_TRUE(DecodeHeaderBlock(str)); | 97 EXPECT_TRUE(DecodeHeaderBlock(str)); |
| 81 return decoded_block(); | 98 return decoded_block(); |
| 82 } | 99 } |
| 83 | 100 |
| 84 void expectEntry(size_t index, | 101 void expectEntry(size_t index, |
| 85 size_t size, | 102 size_t size, |
| 86 const string& name, | 103 const string& name, |
| 87 const string& value) { | 104 const string& value) { |
| 88 const HpackEntry* entry = decoder_peer_.header_table()->GetByIndex(index); | 105 const HpackEntry* entry = decoder_peer_.header_table()->GetByIndex(index); |
| 89 EXPECT_EQ(name, entry->name()) << "index " << index; | 106 EXPECT_EQ(name, entry->name()) << "index " << index; |
| 90 EXPECT_EQ(value, entry->value()); | 107 EXPECT_EQ(value, entry->value()); |
| 91 EXPECT_EQ(size, entry->Size()); | 108 EXPECT_EQ(size, entry->Size()); |
| 92 EXPECT_EQ(index, decoder_peer_.header_table()->IndexOf(entry)); | 109 EXPECT_EQ(index, decoder_peer_.header_table()->IndexOf(entry)); |
| 93 } | 110 } |
| 94 | 111 |
| 95 HpackDecoder decoder_; | 112 HpackDecoder decoder_; |
| 96 test::HpackDecoderPeer decoder_peer_; | 113 test::HpackDecoderPeer decoder_peer_; |
| 97 TestHeadersHandler handler_; | 114 TestHeadersHandler handler_; |
| 115 bool handler_exists_; |
| 98 }; | 116 }; |
| 99 | 117 |
| 100 INSTANTIATE_TEST_CASE_P(WithAndWithoutHeadersHandler, | 118 INSTANTIATE_TEST_CASE_P(WithAndWithoutHeadersHandler, |
| 101 HpackDecoderTest, | 119 HpackDecoderTest, |
| 102 ::testing::Bool()); | 120 ::testing::Bool()); |
| 103 | 121 |
| 104 TEST_P(HpackDecoderTest, HandleControlFrameHeadersData) { | 122 TEST_P(HpackDecoderTest, AddHeaderDataWithHandleControlFrameHeadersData) { |
| 105 // Strings under threshold are concatenated in the buffer. | 123 // Strings under threshold are concatenated in the buffer. |
| 106 EXPECT_TRUE(decoder_.HandleControlFrameHeadersData("small string one", 16)); | 124 EXPECT_TRUE(decoder_.HandleControlFrameHeadersData("small string one", 16)); |
| 107 EXPECT_TRUE(decoder_.HandleControlFrameHeadersData("small string two", 16)); | 125 EXPECT_TRUE(decoder_.HandleControlFrameHeadersData("small string two", 16)); |
| 108 // A string which would push the buffer over the threshold is refused. | 126 // A string which would push the buffer over the threshold is refused. |
| 109 EXPECT_FALSE(decoder_.HandleControlFrameHeadersData( | 127 EXPECT_FALSE(decoder_.HandleControlFrameHeadersData( |
| 110 "fails", kMaxDecodeBufferSize - 32 + 1)); | 128 "fails", kMaxDecodeBufferSize - 32 + 1)); |
| 111 | 129 |
| 112 EXPECT_EQ(decoder_peer_.headers_block_buffer(), | 130 EXPECT_EQ(decoder_peer_.headers_block_buffer(), |
| 113 "small string onesmall string two"); | 131 "small string onesmall string two"); |
| 114 } | 132 } |
| 115 | 133 |
| 134 // Decode with incomplete data in buffer. |
| 135 TEST_P(HpackDecoderTest, DecodeWithIncompleteData) { |
| 136 // No need to wait for more data. |
| 137 EXPECT_TRUE(HandleControlFrameHeadersData("\x82\x85\x82")); |
| 138 EXPECT_EQ("", decoder_peer_.headers_block_buffer()); |
| 139 |
| 140 // Need to wait for more data. |
| 141 EXPECT_TRUE( |
| 142 HandleControlFrameHeadersData("\x40\x03goo" |
| 143 "\x03gar\xbe\x40\x04spam")); |
| 144 EXPECT_EQ("\x40\x04spam", decoder_peer_.headers_block_buffer()); |
| 145 |
| 146 // Add the needed data. |
| 147 EXPECT_TRUE(HandleControlFrameHeadersData("\x04gggs")); |
| 148 EXPECT_EQ("", decoder_peer_.headers_block_buffer()); |
| 149 |
| 150 size_t size = 0; |
| 151 EXPECT_TRUE(HandleControlFrameHeadersComplete(&size)); |
| 152 EXPECT_EQ(24u, size); |
| 153 } |
| 154 |
| 116 TEST_P(HpackDecoderTest, HandleHeaderRepresentation) { | 155 TEST_P(HpackDecoderTest, HandleHeaderRepresentation) { |
| 117 if (GetParam()) { | 156 if (handler_exists_) { |
| 118 decoder_.HandleControlFrameHeadersStart(&handler_); | 157 decoder_.HandleControlFrameHeadersStart(&handler_); |
| 119 } | 158 } |
| 120 | 159 |
| 121 // All cookie crumbs are joined. | 160 // All cookie crumbs are joined. |
| 122 decoder_peer_.HandleHeaderRepresentation("cookie", " part 1"); | 161 decoder_peer_.HandleHeaderRepresentation("cookie", " part 1"); |
| 123 decoder_peer_.HandleHeaderRepresentation("cookie", "part 2 "); | 162 decoder_peer_.HandleHeaderRepresentation("cookie", "part 2 "); |
| 124 decoder_peer_.HandleHeaderRepresentation("cookie", "part3"); | 163 decoder_peer_.HandleHeaderRepresentation("cookie", "part3"); |
| 125 | 164 |
| 126 // Already-delimited headers are passed through. | 165 // Already-delimited headers are passed through. |
| 127 decoder_peer_.HandleHeaderRepresentation("passed-through", | 166 decoder_peer_.HandleHeaderRepresentation("passed-through", |
| (...skipping 30 matching lines...) Expand all Loading... |
| 158 } | 197 } |
| 159 | 198 |
| 160 // Decoding an encoded name with a valid string literal should work. | 199 // Decoding an encoded name with a valid string literal should work. |
| 161 TEST_P(HpackDecoderTest, DecodeNextNameLiteral) { | 200 TEST_P(HpackDecoderTest, DecodeNextNameLiteral) { |
| 162 HpackInputStream input_stream(kLiteralBound, StringPiece("\x00\x04name", 6)); | 201 HpackInputStream input_stream(kLiteralBound, StringPiece("\x00\x04name", 6)); |
| 163 | 202 |
| 164 StringPiece string_piece; | 203 StringPiece string_piece; |
| 165 EXPECT_TRUE(decoder_peer_.DecodeNextName(&input_stream, &string_piece)); | 204 EXPECT_TRUE(decoder_peer_.DecodeNextName(&input_stream, &string_piece)); |
| 166 EXPECT_EQ("name", string_piece); | 205 EXPECT_EQ("name", string_piece); |
| 167 EXPECT_FALSE(input_stream.HasMoreData()); | 206 EXPECT_FALSE(input_stream.HasMoreData()); |
| 207 EXPECT_FALSE(input_stream.NeedMoreData()); |
| 208 input_stream.MarkCurrentPosition(); |
| 209 EXPECT_EQ(6u, input_stream.ParsedBytes()); |
| 210 } |
| 211 |
| 212 // Decoding an encoded name with an incomplete string literal. |
| 213 TEST_P(HpackDecoderTest, DecodeNextNameLiteralWithIncompleteHeader) { |
| 214 HpackInputStream input_stream(kLiteralBound, |
| 215 StringPiece("\x00\x04name\x00\x02g", 9)); |
| 216 |
| 217 StringPiece string_piece; |
| 218 EXPECT_TRUE(decoder_peer_.DecodeNextName(&input_stream, &string_piece)); |
| 219 EXPECT_FALSE(input_stream.NeedMoreData()); |
| 220 input_stream.MarkCurrentPosition(); |
| 221 EXPECT_EQ(6u, input_stream.ParsedBytes()); |
| 222 |
| 223 EXPECT_FALSE(decoder_peer_.DecodeNextName(&input_stream, &string_piece)); |
| 224 EXPECT_TRUE(input_stream.NeedMoreData()); |
| 225 input_stream.MarkCurrentPosition(); |
| 226 EXPECT_EQ(8u, input_stream.ParsedBytes()); |
| 168 } | 227 } |
| 169 | 228 |
| 170 TEST_P(HpackDecoderTest, DecodeNextNameLiteralWithHuffmanEncoding) { | 229 TEST_P(HpackDecoderTest, DecodeNextNameLiteralWithHuffmanEncoding) { |
| 171 string input = a2b_hex("008825a849e95ba97d7f"); | 230 string input = a2b_hex("008825a849e95ba97d7f"); |
| 172 HpackInputStream input_stream(kLiteralBound, input); | 231 HpackInputStream input_stream(kLiteralBound, input); |
| 173 | 232 |
| 174 StringPiece string_piece; | 233 StringPiece string_piece; |
| 175 EXPECT_TRUE(decoder_peer_.DecodeNextName(&input_stream, &string_piece)); | 234 EXPECT_TRUE(decoder_peer_.DecodeNextName(&input_stream, &string_piece)); |
| 176 EXPECT_EQ("custom-key", string_piece); | 235 EXPECT_EQ("custom-key", string_piece); |
| 177 EXPECT_FALSE(input_stream.HasMoreData()); | 236 EXPECT_FALSE(input_stream.HasMoreData()); |
| 237 EXPECT_FALSE(input_stream.NeedMoreData()); |
| 238 input_stream.MarkCurrentPosition(); |
| 239 EXPECT_EQ(input.size(), input_stream.ParsedBytes()); |
| 240 } |
| 241 |
| 242 // Decode with incomplete huffman encoding. |
| 243 TEST_P(HpackDecoderTest, DecodeNextNameLiteralWithIncompleteHuffmanEncoding) { |
| 244 // CHECK(huffman_table_.Initialize(kHpackHuffmanCode, |
| 245 // arraysize(kHpackHuffmanCode))); |
| 246 // Put two copies of the same huffman encoding into input. |
| 247 string input = a2b_hex("008825a849e95ba97d7f008825a849e95ba97d7f"); |
| 248 input.resize(input.size() - 1); // Remove the last byte. |
| 249 HpackInputStream input_stream(kLiteralBound, input); |
| 250 |
| 251 StringPiece string_piece; |
| 252 EXPECT_TRUE(decoder_peer_.DecodeNextName(&input_stream, &string_piece)); |
| 253 EXPECT_FALSE(input_stream.NeedMoreData()); |
| 254 input_stream.MarkCurrentPosition(); |
| 255 EXPECT_EQ(10u, input_stream.ParsedBytes()); |
| 256 |
| 257 EXPECT_FALSE(decoder_peer_.DecodeNextName(&input_stream, &string_piece)); |
| 258 EXPECT_TRUE(input_stream.NeedMoreData()); |
| 259 input_stream.MarkCurrentPosition(); |
| 260 EXPECT_EQ(12u, input_stream.ParsedBytes()); |
| 178 } | 261 } |
| 179 | 262 |
| 180 // Decoding an encoded name with a valid index should work. | 263 // Decoding an encoded name with a valid index should work. |
| 181 TEST_P(HpackDecoderTest, DecodeNextNameIndexed) { | 264 TEST_P(HpackDecoderTest, DecodeNextNameIndexed) { |
| 182 HpackInputStream input_stream(kLiteralBound, "\x01"); | 265 HpackInputStream input_stream(kLiteralBound, "\x01"); |
| 183 | 266 |
| 184 StringPiece string_piece; | 267 StringPiece string_piece; |
| 185 EXPECT_TRUE(decoder_peer_.DecodeNextName(&input_stream, &string_piece)); | 268 EXPECT_TRUE(decoder_peer_.DecodeNextName(&input_stream, &string_piece)); |
| 186 EXPECT_EQ(":authority", string_piece); | 269 EXPECT_EQ(":authority", string_piece); |
| 187 EXPECT_FALSE(input_stream.HasMoreData()); | 270 EXPECT_FALSE(input_stream.HasMoreData()); |
| 271 EXPECT_FALSE(input_stream.NeedMoreData()); |
| 272 input_stream.MarkCurrentPosition(); |
| 273 EXPECT_EQ(1u, input_stream.ParsedBytes()); |
| 188 } | 274 } |
| 189 | 275 |
| 190 // Decoding an encoded name with an invalid index should fail. | 276 // Decoding an encoded name with an invalid index should fail. |
| 191 TEST_P(HpackDecoderTest, DecodeNextNameInvalidIndex) { | 277 TEST_P(HpackDecoderTest, DecodeNextNameInvalidIndex) { |
| 192 // One more than the number of static table entries. | 278 // One more than the number of static table entries. |
| 193 HpackInputStream input_stream(kLiteralBound, "\x3e"); | 279 HpackInputStream input_stream(kLiteralBound, "\x3e"); |
| 194 | 280 |
| 195 StringPiece string_piece; | 281 StringPiece string_piece; |
| 196 EXPECT_FALSE(decoder_peer_.DecodeNextName(&input_stream, &string_piece)); | 282 EXPECT_FALSE(decoder_peer_.DecodeNextName(&input_stream, &string_piece)); |
| 283 EXPECT_FALSE(input_stream.NeedMoreData()); |
| 284 input_stream.MarkCurrentPosition(); |
| 285 EXPECT_EQ(1u, input_stream.ParsedBytes()); |
| 197 } | 286 } |
| 198 | 287 |
| 199 // Decoding indexed static table field should work. | 288 // Decoding indexed static table field should work. |
| 200 TEST_P(HpackDecoderTest, IndexedHeaderStatic) { | 289 TEST_P(HpackDecoderTest, IndexedHeaderStatic) { |
| 201 // Reference static table entries #2 and #5. | 290 // Reference static table entries #2 and #5. |
| 202 SpdyHeaderBlock header_set1 = DecodeBlockExpectingSuccess("\x82\x85"); | 291 SpdyHeaderBlock header_set1 = DecodeBlockExpectingSuccess("\x82\x85"); |
| 203 SpdyHeaderBlock expected_header_set1; | 292 SpdyHeaderBlock expected_header_set1; |
| 204 expected_header_set1[":method"] = "GET"; | 293 expected_header_set1[":method"] = "GET"; |
| 205 expected_header_set1[":path"] = "/index.html"; | 294 expected_header_set1[":path"] = "/index.html"; |
| 206 EXPECT_EQ(expected_header_set1, header_set1); | 295 EXPECT_EQ(expected_header_set1, header_set1); |
| (...skipping 139 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 346 EXPECT_FALSE(DecodeHeaderBlock(StringPiece("\x0f\x2f\x03ooo"))); | 435 EXPECT_FALSE(DecodeHeaderBlock(StringPiece("\x0f\x2f\x03ooo"))); |
| 347 } | 436 } |
| 348 | 437 |
| 349 TEST_P(HpackDecoderTest, LiteralHeaderNeverIndexedInvalidNameIndex) { | 438 TEST_P(HpackDecoderTest, LiteralHeaderNeverIndexedInvalidNameIndex) { |
| 350 // Name is the last static index. Works. | 439 // Name is the last static index. Works. |
| 351 EXPECT_TRUE(DecodeHeaderBlock(StringPiece("\x1f\x2e\x03ooo"))); | 440 EXPECT_TRUE(DecodeHeaderBlock(StringPiece("\x1f\x2e\x03ooo"))); |
| 352 // Name is one beyond the last static index. Fails. | 441 // Name is one beyond the last static index. Fails. |
| 353 EXPECT_FALSE(DecodeHeaderBlock(StringPiece("\x1f\x2f\x03ooo"))); | 442 EXPECT_FALSE(DecodeHeaderBlock(StringPiece("\x1f\x2f\x03ooo"))); |
| 354 } | 443 } |
| 355 | 444 |
| 445 // Decode with incomplete string literal. |
| 446 TEST_P(HpackDecoderTest, StringLiteralIncomplete) { |
| 447 const char input[] = "\x0c/sample/path\x06:path2\x0e/sample/path/"; |
| 448 HpackInputStream input_stream(kLiteralBound, input); |
| 449 StringPiece str; |
| 450 EXPECT_TRUE( |
| 451 decoder_peer_.DecodeNextStringLiteral(&input_stream, false, &str)); |
| 452 EXPECT_FALSE(input_stream.NeedMoreData()); |
| 453 input_stream.MarkCurrentPosition(); |
| 454 EXPECT_EQ(13u, input_stream.ParsedBytes()); |
| 455 |
| 456 EXPECT_TRUE( |
| 457 decoder_peer_.DecodeNextStringLiteral(&input_stream, false, &str)); |
| 458 EXPECT_FALSE(input_stream.NeedMoreData()); |
| 459 input_stream.MarkCurrentPosition(); |
| 460 EXPECT_EQ(20u, input_stream.ParsedBytes()); |
| 461 |
| 462 EXPECT_FALSE( |
| 463 decoder_peer_.DecodeNextStringLiteral(&input_stream, false, &str)); |
| 464 EXPECT_TRUE(input_stream.NeedMoreData()); |
| 465 input_stream.MarkCurrentPosition(); |
| 466 EXPECT_EQ(21u, input_stream.ParsedBytes()); |
| 467 } |
| 468 |
| 356 // Round-tripping the header set from E.2.1 should work. | 469 // Round-tripping the header set from E.2.1 should work. |
| 357 TEST_P(HpackDecoderTest, BasicE21) { | 470 TEST_P(HpackDecoderTest, BasicE21) { |
| 358 HpackEncoder encoder(ObtainHpackHuffmanTable()); | 471 HpackEncoder encoder(ObtainHpackHuffmanTable()); |
| 359 | 472 |
| 360 SpdyHeaderBlock expected_header_set; | 473 SpdyHeaderBlock expected_header_set; |
| 361 expected_header_set[":method"] = "GET"; | 474 expected_header_set[":method"] = "GET"; |
| 362 expected_header_set[":scheme"] = "http"; | 475 expected_header_set[":scheme"] = "http"; |
| 363 expected_header_set[":path"] = "/"; | 476 expected_header_set[":path"] = "/"; |
| 364 expected_header_set[":authority"] = "www.example.com"; | 477 expected_header_set[":authority"] = "www.example.com"; |
| 365 | 478 |
| (...skipping 291 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 657 "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU;" | 770 "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU;" |
| 658 " max-age=3600; version=1"); | 771 " max-age=3600; version=1"); |
| 659 expectEntry(63, 52, "content-encoding", "gzip"); | 772 expectEntry(63, 52, "content-encoding", "gzip"); |
| 660 expectEntry(64, 65, "date", "Mon, 21 Oct 2013 20:13:22 GMT"); | 773 expectEntry(64, 65, "date", "Mon, 21 Oct 2013 20:13:22 GMT"); |
| 661 EXPECT_EQ(215u, decoder_peer_.header_table()->size()); | 774 EXPECT_EQ(215u, decoder_peer_.header_table()->size()); |
| 662 } | 775 } |
| 663 | 776 |
| 664 } // namespace | 777 } // namespace |
| 665 } // namespace test | 778 } // namespace test |
| 666 } // namespace net | 779 } // namespace net |
| OLD | NEW |