| // 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 "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_ptr_util.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, | |
| QuicMakeUnique<NullEncrypter>(perspective)); | |
| if (connection_.version().KnowsWhichDecrypterToUse()) { | |
| connection_.InstallDecrypter(ENCRYPTION_FORWARD_SECURE, | |
| QuicMakeUnique<NullDecrypter>(perspective)); | |
| connection_.RemoveDecrypter(ENCRYPTION_INITIAL); | |
| } else { | |
| connection_.SetDecrypter(ENCRYPTION_FORWARD_SECURE, | |
| QuicMakeUnique<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_ = QuicMakeUnique<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_ = QuicMakeUnique<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 = QuicMakeUnique<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 |