/*
 *  Copyright 2018 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#include "pc/dtls_transport.h"

#include <utility>

#include "pc/ice_transport.h"

namespace webrtc {

namespace {

DtlsTransportState TranslateState(cricket::DtlsTransportState internal_state) {
  switch (internal_state) {
    case cricket::DTLS_TRANSPORT_NEW:
      return DtlsTransportState::kNew;
    case cricket::DTLS_TRANSPORT_CONNECTING:
      return DtlsTransportState::kConnecting;
    case cricket::DTLS_TRANSPORT_CONNECTED:
      return DtlsTransportState::kConnected;
    case cricket::DTLS_TRANSPORT_CLOSED:
      return DtlsTransportState::kClosed;
    case cricket::DTLS_TRANSPORT_FAILED:
      return DtlsTransportState::kFailed;
  }
}

}  // namespace

// Implementation of DtlsTransportInterface
DtlsTransport::DtlsTransport(
    std::unique_ptr<cricket::DtlsTransportInternal> internal)
    : owner_thread_(rtc::Thread::Current()),
      info_(DtlsTransportState::kNew),
      internal_dtls_transport_(std::move(internal)),
      ice_transport_(new rtc::RefCountedObject<IceTransportWithPointer>(
          internal_dtls_transport_->ice_transport())) {
  RTC_DCHECK(internal_dtls_transport_.get());
  internal_dtls_transport_->SignalDtlsState.connect(
      this, &DtlsTransport::OnInternalDtlsState);
  UpdateInformation();
}

DtlsTransport::~DtlsTransport() {
  // We depend on the signaling thread to call Clear() before dropping
  // its last reference to this object.
  RTC_DCHECK(owner_thread_->IsCurrent() || !internal_dtls_transport_);
}

DtlsTransportInformation DtlsTransport::Information() {
  MutexLock lock(&lock_);
  return info_;
}

void DtlsTransport::RegisterObserver(DtlsTransportObserverInterface* observer) {
  RTC_DCHECK_RUN_ON(owner_thread_);
  RTC_DCHECK(observer);
  observer_ = observer;
}

void DtlsTransport::UnregisterObserver() {
  RTC_DCHECK_RUN_ON(owner_thread_);
  observer_ = nullptr;
}

rtc::scoped_refptr<IceTransportInterface> DtlsTransport::ice_transport() {
  return ice_transport_;
}

// Internal functions
void DtlsTransport::Clear() {
  RTC_DCHECK_RUN_ON(owner_thread_);
  RTC_DCHECK(internal());
  bool must_send_event =
      (internal()->dtls_state() != cricket::DTLS_TRANSPORT_CLOSED);
  // The destructor of cricket::DtlsTransportInternal calls back
  // into DtlsTransport, so we can't hold the lock while releasing.
  std::unique_ptr<cricket::DtlsTransportInternal> transport_to_release;
  {
    MutexLock lock(&lock_);
    transport_to_release = std::move(internal_dtls_transport_);
    ice_transport_->Clear();
  }
  UpdateInformation();
  if (observer_ && must_send_event) {
    observer_->OnStateChange(Information());
  }
}

void DtlsTransport::OnInternalDtlsState(
    cricket::DtlsTransportInternal* transport,
    cricket::DtlsTransportState state) {
  RTC_DCHECK_RUN_ON(owner_thread_);
  RTC_DCHECK(transport == internal());
  RTC_DCHECK(state == internal()->dtls_state());
  UpdateInformation();
  if (observer_) {
    observer_->OnStateChange(Information());
  }
}

void DtlsTransport::UpdateInformation() {
  RTC_DCHECK_RUN_ON(owner_thread_);
  MutexLock lock(&lock_);
  if (internal_dtls_transport_) {
    if (internal_dtls_transport_->dtls_state() ==
        cricket::DTLS_TRANSPORT_CONNECTED) {
      bool success = true;
      int ssl_cipher_suite;
      int tls_version;
      int srtp_cipher;
      success &= internal_dtls_transport_->GetSslVersionBytes(&tls_version);
      success &= internal_dtls_transport_->GetSslCipherSuite(&ssl_cipher_suite);
      success &= internal_dtls_transport_->GetSrtpCryptoSuite(&srtp_cipher);
      if (success) {
        info_ = DtlsTransportInformation(
            TranslateState(internal_dtls_transport_->dtls_state()), tls_version,
            ssl_cipher_suite, srtp_cipher,
            internal_dtls_transport_->GetRemoteSSLCertChain());
      } else {
        RTC_LOG(LS_ERROR) << "DtlsTransport in connected state has incomplete "
                             "TLS information";
        info_ = DtlsTransportInformation(
            TranslateState(internal_dtls_transport_->dtls_state()),
            absl::nullopt, absl::nullopt, absl::nullopt,
            internal_dtls_transport_->GetRemoteSSLCertChain());
      }
    } else {
      info_ = DtlsTransportInformation(
          TranslateState(internal_dtls_transport_->dtls_state()));
    }
  } else {
    info_ = DtlsTransportInformation(DtlsTransportState::kClosed);
  }
}

}  // namespace webrtc
