// Copyright (c) 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/quic/quartc/quartc_session.h"

#include "base/rand_util.h"

namespace {

// Default priority for incoming QUIC streams.
// TODO(zhihuang): Determine if this value is correct.
static const net::SpdyPriority kDefaultPriority = 3;

// Arbitrary server port number for net::QuicCryptoClientConfig.
const int kQuicServerPort = 0;

// Length of HKDF input keying material, equal to its number of bytes.
// https://tools.ietf.org/html/rfc5869#section-2.2.
// TODO(zhihuang): Verify that input keying material length is correct.
const size_t kInputKeyingMaterialLength = 32;

// Used by QuicCryptoServerConfig to provide dummy proof credentials.
// TODO(zhihuang): Remove when secure P2P QUIC handshake is possible.
class DummyProofSource : public net::ProofSource {
 public:
  DummyProofSource() {}
  ~DummyProofSource() override {}

  // ProofSource override.
  bool GetProof(const net::IPAddress& server_ip,
                const std::string& hostname,
                const std::string& server_config,
                net::QuicVersion quic_version,
                base::StringPiece chlo_hash,
                const net::QuicTagVector& connection_options,
                scoped_refptr<net::ProofSource::Chain>* out_chain,
                std::string* out_signature,
                std::string* out_leaf_cert_sct) override {
    std::vector<std::string> certs;
    certs.push_back("Dummy cert");
    *out_chain = new ProofSource::Chain(certs);
    *out_signature = "Dummy signature";
    *out_leaf_cert_sct = "Dummy timestamp";
    return true;
  }

  void GetProof(const net::IPAddress& server_ip,
                const std::string& hostname,
                const std::string& server_config,
                net::QuicVersion quic_version,
                base::StringPiece chlo_hash,
                const net::QuicTagVector& connection_options,
                std::unique_ptr<Callback> callback) override {}
};

// Used by QuicCryptoClientConfig to ignore the peer's credentials
// and establish an insecure QUIC connection.
// TODO(zhihuang): Remove when secure P2P QUIC handshake is possible.
class InsecureProofVerifier : public net::ProofVerifier {
 public:
  InsecureProofVerifier() {}
  ~InsecureProofVerifier() override {}

  // ProofVerifier override.
  net::QuicAsyncStatus VerifyProof(
      const std::string& hostname,
      const uint16_t port,
      const std::string& server_config,
      net::QuicVersion quic_version,
      base::StringPiece chlo_hash,
      const std::vector<std::string>& certs,
      const std::string& cert_sct,
      const std::string& signature,
      const net::ProofVerifyContext* context,
      std::string* error_details,
      std::unique_ptr<net::ProofVerifyDetails>* verify_details,
      std::unique_ptr<net::ProofVerifierCallback> callback) override {
    return net::QUIC_SUCCESS;
  }

  net::QuicAsyncStatus VerifyCertChain(
      const std::string& hostname,
      const std::vector<std::string>& certs,
      const net::ProofVerifyContext* context,
      std::string* error_details,
      std::unique_ptr<net::ProofVerifyDetails>* details,
      std::unique_ptr<net::ProofVerifierCallback> callback) override {
    return net::QUIC_SUCCESS;
  }
};
}

namespace net {

QuicConnectionId QuartcCryptoServerStreamHelper::GenerateConnectionIdForReject(
    QuicConnectionId connection_id) const {
  return 0;
}

bool QuartcCryptoServerStreamHelper::CanAcceptClientHello(
    const CryptoHandshakeMessage& message,
    const IPEndPoint& self_address,
    std::string* error_details) const {
  return true;
}

QuartcSession::QuartcSession(std::unique_ptr<QuicConnection> connection,
                             const QuicConfig& config,
                             const std::string& unique_remote_server_id,
                             Perspective perspective,
                             QuicConnectionHelperInterface* helper)
    : QuicSession(connection.get(), nullptr /*visitor*/, config),
      unique_remote_server_id_(unique_remote_server_id),
      perspective_(perspective),
      connection_(std::move(connection)),
      helper_(helper) {
  // Initialization with default crypto configuration.
  if (perspective_ == Perspective::IS_CLIENT) {
    std::unique_ptr<ProofVerifier> proof_verifier(new InsecureProofVerifier);
    quic_crypto_client_config_.reset(
        new QuicCryptoClientConfig(std::move(proof_verifier)));
  } else {
    std::unique_ptr<ProofSource> proof_source(new DummyProofSource);
    std::string source_address_token_secret =
        base::RandBytesAsString(kInputKeyingMaterialLength);
    quic_crypto_server_config_.reset(new QuicCryptoServerConfig(
        source_address_token_secret, helper_->GetRandomGenerator(),
        std::move(proof_source)));
    // Provide server with serialized config string to prove ownership.
    QuicCryptoServerConfig::ConfigOptions options;
    // The |message| is used to handle the return value of AddDefaultConfig
    // which is raw pointer of the CryptoHandshakeMessage.
    std::unique_ptr<CryptoHandshakeMessage> message(
        quic_crypto_server_config_->AddDefaultConfig(
            helper_->GetRandomGenerator(), helper_->GetClock(), options));
  }
}

QuartcSession::~QuartcSession() {}

QuicCryptoStream* QuartcSession::GetCryptoStream() {
  return crypto_stream_.get();
}

QuartcStream* QuartcSession::CreateOutgoingDynamicStream(
    SpdyPriority priority) {
  return CreateDataStream(GetNextOutgoingStreamId(), priority);
}

void QuartcSession::OnCryptoHandshakeEvent(CryptoHandshakeEvent event) {
  QuicSession::OnCryptoHandshakeEvent(event);
  if (event == HANDSHAKE_CONFIRMED) {
    DCHECK(IsEncryptionEstablished());
    DCHECK(IsCryptoHandshakeConfirmed());

    DCHECK(session_delegate_);
    session_delegate_->OnCryptoHandshakeComplete();
  }
}

void QuartcSession::CloseStream(QuicStreamId stream_id) {
  if (IsClosedStream(stream_id)) {
    // When CloseStream has been called recursively (via
    // QuicStream::OnClose), the stream is already closed so return.
    return;
  }
  write_blocked_streams()->UnregisterStream(stream_id);
  QuicSession::CloseStream(stream_id);
}

void QuartcSession::OnConnectionClosed(QuicErrorCode error,
                                       const std::string& error_details,
                                       ConnectionCloseSource source) {
  QuicSession::OnConnectionClosed(error, error_details, source);
  DCHECK(session_delegate_);
  session_delegate_->OnConnectionClosed(
      error, source == ConnectionCloseSource::FROM_PEER);
}

void QuartcSession::StartCryptoHandshake() {
  if (perspective_ == Perspective::IS_CLIENT) {
    QuicServerId server_id(unique_remote_server_id_, kQuicServerPort);
    QuicCryptoClientStream* crypto_stream =
        new QuicCryptoClientStream(server_id, this, new ProofVerifyContext(),
                                   quic_crypto_client_config_.get(), this);
    crypto_stream_.reset(crypto_stream);
    QuicSession::Initialize();
    crypto_stream->CryptoConnect();
  } else {
    quic_compressed_certs_cache_.reset(new QuicCompressedCertsCache(
        QuicCompressedCertsCache::kQuicCompressedCertsCacheSize));
    bool use_stateless_rejects_if_peer_supported = false;
    QuicCryptoServerStream* crypto_stream = new QuicCryptoServerStream(
        quic_crypto_server_config_.get(), quic_compressed_certs_cache_.get(),
        use_stateless_rejects_if_peer_supported, this, &stream_helper_);
    crypto_stream_.reset(crypto_stream);
    QuicSession::Initialize();
  }
}

bool QuartcSession::ExportKeyingMaterial(const std::string& label,
                                         const uint8_t* context,
                                         size_t context_len,
                                         bool used_context,
                                         uint8_t* result,
                                         size_t result_len) {
  std::string quic_context(reinterpret_cast<const char*>(context), context_len);
  std::string quic_result;
  bool success = crypto_stream_->ExportKeyingMaterial(label, quic_context,
                                                      result_len, &quic_result);
  quic_result.copy(reinterpret_cast<char*>(result), result_len);
  DCHECK(quic_result.length() == result_len);
  return success;
}

QuartcStreamInterface* QuartcSession::CreateOutgoingStream(
    const OutgoingStreamParameters& param) {
  // The |param| is for forward-compatibility. Not used for now.
  return CreateOutgoingDynamicStream(kDefaultPriority);
}

void QuartcSession::SetDelegate(
    QuartcSessionInterface::Delegate* session_delegate) {
  if (session_delegate_) {
    LOG(WARNING) << "The delegate for the session has already been set.";
  }
  session_delegate_ = session_delegate;
  DCHECK(session_delegate_);
}

void QuartcSession::OnTransportCanWrite() {
  if (HasDataToWrite()) {
    connection()->OnCanWrite();
  }
}

bool QuartcSession::OnTransportReceived(const char* data, size_t data_len) {
  QuicReceivedPacket packet(data, data_len, clock_.Now());
  ProcessUdpPacket(connection()->self_address(), connection()->peer_address(),
                   packet);
  return true;
}

void QuartcSession::OnProofValid(
    const QuicCryptoClientConfig::CachedState& cached) {
  // TODO(zhihuang): Handle the proof verification.
}

void QuartcSession::OnProofVerifyDetailsAvailable(
    const ProofVerifyDetails& verify_details) {
  // TODO(zhihuang): Handle the proof verification.
}

void QuartcSession::SetClientCryptoConfig(
    QuicCryptoClientConfig* client_config) {
  quic_crypto_client_config_.reset(client_config);
}

void QuartcSession::SetServerCryptoConfig(
    QuicCryptoServerConfig* server_config) {
  quic_crypto_server_config_.reset(server_config);
}

QuicStream* QuartcSession::CreateIncomingDynamicStream(QuicStreamId id) {
  QuartcStream* stream = CreateDataStream(id, kDefaultPriority);
  if (stream) {
    DCHECK(session_delegate_);
    session_delegate_->OnIncomingStream(stream);
  }
  return stream;
}

QuartcStream* QuartcSession::CreateDataStream(QuicStreamId id,
                                              SpdyPriority priority) {
  if (crypto_stream_ == nullptr || !crypto_stream_->encryption_established()) {
    // Encryption not active so no stream created
    return nullptr;
  }
  QuartcStream* stream = new QuartcStream(id, this);
  if (stream) {
    // Make QuicSession take ownership of the stream.
    ActivateStream(std::unique_ptr<QuicStream>(stream));
    // Register the stream to the QuicWriteBlockedList. |priority| is clamped
    // between 0 and 7, with 0 being the highest priority and 7 the lowest
    // priority.
    write_blocked_streams()->RegisterStream(stream->id(), priority);
  }
  return stream;
}

}  // namespace net
