| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "net/third_party/quiche/src/quic/test_tools/simulator/quic_endpoint.h" |
| |
| #include <utility> |
| |
| #include "net/third_party/quiche/src/quic/core/crypto/crypto_handshake_message.h" |
| #include "net/third_party/quiche/src/quic/core/crypto/crypto_protocol.h" |
| #include "net/third_party/quiche/src/quic/core/quic_data_writer.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_str_cat.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_test_output.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h" |
| #include "net/third_party/quiche/src/quic/test_tools/quic_connection_peer.h" |
| #include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h" |
| #include "net/third_party/quiche/src/quic/test_tools/simulator/simulator.h" |
| |
| namespace quic { |
| namespace simulator { |
| |
| const QuicStreamId kDataStream = 3; |
| const QuicByteCount kWriteChunkSize = 128 * 1024; |
| const char kStreamDataContents = 'Q'; |
| |
| // Takes a SHA-1 hash of the name and converts it into five 32-bit integers. |
| static std::vector<uint32_t> HashNameIntoFive32BitIntegers(std::string name) { |
| const std::string hash = test::Sha1Hash(name); |
| |
| std::vector<uint32_t> output; |
| uint32_t current_number = 0; |
| for (size_t i = 0; i < hash.size(); i++) { |
| current_number = (current_number << 8) + hash[i]; |
| if (i % 4 == 3) { |
| output.push_back(i); |
| current_number = 0; |
| } |
| } |
| |
| return output; |
| } |
| |
| QuicSocketAddress GetAddressFromName(std::string name) { |
| const std::vector<uint32_t> hash = HashNameIntoFive32BitIntegers(name); |
| |
| // Generate a random port between 1025 and 65535. |
| const uint16_t port = 1025 + hash[0] % (65535 - 1025 + 1); |
| |
| // Generate a random 10.x.x.x address, where x is between 1 and 254. |
| std::string ip_address{"\xa\0\0\0", 4}; |
| for (size_t i = 1; i < 4; i++) { |
| ip_address[i] = 1 + hash[i] % 254; |
| } |
| QuicIpAddress host; |
| host.FromPackedString(ip_address.c_str(), ip_address.length()); |
| return QuicSocketAddress(host, port); |
| } |
| |
| QuicEndpoint::QuicEndpoint(Simulator* simulator, |
| std::string name, |
| std::string peer_name, |
| Perspective perspective, |
| QuicConnectionId connection_id) |
| : Endpoint(simulator, name), |
| peer_name_(peer_name), |
| writer_(this), |
| nic_tx_queue_(simulator, |
| QuicStringPrintf("%s (TX Queue)", name.c_str()), |
| kMaxOutgoingPacketSize * kTxQueueSize), |
| connection_(connection_id, |
| GetAddressFromName(peer_name), |
| simulator, |
| simulator->GetAlarmFactory(), |
| &writer_, |
| false, |
| perspective, |
| ParsedVersionOfIndex(CurrentSupportedVersions(), 0)), |
| bytes_to_transfer_(0), |
| bytes_transferred_(0), |
| write_blocked_count_(0), |
| wrong_data_received_(false), |
| drop_next_packet_(false), |
| notifier_(nullptr) { |
| nic_tx_queue_.set_listener_interface(this); |
| |
| connection_.SetSelfAddress(GetAddressFromName(name)); |
| connection_.set_visitor(this); |
| connection_.SetEncrypter(ENCRYPTION_FORWARD_SECURE, |
| std::make_unique<NullEncrypter>(perspective)); |
| connection_.SetEncrypter(ENCRYPTION_INITIAL, nullptr); |
| if (connection_.version().KnowsWhichDecrypterToUse()) { |
| connection_.InstallDecrypter(ENCRYPTION_FORWARD_SECURE, |
| std::make_unique<NullDecrypter>(perspective)); |
| connection_.RemoveDecrypter(ENCRYPTION_INITIAL); |
| } else { |
| connection_.SetDecrypter(ENCRYPTION_FORWARD_SECURE, |
| std::make_unique<NullDecrypter>(perspective)); |
| } |
| connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE); |
| if (perspective == Perspective::IS_SERVER) { |
| // Skip version negotiation. |
| test::QuicConnectionPeer::SetNegotiatedVersion(&connection_); |
| } |
| connection_.SetDataProducer(&producer_); |
| connection_.SetSessionNotifier(this); |
| if (connection_.session_decides_what_to_write()) { |
| notifier_ = std::make_unique<test::SimpleSessionNotifier>(&connection_); |
| } |
| |
| // Configure the connection as if it received a handshake. This is important |
| // primarily because |
| // - this enables pacing, and |
| // - this sets the non-handshake timeouts. |
| std::string error; |
| CryptoHandshakeMessage peer_hello; |
| peer_hello.SetValue(kICSL, |
| static_cast<uint32_t>(kMaximumIdleTimeoutSecs - 1)); |
| peer_hello.SetValue(kMIBS, |
| static_cast<uint32_t>(kDefaultMaxStreamsPerConnection)); |
| QuicConfig config; |
| QuicErrorCode error_code = config.ProcessPeerHello( |
| peer_hello, perspective == Perspective::IS_CLIENT ? SERVER : CLIENT, |
| &error); |
| DCHECK_EQ(error_code, QUIC_NO_ERROR) << "Configuration failed: " << error; |
| connection_.SetFromConfig(config); |
| } |
| |
| QuicEndpoint::~QuicEndpoint() { |
| if (trace_visitor_ != nullptr) { |
| const char* perspective_prefix = |
| connection_.perspective() == Perspective::IS_CLIENT ? "C" : "S"; |
| |
| std::string identifier = |
| QuicStrCat(perspective_prefix, connection_.connection_id().ToString()); |
| QuicRecordTestOutput(identifier, |
| trace_visitor_->trace()->SerializeAsString()); |
| } |
| } |
| |
| QuicByteCount QuicEndpoint::bytes_received() const { |
| QuicByteCount total = 0; |
| for (auto& interval : offsets_received_) { |
| total += interval.max() - interval.min(); |
| } |
| return total; |
| } |
| |
| QuicByteCount QuicEndpoint::bytes_to_transfer() const { |
| if (notifier_ != nullptr) { |
| return notifier_->StreamBytesToSend(); |
| } |
| return bytes_to_transfer_; |
| } |
| |
| QuicByteCount QuicEndpoint::bytes_transferred() const { |
| if (notifier_ != nullptr) { |
| return notifier_->StreamBytesSent(); |
| } |
| return bytes_transferred_; |
| } |
| |
| void QuicEndpoint::AddBytesToTransfer(QuicByteCount bytes) { |
| if (notifier_ != nullptr) { |
| if (notifier_->HasBufferedStreamData()) { |
| Schedule(clock_->Now()); |
| } |
| notifier_->WriteOrBufferData(kDataStream, bytes, NO_FIN); |
| return; |
| } |
| |
| if (bytes_to_transfer_ > 0) { |
| Schedule(clock_->Now()); |
| } |
| |
| bytes_to_transfer_ += bytes; |
| WriteStreamData(); |
| } |
| |
| void QuicEndpoint::DropNextIncomingPacket() { |
| drop_next_packet_ = true; |
| } |
| |
| void QuicEndpoint::RecordTrace() { |
| trace_visitor_ = std::make_unique<QuicTraceVisitor>(&connection_); |
| connection_.set_debug_visitor(trace_visitor_.get()); |
| } |
| |
| void QuicEndpoint::AcceptPacket(std::unique_ptr<Packet> packet) { |
| if (packet->destination != name_) { |
| return; |
| } |
| if (drop_next_packet_) { |
| drop_next_packet_ = false; |
| return; |
| } |
| |
| QuicReceivedPacket received_packet(packet->contents.data(), |
| packet->contents.size(), clock_->Now()); |
| connection_.ProcessUdpPacket(connection_.self_address(), |
| connection_.peer_address(), received_packet); |
| } |
| |
| UnconstrainedPortInterface* QuicEndpoint::GetRxPort() { |
| return this; |
| } |
| |
| void QuicEndpoint::SetTxPort(ConstrainedPortInterface* port) { |
| // Any egress done by the endpoint is actually handled by a queue on an NIC. |
| nic_tx_queue_.set_tx_port(port); |
| } |
| |
| void QuicEndpoint::OnPacketDequeued() { |
| if (writer_.IsWriteBlocked() && |
| (nic_tx_queue_.capacity() - nic_tx_queue_.bytes_queued()) >= |
| kMaxOutgoingPacketSize) { |
| writer_.SetWritable(); |
| connection_.OnCanWrite(); |
| } |
| } |
| |
| void QuicEndpoint::OnStreamFrame(const QuicStreamFrame& frame) { |
| // Verify that the data received always matches the expected. |
| DCHECK(frame.stream_id == kDataStream); |
| for (size_t i = 0; i < frame.data_length; i++) { |
| if (frame.data_buffer[i] != kStreamDataContents) { |
| wrong_data_received_ = true; |
| } |
| } |
| offsets_received_.Add(frame.offset, frame.offset + frame.data_length); |
| // Sanity check against very pathological connections. |
| DCHECK_LE(offsets_received_.Size(), 1000u); |
| } |
| |
| void QuicEndpoint::OnCryptoFrame(const QuicCryptoFrame& /*frame*/) {} |
| |
| void QuicEndpoint::OnCanWrite() { |
| if (notifier_ != nullptr) { |
| notifier_->OnCanWrite(); |
| return; |
| } |
| WriteStreamData(); |
| } |
| |
| bool QuicEndpoint::SendProbingData() { |
| if (connection()->sent_packet_manager().MaybeRetransmitOldestPacket( |
| PROBING_RETRANSMISSION)) { |
| return true; |
| } |
| return false; |
| } |
| |
| bool QuicEndpoint::WillingAndAbleToWrite() const { |
| if (notifier_ != nullptr) { |
| return notifier_->WillingToWrite(); |
| } |
| return bytes_to_transfer_ != 0; |
| } |
| bool QuicEndpoint::HasPendingHandshake() const { |
| return false; |
| } |
| bool QuicEndpoint::ShouldKeepConnectionAlive() const { |
| return true; |
| } |
| |
| bool QuicEndpoint::AllowSelfAddressChange() const { |
| return false; |
| } |
| |
| bool QuicEndpoint::OnFrameAcked(const QuicFrame& frame, |
| QuicTime::Delta ack_delay_time, |
| QuicTime receive_timestamp) { |
| if (notifier_ != nullptr) { |
| return notifier_->OnFrameAcked(frame, ack_delay_time, receive_timestamp); |
| } |
| return false; |
| } |
| |
| void QuicEndpoint::OnFrameLost(const QuicFrame& frame) { |
| DCHECK(notifier_); |
| notifier_->OnFrameLost(frame); |
| } |
| |
| void QuicEndpoint::RetransmitFrames(const QuicFrames& frames, |
| TransmissionType type) { |
| DCHECK(notifier_); |
| notifier_->RetransmitFrames(frames, type); |
| } |
| |
| bool QuicEndpoint::IsFrameOutstanding(const QuicFrame& frame) const { |
| DCHECK(notifier_); |
| return notifier_->IsFrameOutstanding(frame); |
| } |
| |
| bool QuicEndpoint::HasUnackedCryptoData() const { |
| return false; |
| } |
| |
| bool QuicEndpoint::HasUnackedStreamData() const { |
| if (notifier_ != nullptr) { |
| return notifier_->HasUnackedStreamData(); |
| } |
| return false; |
| } |
| |
| QuicEndpoint::Writer::Writer(QuicEndpoint* endpoint) |
| : endpoint_(endpoint), is_blocked_(false) {} |
| |
| QuicEndpoint::Writer::~Writer() {} |
| |
| WriteResult QuicEndpoint::Writer::WritePacket( |
| const char* buffer, |
| size_t buf_len, |
| const QuicIpAddress& /*self_address*/, |
| const QuicSocketAddress& /*peer_address*/, |
| PerPacketOptions* options) { |
| DCHECK(!IsWriteBlocked()); |
| DCHECK(options == nullptr); |
| DCHECK(buf_len <= kMaxOutgoingPacketSize); |
| |
| // Instead of losing a packet, become write-blocked when the egress queue is |
| // full. |
| if (endpoint_->nic_tx_queue_.packets_queued() > kTxQueueSize) { |
| is_blocked_ = true; |
| endpoint_->write_blocked_count_++; |
| return WriteResult(WRITE_STATUS_BLOCKED, 0); |
| } |
| |
| auto packet = std::make_unique<Packet>(); |
| packet->source = endpoint_->name(); |
| packet->destination = endpoint_->peer_name_; |
| packet->tx_timestamp = endpoint_->clock_->Now(); |
| |
| packet->contents = std::string(buffer, buf_len); |
| packet->size = buf_len; |
| |
| endpoint_->nic_tx_queue_.AcceptPacket(std::move(packet)); |
| |
| return WriteResult(WRITE_STATUS_OK, buf_len); |
| } |
| |
| bool QuicEndpoint::Writer::IsWriteBlocked() const { |
| return is_blocked_; |
| } |
| |
| void QuicEndpoint::Writer::SetWritable() { |
| is_blocked_ = false; |
| } |
| |
| QuicByteCount QuicEndpoint::Writer::GetMaxPacketSize( |
| const QuicSocketAddress& /*peer_address*/) const { |
| return kMaxOutgoingPacketSize; |
| } |
| |
| bool QuicEndpoint::Writer::SupportsReleaseTime() const { |
| return false; |
| } |
| |
| bool QuicEndpoint::Writer::IsBatchMode() const { |
| return false; |
| } |
| |
| char* QuicEndpoint::Writer::GetNextWriteLocation( |
| const QuicIpAddress& /*self_address*/, |
| const QuicSocketAddress& /*peer_address*/) { |
| return nullptr; |
| } |
| |
| WriteResult QuicEndpoint::Writer::Flush() { |
| return WriteResult(WRITE_STATUS_OK, 0); |
| } |
| |
| WriteStreamDataResult QuicEndpoint::DataProducer::WriteStreamData( |
| QuicStreamId /*id*/, |
| QuicStreamOffset /*offset*/, |
| QuicByteCount data_length, |
| QuicDataWriter* writer) { |
| writer->WriteRepeatedByte(kStreamDataContents, data_length); |
| return WRITE_SUCCESS; |
| } |
| |
| bool QuicEndpoint::DataProducer::WriteCryptoData(EncryptionLevel /*level*/, |
| QuicStreamOffset /*offset*/, |
| QuicByteCount /*data_length*/, |
| QuicDataWriter* /*writer*/) { |
| QUIC_BUG << "QuicEndpoint::DataProducer::WriteCryptoData is unimplemented"; |
| return false; |
| } |
| |
| void QuicEndpoint::WriteStreamData() { |
| // Instantiate a flusher which would normally be here due to QuicSession. |
| QuicConnection::ScopedPacketFlusher flusher(&connection_); |
| |
| while (bytes_to_transfer_ > 0) { |
| // Transfer data in chunks of size at most |kWriteChunkSize|. |
| const size_t transmission_size = |
| std::min(kWriteChunkSize, bytes_to_transfer_); |
| |
| QuicConsumedData consumed_data = connection_.SendStreamData( |
| kDataStream, transmission_size, bytes_transferred_, NO_FIN); |
| |
| DCHECK(consumed_data.bytes_consumed <= transmission_size); |
| bytes_transferred_ += consumed_data.bytes_consumed; |
| bytes_to_transfer_ -= consumed_data.bytes_consumed; |
| if (consumed_data.bytes_consumed != transmission_size) { |
| return; |
| } |
| } |
| } |
| |
| QuicEndpointMultiplexer::QuicEndpointMultiplexer( |
| std::string name, |
| const std::vector<QuicEndpoint*>& endpoints) |
| : Endpoint((*endpoints.begin())->simulator(), name) { |
| for (QuicEndpoint* endpoint : endpoints) { |
| mapping_.insert(std::make_pair(endpoint->name(), endpoint)); |
| } |
| } |
| |
| QuicEndpointMultiplexer::~QuicEndpointMultiplexer() {} |
| |
| void QuicEndpointMultiplexer::AcceptPacket(std::unique_ptr<Packet> packet) { |
| auto key_value_pair_it = mapping_.find(packet->destination); |
| if (key_value_pair_it == mapping_.end()) { |
| return; |
| } |
| |
| key_value_pair_it->second->GetRxPort()->AcceptPacket(std::move(packet)); |
| } |
| UnconstrainedPortInterface* QuicEndpointMultiplexer::GetRxPort() { |
| return this; |
| } |
| void QuicEndpointMultiplexer::SetTxPort(ConstrainedPortInterface* port) { |
| for (auto& key_value_pair : mapping_) { |
| key_value_pair.second->SetTxPort(port); |
| } |
| } |
| |
| } // namespace simulator |
| } // namespace quic |