folly::AsyncSocket::ReadResult AsyncKTLSSocket::processHandshakeData()

in fizz/experimental/ktls/AsyncKTLSSocket.cpp [192:288]


folly::AsyncSocket::ReadResult AsyncKTLSSocket::processHandshakeData(
    folly::ByteRange payload) {
  // [note.handshake_fits_in_record_assumption]
  //
  // It is extremely likely (unless the peer is malicious, or the user
  // provides a readCallback that returns extremely small buffers) that any
  // post handshake message (of which, there are only a few) sent by the
  // peer can fit in one record (and consequently, can be handled directly
  // by this call).
  //
  // We try to parse the handshake message directly off of `payloadBuf`
  // first, before we fall back to copying the contents of the buffer into
  // an internal IOBufQueue.
  auto payloadBuf =
      folly::IOBuf::wrapBufferAsValue(payload.data(), payload.size());

  VLOG(10) << "AsyncKTLSSocket::processHandshakeData()";
  folly::Optional<fizz::Param> handshakeMessage;

  // TODO: This can probably be simplified
  try {
    if (FOLLY_LIKELY(unparsedHandshakeData_ == nullptr)) {
      // TODO: Refactor fizz::ReadRecordLayer decoding logic to work on IOBuf
      // so we can avoid constructing an iobufqueue with a single element (
      // this forces a heap allocation...)
      folly::IOBufQueue tmp{folly::IOBufQueue::cacheChainLength()};
      tmp.append(payloadBuf);
      handshakeMessage = fizz::ReadRecordLayer::decodeHandshakeMessage(tmp);
      if (!handshakeMessage) {
        unparsedHandshakeData_ = std::make_unique<folly::IOBufQueue>(
            folly::IOBufQueue::cacheChainLength());
        payloadBuf.makeManaged();
        unparsedHandshakeData_->append(payloadBuf.clone());
        return ReadResult(READ_BLOCKING);
      }
    } else {
      // We already have data saved from last time
      payloadBuf.makeManaged();
      unparsedHandshakeData_->append(payloadBuf.clone());
      handshakeMessage = fizz::ReadRecordLayer::decodeHandshakeMessage(
          *unparsedHandshakeData_);
      if (!handshakeMessage) {
        return ReadResult(READ_BLOCKING);
      } else {
        unparsedHandshakeData_.reset();
      }
    }
  } catch (std::exception& ex) {
    // TODO: Send a decode_error alert
    return ReadResult(
        READ_ERROR,
        std::make_unique<folly::AsyncSocketException>(
            folly::AsyncSocketException::SSL_ERROR,
            folly::to<std::string>(
                "error decoding handshake data received by ktls: ",
                ex.what())));
  }

  // At this point, `handshakeMessage` is guaranteed to be valid.
  //
  // However, the *contents* of the handshake struct may be pointing to
  // borrowed memory. TLSCallback implementations must unshare() any
  // buffers prior to retaining them
  //
  // TODO is there a less error prone way to enforce this?
  DCHECK(handshakeMessage.hasValue());
  auto param = std::move(handshakeMessage).value();

  switch (param.type()) {
    case decltype(param)::Type::KeyUpdate_E:
      // The kTLS implementation in the kernel does not currently support
      // switching keys.
      VLOG(10) << "Received key update on kTLS connection";
      return ReadResult(
          READ_ERROR,
          std::make_unique<folly::AsyncSocketException>(
              folly::AsyncSocketException::SSL_ERROR,
              "ktls does not support key_updates"));
    case decltype(param)::Type::NewSessionTicket_E:
      VLOG(10) << "Received NewSessionTicket on KTLS connection";
      tlsCallback_->receivedNewSessionTicket(
          this, std::move(*param.asNewSessionTicket()));
      break;
    default:
      // Treat any other post-handshake message as an unexpected message
      // TODO: Send alert.
      return ReadResult(
          READ_ERROR,
          std::make_unique<folly::AsyncSocketException>(
              folly::AsyncSocketException::SSL_ERROR,
              "ktls received unexpected handshake content"));
  }

  // If the socket is still readable, the next record will be processed
  // in the next loop iteration. AsyncSocket is level triggered.
  return ReadResult(READ_BLOCKING);
}