quic_data_stream.cc revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
15d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)// Copyright 2013 The Chromium Authors. All rights reserved. 25d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 35d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)// found in the LICENSE file. 45d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 55d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#include "net/quic/quic_data_stream.h" 65d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 75d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#include "base/logging.h" 85d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#include "net/quic/quic_session.h" 95d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#include "net/quic/quic_utils.h" 105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#include "net/quic/quic_write_blocked_list.h" 115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)using base::StringPiece; 135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)using std::min; 145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)namespace net { 165d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#define ENDPOINT (session()->is_server() ? "Server: " : " Client: ") 185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)namespace { 205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)// This is somewhat arbitrary. It's possible, but unlikely, we will either fail 225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)// to set a priority client-side, or cancel a stream before stripping the 235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)// priority from the wire server-side. In either case, start out with a 245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)// priority in the middle. 255d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)QuicPriority kDefaultPriority = 3; 265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)} // namespace 285d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)QuicDataStream::QuicDataStream(QuicStreamId id, 305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) QuicSession* session) 315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) : ReliableQuicStream(id, session), 325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) visitor_(NULL), 335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) headers_decompressed_(false), 345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) priority_(kDefaultPriority), 355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) decompression_failed_(false), 365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) priority_parsed_(false) { 375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) DCHECK_NE(kCryptoStreamId, id); 38a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // Don't receive any callbacks from the sequencer until headers 39a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // are complete. 40a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) sequencer()->SetBlockedUntilFlush(); 415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)} 425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)QuicDataStream::~QuicDataStream() { 445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)} 455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)size_t QuicDataStream::WriteHeaders(const SpdyHeaderBlock& header_block, 475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) bool fin) { 485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) size_t bytes_written = session()->WriteHeaders(id(), header_block, fin); 495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (fin) { 505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // TODO(rch): Add test to ensure fin_sent_ is set whenever a fin is sent. 515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) set_fin_sent(true); 525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) CloseWriteSide(); 535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return bytes_written; 555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)} 565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)size_t QuicDataStream::Readv(const struct iovec* iov, size_t iov_len) { 585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (FinishedReadingHeaders()) { 595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // If the headers have been read, simply delegate to the sequencer's 605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Readv method. 615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return sequencer()->Readv(iov, iov_len); 625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Otherwise, copy decompressed header data into |iov|. 645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) size_t bytes_consumed = 0; 655d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) size_t iov_index = 0; 665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) while (iov_index < iov_len && 675d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) decompressed_headers_.length() > bytes_consumed) { 685d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) size_t bytes_to_read = min(iov[iov_index].iov_len, 695d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) decompressed_headers_.length() - bytes_consumed); 705d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) char* iov_ptr = static_cast<char*>(iov[iov_index].iov_base); 715d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) memcpy(iov_ptr, 725d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) decompressed_headers_.data() + bytes_consumed, bytes_to_read); 735d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) bytes_consumed += bytes_to_read; 745d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) ++iov_index; 755d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 765d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) decompressed_headers_.erase(0, bytes_consumed); 775d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (FinishedReadingHeaders()) { 785d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) sequencer()->FlushBufferedFrames(); 795d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 805d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return bytes_consumed; 815d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)} 825d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 835d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)int QuicDataStream::GetReadableRegions(iovec* iov, size_t iov_len) { 845d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (FinishedReadingHeaders()) { 855d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return sequencer()->GetReadableRegions(iov, iov_len); 865d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 875d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (iov_len == 0) { 885d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return 0; 895d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 905d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) iov[0].iov_base = static_cast<void*>( 915d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) const_cast<char*>(decompressed_headers_.data())); 925d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) iov[0].iov_len = decompressed_headers_.length(); 935d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return 1; 945d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)} 955d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 965d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)bool QuicDataStream::IsDoneReading() const { 975d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (!headers_decompressed_ || !decompressed_headers_.empty()) { 985d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return false; 995d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 1005d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return sequencer()->IsClosed(); 1015d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)} 1025d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 1035d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)bool QuicDataStream::HasBytesToRead() const { 1045d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return !decompressed_headers_.empty() || sequencer()->HasBytesToRead(); 1055d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)} 1065d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 1075d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)void QuicDataStream::set_priority(QuicPriority priority) { 1085d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) DCHECK_EQ(0u, stream_bytes_written()); 1095d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) priority_ = priority; 1105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)} 1115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 1125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)QuicPriority QuicDataStream::EffectivePriority() const { 1135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return priority(); 1145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)} 1155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 1165d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)uint32 QuicDataStream::ProcessRawData(const char* data, uint32 data_len) { 1175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (!FinishedReadingHeaders()) { 1185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) LOG(DFATAL) << "ProcessRawData called before headers have been finished"; 1195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return 0; 1205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 1215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return ProcessData(data, data_len); 1225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)} 1235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 1245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)const IPEndPoint& QuicDataStream::GetPeerAddress() { 1255d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return session()->peer_address(); 1265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)} 1275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 1285d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)bool QuicDataStream::GetSSLInfo(SSLInfo* ssl_info) { 1295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return session()->GetSSLInfo(ssl_info); 1305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)} 1315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 1325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)uint32 QuicDataStream::ProcessHeaderData() { 1335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (decompressed_headers_.empty()) { 1345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return 0; 1355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 1365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 1375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) size_t bytes_processed = ProcessData(decompressed_headers_.data(), 1385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) decompressed_headers_.length()); 1395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (bytes_processed == decompressed_headers_.length()) { 1405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) decompressed_headers_.clear(); 1415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } else { 1425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) decompressed_headers_ = decompressed_headers_.erase(0, bytes_processed); 1435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 1445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return bytes_processed; 1455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)} 1465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 1475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)void QuicDataStream::OnStreamHeaders(StringPiece headers_data) { 1485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) headers_data.AppendToString(&decompressed_headers_); 1495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) ProcessHeaderData(); 1505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)} 1515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 1525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)void QuicDataStream::OnStreamHeadersPriority(QuicPriority priority) { 1535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) DCHECK(session()->connection()->is_server()); 1545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) set_priority(priority); 1555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)} 1565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 1575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)void QuicDataStream::OnStreamHeadersComplete(bool fin, size_t frame_len) { 1585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) headers_decompressed_ = true; 1595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (fin) { 1605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) sequencer()->OnStreamFrame(QuicStreamFrame(id(), fin, 0, IOVector())); 1615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 1625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) ProcessHeaderData(); 1635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (FinishedReadingHeaders()) { 1645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) sequencer()->FlushBufferedFrames(); 1655d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 1665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)} 1675d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 1685d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)void QuicDataStream::OnClose() { 1695d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) ReliableQuicStream::OnClose(); 1705d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 1715d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (visitor_) { 1725d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) Visitor* visitor = visitor_; 1735d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Calling Visitor::OnClose() may result the destruction of the visitor, 1745d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // so we need to ensure we don't call it again. 1755d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) visitor_ = NULL; 1765d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) visitor->OnClose(this); 1775d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 1785d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)} 1795d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 180a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)bool QuicDataStream::FinishedReadingHeaders() { 181a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return headers_decompressed_ && decompressed_headers_.empty(); 1825d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)} 1835d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 184a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)void QuicDataStream::OnWindowUpdateFrame(const QuicWindowUpdateFrame& frame) { 185a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) DVLOG(1) << "Received WindowUpdateFrame for stream: " << id() 186a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) << ", with byte offset: " << frame.byte_offset; 187a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // TODO(rjshade): Adjust flow control window. 1885d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)} 1895d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 1905d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)} // namespace net 191