/*
 *  Copyright 2004 The WebRTC Project Authors. All rights reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#include "p2p/base/port.h"

#include <cstddef>
#include <cstdint>
#include <functional>
#include <memory>
#include <optional>
#include <span>
#include <string>
#include <utility>
#include <vector>

#include "absl/algorithm/container.h"
#include "absl/functional/any_invocable.h"
#include "absl/memory/memory.h"
#include "absl/strings/string_view.h"
#include "api/candidate.h"
#include "api/local_network_access_permission.h"
#include "api/sequence_checker.h"
#include "api/task_queue/task_queue_base.h"
#include "api/transport/stun.h"
#include "api/units/time_delta.h"
#include "p2p/base/connection.h"
#include "p2p/base/p2p_constants.h"
#include "p2p/base/port_interface.h"
#include "p2p/base/stun_request.h"
#include "p2p/base/transport_description.h"
#include "rtc_base/async_packet_socket.h"
#include "rtc_base/byte_buffer.h"
#include "rtc_base/checks.h"
#include "rtc_base/crypto_random.h"
#include "rtc_base/dscp.h"
#include "rtc_base/ip_address.h"
#include "rtc_base/logging.h"
#include "rtc_base/mdns_responder_interface.h"
#include "rtc_base/net_helper.h"
#include "rtc_base/net_helpers.h"
#include "rtc_base/network.h"
#include "rtc_base/network/received_packet.h"
#include "rtc_base/network/sent_packet.h"
#include "rtc_base/socket_address.h"
#include "rtc_base/span_helpers.h"
#include "rtc_base/string_encode.h"
#include "rtc_base/string_utils.h"
#include "rtc_base/strings/string_builder.h"
#include "rtc_base/trace_event.h"
#include "rtc_base/weak_ptr.h"

namespace webrtc {
namespace {

PacketInfoProtocolType ConvertProtocolTypeToPacketInfoProtocolType(
    ProtocolType type) {
  switch (type) {
    case ProtocolType::PROTO_UDP:
      return PacketInfoProtocolType::kUdp;
    case ProtocolType::PROTO_TCP:
      return PacketInfoProtocolType::kTcp;
    case ProtocolType::PROTO_SSLTCP:
      return PacketInfoProtocolType::kSsltcp;
    case ProtocolType::PROTO_TLS:
      return PacketInfoProtocolType::kTls;
    default:
      return PacketInfoProtocolType::kUnknown;
  }
}

// The delay before we begin checking if this port is useless. We set
// it to a little higher than a total STUN timeout.
const int kPortTimeoutDelay = STUN_TOTAL_TIMEOUT + 5000;

}  // namespace

Port::Port(const PortParametersRef& args, IceCandidateType type)
    : Port(args, type, 0, 0, true) {}

Port::Port(const PortParametersRef& args,
           IceCandidateType type,
           uint16_t min_port,
           uint16_t max_port,
           bool shared_socket /*= false*/)
    : env_(args.env),
      thread_(args.network_thread),
      factory_(args.socket_factory),
      lna_permission_factory_(args.lna_permission_factory),
      type_(type),
      send_retransmit_count_attribute_(false),
      network_(args.network),
      min_port_(min_port),
      max_port_(max_port),
      content_name_(args.content_name),
      component_(ICE_CANDIDATE_COMPONENT_DEFAULT),
      generation_(0),
      ice_username_fragment_(args.ice_username_fragment),
      password_(args.ice_password),
      timeout_delay_(kPortTimeoutDelay),
      enable_port_packets_(false),
      ice_role_(ICEROLE_UNKNOWN),
      tiebreaker_(0),
      shared_socket_(shared_socket),
      network_cost_(args.network->GetCost(env_.field_trials())),
      role_conflict_callback_(nullptr),
      weak_factory_(this) {
  RTC_DCHECK_RUN_ON(thread_);
  RTC_DCHECK(factory_ != nullptr);
  // TODO(pthatcher): Remove this old behavior once we're sure no one
  // relies on it.  If the username_fragment and password are empty,
  // we should just create one.
  if (ice_username_fragment_.empty()) {
    RTC_DCHECK(password_.empty());
    ice_username_fragment_ = CreateRandomString(ICE_UFRAG_LENGTH);
    password_ = CreateRandomString(ICE_PWD_LENGTH);
  }
  // TODO: https://issues.webrtc.org/462023697 - remove need for const_cast
  const_cast<::webrtc::Network*>(network_)->SubscribeTypeChanged(
      this, [this](const ::webrtc::Network* network) {
        OnNetworkTypeChanged(network);
      });
  const_cast<::webrtc::Network*>(network_)->SubscribeNetworkSliceChanged(
      this, [this](const ::webrtc::Network* network) {
        OnNetworkSliceChanged(network);
      });

  PostDestroyIfDead(/*delayed=*/true);
  RTC_LOG(LS_INFO) << ToString() << ": Port created with network cost "
                   << network_cost_;
}

Port::~Port() {
  RTC_DCHECK_RUN_ON(thread_);
  DestroyAllConnections();
  CancelPendingTasks();
  const_cast<::webrtc::Network*>(network_)->UnsubscribeTypeChanged(this);
  const_cast<::webrtc::Network*>(network_)->UnsubscribeNetworkSliceChanged(
      this);
}

IceCandidateType Port::Type() const {
  return type_;
}
const Network* Port::Network() const {
  return network_;
}

IceRole Port::GetIceRole() const {
  RTC_DCHECK_RUN_ON(thread_);
  return ice_role_;
}

void Port::SetIceRole(IceRole role) {
  RTC_DCHECK_RUN_ON(thread_);
  ice_role_ = role;
}

void Port::SetIceTiebreaker(uint64_t tiebreaker) {
  RTC_DCHECK_RUN_ON(thread_);
  tiebreaker_ = tiebreaker;
}

uint64_t Port::IceTiebreaker() const {
  RTC_DCHECK_RUN_ON(thread_);
  return tiebreaker_;
}

bool Port::SharedSocket() const {
  RTC_DCHECK_RUN_ON(thread_);
  return shared_socket_;
}

void Port::SetIceParameters(int component,
                            absl::string_view username_fragment,
                            absl::string_view password) {
  RTC_DCHECK_RUN_ON(thread_);
  component_ = component;
  ice_username_fragment_ = std::string(username_fragment);
  password_ = std::string(password);
  for (Candidate& c : candidates_) {
    c.set_component(component);
    c.set_username(username_fragment);
    c.set_password(password);
  }

  // In case any connections exist make sure we update them too.
  for (auto& [unused, connection] : connections_) {
    connection->UpdateLocalIceParameters(component, username_fragment,
                                         password);
  }
}

const std::vector<Candidate>& Port::Candidates() const {
  return candidates_;
}

Connection* Port::GetConnection(const SocketAddress& remote_addr) {
  RTC_DCHECK_RUN_ON(thread_);
  AddressMap::const_iterator iter = connections_.find(remote_addr);
  if (iter != connections_.end())
    return iter->second;
  else
    return nullptr;
}

void Port::AddAddress(const SocketAddress& address,
                      const SocketAddress& base_address,
                      const SocketAddress& related_address,
                      absl::string_view protocol,
                      absl::string_view relay_protocol,
                      absl::string_view tcptype,
                      IceCandidateType type,
                      uint32_t type_preference,
                      uint32_t relay_preference,
                      absl::string_view url,
                      bool is_final) {
  RTC_DCHECK_RUN_ON(thread_);

  // TODO(tommi): Set relay_protocol and optionally provide the base address
  // to automatically compute the foundation in the ctor? It would be a good
  // thing for the Candidate class to know the base address and keep it const.
  Candidate c(component_, protocol, address, 0U, username_fragment(), password_,
              type, generation_, "", network_->id(), network_cost_);
  // Set the relay protocol before computing the foundation field.
  c.set_relay_protocol(relay_protocol);
  // TODO(bugs.webrtc.org/14605): ensure IceTiebreaker() is set.
  c.ComputeFoundation(base_address, tiebreaker_);

  c.set_priority(
      c.GetPriority(type_preference, network_->preference(), relay_preference,
                    env().field_trials().IsEnabled(
                        "WebRTC-IncreaseIceCandidatePriorityHostSrflx")));
#if RTC_DCHECK_IS_ON
  if (protocol == TCP_PROTOCOL_NAME && c.is_local()) {
    RTC_DCHECK(!tcptype.empty());
  }
#endif
  c.set_tcptype(tcptype);
  c.set_network_name(network_->name());
  c.set_network_type(network_->type());
  c.set_underlying_type_for_vpn(network_->underlying_type_for_vpn());
  c.set_url(url);
  c.set_related_address(related_address);
  c.set_network_slice(network_->network_slice());

  bool pending = MaybeObfuscateAddress(c, is_final);

  if (!pending) {
    FinishAddingAddress(c, is_final);
  }
}

bool Port::MaybeObfuscateAddress(const Candidate& c, bool is_final) {
  // TODO(bugs.webrtc.org/9723): Use a config to control the feature of IP
  // handling with mDNS.
  if (network_->GetMdnsResponder() == nullptr) {
    return false;
  }
  if (!c.is_local()) {
    return false;
  }

  auto copy = c;
  auto weak_ptr = weak_factory_.GetWeakPtr();
  auto callback = [weak_ptr, copy, is_final](const IPAddress& addr,
                                             absl::string_view name) mutable {
    RTC_DCHECK(copy.address().ipaddr() == addr);
    SocketAddress hostname_address(name, copy.address().port());
    // In Port and Connection, we need the IP address information to
    // correctly handle the update of candidate type to prflx. The removal
    // of IP address when signaling this candidate will take place in
    // BasicPortAllocatorSession::OnCandidateReady, via SanitizeCandidate.
    hostname_address.SetResolvedIP(addr);
    copy.set_address(hostname_address);
    copy.set_related_address(SocketAddress());
    if (weak_ptr != nullptr) {
      RTC_DCHECK_RUN_ON(weak_ptr->thread_);
      weak_ptr->set_mdns_name_registration_status(
          MdnsNameRegistrationStatus::kCompleted);
      weak_ptr->FinishAddingAddress(copy, is_final);
    }
  };
  set_mdns_name_registration_status(MdnsNameRegistrationStatus::kInProgress);
  network_->GetMdnsResponder()->CreateNameForAddress(copy.address().ipaddr(),
                                                     callback);
  return true;
}

void Port::FinishAddingAddress(const Candidate& c, bool is_final) {
  candidates_.push_back(c);
  NotifyCandidateReady(this, c);

  PostAddAddress(is_final);
}

void Port::PostAddAddress(bool is_final) {
  if (is_final) {
    NotifyPortComplete(this);
  }
}

[[deprecated]] void Port::SubscribePortComplete(
    absl::AnyInvocable<void(Port*)> callback) {
  RTC_DCHECK_RUN_ON(thread_);
  port_complete_callback_list_.AddReceiver(std::move(callback));
}

void Port::SubscribePortComplete(const void* tag,
                                 absl::AnyInvocable<void(Port*)> callback) {
  RTC_DCHECK_RUN_ON(thread_);
  port_complete_callback_list_.AddReceiver(tag, std::move(callback));
}

[[deprecated]] void Port::SubscribeCandidateError(
    std::function<void(Port*, const IceCandidateErrorEvent&)> callback) {
  RTC_DCHECK_RUN_ON(thread_);
  candidate_error_callback_list_.AddReceiver(std::move(callback));
}

void Port::SubscribeCandidateError(
    const void* tag,
    std::function<void(Port*, const IceCandidateErrorEvent&)> callback) {
  RTC_DCHECK_RUN_ON(thread_);
  candidate_error_callback_list_.AddReceiver(tag, std::move(callback));
}

void Port::SendCandidateError(const IceCandidateErrorEvent& event) {
  RTC_DCHECK_RUN_ON(thread_);
  candidate_error_callback_list_.Send(this, event);
}

[[deprecated]] void Port::SubscribeCandidateReadyCallback(
    absl::AnyInvocable<void(Port*, const Candidate&)> callback) {
  RTC_DCHECK_RUN_ON(thread_);
  candidate_ready_callback_list_.AddReceiver(std::move(callback));
}

void Port::SubscribeCandidateReadyCallback(
    const void* tag,
    absl::AnyInvocable<void(Port*, const Candidate&)> callback) {
  RTC_DCHECK_RUN_ON(thread_);
  candidate_ready_callback_list_.AddReceiver(tag, std::move(callback));
}

[[deprecated]] void Port::SubscribePortError(
    absl::AnyInvocable<void(Port*)> callback) {
  RTC_DCHECK_RUN_ON(thread_);
  port_error_callback_list_.AddReceiver(std::move(callback));
}

void Port::SubscribePortError(const void* tag,
                              absl::AnyInvocable<void(Port*)> callback) {
  RTC_DCHECK_RUN_ON(thread_);
  port_error_callback_list_.AddReceiver(tag, std::move(callback));
}

void Port::AddOrReplaceConnection(Connection* conn) {
  RTC_DCHECK_RUN_ON(thread_);
  auto ret = connections_.insert(
      std::make_pair(conn->remote_candidate().address(), conn));
  // If there is a different connection on the same remote address, replace
  // it with the new one and destroy the old one.
  if (ret.second == false && ret.first->second != conn) {
    RTC_LOG(LS_WARNING)
        << ToString()
        << ": A new connection was created on an existing remote address. "
           "New remote candidate: "
        << conn->remote_candidate().ToSensitiveString();
    std::unique_ptr<Connection> old_conn = absl::WrapUnique(ret.first->second);
    ret.first->second = conn;
    HandleConnectionDestroyed(old_conn.get());
    old_conn->Shutdown();
  }
}

void Port::OnReadPacket(const ReceivedIpPacket& packet, ProtocolType proto) {
  RTC_DCHECK_RUN_ON(thread_);

  const SocketAddress& addr = packet.source_address();
  std::span<const uint8_t> data = packet.payload();
  // If the user has enabled port packets, just hand this over.
  if (enable_port_packets_) {
    NotifyReadPacket(this, data, addr);
    return;
  }

  // If this is an authenticated STUN request, then signal unknown address and
  // send back a proper binding response.
  std::unique_ptr<IceMessage> msg;
  std::string remote_username;
  if (!GetStunMessage(data, addr, &msg, &remote_username)) {
    RTC_LOG(LS_ERROR) << ToString()
                      << ": Received non-STUN packet from unknown address: "
                      << addr.ToSensitiveString();
  } else if (!msg) {
    // STUN message handled already
  } else if (msg->type() == STUN_BINDING_REQUEST) {
    RTC_LOG(LS_INFO) << "Received " << StunMethodToString(msg->type())
                     << " id=" << hex_encode(msg->transaction_id())
                     << " from unknown address " << addr.ToSensitiveString();
    // We need to signal an unknown address before we handle any role conflict
    // below. Otherwise there would be no candidate pair and TURN entry created
    // to send the error response in case of a role conflict.
    NotifyUnknownAddress(this, addr, proto, msg.get(), remote_username, false);
    // Check for role conflicts.
    if (!MaybeIceRoleConflict(addr, msg.get(), remote_username)) {
      RTC_LOG(LS_INFO) << "Received conflicting role from the peer.";
      return;
    }
  } else if (msg->type() == GOOG_PING_REQUEST) {
    // This is a PING sent to a connection that was destroyed.
    // Send back that this is the case and a authenticated BINDING
    // is needed.
    SendBindingErrorResponse(msg.get(), addr, STUN_ERROR_BAD_REQUEST,
                             STUN_ERROR_REASON_BAD_REQUEST);
  } else {
    // NOTE(tschmelcher): STUN_BINDING_RESPONSE is benign. It occurs if we
    // pruned a connection for this port while it had STUN requests in flight,
    // because we then get back responses for them, which this code correctly
    // does not handle.
    if (msg->type() != STUN_BINDING_RESPONSE &&
        msg->type() != GOOG_PING_RESPONSE &&
        msg->type() != GOOG_PING_ERROR_RESPONSE) {
      RTC_LOG(LS_ERROR) << ToString()
                        << ": Received unexpected STUN message type: "
                        << msg->type() << " from unknown address: "
                        << addr.ToSensitiveString();
    }
  }
}

void Port::OnReadyToSend() {
  RTC_DCHECK_RUN_ON(thread_);
  AddressMap::iterator iter = connections_.begin();
  for (; iter != connections_.end(); ++iter) {
    iter->second->OnReadyToSend();
  }
}

void Port::AddPrflxCandidate(const Candidate& local) {
  RTC_DCHECK_RUN_ON(thread_);
  candidates_.push_back(local);
}

bool Port::GetStunMessage(std::span<const uint8_t> data,
                          const SocketAddress& addr,
                          std::unique_ptr<IceMessage>* out_msg,
                          std::string* out_username) {
  RTC_DCHECK_RUN_ON(thread_);
  // NOTE: This could clearly be optimized to avoid allocating any memory.
  //       However, at the data rates we'll be looking at on the client side,
  //       this probably isn't worth worrying about.
  RTC_DCHECK(out_msg != nullptr);
  RTC_DCHECK(out_username != nullptr);
  out_username->clear();

  // Don't bother parsing the packet if we can tell it's not STUN.
  // In ICE mode, all STUN packets will have a valid fingerprint.
  // Except GOOG_PING_REQUEST/RESPONSE that does not send fingerprint.
  int types[] = {GOOG_PING_REQUEST, GOOG_PING_RESPONSE,
                 GOOG_PING_ERROR_RESPONSE};
  if (!StunMessage::IsStunMethod(types, data) &&
      !StunMessage::ValidateFingerprint(data)) {
    return false;
  }

  // Parse the request message.  If the packet is not a complete and correct
  // STUN message, then ignore it.
  std::unique_ptr<IceMessage> stun_msg(new IceMessage());
  ByteBufferReader buf(data);
  if (!stun_msg->Read(&buf) || (buf.Length() > 0)) {
    return false;
  }

  // Get list of attributes in the "comprehension-required" range that were not
  // comprehended. If one or more is found, the behavior differs based on the
  // type of the incoming message; see below.
  std::vector<uint16_t> unknown_attributes =
      stun_msg->GetNonComprehendedAttributes();

  if (stun_msg->type() == STUN_BINDING_REQUEST) {
    // Check for the presence of USERNAME and MESSAGE-INTEGRITY (if ICE) first.
    // If not present, fail with a 400 Bad Request.
    if (!stun_msg->GetByteString(STUN_ATTR_USERNAME) ||
        !stun_msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY)) {
      RTC_LOG(LS_ERROR) << ToString() << ": Received "
                        << StunMethodToString(stun_msg->type())
                        << " without username/M-I from: "
                        << addr.ToSensitiveString();
      SendBindingErrorResponse(stun_msg.get(), addr, STUN_ERROR_BAD_REQUEST,
                               STUN_ERROR_REASON_BAD_REQUEST);
      return true;
    }

    // If the username is bad or unknown, fail with a 401 Unauthorized.
    std::string local_ufrag;
    std::string remote_ufrag;
    if (!ParseStunUsername(stun_msg.get(), &local_ufrag, &remote_ufrag) ||
        local_ufrag != username_fragment()) {
      RTC_LOG(LS_ERROR) << ToString() << ": Received "
                        << StunMethodToString(stun_msg->type())
                        << " with bad local username " << local_ufrag
                        << " from " << addr.ToSensitiveString();
      SendBindingErrorResponse(stun_msg.get(), addr, STUN_ERROR_UNAUTHORIZED,
                               STUN_ERROR_REASON_UNAUTHORIZED);
      return true;
    }

    // If ICE, and the MESSAGE-INTEGRITY is bad, fail with a 401 Unauthorized
    if (stun_msg->ValidateMessageIntegrity(password_) !=
        StunMessage::IntegrityStatus::kIntegrityOk) {
      RTC_LOG(LS_ERROR) << ToString() << ": Received "
                        << StunMethodToString(stun_msg->type())
                        << " with bad M-I from " << addr.ToSensitiveString()
                        << ", password_=" << password_;
      SendBindingErrorResponse(stun_msg.get(), addr, STUN_ERROR_UNAUTHORIZED,
                               STUN_ERROR_REASON_UNAUTHORIZED);
      return true;
    }

    // If a request contains unknown comprehension-required attributes, reply
    // with an error. See RFC5389 section 7.3.1.
    if (!unknown_attributes.empty()) {
      SendUnknownAttributesErrorResponse(stun_msg.get(), addr,
                                         unknown_attributes);
      return true;
    }

    out_username->assign(remote_ufrag);
  } else if ((stun_msg->type() == STUN_BINDING_RESPONSE) ||
             (stun_msg->type() == STUN_BINDING_ERROR_RESPONSE)) {
    if (stun_msg->type() == STUN_BINDING_ERROR_RESPONSE) {
      if (const StunErrorCodeAttribute* error_code = stun_msg->GetErrorCode()) {
        RTC_LOG(LS_ERROR) << ToString() << ": Received "
                          << StunMethodToString(stun_msg->type())
                          << ": class=" << error_code->eclass()
                          << " number=" << error_code->number() << " reason='"
                          << error_code->reason() << "' from "
                          << addr.ToSensitiveString();
        // Return message to allow error-specific processing
      } else {
        RTC_LOG(LS_ERROR) << ToString() << ": Received "
                          << StunMethodToString(stun_msg->type())
                          << " without a error code from "
                          << addr.ToSensitiveString();
        return true;
      }
    }
    // If a response contains unknown comprehension-required attributes, it's
    // simply discarded and the transaction is considered failed. See RFC5389
    // sections 7.3.3 and 7.3.4.
    if (!unknown_attributes.empty()) {
      RTC_LOG(LS_ERROR) << ToString()
                        << ": Discarding STUN response due to unknown "
                           "comprehension-required attribute";
      return true;
    }
    // NOTE: Username should not be used in verifying response messages.
    out_username->clear();
  } else if (stun_msg->type() == STUN_BINDING_INDICATION) {
    RTC_LOG(LS_VERBOSE) << ToString() << ": Received "
                        << StunMethodToString(stun_msg->type()) << ": from "
                        << addr.ToSensitiveString();
    out_username->clear();

    // If an indication contains unknown comprehension-required attributes,[]
    // it's simply discarded. See RFC5389 section 7.3.2.
    if (!unknown_attributes.empty()) {
      RTC_LOG(LS_ERROR) << ToString()
                        << ": Discarding STUN indication due to "
                           "unknown comprehension-required attribute";
      return true;
    }
    // No stun attributes will be verified, if it's stun indication message.
    // Returning from end of the this method.
  } else if (stun_msg->type() == GOOG_PING_REQUEST) {
    if (stun_msg->ValidateMessageIntegrity(password_) !=
        StunMessage::IntegrityStatus::kIntegrityOk) {
      RTC_LOG(LS_ERROR) << ToString() << ": Received "
                        << StunMethodToString(stun_msg->type())
                        << " with bad M-I from " << addr.ToSensitiveString()
                        << ", password_=" << password_;
      SendBindingErrorResponse(stun_msg.get(), addr, STUN_ERROR_UNAUTHORIZED,
                               STUN_ERROR_REASON_UNAUTHORIZED);
      return true;
    }
    RTC_LOG(LS_VERBOSE) << ToString() << ": Received "
                        << StunMethodToString(stun_msg->type()) << " from "
                        << addr.ToSensitiveString();
    out_username->clear();
  } else if (stun_msg->type() == GOOG_PING_RESPONSE ||
             stun_msg->type() == GOOG_PING_ERROR_RESPONSE) {
    // note: the MessageIntegrity32 will be verified in Connection.cc
    RTC_LOG(LS_VERBOSE) << ToString() << ": Received "
                        << StunMethodToString(stun_msg->type()) << " from "
                        << addr.ToSensitiveString();
    out_username->clear();
  } else {
    RTC_LOG(LS_ERROR) << ToString()
                      << ": Received STUN packet with invalid type ("
                      << stun_msg->type() << ") from "
                      << addr.ToSensitiveString();
    return true;
  }

  // Return the STUN message found.
  *out_msg = std::move(stun_msg);
  return true;
}

bool Port::IsCompatibleAddress(const SocketAddress& addr) {
  // Get a representative IP for the Network this port is configured to use.
  IPAddress ip = network_->GetBestIP();
  // We use single-stack sockets, so families must match.
  if (addr.family() != ip.family()) {
    return false;
  }
  // Link-local IPv6 ports can only connect to other link-local IPv6 ports.
  if (ip.family() == AF_INET6 &&
      (IPIsLinkLocal(ip) != IPIsLinkLocal(addr.ipaddr()))) {
    return false;
  }
  return true;
}

DiffServCodePoint Port::StunDscpValue() const {
  // By default, inherit from whatever the MediaChannel sends.
  return DSCP_NO_CHANGE;
}

void Port::DestroyAllConnections() {
  RTC_DCHECK_RUN_ON(thread_);
  for (auto& [unused, connection] : connections_) {
    connection->Shutdown();
    delete connection;
  }
  connections_.clear();
}

void Port::set_timeout_delay(int delay) {
  RTC_DCHECK_RUN_ON(thread_);
  // Although this method is meant to only be used by tests, some downstream
  // projects have started using it. Ideally we should update our tests to not
  // require to modify this state and instead use a testing harness that allows
  // adjusting the clock and then just use the kPortTimeoutDelay constant
  // directly.
  timeout_delay_ = delay;
}

bool Port::ParseStunUsername(const StunMessage* stun_msg,
                             std::string* local_ufrag,
                             std::string* remote_ufrag) const {
  // The packet must include a username that either begins or ends with our
  // fragment.  It should begin with our fragment if it is a request and it
  // should end with our fragment if it is a response.
  local_ufrag->clear();
  remote_ufrag->clear();
  const StunByteStringAttribute* username_attr =
      stun_msg->GetByteString(STUN_ATTR_USERNAME);
  if (username_attr == nullptr)
    return false;

  // RFRAG:LFRAG
  const absl::string_view username = username_attr->string_view();
  size_t colon_pos = username.find(':');
  if (colon_pos == absl::string_view::npos) {
    return false;
  }

  *local_ufrag = std::string(username.substr(0, colon_pos));
  *remote_ufrag = std::string(username.substr(colon_pos + 1, username.size()));
  return true;
}

bool Port::MaybeIceRoleConflict(const SocketAddress& addr,
                                IceMessage* stun_msg,
                                absl::string_view remote_ufrag) {
  RTC_DCHECK_RUN_ON(thread_);
  // Validate ICE_CONTROLLING or ICE_CONTROLLED attributes.
  bool ret = true;
  IceRole remote_ice_role = ICEROLE_UNKNOWN;
  uint64_t remote_tiebreaker = 0;
  const StunUInt64Attribute* stun_attr =
      stun_msg->GetUInt64(STUN_ATTR_ICE_CONTROLLING);
  if (stun_attr) {
    remote_ice_role = ICEROLE_CONTROLLING;
    remote_tiebreaker = stun_attr->value();
  }

  // If `remote_ufrag` is same as port local username fragment and
  // tie breaker value received in the ping message matches port
  // tiebreaker value this must be a loopback call.
  // We will treat this as valid scenario.
  if (remote_ice_role == ICEROLE_CONTROLLING &&
      username_fragment() == remote_ufrag &&
      remote_tiebreaker == IceTiebreaker()) {
    return true;
  }

  stun_attr = stun_msg->GetUInt64(STUN_ATTR_ICE_CONTROLLED);
  if (stun_attr) {
    remote_ice_role = ICEROLE_CONTROLLED;
    remote_tiebreaker = stun_attr->value();
  }

  switch (ice_role_) {
    case ICEROLE_CONTROLLING:
      if (ICEROLE_CONTROLLING == remote_ice_role) {
        if (remote_tiebreaker >= tiebreaker_) {
          NotifyRoleConflict();
        } else {
          // Send Role Conflict (487) error response.
          SendBindingErrorResponse(stun_msg, addr, STUN_ERROR_ROLE_CONFLICT,
                                   STUN_ERROR_REASON_ROLE_CONFLICT);
          ret = false;
        }
      }
      break;
    case ICEROLE_CONTROLLED:
      if (ICEROLE_CONTROLLED == remote_ice_role) {
        if (remote_tiebreaker < tiebreaker_) {
          NotifyRoleConflict();
        } else {
          // Send Role Conflict (487) error response.
          SendBindingErrorResponse(stun_msg, addr, STUN_ERROR_ROLE_CONFLICT,
                                   STUN_ERROR_REASON_ROLE_CONFLICT);
          ret = false;
        }
      }
      break;
    default:
      RTC_DCHECK_NOTREACHED();
  }
  return ret;
}

std::string Port::CreateStunUsername(absl::string_view remote_username) const {
  RTC_DCHECK_RUN_ON(thread_);
  return std::string(remote_username) + ":" + username_fragment();
}

bool Port::HandleIncomingPacket(AsyncPacketSocket* /* socket */,
                                const ReceivedIpPacket& /* packet */) {
  RTC_DCHECK_NOTREACHED();
  return false;
}

bool Port::CanHandleIncomingPacketsFrom(const SocketAddress&) const {
  return false;
}

void Port::SendBindingErrorResponse(StunMessage* message,
                                    const SocketAddress& addr,
                                    int error_code,
                                    absl::string_view reason) {
  RTC_DCHECK_RUN_ON(thread_);
  RTC_DCHECK(message->type() == STUN_BINDING_REQUEST ||
             message->type() == GOOG_PING_REQUEST);

  // Fill in the response message.
  StunMessage response(message->type() == STUN_BINDING_REQUEST
                           ? STUN_BINDING_ERROR_RESPONSE
                           : GOOG_PING_ERROR_RESPONSE,
                       message->transaction_id());

  // When doing GICE, we need to write out the error code incorrectly to
  // maintain backwards compatibility.
  auto error_attr = StunAttribute::CreateErrorCode();
  error_attr->SetCode(error_code);
  error_attr->SetReason(std::string(reason));
  response.AddAttribute(std::move(error_attr));

  // Per Section 10.1.2, certain error cases don't get a MESSAGE-INTEGRITY,
  // because we don't have enough information to determine the shared secret.
  if (error_code != STUN_ERROR_BAD_REQUEST &&
      error_code != STUN_ERROR_UNAUTHORIZED &&
      message->type() != GOOG_PING_REQUEST) {
    if (message->type() == STUN_BINDING_REQUEST) {
      response.AddMessageIntegrity(password_);
    } else {
      response.AddMessageIntegrity32(password_);
    }
  }

  if (message->type() == STUN_BINDING_REQUEST) {
    response.AddFingerprint();
  }

  // Send the response message.
  ByteBufferWriter buf;
  response.Write(&buf);
  AsyncSocketPacketOptions options(StunDscpValue());
  options.info_signaled_after_sent.packet_type =
      PacketType::kIceConnectivityCheckResponse;
  SendTo(buf.DataView(), addr, options, false);
  RTC_LOG(LS_INFO) << ToString() << ": Sending STUN "
                   << StunMethodToString(response.type())
                   << ": reason=" << reason << " to "
                   << addr.ToSensitiveString();
}

void Port::SendUnknownAttributesErrorResponse(
    StunMessage* message,
    const SocketAddress& addr,
    const std::vector<uint16_t>& unknown_types) {
  RTC_DCHECK_RUN_ON(thread_);
  RTC_DCHECK(message->type() == STUN_BINDING_REQUEST);

  // Fill in the response message.
  StunMessage response(STUN_BINDING_ERROR_RESPONSE, message->transaction_id());

  auto error_attr = StunAttribute::CreateErrorCode();
  error_attr->SetCode(STUN_ERROR_UNKNOWN_ATTRIBUTE);
  error_attr->SetReason(STUN_ERROR_REASON_UNKNOWN_ATTRIBUTE);
  response.AddAttribute(std::move(error_attr));

  std::unique_ptr<StunUInt16ListAttribute> unknown_attr =
      StunAttribute::CreateUnknownAttributes();
  for (uint16_t type : unknown_types) {
    unknown_attr->AddType(type);
  }
  response.AddAttribute(std::move(unknown_attr));

  response.AddMessageIntegrity(password_);
  response.AddFingerprint();

  // Send the response message.
  ByteBufferWriter buf;
  response.Write(&buf);
  AsyncSocketPacketOptions options(StunDscpValue());
  options.info_signaled_after_sent.packet_type =
      PacketType::kIceConnectivityCheckResponse;
  SendTo(buf.DataView(), addr, options, false);
  RTC_LOG(LS_ERROR) << ToString() << ": Sending STUN binding error: reason="
                    << STUN_ERROR_UNKNOWN_ATTRIBUTE << " to "
                    << addr.ToSensitiveString();
}

void Port::KeepAliveUntilPruned() {
  RTC_DCHECK_RUN_ON(thread_);

  // If it is pruned, we won't bring it up again.
  if (state_ == State::INIT) {
    state_ = State::KEEP_ALIVE_UNTIL_PRUNED;
  }
}

void Port::Prune() {
  RTC_DCHECK_RUN_ON(thread_);
  state_ = State::PRUNED;
  PostDestroyIfDead(/*delayed=*/false);
}

// Call to stop any currently pending operations from running.
void Port::CancelPendingTasks() {
  TRACE_EVENT0("webrtc", "Port::CancelPendingTasks");
  RTC_DCHECK_RUN_ON(thread_);
  weak_factory_.InvalidateWeakPtrs();
}

void Port::PostDestroyIfDead(bool delayed) {
  RTC_DCHECK_RUN_ON(thread_);
  WeakPtr<Port> weak_ptr = NewWeakPtr();
  auto task = [weak_ptr = std::move(weak_ptr)] {
    if (weak_ptr) {
      weak_ptr->DestroyIfDead();
    }
  };
  if (delayed) {
    thread_->PostDelayedTask(std::move(task),
                             TimeDelta::Millis(timeout_delay_));
  } else {
    thread_->PostTask(std::move(task));
  }
}

void Port::DestroyIfDead() {
  RTC_DCHECK_RUN_ON(thread_);
  bool dead =
      (state_ == State::INIT || state_ == State::PRUNED) &&
      connections_.empty() &&
      env_.clock().TimeInMilliseconds() - last_time_all_connections_removed_ >=
          timeout_delay_;
  if (dead) {
    Destroy();
  }
}

[[deprecated]] void Port::SubscribePortDestroyed(
    std::function<void(PortInterface*)> callback) {
  RTC_DCHECK_RUN_ON(thread_);
  port_destroyed_callback_list_.AddReceiver(std::move(callback));
}

void Port::SubscribePortDestroyed(
    const void* tag,
    std::function<void(PortInterface*)> callback) {
  RTC_DCHECK_RUN_ON(thread_);
  port_destroyed_callback_list_.AddReceiver(tag, std::move(callback));
}

void Port::SendPortDestroyed(Port* port) {
  RTC_DCHECK_RUN_ON(thread_);
  port_destroyed_callback_list_.Send(port);
}
void Port::OnNetworkTypeChanged(const ::webrtc::Network* network) {
  RTC_DCHECK(network == network_);

  UpdateNetworkCost();
}

void Port::OnNetworkSliceChanged(const ::webrtc::Network* network) {
  RTC_DCHECK_RUN_ON(thread_);
  RTC_DCHECK(network == network_);

  for (Candidate& candidate : candidates_) {
    candidate.set_network_slice(network_->network_slice());
  }

  // The network slice affects the network cost as well, so also update the cost
  // when the slice changes.
  UpdateNetworkCost();
}

std::string Port::ToString() const {
  RTC_DCHECK_RUN_ON(thread_);
  StringBuilder ss;
  ss << "Port[" << ToHex(reinterpret_cast<uintptr_t>(this)) << ":"
     << content_name_ << ":" << component_ << ":" << generation_ << ":"
     << IceCandidateTypeToString(type_) << ":" << network_->ToString() << "]";
  return ss.Release();
}

// TODO(honghaiz): Make the network cost configurable from user setting.
void Port::UpdateNetworkCost() {
  RTC_DCHECK_RUN_ON(thread_);
  uint16_t new_cost = network_->GetCost(env().field_trials());
  if (network_cost_ == new_cost) {
    return;
  }
  RTC_LOG(LS_INFO) << "Network cost changed from " << network_cost_ << " to "
                   << new_cost
                   << ". Number of candidates created: " << candidates_.size()
                   << ". Number of connections created: "
                   << connections_.size();
  network_cost_ = new_cost;
  for (Candidate& candidate : candidates_)
    candidate.set_network_cost(network_cost_);

  for (auto& [unused, connection] : connections_)
    connection->SetLocalCandidateNetworkCost(network_cost_);
}

void Port::EnablePortPackets() {
  RTC_DCHECK_RUN_ON(thread_);
  enable_port_packets_ = true;
}

bool Port::OnConnectionDestroyed(Connection* conn) {
  RTC_DCHECK_RUN_ON(thread_);
  if (connections_.erase(conn->remote_candidate().address()) == 0) {
    // This could indicate a programmer error outside of webrtc so while we
    // do have this check here to alert external developers, we also need to
    // handle it since it might be a corner case not caught in tests.
    RTC_DCHECK_NOTREACHED() << "Calling Destroy recursively?";
    return false;
  }

  HandleConnectionDestroyed(conn);

  // Ports time out after all connections fail if it is not marked as
  // "keep alive until pruned."
  // Note: If a new connection is added after this message is posted, but it
  // fails and is removed before kPortTimeoutDelay, then this message will
  // not cause the Port to be destroyed.
  if (connections_.empty()) {
    last_time_all_connections_removed_ = env_.clock().TimeInMilliseconds();
    PostDestroyIfDead(/*delayed=*/true);
  }

  return true;
}

void Port::DestroyConnectionInternal(Connection* conn, bool async) {
  RTC_DCHECK_RUN_ON(thread_);
  if (!OnConnectionDestroyed(conn))
    return;

  conn->Shutdown();
  if (async) {
    // Unwind the stack before deleting the object in case upstream callers
    // need to refer to the Connection's state as part of teardown.
    // NOTE: We move ownership of `conn` into the capture section of the lambda
    // so that the object will always be deleted, including if PostTask fails.
    // In such a case (only tests), deletion would happen inside of the call
    // to `DestroyConnection()`.
    thread_->PostTask([conn = absl::WrapUnique(conn)]() {});
  } else {
    delete conn;
  }
}

void Port::Destroy() {
  RTC_DCHECK_RUN_ON(thread_);
  RTC_DCHECK(connections_.empty());
  RTC_LOG(LS_INFO) << ToString() << ": Port deleted";
  SendPortDestroyed(this);
  delete this;
}

const std::string& Port::username_fragment() const {
  RTC_DCHECK_RUN_ON(thread_);
  return ice_username_fragment_;
}

void Port::CopyPortInformationToPacketInfo(PacketInfo* info) const {
  info->protocol = ConvertProtocolTypeToPacketInfoProtocolType(GetProtocol());
  info->network_id = Network()->id();
}

void Port::MaybeRequestLocalNetworkAccessPermission(
    const SocketAddress& address,
    absl::AnyInvocable<void(LocalNetworkAccessPermissionStatus)> callback) {
  RTC_DCHECK_RUN_ON(thread_);
  if (!lna_permission_factory_) {
    std::move(callback)(LocalNetworkAccessPermissionStatus::kGranted);
    return;
  }

  std::unique_ptr<LocalNetworkAccessPermissionInterface> permission_query =
      lna_permission_factory_->Create();
  if (!permission_query->ShouldRequestPermission(address)) {
    std::move(callback)(LocalNetworkAccessPermissionStatus::kGranted);
    return;
  }

  RTC_LOG(LS_VERBOSE) << "Asynchronously requesting LNA permission."
                      << address.HostAsSensitiveURIString();

  auto* permission_query_ptr = permission_query.get();
  permission_queries_.push_back(std::move(permission_query));

  permission_query_ptr->RequestPermission(
      address, [this, permission_query_ptr, callback = std::move(callback)](
                   LocalNetworkAccessPermissionStatus status) mutable {
        OnRequestLocalNetworkAccessPermission(permission_query_ptr,
                                              std::move(callback), status);
      });
}

void Port::OnRequestLocalNetworkAccessPermission(
    LocalNetworkAccessPermissionInterface* permission_query,
    absl::AnyInvocable<void(LocalNetworkAccessPermissionStatus)> callback,
    LocalNetworkAccessPermissionStatus status) {
  RTC_DCHECK_RUN_ON(thread_);
  auto it =
      absl::c_find_if(permission_queries_, [permission_query](const auto& q) {
        return q.get() == permission_query;
      });
  if (it == permission_queries_.end()) {
    RTC_LOG(LS_ERROR) << "Unexpected LocalNetworkAccessPermission return";
    RTC_DCHECK_NOTREACHED();
    return;
  }

  permission_queries_.erase(it);
  std::move(callback)(status);
}

void Port::SubscribeRoleConflict(absl::AnyInvocable<void()> callback) {
  RTC_DCHECK_RUN_ON(thread_);
  RTC_DCHECK(callback);
  RTC_DCHECK(!role_conflict_callback_);
  role_conflict_callback_ = std::move(callback);
}

void Port::NotifyRoleConflict() {
  RTC_DCHECK_RUN_ON(thread_);
  role_conflict_callback_();
}

[[deprecated]] void Port::SubscribeUnknownAddress(
    absl::AnyInvocable<void(PortInterface*,
                            const SocketAddress&,
                            ProtocolType,
                            IceMessage*,
                            const std::string&,
                            bool)> callback) {
  unknown_address_callbacks_.AddReceiver(std::move(callback));
}

void Port::SubscribeUnknownAddress(const void* tag,
                                   absl::AnyInvocable<void(PortInterface*,
                                                           const SocketAddress&,
                                                           ProtocolType,
                                                           IceMessage*,
                                                           const std::string&,
                                                           bool)> callback) {
  unknown_address_callbacks_.AddReceiver(tag, std::move(callback));
}

void Port::NotifyUnknownAddress(PortInterface* port,
                                const SocketAddress& address,
                                ProtocolType proto,
                                IceMessage* msg,
                                const std::string& rf,
                                bool port_muxed) {
  unknown_address_callbacks_.Send(port, address, proto, msg, rf, port_muxed);
}

// deprecated
void Port::SubscribeReadPacket(
    absl::AnyInvocable<
        void(PortInterface*, const char*, size_t, const SocketAddress&)>
        callback) {
  SubscribeReadPacket(
      nullptr, [cb = std::move(callback)](PortInterface* port,
                                          std::span<const uint8_t> data,
                                          const SocketAddress& addr) mutable {
        cb(port, AsCharSpan(data).data(), data.size(), addr);
      });
}

void Port::SubscribeReadPacket(
    const void* tag,
    absl::AnyInvocable<void(PortInterface*,
                            std::span<const uint8_t>,
                            const SocketAddress&)> callback) {
  read_packet_callbacks_.AddReceiver(tag, std::move(callback));
}

void Port::NotifyReadPacket(PortInterface* port,
                            std::span<const uint8_t> data,
                            const SocketAddress& remote_address) {
  read_packet_callbacks_.Send(port, data, remote_address);
}

[[deprecated]] void Port::SubscribeSentPacket(

    absl::AnyInvocable<void(const SentPacketInfo&)> callback) {
  sent_packet_callbacks_.AddReceiver(std::move(callback));
}

void Port::SubscribeSentPacket(
    const void* tag,
    absl::AnyInvocable<void(const SentPacketInfo&)> callback) {
  sent_packet_callbacks_.AddReceiver(tag, std::move(callback));
}

void Port::NotifySentPacket(const SentPacketInfo& packet) {
  sent_packet_callbacks_.Send(packet);
}

}  // namespace webrtc
