chromium/chrome/browser/media/router/providers/openscreen/network_service_quic_packet_writer.cc

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/media/router/providers/openscreen/network_service_quic_packet_writer.h"

#include <algorithm>
#include <string>
#include <utility>
#include <vector>

#include "base/check.h"
#include "base/containers/span.h"
#include "base/memory/scoped_refptr.h"
#include "base/task/single_thread_task_runner.h"
#include "net/base/io_buffer.h"
#include "net/base/ip_endpoint.h"
#include "net/third_party/quiche/src/quiche/quic/core/quic_constants.h"

namespace media_router {
namespace {

// Set a reasonable maximum number of packets in flight, for a total of
// 2 megabytes theoretical maximum. May need to change as device requirements
// become more clear here.
constexpr std::size_t kMaxPacketsInFlight =
    (2 * 1024 * 1024) / quic::kMaxOutgoingPacketSize;

const net::IPEndPoint ConvertToEndpoint(const quic::QuicSocketAddress& sa) {
  const std::string ip_bytes = sa.host().ToPackedString();
  CHECK(ip_bytes.length() == 4 || ip_bytes.length() == 16);
  const net::IPAddress ip_address(
      reinterpret_cast<const uint8_t*>(ip_bytes.data()), ip_bytes.length());
  return net::IPEndPoint(ip_address, sa.port());
}
}  // namespace

NetworkServiceQuicPacketWriter::NetworkServiceQuicPacketWriter(
    std::unique_ptr<AsyncPacketSender> async_packet_sender,
    NetworkServiceQuicPacketWriter::Delegate* delegate,
    const scoped_refptr<base::SingleThreadTaskRunner>& task_runner)
    : max_packets_in_flight_(kMaxPacketsInFlight),
      is_write_blocked_(false),
      task_runner_(task_runner),
      async_packet_sender_(std::move(async_packet_sender)),
      delegate_(delegate) {
  DCHECK(async_packet_sender_ != nullptr);
  DCHECK(delegate_ != nullptr);
  DCHECK(task_runner_ != nullptr);
}

NetworkServiceQuicPacketWriter::~NetworkServiceQuicPacketWriter() = default;

quic::WriteResult NetworkServiceQuicPacketWriter::Flush() {
  // In PassThrough mode, this method isn't used.
  return quic::WriteResult(quic::WRITE_STATUS_OK, 0);
}

bool NetworkServiceQuicPacketWriter::IsBatchMode() const {
  // False here means we are always in PassThrough mode.
  return false;
}

bool NetworkServiceQuicPacketWriter::IsWriteBlocked() const {
  return is_write_blocked_;
}

quic::QuicByteCount NetworkServiceQuicPacketWriter::GetMaxPacketSize(
    const quic::QuicSocketAddress& peer_address) const {
  // In PassThrough mode, the max packet should be no larger than the max
  // quic packet size due to no batching.
  return quic::kMaxOutgoingPacketSize;
}

quic::QuicPacketBuffer NetworkServiceQuicPacketWriter::GetNextWriteLocation(
    const quic::QuicIpAddress& self_address,
    const quic::QuicSocketAddress& peer_address) {
  // In PassThrough mode, this method isn't used and should return
  // a null QuicPacketBuffer.
  return {nullptr, nullptr};
}

void NetworkServiceQuicPacketWriter::SetWritable() {
  if (!task_runner_->BelongsToCurrentThread()) {
    EnqueueTask(&NetworkServiceQuicPacketWriter::SetWritable);
    return;
  }

  writable_ = true;
  UpdateIsWriteBlocked();
}

std::optional<int> NetworkServiceQuicPacketWriter::MessageTooBigErrorCode()
    const {
  return net::ERR_MSG_TOO_BIG;
}

bool NetworkServiceQuicPacketWriter::SupportsReleaseTime() const {
  return false;
}

quic::WriteResult NetworkServiceQuicPacketWriter::WritePacket(
    const char* buffer,
    size_t buf_len,
    const quic::QuicIpAddress& self_address,
    const quic::QuicSocketAddress& peer_address,
    quic::PerPacketOptions* options) {
  if (is_write_blocked_) {
    return quic::WriteResult(quic::WRITE_STATUS_BLOCKED, EWOULDBLOCK);
  }

  // The helper may run synchronously if we are on the right thread, else
  // it will enqueue a task.
  WritePacketHelper(
      ConvertToEndpoint(peer_address),
      base::make_span(reinterpret_cast<const uint8_t*>(buffer), buf_len));

  // Assume we successfully wrote the entire packet. The client will receive
  // any write errors through the delegate they are forced to provide us.
  return quic::WriteResult(quic::WRITE_STATUS_OK, buf_len);
}

void NetworkServiceQuicPacketWriter::OnWriteComplete(int net_error) {
  if (!task_runner_->BelongsToCurrentThread()) {
    EnqueueTask(&NetworkServiceQuicPacketWriter::OnWriteComplete, net_error);
    return;
  }

  const net::Error error = static_cast<net::Error>(net_error);
  if (error != net::Error::OK) {
    delegate_->OnWriteError(error);
    writable_ = false;
  }

  --packets_in_flight_;
  UpdateIsWriteBlocked();
}

void NetworkServiceQuicPacketWriter::UpdateIsWriteBlocked() {
  const bool was_blocked = is_write_blocked_;

  is_write_blocked_ =
      !writable_ || (packets_in_flight_ >= max_packets_in_flight_);
  if (was_blocked && !is_write_blocked_) {
    delegate_->OnWriteUnblocked();
  }
}

void NetworkServiceQuicPacketWriter::WritePacketHelper(
    const net::IPEndPoint endpoint,
    base::span<const uint8_t> span) {
  if (!task_runner_->BelongsToCurrentThread()) {
    EnqueueTask(&NetworkServiceQuicPacketWriter::WritePacketHelper, endpoint,
                span);
    return;
  }

  const net::Error error = async_packet_sender_->SendTo(
      endpoint, span,
      base::BindOnce(&NetworkServiceQuicPacketWriter::OnWriteComplete,
                     weak_factory_.GetWeakPtr()));

  if (error != net::Error::OK) {
    delegate_->OnWriteError(error);
    writable_ = false;
    UpdateIsWriteBlocked();
    return;
  }

  ++packets_in_flight_;
  UpdateIsWriteBlocked();
}

}  // namespace media_router