// Copyright 2013 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifdef UNSAFE_BUFFERS_BUILD // TODO(crbug.com/40284755): Remove this and spanify to fix the errors. #pragma allow_unsafe_buffers #endif #include "net/websockets/websocket_channel.h" #include <stddef.h> #include <string.h> #include <algorithm> #include <iostream> #include <iterator> #include <string> #include <string_view> #include <tuple> #include <utility> #include <vector> #include "base/check.h" #include "base/dcheck_is_on.h" #include "base/functional/bind.h" #include "base/functional/callback.h" #include "base/location.h" #include "base/memory/raw_ptr.h" #include "base/memory/weak_ptr.h" #include "base/ranges/algorithm.h" #include "base/run_loop.h" #include "base/task/single_thread_task_runner.h" #include "base/time/time.h" #include "net/base/auth.h" #include "net/base/completion_once_callback.h" #include "net/base/io_buffer.h" #include "net/base/ip_address.h" #include "net/base/ip_endpoint.h" #include "net/base/isolation_info.h" #include "net/base/net_errors.h" #include "net/base/test_completion_callback.h" #include "net/cookies/site_for_cookies.h" #include "net/http/http_request_headers.h" #include "net/http/http_response_headers.h" #include "net/log/net_log_with_source.h" #include "net/ssl/ssl_info.h" #include "net/storage_access_api/status.h" #include "net/test/test_with_task_environment.h" #include "net/traffic_annotation/network_traffic_annotation.h" #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" #include "net/url_request/url_request_context.h" #include "net/url_request/url_request_context_builder.h" #include "net/url_request/url_request_test_util.h" #include "net/websockets/websocket_errors.h" #include "net/websockets/websocket_event_interface.h" #include "net/websockets/websocket_handshake_request_info.h" #include "net/websockets/websocket_handshake_response_info.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" #include "url/origin.h" // Hacky macros to construct the body of a Close message from a code and a // string, while ensuring the result is a compile-time constant string. // Use like CLOSE_DATA(NORMAL_CLOSURE, "Explanation String") #define CLOSE_DATA(code, string) … #define WEBSOCKET_CLOSE_CODE_AS_STRING_NORMAL_CLOSURE … #define WEBSOCKET_CLOSE_CODE_AS_STRING_GOING_AWAY … #define WEBSOCKET_CLOSE_CODE_AS_STRING_PROTOCOL_ERROR … #define WEBSOCKET_CLOSE_CODE_AS_STRING_ABNORMAL_CLOSURE … #define WEBSOCKET_CLOSE_CODE_AS_STRING_SERVER_ERROR … namespace net { class WebSocketBasicHandshakeStream; class WebSocketHttp2HandshakeStream; // Printing helpers to allow GoogleMock to print frames. These are explicitly // designed to look like the static initialisation format we use in these // tests. They have to live in the net namespace in order to be found by // GoogleMock; a nested anonymous namespace will not work. std::ostream& operator<<(std::ostream& os, const WebSocketFrameHeader& header) { … } std::ostream& operator<<(std::ostream& os, const WebSocketFrame& frame) { … } std::ostream& operator<<( std::ostream& os, const std::vector<std::unique_ptr<WebSocketFrame>>& frames) { … } std::ostream& operator<<( std::ostream& os, const std::vector<std::unique_ptr<WebSocketFrame>>* vector) { … } namespace { TimeDelta; _; AnyNumber; DefaultValue; DoAll; InSequence; MockFunction; NotNull; Return; ReturnRef; SaveArg; StrictMock; // A selection of characters that have traditionally been mangled in some // environment or other, for testing 8-bit cleanliness. constexpr char kBinaryBlob[] = …; constexpr size_t kBinaryBlobSize = …; constexpr int kVeryBigTimeoutMillis = …; // TestTimeouts::tiny_timeout() is 100ms! I could run halfway around the world // in that time! I would like my tests to run a bit quicker. constexpr int kVeryTinyTimeoutMillis = …; ChannelState; constexpr ChannelState CHANNEL_ALIVE = …; constexpr ChannelState CHANNEL_DELETED = …; // This typedef mainly exists to avoid having to repeat the "NOLINT" incantation // all over the place. Checkpoint; // NOLINT // This mock is for testing expectations about how the EventInterface is used. class MockWebSocketEventInterface : public WebSocketEventInterface { … }; // This fake EventInterface is for tests which need a WebSocketEventInterface // implementation but are not verifying how it is used. class FakeWebSocketEventInterface : public WebSocketEventInterface { … }; // This fake WebSocketStream is for tests that require a WebSocketStream but are // not testing the way it is used. It has minimal functionality to return // the |protocol| and |extensions| that it was constructed with. class FakeWebSocketStream : public WebSocketStream { … }; // To make the static initialisers easier to read, we use enums rather than // bools. enum IsFinal { … }; enum IsMasked { … }; // This is used to initialise a WebSocketFrame but is statically initialisable. struct InitFrame { … }; // For GoogleMock std::ostream& operator<<(std::ostream& os, const InitFrame& frame) { … } template <size_t N> std::ostream& operator<<(std::ostream& os, const InitFrame (&frames)[N]) { … } // Convert a const array of InitFrame structs to the format used at // runtime. Templated on the size of the array to save typing. template <size_t N> std::vector<std::unique_ptr<WebSocketFrame>> CreateFrameVector( const InitFrame (&source_frames)[N], std::vector<scoped_refptr<IOBuffer>>* result_frame_data) { … } // A GoogleMock action which can be used to respond to call to ReadFrames with // some frames. Use like ReadFrames(_, _).WillOnce(ReturnFrames(&frames, // &result_frame_data_)); |frames| is an array of InitFrame. |frames| needs to // be passed by pointer because otherwise it will be treated as a pointer and // the array size information will be lost. ACTION_P2(ReturnFrames, source_frames, result_frame_data) { … } // The implementation of a GoogleMock matcher which can be used to compare a // std::vector<std::unique_ptr<WebSocketFrame>>* against an expectation defined // as an // array of InitFrame objects. Although it is possible to compose built-in // GoogleMock matchers to check the contents of a WebSocketFrame, the results // are so unreadable that it is better to use this matcher. template <size_t N> class EqualsFramesMatcher : public ::testing::MatcherInterface< std::vector<std::unique_ptr<WebSocketFrame>>*> { … }; // The definition of EqualsFrames GoogleMock matcher. Unlike the ReturnFrames // action, this can take the array by reference. template <size_t N> ::testing::Matcher<std::vector<std::unique_ptr<WebSocketFrame>>*> EqualsFrames( const InitFrame (&frames)[N]) { … } // A GoogleMock action to run a Closure. ACTION_P(InvokeClosure, test_closure) { … } // A FakeWebSocketStream whose ReadFrames() function returns data. class ReadableFakeWebSocketStream : public FakeWebSocketStream { … }; // A FakeWebSocketStream where writes always complete successfully and // synchronously. class WriteableFakeWebSocketStream : public FakeWebSocketStream { … }; // A FakeWebSocketStream where writes always fail. class UnWriteableFakeWebSocketStream : public FakeWebSocketStream { … }; // A FakeWebSocketStream which echoes any frames written back. Clears the // "masked" header bit, but makes no other checks for validity. Tests using this // must run the MessageLoop to receive the callback(s). If a message with opcode // Close is echoed, then an ERR_CONNECTION_CLOSED is returned in the next // callback. The test must do something to cause WriteFrames() to be called, // otherwise the ReadFrames() callback will never be called. class EchoeyFakeWebSocketStream : public FakeWebSocketStream { … }; // A FakeWebSocketStream where writes trigger a connection reset. // This differs from UnWriteableFakeWebSocketStream in that it is asynchronous // and triggers ReadFrames to return a reset as well. Tests using this need to // run the message loop. There are two tricky parts here: // 1. Calling the write callback may call Close(), after which the read callback // should not be called. // 2. Calling either callback may delete the stream altogether. class ResetOnWriteFakeWebSocketStream : public FakeWebSocketStream { … }; // This mock is for verifying that WebSocket protocol semantics are obeyed (to // the extent that they are implemented in WebSocketCommon). class MockWebSocketStream : public WebSocketStream { … }; class MockWebSocketStreamRequest : public WebSocketStreamRequest { … }; struct WebSocketStreamCreationCallbackArgumentSaver { … }; std::vector<char> AsVector(std::string_view s) { … } // Converts a std::string_view to a IOBuffer. For test purposes, it is // convenient to be able to specify data as a string, but the // WebSocketEventInterface requires the IOBuffer type. scoped_refptr<IOBuffer> AsIOBuffer(std::string_view s) { … } class FakeSSLErrorCallbacks : public WebSocketEventInterface::SSLErrorCallbacks { … }; // Base class for all test fixtures. class WebSocketChannelTest : public TestWithTaskEnvironment { … }; // enum of WebSocketEventInterface calls. These are intended to be or'd together // in order to instruct WebSocketChannelDeletingTest when it should fail. enum EventInterfaceCall { … }; // Base class for tests which verify that EventInterface methods are called // appropriately. class WebSocketChannelEventInterfaceTest : public WebSocketChannelTest { … }; // Base class for tests which verify that WebSocketStream methods are called // appropriately by using a MockWebSocketStream. class WebSocketChannelStreamTest : public WebSocketChannelEventInterfaceTest { … }; // Fixture for tests which test UTF-8 validation of sent Text frames via the // EventInterface. class WebSocketChannelSendUtf8Test : public WebSocketChannelEventInterfaceTest { … }; // Fixture for tests which test UTF-8 validation of received Text frames using a // mock WebSocketStream. class WebSocketChannelReceiveUtf8Test : public WebSocketChannelStreamTest { … }; // Simple test that everything that should be passed to the stream creation // callback is passed to the argument saver. TEST_F(WebSocketChannelTest, EverythingIsPassedToTheCreatorFunction) { … } TEST_F(WebSocketChannelEventInterfaceTest, ConnectSuccessReported) { … } TEST_F(WebSocketChannelEventInterfaceTest, ConnectFailureReported) { … } TEST_F(WebSocketChannelEventInterfaceTest, NonWebSocketSchemeRejected) { … } TEST_F(WebSocketChannelEventInterfaceTest, ProtocolPassed) { … } TEST_F(WebSocketChannelEventInterfaceTest, ExtensionsPassed) { … } // The first frames from the server can arrive together with the handshake, in // which case they will be available as soon as ReadFrames() is called the first // time. TEST_F(WebSocketChannelEventInterfaceTest, DataLeftFromHandshake) { … } // A remote server could accept the handshake, but then immediately send a // Close frame. TEST_F(WebSocketChannelEventInterfaceTest, CloseAfterHandshake) { … } // Do not close until browser has sent all pending frames. TEST_F(WebSocketChannelEventInterfaceTest, ShouldCloseWhileNoDataFrames) { … } // A remote server could close the connection immediately after sending the // handshake response (most likely a bug in the server). TEST_F(WebSocketChannelEventInterfaceTest, ConnectionCloseAfterHandshake) { … } TEST_F(WebSocketChannelEventInterfaceTest, NormalAsyncRead) { … } // Extra data can arrive while a read is being processed, resulting in the next // read completing synchronously. TEST_F(WebSocketChannelEventInterfaceTest, AsyncThenSyncRead) { … } // Data frames are delivered the same regardless of how many reads they arrive // as. TEST_F(WebSocketChannelEventInterfaceTest, FragmentedMessage) { … } // A message can consist of one frame with null payload. TEST_F(WebSocketChannelEventInterfaceTest, NullMessage) { … } // Connection closed by the remote host without a closing handshake. TEST_F(WebSocketChannelEventInterfaceTest, AsyncAbnormalClosure) { … } // A connection reset should produce the same event as an unexpected closure. TEST_F(WebSocketChannelEventInterfaceTest, ConnectionReset) { … } // RFC6455 5.1 "A client MUST close a connection if it detects a masked frame." TEST_F(WebSocketChannelEventInterfaceTest, MaskedFramesAreRejected) { … } // RFC6455 5.2 "If an unknown opcode is received, the receiving endpoint MUST // _Fail the WebSocket Connection_." TEST_F(WebSocketChannelEventInterfaceTest, UnknownOpCodeIsRejected) { … } // RFC6455 5.4 "Control frames ... MAY be injected in the middle of a // fragmented message." TEST_F(WebSocketChannelEventInterfaceTest, ControlFrameInDataMessage) { … } // It seems redundant to repeat the entirety of the above test, so just test a // Pong with null data. TEST_F(WebSocketChannelEventInterfaceTest, PongWithNullData) { … } // If a frame has an invalid header, then the connection is closed and // subsequent frames must not trigger events. TEST_F(WebSocketChannelEventInterfaceTest, FrameAfterInvalidFrame) { … } // If a write fails, the channel is dropped. TEST_F(WebSocketChannelEventInterfaceTest, FailedWrite) { … } // OnDropChannel() is called exactly once when StartClosingHandshake() is used. TEST_F(WebSocketChannelEventInterfaceTest, SendCloseDropsChannel) { … } // StartClosingHandshake() also works before connection completes, and calls // OnDropChannel. TEST_F(WebSocketChannelEventInterfaceTest, CloseDuringConnection) { … } // OnDropChannel() is only called once when a write() on the socket triggers a // connection reset. TEST_F(WebSocketChannelEventInterfaceTest, OnDropChannelCalledOnce) { … } // When the remote server sends a Close frame with an empty payload, // WebSocketChannel should report code 1005, kWebSocketErrorNoStatusReceived. TEST_F(WebSocketChannelEventInterfaceTest, CloseWithNoPayloadGivesStatus1005) { … } // A version of the above test with null payload. TEST_F(WebSocketChannelEventInterfaceTest, CloseWithNullPayloadGivesStatus1005) { … } // If ReadFrames() returns ERR_WS_PROTOCOL_ERROR, then the connection must be // failed. TEST_F(WebSocketChannelEventInterfaceTest, SyncProtocolErrorGivesStatus1002) { … } // Async version of above test. TEST_F(WebSocketChannelEventInterfaceTest, AsyncProtocolErrorGivesStatus1002) { … } TEST_F(WebSocketChannelEventInterfaceTest, StartHandshakeRequest) { … } TEST_F(WebSocketChannelEventInterfaceTest, FailJustAfterHandshake) { … } // Any frame after close is invalid. This test uses a Text frame. See also // test "PingAfterCloseIfRejected". TEST_F(WebSocketChannelEventInterfaceTest, DataAfterCloseIsRejected) { … } // A Close frame with a one-byte payload elicits a specific console error // message. TEST_F(WebSocketChannelEventInterfaceTest, OneByteClosePayloadMessage) { … } // A Close frame with a reserved status code also elicits a specific console // error message. TEST_F(WebSocketChannelEventInterfaceTest, ClosePayloadReservedStatusMessage) { … } // A Close frame with invalid UTF-8 also elicits a specific console error // message. TEST_F(WebSocketChannelEventInterfaceTest, ClosePayloadInvalidReason) { … } // The reserved bits must all be clear on received frames. Extensions should // clear the bits when they are set correctly before passing on the frame. TEST_F(WebSocketChannelEventInterfaceTest, ReservedBitsMustNotBeSet) { … } // The closing handshake times out and sends an OnDropChannel event if no // response to the client Close message is received. TEST_F(WebSocketChannelEventInterfaceTest, ClientInitiatedClosingHandshakeTimesOut) { … } // The closing handshake times out and sends an OnDropChannel event if a Close // message is received but the connection isn't closed by the remote host. TEST_F(WebSocketChannelEventInterfaceTest, ServerInitiatedClosingHandshakeTimesOut) { … } // We should stop calling ReadFrames() when data frames are pending. TEST_F(WebSocketChannelStreamTest, PendingDataFrameStopsReadFrames) { … } TEST_F(WebSocketChannelEventInterfaceTest, SingleFrameMessage) { … } TEST_F(WebSocketChannelEventInterfaceTest, EmptyMessage) { … } // A close frame should not overtake data frames. TEST_F(WebSocketChannelEventInterfaceTest, CloseFrameShouldNotOvertakeDataFrames) { … } // RFC6455 5.1 "a client MUST mask all frames that it sends to the server". // WebSocketChannel actually only sets the mask bit in the header, it doesn't // perform masking itself (not all transports actually use masking). TEST_F(WebSocketChannelStreamTest, SentFramesAreMasked) { … } // RFC6455 5.5.1 "The application MUST NOT send any more data frames after // sending a Close frame." TEST_F(WebSocketChannelStreamTest, NothingIsSentAfterClose) { … } // RFC6455 5.5.1 "If an endpoint receives a Close frame and did not previously // send a Close frame, the endpoint MUST send a Close frame in response." TEST_F(WebSocketChannelStreamTest, CloseIsEchoedBack) { … } // The converse of the above case; after sending a Close frame, we should not // send another one. TEST_F(WebSocketChannelStreamTest, CloseOnlySentOnce) { … } // Invalid close status codes should not be sent on the network. TEST_F(WebSocketChannelStreamTest, InvalidCloseStatusCodeNotSent) { … } // A Close frame with a reason longer than 123 bytes cannot be sent on the // network. TEST_F(WebSocketChannelStreamTest, LongCloseReasonNotSent) { … } // We generate code 1005, kWebSocketErrorNoStatusReceived, when there is no // status in the Close message from the other side. Code 1005 is not allowed to // appear on the wire, so we should not echo it back. See test // CloseWithNoPayloadGivesStatus1005, above, for confirmation that code 1005 is // correctly generated internally. TEST_F(WebSocketChannelStreamTest, Code1005IsNotEchoed) { … } TEST_F(WebSocketChannelStreamTest, Code1005IsNotEchoedNull) { … } // Receiving an invalid UTF-8 payload in a Close frame causes us to fail the // connection. TEST_F(WebSocketChannelStreamTest, CloseFrameInvalidUtf8) { … } // RFC6455 5.5.2 "Upon receipt of a Ping frame, an endpoint MUST send a Pong // frame in response" // 5.5.3 "A Pong frame sent in response to a Ping frame must have identical // "Application data" as found in the message body of the Ping frame being // replied to." TEST_F(WebSocketChannelStreamTest, PingRepliedWithPong) { … } // A ping with a null payload should be responded to with a Pong with a null // payload. TEST_F(WebSocketChannelStreamTest, NullPingRepliedWithNullPong) { … } TEST_F(WebSocketChannelStreamTest, PongInTheMiddleOfDataMessage) { … } // WriteFrames() may not be called until the previous write has completed. // WebSocketChannel must buffer writes that happen in the meantime. TEST_F(WebSocketChannelStreamTest, WriteFramesOneAtATime) { … } // WebSocketChannel must buffer frames while it is waiting for a write to // complete, and then send them in a single batch. The batching behaviour is // important to get good throughput in the "many small messages" case. TEST_F(WebSocketChannelStreamTest, WaitingMessagesAreBatched) { … } // For convenience, most of these tests use Text frames. However, the WebSocket // protocol also has Binary frames and those need to be 8-bit clean. For the // sake of completeness, this test verifies that they are. TEST_F(WebSocketChannelStreamTest, WrittenBinaryFramesAre8BitClean) { … } // Test the read path for 8-bit cleanliness as well. TEST_F(WebSocketChannelEventInterfaceTest, ReadBinaryFramesAre8BitClean) { … } // Invalid UTF-8 is not permitted in Text frames. TEST_F(WebSocketChannelSendUtf8Test, InvalidUtf8Rejected) { … } // A Text message cannot end with a partial UTF-8 character. TEST_F(WebSocketChannelSendUtf8Test, IncompleteCharacterInFinalFrame) { … } // A non-final Text frame may end with a partial UTF-8 character (compare to // previous test). TEST_F(WebSocketChannelSendUtf8Test, IncompleteCharacterInNonFinalFrame) { … } // UTF-8 parsing context must be retained between frames. TEST_F(WebSocketChannelSendUtf8Test, ValidCharacterSplitBetweenFrames) { … } // Similarly, an invalid character should be detected even if split. TEST_F(WebSocketChannelSendUtf8Test, InvalidCharacterSplit) { … } // An invalid character must be detected in continuation frames. TEST_F(WebSocketChannelSendUtf8Test, InvalidByteInContinuation) { … } // However, continuation frames of a Binary frame will not be tested for UTF-8 // validity. TEST_F(WebSocketChannelSendUtf8Test, BinaryContinuationNotChecked) { … } // Multiple text messages can be validated without the validation state getting // confused. TEST_F(WebSocketChannelSendUtf8Test, ValidateMultipleTextMessages) { … } // UTF-8 validation is enforced on received Text frames. TEST_F(WebSocketChannelEventInterfaceTest, ReceivedInvalidUtf8) { … } // Invalid UTF-8 is not sent over the network. TEST_F(WebSocketChannelStreamTest, InvalidUtf8TextFrameNotSent) { … } // The rest of the tests for receiving invalid UTF-8 test the communication with // the server. Since there is only one code path, it would be redundant to // perform the same tests on the EventInterface as well. // If invalid UTF-8 is received in a Text frame, the connection is failed. TEST_F(WebSocketChannelReceiveUtf8Test, InvalidTextFrameRejected) { … } // A received Text message is not permitted to end with a partial UTF-8 // character. TEST_F(WebSocketChannelReceiveUtf8Test, IncompleteCharacterReceived) { … } // However, a non-final Text frame may end with a partial UTF-8 character. TEST_F(WebSocketChannelReceiveUtf8Test, IncompleteCharacterIncompleteMessage) { … } // However, it will become an error if it is followed by an empty final frame. TEST_F(WebSocketChannelReceiveUtf8Test, TricksyIncompleteCharacter) { … } // UTF-8 parsing context must be retained between received frames of the same // message. TEST_F(WebSocketChannelReceiveUtf8Test, ReceivedParsingContextRetained) { … } // An invalid character must be detected even if split between frames. TEST_F(WebSocketChannelReceiveUtf8Test, SplitInvalidCharacterReceived) { … } // An invalid character received in a continuation frame must be detected. TEST_F(WebSocketChannelReceiveUtf8Test, InvalidReceivedIncontinuation) { … } // Continuations of binary frames must not be tested for UTF-8 validity. TEST_F(WebSocketChannelReceiveUtf8Test, ReceivedBinaryNotUtf8Tested) { … } // Multiple Text messages can be validated. TEST_F(WebSocketChannelReceiveUtf8Test, ValidateMultipleReceived) { … } // A new data message cannot start in the middle of another data message. TEST_F(WebSocketChannelEventInterfaceTest, BogusContinuation) { … } // A new message cannot start with a Continuation frame. TEST_F(WebSocketChannelEventInterfaceTest, MessageStartingWithContinuation) { … } // A frame passed to the renderer must be either non-empty or have the final bit // set. TEST_F(WebSocketChannelEventInterfaceTest, DataFramesNonEmptyOrFinal) { … } // Calls to OnSSLCertificateError() must be passed through to the event // interface with the correct URL attached. TEST_F(WebSocketChannelEventInterfaceTest, OnSSLCertificateErrorCalled) { … } // Calls to OnAuthRequired() must be passed through to the event interface. TEST_F(WebSocketChannelEventInterfaceTest, OnAuthRequiredCalled) { … } // If we receive another frame after Close, it is not valid. It is not // completely clear what behaviour is required from the standard in this case, // but the current implementation fails the connection. Since a Close has // already been sent, this just means closing the connection. TEST_F(WebSocketChannelStreamTest, PingAfterCloseIsRejected) { … } // A protocol error from the remote server should result in a close frame with // status 1002, followed by the connection closing. TEST_F(WebSocketChannelStreamTest, ProtocolError) { … } // Set the closing handshake timeout to a very tiny value before connecting. class WebSocketChannelStreamTimeoutTest : public WebSocketChannelStreamTest { … }; // In this case the server initiates the closing handshake with a Close // message. WebSocketChannel responds with a matching Close message, and waits // for the server to close the TCP/IP connection. The server never closes the // connection, so the closing handshake times out and WebSocketChannel closes // the connection itself. TEST_F(WebSocketChannelStreamTimeoutTest, ServerInitiatedCloseTimesOut) { … } // In this case the client initiates the closing handshake by sending a Close // message. WebSocketChannel waits for a Close message in response from the // server. The server never responds to the Close message, so the closing // handshake times out and WebSocketChannel closes the connection. TEST_F(WebSocketChannelStreamTimeoutTest, ClientInitiatedCloseTimesOut) { … } // In this case the client initiates the closing handshake and the server // responds with a matching Close message. WebSocketChannel waits for the server // to close the TCP/IP connection, but it never does. The closing handshake // times out and WebSocketChannel closes the connection. TEST_F(WebSocketChannelStreamTimeoutTest, ConnectionCloseTimesOut) { … } } // namespace } // namespace net