@ 261106.2
current
#include "guard.h"
#ifndef OPENSSL_NO_QUIC
#include <crypto/crypto_util.h>
#include <ngtcp2/ngtcp2.h>
#include <ngtcp2/ngtcp2_crypto.h>
#include <node_sockaddr-inl.h>
#include <uv.h>
#include <cstring>
#include <string>
#include "cid.h"
#include "defs.h"
#include "endpoint.h"
#include "ncrypto.h"
#include "packet.h"
#include "tokens.h"

namespace node::quic {

namespace {
static constexpr size_t kRandlen = NGTCP2_MIN_STATELESS_RESET_RANDLEN * 5;
static constexpr size_t kMinStatelessResetLen = 41;
}  // namespace

std::string PathDescriptor::ToString() const {
  DebugIndentScope indent;
  auto prefix = indent.Prefix();
  std::string res = "{";
  res += prefix + "version: " + std::to_string(version);
  res += prefix + "dcid: " + dcid.ToString();
  res += prefix + "scid: " + scid.ToString();
  res += prefix + "local address: " + local_address.ToString();
  res += prefix + "remote address: " + remote_address.ToString();
  res += indent.Close();
  return res;
}

// ============================================================================
// Packet

Packet::Packet(uint8_t* data,
               size_t capacity,
               Listener* listener,
               const SocketAddress& destination)
    : data_(data),
      capacity_(capacity),
      length_(capacity),
      listener_(listener),
      destination_(destination),
      req_{} {}

std::string Packet::ToString() const {
  std::string res = "Packet(";
#ifdef DEBUG
  if (diagnostic_label_) {
    res += diagnostic_label_;
    res += ", ";
  }
#endif
  res += std::to_string(length_);
  res += ")";
  return res;
}

// ============================================================================
// Static factory methods

Packet::Ptr Packet::CreateRetryPacket(Endpoint& endpoint,
                                      const PathDescriptor& path_descriptor,
                                      const TokenSecret& token_secret) {
  auto& random = CID::Factory::random();
  CID cid = random.Generate();
  RetryToken token(path_descriptor.version,
                   path_descriptor.remote_address,
                   cid,
                   path_descriptor.dcid,
                   token_secret);
  if (!token) return Ptr();

  const ngtcp2_vec& vec = token;

  size_t pktlen =
      vec.len + (2 * NGTCP2_MAX_CIDLEN) + path_descriptor.scid.length() + 8;

  auto packet =
      endpoint.CreatePacket(path_descriptor.remote_address, pktlen, "retry");
  if (!packet) return packet;

  ngtcp2_vec dest = *packet;

  ssize_t nwrite = ngtcp2_crypto_write_retry(dest.base,
                                             pktlen,
                                             path_descriptor.version,
                                             path_descriptor.scid,
                                             cid,
                                             path_descriptor.dcid,
                                             vec.base,
                                             vec.len);
  if (nwrite <= 0) return Ptr();
  packet->Truncate(static_cast<size_t>(nwrite));
  return packet;
}

Packet::Ptr Packet::CreateConnectionClosePacket(
    Endpoint& endpoint,
    const SocketAddress& destination,
    ngtcp2_conn* conn,
    const QuicError& error) {
  auto packet = endpoint.CreatePacket(
      destination, kDefaultMaxPacketLength, "connection close");
  if (!packet) return packet;
  ngtcp2_vec vec = *packet;

  ngtcp2_ccerr wire_error = error.ToNgtcp2ConnectionCloseError();
  ssize_t nwrite = ngtcp2_conn_write_connection_close(
      conn, nullptr, nullptr, vec.base, vec.len, &wire_error, uv_hrtime());
  if (nwrite < 0) return Ptr();
  packet->Truncate(static_cast<size_t>(nwrite));
  return packet;
}

Packet::Ptr Packet::CreateImmediateConnectionClosePacket(
    Endpoint& endpoint,
    const PathDescriptor& path_descriptor,
    const QuicError& reason) {
  auto packet = endpoint.CreatePacket(path_descriptor.remote_address,
                                      kDefaultMaxPacketLength,
                                      "immediate connection close (endpoint)");
  if (!packet) return packet;
  ngtcp2_vec vec = *packet;
  // ngtcp2_crypto_write_connection_close expects dcid to be the
  // client's SCID and scid to be the client's DCID (mirrored).
  // PathDescriptor carries the incoming packet's CIDs as-is, so
  // we swap here.
  ssize_t nwrite = ngtcp2_crypto_write_connection_close(vec.base,
                                                        vec.len,
                                                        path_descriptor.version,
                                                        path_descriptor.scid,
                                                        path_descriptor.dcid,
                                                        reason.code(),
                                                        nullptr,
                                                        0);
  if (nwrite <= 0) return Ptr();
  packet->Truncate(static_cast<size_t>(nwrite));
  return packet;
}

Packet::Ptr Packet::CreateStatelessResetPacket(
    Endpoint& endpoint,
    const PathDescriptor& path_descriptor,
    const TokenSecret& token_secret,
    size_t source_len) {
  // Per the QUIC spec, a stateless reset token must be strictly smaller than
  // the packet that triggered it.
  size_t pktlen = source_len - 1;
  if (pktlen < kMinStatelessResetLen) return Ptr();

  StatelessResetToken token(token_secret, path_descriptor.dcid);
  uint8_t random[kRandlen];
  CHECK(ncrypto::CSPRNG(random, kRandlen));

  auto packet = endpoint.CreatePacket(path_descriptor.remote_address,
                                      kDefaultMaxPacketLength,
                                      "stateless reset");
  if (!packet) return packet;
  ngtcp2_vec vec = *packet;

  auto nwrite = ngtcp2_pkt_write_stateless_reset2(
      vec.base, pktlen, token, random, kRandlen);
  if (nwrite < static_cast<ssize_t>(kMinStatelessResetLen)) {
    return Ptr();
  }

  packet->Truncate(static_cast<size_t>(nwrite));
  return packet;
}

Packet::Ptr Packet::CreateVersionNegotiationPacket(
    Endpoint& endpoint, const PathDescriptor& path_descriptor) {
  const auto generateReservedVersion = [&] {
    socklen_t addrlen = path_descriptor.remote_address.length();
    uint32_t h = 0x811C9DC5u;
    uint32_t ver = htonl(path_descriptor.version);
    const uint8_t* p = path_descriptor.remote_address.raw();
    const uint8_t* ep = p + addrlen;
    for (; p != ep; ++p) {
      h ^= *p;
      h *= 0x01000193u;
    }
    p = reinterpret_cast<const uint8_t*>(&ver);
    ep = p + sizeof(path_descriptor.version);
    for (; p != ep; ++p) {
      h ^= *p;
      h *= 0x01000193u;
    }
    h &= 0xf0f0f0f0u;
    h |= NGTCP2_RESERVED_VERSION_MASK;
    return h;
  };

  uint32_t sv[3] = {
      generateReservedVersion(), NGTCP2_PROTO_VER_MIN, NGTCP2_PROTO_VER_MAX};

  size_t pktlen = path_descriptor.dcid.length() +
                  path_descriptor.scid.length() + (sizeof(sv)) + 7;

  auto packet = endpoint.CreatePacket(path_descriptor.remote_address,
                                      kDefaultMaxPacketLength,
                                      "version negotiation");
  if (!packet) return packet;
  ngtcp2_vec vec = *packet;

  // ngtcp2_pkt_write_version_negotiation expects dcid to be the
  // client's SCID and scid to be the client's DCID (mirrored).
  // PathDescriptor carries the incoming packet's CIDs as-is, so
  // we swap here.
  ssize_t nwrite =
      ngtcp2_pkt_write_version_negotiation(vec.base,
                                           pktlen,
                                           0,
                                           path_descriptor.scid,
                                           path_descriptor.scid.length(),
                                           path_descriptor.dcid,
                                           path_descriptor.dcid.length(),
                                           sv,
                                           arraysize(sv));
  if (nwrite <= 0) return Ptr();
  packet->Truncate(static_cast<size_t>(nwrite));
  return packet;
}

}  // namespace node::quic

#endif  // OPENSSL_NO_QUIC