CodecResult QuicReadCodec::parseLongHeaderPacket()

in quic/codec/QuicReadCodec.cpp [86:249]


CodecResult QuicReadCodec::parseLongHeaderPacket(
    BufQueue& queue,
    const AckStates& ackStates) {
  folly::io::Cursor cursor(queue.front());
  const uint8_t initialByte = *cursor.peekBytes().data();

  auto res = tryParseLongHeader(cursor, nodeType_);
  if (res.hasError()) {
    VLOG(4) << "Failed to parse long header " << connIdToHex();
    queue.move();
    return CodecResult(Nothing());
  }
  auto parsedLongHeader = std::move(res.value());
  auto type = parsedLongHeader.header.getHeaderType();

  // As soon as we have parsed out the long header we can split off any
  // coalesced packets. We do this early since the spec mandates that decryption
  // failure must not stop the processing of subsequent coalesced packets.
  auto longHeader = std::move(parsedLongHeader.header);

  if (type == LongHeader::Types::Retry) {
    Buf integrityTag;
    cursor.clone(integrityTag, kRetryIntegrityTagLen);
    queue.move();
    return RetryPacket(
        std::move(longHeader), std::move(integrityTag), initialByte);
  }

  uint64_t packetNumberOffset = cursor.getCurrentPosition();
  size_t currentPacketLen =
      packetNumberOffset + parsedLongHeader.packetLength.packetLength;
  if (queue.chainLength() < currentPacketLen) {
    // Packet appears truncated, there's no parse-able data left.
    queue.move();
    return CodecResult(Nothing());
  }
  auto currentPacketData = queue.splitAtMost(currentPacketLen);
  cursor.reset(currentPacketData.get());
  cursor.skip(packetNumberOffset);
  // Sample starts after the max packet number size. This ensures that we
  // have enough bytes to skip before we can start reading the sample.
  if (!cursor.canAdvance(kMaxPacketNumEncodingSize)) {
    VLOG(4) << "Dropping packet, not enough for packet number "
            << connIdToHex();
    // Packet appears truncated, there's no parse-able data left.
    queue.move();
    return CodecResult(Nothing());
  }
  cursor.skip(kMaxPacketNumEncodingSize);
  Sample sample;
  if (!cursor.canAdvance(sample.size())) {
    VLOG(4) << "Dropping packet, sample too small " << connIdToHex();
    // Packet appears truncated, there's no parse-able data left.
    queue.move();
    return CodecResult(Nothing());
  }
  cursor.pull(sample.data(), sample.size());
  const PacketNumberCipher* headerCipher{nullptr};
  const Aead* cipher{nullptr};
  auto protectionType = longHeader.getProtectionType();
  switch (protectionType) {
    case ProtectionType::Initial:
      if (!initialHeaderCipher_) {
        VLOG(4) << nodeToString(nodeType_)
                << " dropping initial packet after initial keys dropped"
                << connIdToHex();
        return CodecResult(Nothing());
      }
      headerCipher = initialHeaderCipher_.get();
      cipher = initialReadCipher_.get();
      break;
    case ProtectionType::Handshake:
      headerCipher = handshakeHeaderCipher_.get();
      cipher = handshakeReadCipher_.get();
      break;
    case ProtectionType::ZeroRtt:
      if (handshakeDoneTime_) {
        // TODO actually drop the 0-rtt keys in addition to dropping packets.
        auto timeBetween = Clock::now() - *handshakeDoneTime_;
        if (timeBetween > kTimeToRetainZeroRttKeys) {
          VLOG(4) << nodeToString(nodeType_)
                  << " dropping zero rtt packet for exceeding key timeout"
                  << connIdToHex();
          return CodecResult(Nothing());
        }
      }
      headerCipher = zeroRttHeaderCipher_.get();
      cipher = zeroRttReadCipher_.get();
      break;
    case ProtectionType::KeyPhaseZero:
    case ProtectionType::KeyPhaseOne:
      CHECK(false) << "one rtt protection type in long header";
  }
  if (!headerCipher || !cipher) {
    return CodecResult(
        CipherUnavailable(std::move(currentPacketData), protectionType));
  }

  PacketNum expectedNextPacketNum = 0;
  folly::Optional<PacketNum> largestReceivedPacketNum;
  switch (longHeaderTypeToProtectionType(type)) {
    case ProtectionType::Initial:
      largestReceivedPacketNum =
          ackStates.initialAckState.largestReceivedPacketNum;
      break;
    case ProtectionType::Handshake:
      largestReceivedPacketNum =
          ackStates.handshakeAckState.largestReceivedPacketNum;
      break;
    case ProtectionType::ZeroRtt:
      largestReceivedPacketNum =
          ackStates.appDataAckState.largestReceivedPacketNum;
      break;
    default:
      folly::assume_unreachable();
  }
  if (largestReceivedPacketNum) {
    expectedNextPacketNum = 1 + *largestReceivedPacketNum;
  }
  folly::MutableByteRange initialByteRange(
      currentPacketData->writableData(), 1);
  folly::MutableByteRange packetNumberByteRange(
      currentPacketData->writableData() + packetNumberOffset,
      kMaxPacketNumEncodingSize);
  headerCipher->decryptLongHeader(
      folly::range(sample), initialByteRange, packetNumberByteRange);
  std::pair<PacketNum, size_t> packetNum = parsePacketNumber(
      initialByteRange.data()[0], packetNumberByteRange, expectedNextPacketNum);

  longHeader.setPacketNumber(packetNum.first);
  BufQueue decryptQueue;
  decryptQueue.append(std::move(currentPacketData));
  size_t aadLen = packetNumberOffset + packetNum.second;
  auto headerData = decryptQueue.splitAtMost(aadLen);
  // parsing verifies that packetLength >= packet number length.
  auto encryptedData = decryptQueue.splitAtMost(
      parsedLongHeader.packetLength.packetLength - packetNum.second);
  if (!encryptedData) {
    // There should normally be some integrity tag at least in the data,
    // however allowing the aead to process the data even if the tag is not
    // present helps with writing tests.
    encryptedData = folly::IOBuf::create(0);
  }

  Buf decrypted;
  auto decryptAttempt = cipher->tryDecrypt(
      std::move(encryptedData), headerData.get(), packetNum.first);
  if (!decryptAttempt) {
    VLOG(4) << "Unable to decrypt packet=" << packetNum.first
            << " packetNumLen=" << parsePacketNumberLength(initialByte)
            << " protectionType=" << toString(protectionType) << " "
            << connIdToHex();
    return CodecResult(Nothing());
  }
  decrypted = std::move(*decryptAttempt);

  if (!decrypted) {
    // TODO better way of handling this (tests break without this)
    decrypted = folly::IOBuf::create(0);
  }

  return decodeRegularPacket(
      std::move(longHeader), params_, std::move(decrypted));
}