void AsyncSSLSocket::clientHelloParsingCallback(
    int written,
    int /* version */,
    int contentType,
    const void* buf,
    size_t len,
    SSL* ssl,
    void* arg) {
  AsyncSSLSocket* sock = static_cast<AsyncSSLSocket*>(arg);
  if (written != 0) {
    sock->resetClientHelloParsing(ssl);
    return;
  }
  if (contentType != SSL3_RT_HANDSHAKE) {
    return;
  }
  if (len == 0) {
    return;
  }

  auto& clientHelloBuf = sock->clientHelloInfo_->clientHelloBuf_;
  clientHelloBuf.append(IOBuf::wrapBuffer(buf, len));
  try {
    Cursor cursor(clientHelloBuf.front());
    if (cursor.read<uint8_t>() != SSL3_MT_CLIENT_HELLO) {
      sock->resetClientHelloParsing(ssl);
      return;
    }

    if (cursor.totalLength() < 3) {
      clientHelloBuf.trimEnd(len);
      clientHelloBuf.append(IOBuf::copyBuffer(buf, len));
      return;
    }

    uint32_t messageLength = cursor.read<uint8_t>();
    messageLength <<= 8;
    messageLength |= cursor.read<uint8_t>();
    messageLength <<= 8;
    messageLength |= cursor.read<uint8_t>();
    if (cursor.totalLength() < messageLength) {
      clientHelloBuf.trimEnd(len);
      clientHelloBuf.append(IOBuf::copyBuffer(buf, len));
      return;
    }

    sock->clientHelloInfo_->clientHelloMajorVersion_ = cursor.read<uint8_t>();
    sock->clientHelloInfo_->clientHelloMinorVersion_ = cursor.read<uint8_t>();

    cursor.skip(4); // gmt_unix_time
    cursor.skip(28); // random_bytes

    cursor.skip(cursor.read<uint8_t>()); // session_id

    uint16_t cipherSuitesLength = cursor.readBE<uint16_t>();
    for (int i = 0; i < cipherSuitesLength; i += 2) {
      sock->clientHelloInfo_->clientHelloCipherSuites_.push_back(
          cursor.readBE<uint16_t>());
    }

    uint8_t compressionMethodsLength = cursor.read<uint8_t>();
    for (int i = 0; i < compressionMethodsLength; ++i) {
      sock->clientHelloInfo_->clientHelloCompressionMethods_.push_back(
          cursor.readBE<uint8_t>());
    }

    if (cursor.totalLength() > 0) {
      uint16_t extensionsLength = cursor.readBE<uint16_t>();
      while (extensionsLength) {
        ssl::TLSExtension extensionType =
            static_cast<ssl::TLSExtension>(cursor.readBE<uint16_t>());
        sock->clientHelloInfo_->clientHelloExtensions_.push_back(extensionType);
        extensionsLength -= 2;
        uint16_t extensionDataLength = cursor.readBE<uint16_t>();
        extensionsLength -= 2;
        extensionsLength -= extensionDataLength;

        if (extensionType == ssl::TLSExtension::SIGNATURE_ALGORITHMS) {
          cursor.skip(2);
          extensionDataLength -= 2;
          while (extensionDataLength) {
            ssl::HashAlgorithm hashAlg =
                static_cast<ssl::HashAlgorithm>(cursor.readBE<uint8_t>());
            ssl::SignatureAlgorithm sigAlg =
                static_cast<ssl::SignatureAlgorithm>(cursor.readBE<uint8_t>());
            extensionDataLength -= 2;
            sock->clientHelloInfo_->clientHelloSigAlgs_.emplace_back(
                hashAlg, sigAlg);
          }
        } else if (extensionType == ssl::TLSExtension::SUPPORTED_VERSIONS) {
          cursor.skip(1);
          extensionDataLength -= 1;
          while (extensionDataLength) {
            sock->clientHelloInfo_->clientHelloSupportedVersions_.push_back(
                cursor.readBE<uint16_t>());
            extensionDataLength -= 2;
          }
        } else if (extensionType == ssl::TLSExtension::SERVER_NAME) {
          cursor.skip(2);
          extensionDataLength -= 2;
          while (extensionDataLength) {
            static_assert(
                std::is_same<
                    typename std::underlying_type<ssl::NameType>::type,
                    uint8_t>::value,
                "unexpected underlying type");

            ssl::NameType typ =
                static_cast<ssl::NameType>(cursor.readBE<uint8_t>());
            uint16_t nameLength = cursor.readBE<uint16_t>();

            if (typ == NameType::HOST_NAME &&
                sock->clientHelloInfo_->clientHelloSNIHostname_.empty() &&
                cursor.canAdvance(nameLength)) {
              sock->clientHelloInfo_->clientHelloSNIHostname_ =
                  cursor.readFixedString(nameLength);
            } else {
              // Must attempt to skip |nameLength| in order to keep cursor
              // in sync. If the remaining buffer length is smaller than
              // nameLength, this will throw.
              cursor.skip(nameLength);
            }
            extensionDataLength -=
                sizeof(typ) + sizeof(nameLength) + nameLength;
          }
        } else {
          cursor.skip(extensionDataLength);
        }
      }
    }
  } catch (std::out_of_range&) {
    // we'll use what we found and cleanup below.
    VLOG(4) << "AsyncSSLSocket::clientHelloParsingCallback(): "
            << "buffer finished unexpectedly."
            << " AsyncSSLSocket socket=" << sock;
  }

  sock->resetClientHelloParsing(ssl);
}