15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Copyright (c) 2012 The Chromium Authors. All rights reserved. 25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// found in the LICENSE file. 45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "net/quic/quic_reliable_client_stream.h" 65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "net/base/net_errors.h" 858537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)#include "net/base/test_completion_callback.h" 95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "net/quic/quic_client_session.h" 105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "net/quic/quic_utils.h" 11a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)#include "net/quic/spdy_utils.h" 125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "net/quic/test_tools/quic_test_utils.h" 135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "testing/gmock/include/gmock/gmock.h" 145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)using testing::AnyNumber; 165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)using testing::Return; 175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)using testing::StrEq; 1858537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)using testing::_; 195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)namespace net { 215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)namespace test { 225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)namespace { 235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 24a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)const QuicConnectionId kStreamId = 3; 255d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class MockDelegate : public QuicReliableClientStream::Delegate { 275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) public: 285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) MockDelegate() {} 295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) MOCK_METHOD0(OnSendData, int()); 315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) MOCK_METHOD2(OnSendDataComplete, int(int, bool*)); 325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) MOCK_METHOD2(OnDataReceived, int(const char*, int)); 335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) MOCK_METHOD1(OnClose, void(QuicErrorCode)); 342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) MOCK_METHOD1(OnError, void(int)); 35d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) MOCK_METHOD0(HasSendHeadersComplete, bool()); 365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) private: 385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) DISALLOW_COPY_AND_ASSIGN(MockDelegate); 395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}; 405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)class QuicReliableClientStreamTest 425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) : public ::testing::TestWithParam<QuicVersion> { 435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) public: 445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) QuicReliableClientStreamTest() 455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) : session_(new MockConnection(false, SupportedVersions(GetParam()))) { 465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) stream_ = new QuicReliableClientStream(kStreamId, &session_, BoundNetLog()); 475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) session_.ActivateStream(stream_); 485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) stream_->SetDelegate(&delegate_); 495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 51a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) void InitializeHeaders() { 52a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) headers_[":host"] = "www.google.com"; 53a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) headers_[":path"] = "/index.hml"; 54a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) headers_[":scheme"] = "https"; 55a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) headers_["cookie"] = 56a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) "__utma=208381060.1228362404.1372200928.1372200928.1372200928.1; " 57a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) "__utmc=160408618; " 58a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) "GX=DQAAAOEAAACWJYdewdE9rIrW6qw3PtVi2-d729qaa-74KqOsM1NVQblK4VhX" 59a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) "hoALMsy6HOdDad2Sz0flUByv7etmo3mLMidGrBoljqO9hSVA40SLqpG_iuKKSHX" 60a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) "RW3Np4bq0F0SDGDNsW0DSmTS9ufMRrlpARJDS7qAI6M3bghqJp4eABKZiRqebHT" 61a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) "pMU-RXvTI5D5oCF1vYxYofH_l1Kviuiy3oQ1kS1enqWgbhJ2t61_SNdv-1XJIS0" 62a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) "O3YeHLmVCs62O6zp89QwakfAWK9d3IDQvVSJzCQsvxvNIvaZFa567MawWlXg0Rh" 63a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) "1zFMi5vzcns38-8_Sns; " 64a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) "GA=v*2%2Fmem*57968640*47239936%2Fmem*57968640*47114716%2Fno-nm-" 65a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) "yj*15%2Fno-cc-yj*5%2Fpc-ch*133685%2Fpc-s-cr*133947%2Fpc-s-t*1339" 66a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) "47%2Fno-nm-yj*4%2Fno-cc-yj*1%2Fceft-as*1%2Fceft-nqas*0%2Fad-ra-c" 67a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) "v_p%2Fad-nr-cv_p-f*1%2Fad-v-cv_p*859%2Fad-ns-cv_p-f*1%2Ffn-v-ad%" 68a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) "2Fpc-t*250%2Fpc-cm*461%2Fpc-s-cr*722%2Fpc-s-t*722%2Fau_p*4" 69a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) "SICAID=AJKiYcHdKgxum7KMXG0ei2t1-W4OD1uW-ecNsCqC0wDuAXiDGIcT_HA2o1" 70a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) "3Rs1UKCuBAF9g8rWNOFbxt8PSNSHFuIhOo2t6bJAVpCsMU5Laa6lewuTMYI8MzdQP" 71a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) "ARHKyW-koxuhMZHUnGBJAM1gJODe0cATO_KGoX4pbbFxxJ5IicRxOrWK_5rU3cdy6" 72a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) "edlR9FsEdH6iujMcHkbE5l18ehJDwTWmBKBzVD87naobhMMrF6VvnDGxQVGp9Ir_b" 73a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) "Rgj3RWUoPumQVCxtSOBdX0GlJOEcDTNCzQIm9BSfetog_eP_TfYubKudt5eMsXmN6" 74a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) "QnyXHeGeK2UINUzJ-D30AFcpqYgH9_1BvYSpi7fc7_ydBU8TaD8ZRxvtnzXqj0RfG" 75a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) "tuHghmv3aD-uzSYJ75XDdzKdizZ86IG6Fbn1XFhYZM-fbHhm3mVEXnyRW4ZuNOLFk" 76a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) "Fas6LMcVC6Q8QLlHYbXBpdNFuGbuZGUnav5C-2I_-46lL0NGg3GewxGKGHvHEfoyn" 77a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) "EFFlEYHsBQ98rXImL8ySDycdLEFvBPdtctPmWCfTxwmoSMLHU2SCVDhbqMWU5b0yr" 78a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) "JBCScs_ejbKaqBDoB7ZGxTvqlrB__2ZmnHHjCr8RgMRtKNtIeuZAo "; 79a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) } 80a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) 815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) testing::StrictMock<MockDelegate> delegate_; 825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) MockSession session_; 835d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) QuicReliableClientStream* stream_; 84c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) QuicCryptoClientConfig crypto_config_; 85a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) SpdyHeaderBlock headers_; 865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}; 875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 885d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)INSTANTIATE_TEST_CASE_P(Version, QuicReliableClientStreamTest, 895d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) ::testing::ValuesIn(QuicSupportedVersions())); 905d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 915d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)TEST_P(QuicReliableClientStreamTest, OnFinRead) { 92a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) InitializeHeaders(); 93a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) string uncompressed_headers = 94a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) SpdyUtils::SerializeUncompressedHeaders(headers_); 95a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) EXPECT_CALL(delegate_, OnDataReceived(StrEq(uncompressed_headers.data()), 96a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) uncompressed_headers.size())); 975d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) QuicStreamOffset offset = 0; 98a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) stream_->OnStreamHeaders(uncompressed_headers); 99a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) stream_->OnStreamHeadersComplete(false, uncompressed_headers.length()); 1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 101a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) IOVector iov; 1025d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) QuicStreamFrame frame2(kStreamId, true, offset, iov); 103a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) EXPECT_CALL(delegate_, OnClose(QUIC_NO_ERROR)); 1045d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) stream_->OnStreamFrame(frame2); 1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1075d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)TEST_P(QuicReliableClientStreamTest, ProcessData) { 1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) const char data[] = "hello world!"; 1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) EXPECT_CALL(delegate_, OnDataReceived(StrEq(data), arraysize(data))); 1102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) EXPECT_CALL(delegate_, OnClose(QUIC_NO_ERROR)); 1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) EXPECT_EQ(arraysize(data), stream_->ProcessData(data, arraysize(data))); 1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)TEST_P(QuicReliableClientStreamTest, ProcessDataWithError) { 1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) const char data[] = "hello world!"; 1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) EXPECT_CALL(delegate_, 1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) OnDataReceived(StrEq(data), 1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) arraysize(data))).WillOnce(Return(ERR_UNEXPECTED)); 120c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) EXPECT_CALL(delegate_, OnClose(QUIC_NO_ERROR)); 1212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) EXPECT_EQ(0u, stream_->ProcessData(data, arraysize(data))); 1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)TEST_P(QuicReliableClientStreamTest, OnError) { 1272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) EXPECT_CALL(delegate_, OnError(ERR_INTERNET_DISCONNECTED)); 1282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) stream_->OnError(ERR_INTERNET_DISCONNECTED); 1305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) EXPECT_FALSE(stream_->GetDelegate()); 1312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)} 1322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)TEST_P(QuicReliableClientStreamTest, WriteStreamData) { 13458537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) EXPECT_CALL(delegate_, OnClose(QUIC_NO_ERROR)); 13558537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) 13658537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) const char kData1[] = "hello world"; 13758537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) const size_t kDataLen = arraysize(kData1); 13858537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) 13958537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) // All data written. 140f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) EXPECT_CALL(session_, WritevData(stream_->id(), _, _, _, _, _)).WillOnce( 14158537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) Return(QuicConsumedData(kDataLen, true))); 14258537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) TestCompletionCallback callback; 1435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) EXPECT_EQ(OK, stream_->WriteStreamData(base::StringPiece(kData1, kDataLen), 1445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) true, callback.callback())); 14558537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)} 14658537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) 1475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)TEST_P(QuicReliableClientStreamTest, WriteStreamDataAsync) { 1485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) EXPECT_CALL(delegate_, HasSendHeadersComplete()).Times(AnyNumber()); 14958537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) EXPECT_CALL(delegate_, OnClose(QUIC_NO_ERROR)); 15058537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) 15158537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) const char kData1[] = "hello world"; 15258537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) const size_t kDataLen = arraysize(kData1); 15358537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) 15458537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) // No data written. 155f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) EXPECT_CALL(session_, WritevData(stream_->id(), _, _, _, _, _)).WillOnce( 15658537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) Return(QuicConsumedData(0, false))); 15758537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) TestCompletionCallback callback; 15858537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) EXPECT_EQ(ERR_IO_PENDING, 1595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) stream_->WriteStreamData(base::StringPiece(kData1, kDataLen), 1605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) true, callback.callback())); 16158537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) ASSERT_FALSE(callback.have_result()); 16258537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) 16358537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) // All data written. 164f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) EXPECT_CALL(session_, WritevData(stream_->id(), _, _, _, _, _)).WillOnce( 16558537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) Return(QuicConsumedData(kDataLen, true))); 1665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) stream_->OnCanWrite(); 16758537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) ASSERT_TRUE(callback.have_result()); 16858537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) EXPECT_EQ(OK, callback.WaitForResult()); 16958537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)} 17058537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) 1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} // namespace 1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} // namespace test 1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} // namespace net 174