// Copyright (c) 2008 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/base/cert_verifier.h"

#if defined(USE_NSS)
#include <private/pprthred.h>  // PR_DetatchThread
#endif

#include "base/message_loop.h"
#include "base/worker_pool.h"
#include "net/base/cert_verify_result.h"
#include "net/base/net_errors.h"
#include "net/base/x509_certificate.h"

namespace net {

class CertVerifier::Request :
    public base::RefCountedThreadSafe<CertVerifier::Request> {
 public:
  Request(CertVerifier* verifier,
          X509Certificate* cert,
          const std::string& hostname,
          int flags,
          CertVerifyResult* verify_result,
          CompletionCallback* callback)
      : cert_(cert),
        hostname_(hostname),
        flags_(flags),
        verifier_(verifier),
        verify_result_(verify_result),
        callback_(callback),
        origin_loop_(MessageLoop::current()),
        error_(OK) {
  }

  void DoVerify() {
    // Running on the worker thread
    error_ = cert_->Verify(hostname_, flags_, &result_);
#if defined(USE_NSS)
    // Detach the thread from NSPR.
    // Calling NSS functions attaches the thread to NSPR, which stores
    // the NSPR thread ID in thread-specific data.
    // The threads in our thread pool terminate after we have called
    // PR_Cleanup.  Unless we detach them from NSPR, net_unittests gets
    // segfaults on shutdown when the threads' thread-specific data
    // destructors run.
    PR_DetachThread();
#endif

    Task* reply = NewRunnableMethod(this, &Request::DoCallback);

    // The origin loop could go away while we are trying to post to it, so we
    // need to call its PostTask method inside a lock.  See ~CertVerifier.
    {
      AutoLock locked(origin_loop_lock_);
      if (origin_loop_) {
        origin_loop_->PostTask(FROM_HERE, reply);
        reply = NULL;
      }
    }

    // Does nothing if it got posted.
    delete reply;
  }

  void DoCallback() {
    // Running on the origin thread.

    // We may have been cancelled!
    if (!verifier_)
      return;

    *verify_result_ = result_;

    // Drop the verifier's reference to us.  Do this before running the
    // callback since the callback might result in the verifier being
    // destroyed.
    verifier_->request_ = NULL;

    callback_->Run(error_);
  }

  void Cancel() {
    verifier_ = NULL;

    AutoLock locked(origin_loop_lock_);
    origin_loop_ = NULL;
  }

 private:
  friend class base::RefCountedThreadSafe<CertVerifier::Request>;

  ~Request() {}

  // Set on the origin thread, read on the worker thread.
  scoped_refptr<X509Certificate> cert_;
  std::string hostname_;
  // bitwise OR'd of X509Certificate::VerifyFlags.
  int flags_;

  // Only used on the origin thread (where Verify was called).
  CertVerifier* verifier_;
  CertVerifyResult* verify_result_;
  CompletionCallback* callback_;

  // Used to post ourselves onto the origin thread.
  Lock origin_loop_lock_;
  MessageLoop* origin_loop_;

  // Assigned on the worker thread, read on the origin thread.
  int error_;
  CertVerifyResult result_;
};

//-----------------------------------------------------------------------------

CertVerifier::CertVerifier() {
}

CertVerifier::~CertVerifier() {
  if (request_)
    request_->Cancel();
}

int CertVerifier::Verify(X509Certificate* cert,
                         const std::string& hostname,
                         int flags,
                         CertVerifyResult* verify_result,
                         CompletionCallback* callback) {
  DCHECK(!request_) << "verifier already in use";

  // Do a synchronous verification.
  if (!callback) {
    CertVerifyResult result;
    int rv = cert->Verify(hostname, flags, &result);
    *verify_result = result;
    return rv;
  }

  request_ = new Request(this, cert, hostname, flags, verify_result, callback);

  // Dispatch to worker thread...
  if (!WorkerPool::PostTask(FROM_HERE,
          NewRunnableMethod(request_.get(), &Request::DoVerify), true)) {
    NOTREACHED();
    request_ = NULL;
    return ERR_FAILED;
  }

  return ERR_IO_PENDING;
}

}  // namespace net
