| OLD | NEW |
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 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/websockets/websocket_stream.h" | 5 #include "net/websockets/websocket_stream.h" |
| 6 | 6 |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 #include <string> | 8 #include <string> |
| 9 #include <utility> | 9 #include <utility> |
| 10 #include <vector> | 10 #include <vector> |
| 11 | 11 |
| 12 #include "base/memory/scoped_vector.h" |
| 12 #include "base/run_loop.h" | 13 #include "base/run_loop.h" |
| 13 #include "net/base/net_errors.h" | 14 #include "net/base/net_errors.h" |
| 14 #include "net/http/http_request_headers.h" | 15 #include "net/http/http_request_headers.h" |
| 15 #include "net/http/http_response_headers.h" | 16 #include "net/http/http_response_headers.h" |
| 16 #include "net/socket/client_socket_handle.h" | 17 #include "net/socket/client_socket_handle.h" |
| 17 #include "net/socket/socket_test_util.h" | 18 #include "net/socket/socket_test_util.h" |
| 18 #include "net/url_request/url_request_test_util.h" | 19 #include "net/url_request/url_request_test_util.h" |
| 19 #include "net/websockets/websocket_basic_handshake_stream.h" | 20 #include "net/websockets/websocket_basic_handshake_stream.h" |
| 21 #include "net/websockets/websocket_frame.h" |
| 20 #include "net/websockets/websocket_handshake_request_info.h" | 22 #include "net/websockets/websocket_handshake_request_info.h" |
| 21 #include "net/websockets/websocket_handshake_response_info.h" | 23 #include "net/websockets/websocket_handshake_response_info.h" |
| 22 #include "net/websockets/websocket_handshake_stream_create_helper.h" | 24 #include "net/websockets/websocket_handshake_stream_create_helper.h" |
| 23 #include "net/websockets/websocket_test_util.h" | 25 #include "net/websockets/websocket_test_util.h" |
| 24 #include "testing/gtest/include/gtest/gtest.h" | 26 #include "testing/gtest/include/gtest/gtest.h" |
| 25 #include "url/gurl.h" | 27 #include "url/gurl.h" |
| 26 | 28 |
| 27 namespace net { | 29 namespace net { |
| 28 namespace { | 30 namespace { |
| 29 | 31 |
| (...skipping 147 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 177 scoped_ptr<WebSocketStreamRequest> stream_request_; | 179 scoped_ptr<WebSocketStreamRequest> stream_request_; |
| 178 // Only set if the connection succeeded. | 180 // Only set if the connection succeeded. |
| 179 scoped_ptr<WebSocketStream> stream_; | 181 scoped_ptr<WebSocketStream> stream_; |
| 180 // Only set if the connection failed. | 182 // Only set if the connection failed. |
| 181 std::string failure_message_; | 183 std::string failure_message_; |
| 182 bool has_failed_; | 184 bool has_failed_; |
| 183 scoped_ptr<WebSocketHandshakeRequestInfo> request_info_; | 185 scoped_ptr<WebSocketHandshakeRequestInfo> request_info_; |
| 184 scoped_ptr<WebSocketHandshakeResponseInfo> response_info_; | 186 scoped_ptr<WebSocketHandshakeResponseInfo> response_info_; |
| 185 }; | 187 }; |
| 186 | 188 |
| 189 // There are enough tests of the Sec-WebSocket-Extensions header that they |
| 190 // deserve their own test fixture. |
| 191 class WebSocketStreamCreateExtensionTest : public WebSocketStreamCreateTest { |
| 192 public: |
| 193 // Performs a standard connect, with the value of the Sec-WebSocket-Extensions |
| 194 // header in the response set to |extensions_header_value|. Runs the event |
| 195 // loop to allow the connect to complete. |
| 196 void CreateAndConnectWithExtensions( |
| 197 const std::string& extensions_header_value) { |
| 198 CreateAndConnectStandard( |
| 199 "ws://localhost/testing_path", |
| 200 "/testing_path", |
| 201 NoSubProtocols(), |
| 202 "http://localhost/", |
| 203 "", |
| 204 "Sec-WebSocket-Extensions: " + extensions_header_value + "\r\n"); |
| 205 RunUntilIdle(); |
| 206 } |
| 207 }; |
| 208 |
| 187 // Confirm that the basic case works as expected. | 209 // Confirm that the basic case works as expected. |
| 188 TEST_F(WebSocketStreamCreateTest, SimpleSuccess) { | 210 TEST_F(WebSocketStreamCreateTest, SimpleSuccess) { |
| 189 CreateAndConnectStandard( | 211 CreateAndConnectStandard( |
| 190 "ws://localhost/", "/", NoSubProtocols(), "http://localhost/", "", ""); | 212 "ws://localhost/", "/", NoSubProtocols(), "http://localhost/", "", ""); |
| 191 EXPECT_FALSE(request_info_); | 213 EXPECT_FALSE(request_info_); |
| 192 EXPECT_FALSE(response_info_); | 214 EXPECT_FALSE(response_info_); |
| 193 RunUntilIdle(); | 215 RunUntilIdle(); |
| 194 EXPECT_FALSE(has_failed()); | 216 EXPECT_FALSE(has_failed()); |
| 195 EXPECT_TRUE(stream_); | 217 EXPECT_TRUE(stream_); |
| 196 EXPECT_TRUE(request_info_); | 218 EXPECT_TRUE(request_info_); |
| (...skipping 25 matching lines...) Expand all Loading... |
| 222 ASSERT_TRUE(request_info_); | 244 ASSERT_TRUE(request_info_); |
| 223 ASSERT_TRUE(response_info_); | 245 ASSERT_TRUE(response_info_); |
| 224 std::vector<HeaderKeyValuePair> request_headers = | 246 std::vector<HeaderKeyValuePair> request_headers = |
| 225 ToVector(request_info_->headers); | 247 ToVector(request_info_->headers); |
| 226 // We examine the contents of request_info_ and response_info_ | 248 // We examine the contents of request_info_ and response_info_ |
| 227 // mainly only in this test case. | 249 // mainly only in this test case. |
| 228 EXPECT_EQ(GURL("ws://localhost/"), request_info_->url); | 250 EXPECT_EQ(GURL("ws://localhost/"), request_info_->url); |
| 229 EXPECT_EQ(GURL("ws://localhost/"), response_info_->url); | 251 EXPECT_EQ(GURL("ws://localhost/"), response_info_->url); |
| 230 EXPECT_EQ(101, response_info_->status_code); | 252 EXPECT_EQ(101, response_info_->status_code); |
| 231 EXPECT_EQ("Switching Protocols", response_info_->status_text); | 253 EXPECT_EQ("Switching Protocols", response_info_->status_text); |
| 232 EXPECT_EQ(9u, request_headers.size()); | 254 EXPECT_EQ(10u, request_headers.size()); |
| 233 EXPECT_EQ(HeaderKeyValuePair("Host", "localhost"), request_headers[0]); | 255 EXPECT_EQ(HeaderKeyValuePair("Host", "localhost"), request_headers[0]); |
| 234 EXPECT_EQ(HeaderKeyValuePair("Connection", "Upgrade"), request_headers[1]); | 256 EXPECT_EQ(HeaderKeyValuePair("Connection", "Upgrade"), request_headers[1]); |
| 235 EXPECT_EQ(HeaderKeyValuePair("Upgrade", "websocket"), request_headers[2]); | 257 EXPECT_EQ(HeaderKeyValuePair("Upgrade", "websocket"), request_headers[2]); |
| 236 EXPECT_EQ(HeaderKeyValuePair("Origin", "http://localhost/"), | 258 EXPECT_EQ(HeaderKeyValuePair("Origin", "http://localhost/"), |
| 237 request_headers[3]); | 259 request_headers[3]); |
| 238 EXPECT_EQ(HeaderKeyValuePair("Sec-WebSocket-Version", "13"), | 260 EXPECT_EQ(HeaderKeyValuePair("Sec-WebSocket-Version", "13"), |
| 239 request_headers[4]); | 261 request_headers[4]); |
| 240 EXPECT_EQ(HeaderKeyValuePair("User-Agent", ""), request_headers[5]); | 262 EXPECT_EQ(HeaderKeyValuePair("User-Agent", ""), request_headers[5]); |
| 241 EXPECT_EQ(HeaderKeyValuePair("Accept-Encoding", "gzip,deflate"), | 263 EXPECT_EQ(HeaderKeyValuePair("Accept-Encoding", "gzip,deflate"), |
| 242 request_headers[6]); | 264 request_headers[6]); |
| 243 EXPECT_EQ(HeaderKeyValuePair("Accept-Language", "en-us,fr"), | 265 EXPECT_EQ(HeaderKeyValuePair("Accept-Language", "en-us,fr"), |
| 244 request_headers[7]); | 266 request_headers[7]); |
| 245 EXPECT_EQ("Sec-WebSocket-Key", request_headers[8].first); | 267 EXPECT_EQ("Sec-WebSocket-Key", request_headers[8].first); |
| 268 EXPECT_EQ(HeaderKeyValuePair("Sec-WebSocket-Extensions", |
| 269 "permessage-deflate; client_max_window_bits"), |
| 270 request_headers[9]); |
| 246 | 271 |
| 247 std::vector<HeaderKeyValuePair> response_headers = | 272 std::vector<HeaderKeyValuePair> response_headers = |
| 248 ToVector(*response_info_->headers); | 273 ToVector(*response_info_->headers); |
| 249 ASSERT_EQ(6u, response_headers.size()); | 274 ASSERT_EQ(6u, response_headers.size()); |
| 250 // Sort the headers for ease of verification. | 275 // Sort the headers for ease of verification. |
| 251 std::sort(response_headers.begin(), response_headers.end()); | 276 std::sort(response_headers.begin(), response_headers.end()); |
| 252 | 277 |
| 253 EXPECT_EQ(HeaderKeyValuePair("Connection", "Upgrade"), response_headers[0]); | 278 EXPECT_EQ(HeaderKeyValuePair("Connection", "Upgrade"), response_headers[0]); |
| 254 EXPECT_EQ("Sec-WebSocket-Accept", response_headers[1].first); | 279 EXPECT_EQ("Sec-WebSocket-Accept", response_headers[1].first); |
| 255 EXPECT_EQ(HeaderKeyValuePair("Upgrade", "websocket"), response_headers[2]); | 280 EXPECT_EQ(HeaderKeyValuePair("Upgrade", "websocket"), response_headers[2]); |
| (...skipping 126 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 382 "Sec-WebSocket-Protocol: chatv21.chromium.org\r\n"); | 407 "Sec-WebSocket-Protocol: chatv21.chromium.org\r\n"); |
| 383 RunUntilIdle(); | 408 RunUntilIdle(); |
| 384 EXPECT_FALSE(stream_); | 409 EXPECT_FALSE(stream_); |
| 385 EXPECT_TRUE(has_failed()); | 410 EXPECT_TRUE(has_failed()); |
| 386 EXPECT_EQ("Error during WebSocket handshake: " | 411 EXPECT_EQ("Error during WebSocket handshake: " |
| 387 "'Sec-WebSocket-Protocol' header value 'chatv21.chromium.org' " | 412 "'Sec-WebSocket-Protocol' header value 'chatv21.chromium.org' " |
| 388 "in response does not match any of sent values", | 413 "in response does not match any of sent values", |
| 389 failure_message()); | 414 failure_message()); |
| 390 } | 415 } |
| 391 | 416 |
| 417 // permessage-deflate extension basic success case. |
| 418 TEST_F(WebSocketStreamCreateExtensionTest, PerMessageDeflateSuccess) { |
| 419 CreateAndConnectWithExtensions("permessage-deflate"); |
| 420 EXPECT_TRUE(stream_); |
| 421 EXPECT_FALSE(has_failed()); |
| 422 } |
| 423 |
| 424 // permessage-deflate extensions success with all parameters. |
| 425 TEST_F(WebSocketStreamCreateExtensionTest, PerMessageDeflateParamsSuccess) { |
| 426 CreateAndConnectWithExtensions( |
| 427 "permessage-deflate; client_no_context_takeover; " |
| 428 "server_max_window_bits=11; client_max_window_bits=13; " |
| 429 "server_no_context_takeover"); |
| 430 EXPECT_TRUE(stream_); |
| 431 EXPECT_FALSE(has_failed()); |
| 432 } |
| 433 |
| 434 // Verify that incoming messages are actually decompressed with |
| 435 // permessage-deflate enabled. |
| 436 TEST_F(WebSocketStreamCreateExtensionTest, PerMessageDeflateInflates) { |
| 437 CreateAndConnectCustomResponse( |
| 438 "ws://localhost/testing_path", |
| 439 "/testing_path", |
| 440 NoSubProtocols(), |
| 441 "http://localhost/", |
| 442 "", |
| 443 WebSocketStandardResponse( |
| 444 "Sec-WebSocket-Extensions: permessage-deflate\r\n") + |
| 445 std::string( |
| 446 "\xc1\x07" // WebSocket header (FIN + RSV1, Text payload 7 bytes) |
| 447 "\xf2\x48\xcd\xc9\xc9\x07\x00", // "Hello" DEFLATE compressed |
| 448 9)); |
| 449 RunUntilIdle(); |
| 450 |
| 451 ASSERT_TRUE(stream_); |
| 452 ScopedVector<WebSocketFrame> frames; |
| 453 CompletionCallback callback; |
| 454 ASSERT_EQ(OK, stream_->ReadFrames(&frames, callback)); |
| 455 ASSERT_EQ(1U, frames.size()); |
| 456 ASSERT_EQ(5U, frames[0]->header.payload_length); |
| 457 EXPECT_EQ("Hello", std::string(frames[0]->data->data(), 5)); |
| 458 } |
| 459 |
| 392 // Unknown extension in the response is rejected | 460 // Unknown extension in the response is rejected |
| 393 TEST_F(WebSocketStreamCreateTest, UnknownExtension) { | 461 TEST_F(WebSocketStreamCreateExtensionTest, UnknownExtension) { |
| 394 CreateAndConnectStandard("ws://localhost/testing_path", | 462 CreateAndConnectWithExtensions("x-unknown-extension"); |
| 395 "/testing_path", | |
| 396 NoSubProtocols(), | |
| 397 "http://localhost/", | |
| 398 "", | |
| 399 "Sec-WebSocket-Extensions: x-unknown-extension\r\n"); | |
| 400 RunUntilIdle(); | |
| 401 EXPECT_FALSE(stream_); | 463 EXPECT_FALSE(stream_); |
| 402 EXPECT_TRUE(has_failed()); | 464 EXPECT_TRUE(has_failed()); |
| 403 EXPECT_EQ("Error during WebSocket handshake: " | 465 EXPECT_EQ("Error during WebSocket handshake: " |
| 404 "Found an unsupported extension 'x-unknown-extension' " | 466 "Found an unsupported extension 'x-unknown-extension' " |
| 405 "in 'Sec-WebSocket-Extensions' header", | 467 "in 'Sec-WebSocket-Extensions' header", |
| 406 failure_message()); | 468 failure_message()); |
| 407 } | 469 } |
| 408 | 470 |
| 471 // Malformed extensions are rejected (this file does not cover all possible |
| 472 // parse failures, as the parser is covered thoroughly by its own unit tests). |
| 473 TEST_F(WebSocketStreamCreateExtensionTest, MalformedExtension) { |
| 474 CreateAndConnectWithExtensions(";"); |
| 475 EXPECT_FALSE(stream_); |
| 476 EXPECT_TRUE(has_failed()); |
| 477 EXPECT_EQ( |
| 478 "Error during WebSocket handshake: 'Sec-WebSocket-Extensions' header " |
| 479 "value is rejected by the parser: ;", |
| 480 failure_message()); |
| 481 } |
| 482 |
| 483 // The permessage-deflate extension may only be specified once. |
| 484 TEST_F(WebSocketStreamCreateExtensionTest, OnlyOnePerMessageDeflateAllowed) { |
| 485 CreateAndConnectWithExtensions( |
| 486 "permessage-deflate, permessage-deflate; client_max_window_bits=10"); |
| 487 EXPECT_FALSE(stream_); |
| 488 EXPECT_TRUE(has_failed()); |
| 489 EXPECT_EQ( |
| 490 "Error during WebSocket handshake: Received duplicate permessage-deflate " |
| 491 "response", |
| 492 failure_message()); |
| 493 } |
| 494 |
| 495 // permessage-deflate parameters may not be duplicated. |
| 496 TEST_F(WebSocketStreamCreateExtensionTest, NoDuplicateParameters) { |
| 497 CreateAndConnectWithExtensions( |
| 498 "permessage-deflate; client_no_context_takeover; " |
| 499 "client_no_context_takeover"); |
| 500 EXPECT_FALSE(stream_); |
| 501 EXPECT_TRUE(has_failed()); |
| 502 EXPECT_EQ( |
| 503 "Error during WebSocket handshake: Received duplicate permessage-deflate " |
| 504 "extension parameter client_no_context_takeover", |
| 505 failure_message()); |
| 506 } |
| 507 |
| 508 // permessage-deflate parameters must start with "client_" or "server_" |
| 509 TEST_F(WebSocketStreamCreateExtensionTest, BadParameterPrefix) { |
| 510 CreateAndConnectWithExtensions( |
| 511 "permessage-deflate; absurd_no_context_takeover"); |
| 512 EXPECT_FALSE(stream_); |
| 513 EXPECT_TRUE(has_failed()); |
| 514 EXPECT_EQ( |
| 515 "Error during WebSocket handshake: Received an unexpected " |
| 516 "permessage-deflate extension parameter", |
| 517 failure_message()); |
| 518 } |
| 519 |
| 520 // permessage-deflate parameters must be either *_no_context_takeover or |
| 521 // *_max_window_bits |
| 522 TEST_F(WebSocketStreamCreateExtensionTest, BadParameterSuffix) { |
| 523 CreateAndConnectWithExtensions( |
| 524 "permessage-deflate; client_max_content_bits=5"); |
| 525 EXPECT_FALSE(stream_); |
| 526 EXPECT_TRUE(has_failed()); |
| 527 EXPECT_EQ( |
| 528 "Error during WebSocket handshake: Received an unexpected " |
| 529 "permessage-deflate extension parameter", |
| 530 failure_message()); |
| 531 } |
| 532 |
| 533 // *_no_context_takeover parameters must not have an argument |
| 534 TEST_F(WebSocketStreamCreateExtensionTest, BadParameterValue) { |
| 535 CreateAndConnectWithExtensions( |
| 536 "permessage-deflate; client_no_context_takeover=true"); |
| 537 EXPECT_FALSE(stream_); |
| 538 EXPECT_TRUE(has_failed()); |
| 539 EXPECT_EQ( |
| 540 "Error during WebSocket handshake: Received invalid " |
| 541 "client_no_context_takeover parameter", |
| 542 failure_message()); |
| 543 } |
| 544 |
| 545 // *_max_window_bits must have an argument |
| 546 TEST_F(WebSocketStreamCreateExtensionTest, NoMaxWindowBitsArgument) { |
| 547 CreateAndConnectWithExtensions("permessage-deflate; client_max_window_bits"); |
| 548 EXPECT_FALSE(stream_); |
| 549 EXPECT_TRUE(has_failed()); |
| 550 EXPECT_EQ( |
| 551 "Error during WebSocket handshake: client_max_window_bits must have " |
| 552 "value", |
| 553 failure_message()); |
| 554 } |
| 555 |
| 556 // *_max_window_bits must be an integer |
| 557 TEST_F(WebSocketStreamCreateExtensionTest, MaxWindowBitsValueInteger) { |
| 558 CreateAndConnectWithExtensions( |
| 559 "permessage-deflate; server_max_window_bits=banana"); |
| 560 EXPECT_FALSE(stream_); |
| 561 EXPECT_TRUE(has_failed()); |
| 562 EXPECT_EQ( |
| 563 "Error during WebSocket handshake: Received invalid " |
| 564 "server_max_window_bits parameter", |
| 565 failure_message()); |
| 566 } |
| 567 |
| 568 // *_max_window_bits must be >= 8 |
| 569 TEST_F(WebSocketStreamCreateExtensionTest, MaxWindowBitsValueTooSmall) { |
| 570 CreateAndConnectWithExtensions( |
| 571 "permessage-deflate; server_max_window_bits=7"); |
| 572 EXPECT_FALSE(stream_); |
| 573 EXPECT_TRUE(has_failed()); |
| 574 EXPECT_EQ( |
| 575 "Error during WebSocket handshake: Received invalid " |
| 576 "server_max_window_bits parameter", |
| 577 failure_message()); |
| 578 } |
| 579 |
| 580 // *_max_window_bits must be <= 15 |
| 581 TEST_F(WebSocketStreamCreateExtensionTest, MaxWindowBitsValueTooBig) { |
| 582 CreateAndConnectWithExtensions( |
| 583 "permessage-deflate; client_max_window_bits=16"); |
| 584 EXPECT_FALSE(stream_); |
| 585 EXPECT_TRUE(has_failed()); |
| 586 EXPECT_EQ( |
| 587 "Error during WebSocket handshake: Received invalid " |
| 588 "client_max_window_bits parameter", |
| 589 failure_message()); |
| 590 } |
| 591 |
| 592 // *_max_window_bits must not start with 0 |
| 593 TEST_F(WebSocketStreamCreateExtensionTest, MaxWindowBitsValueStartsWithZero) { |
| 594 CreateAndConnectWithExtensions( |
| 595 "permessage-deflate; client_max_window_bits=08"); |
| 596 EXPECT_FALSE(stream_); |
| 597 EXPECT_TRUE(has_failed()); |
| 598 EXPECT_EQ( |
| 599 "Error during WebSocket handshake: Received invalid " |
| 600 "client_max_window_bits parameter", |
| 601 failure_message()); |
| 602 } |
| 603 |
| 604 // *_max_window_bits must not start with + |
| 605 TEST_F(WebSocketStreamCreateExtensionTest, MaxWindowBitsValueStartsWithPlus) { |
| 606 CreateAndConnectWithExtensions( |
| 607 "permessage-deflate; server_max_window_bits=+9"); |
| 608 EXPECT_FALSE(stream_); |
| 609 EXPECT_TRUE(has_failed()); |
| 610 EXPECT_EQ( |
| 611 "Error during WebSocket handshake: Received invalid " |
| 612 "server_max_window_bits parameter", |
| 613 failure_message()); |
| 614 } |
| 615 |
| 616 // TODO(ricea): Check that WebSocketDeflateStream is initialised with the |
| 617 // arguments from the server. This is difficult because the data written to the |
| 618 // socket is randomly masked. |
| 619 |
| 409 // Additional Sec-WebSocket-Accept headers should be rejected. | 620 // Additional Sec-WebSocket-Accept headers should be rejected. |
| 410 TEST_F(WebSocketStreamCreateTest, DoubleAccept) { | 621 TEST_F(WebSocketStreamCreateTest, DoubleAccept) { |
| 411 CreateAndConnectStandard( | 622 CreateAndConnectStandard( |
| 412 "ws://localhost/", | 623 "ws://localhost/", |
| 413 "/", | 624 "/", |
| 414 NoSubProtocols(), | 625 NoSubProtocols(), |
| 415 "http://localhost/", | 626 "http://localhost/", |
| 416 "", | 627 "", |
| 417 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"); | 628 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"); |
| 418 RunUntilIdle(); | 629 RunUntilIdle(); |
| (...skipping 324 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 743 stream_request_.reset(); | 954 stream_request_.reset(); |
| 744 RunUntilIdle(); | 955 RunUntilIdle(); |
| 745 EXPECT_FALSE(has_failed()); | 956 EXPECT_FALSE(has_failed()); |
| 746 EXPECT_FALSE(stream_); | 957 EXPECT_FALSE(stream_); |
| 747 EXPECT_TRUE(request_info_); | 958 EXPECT_TRUE(request_info_); |
| 748 EXPECT_FALSE(response_info_); | 959 EXPECT_FALSE(response_info_); |
| 749 } | 960 } |
| 750 | 961 |
| 751 } // namespace | 962 } // namespace |
| 752 } // namespace net | 963 } // namespace net |
| OLD | NEW |