/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef mozilla_dom_WebAuthnResult_h_
#define mozilla_dom_WebAuthnResult_h_

#include "mozilla/Maybe.h"
#include "nsIWebAuthnResult.h"
#include "nsString.h"
#include "nsTArray.h"

#ifdef MOZ_WIDGET_ANDROID
#  include "JavaBuiltins.h"
#  include "mozilla/java/WebAuthnUtilsNatives.h"
#endif

#ifdef XP_WIN
#  include <windows.h>

#  include "mozilla/dom/PWebAuthnTransactionParent.h"
#  include "winwebauthn/webauthn.h"
#endif

namespace mozilla::dom {

class WebAuthnRegisterResult final : public nsIWebAuthnRegisterResult {
 public:
  NS_DECL_THREADSAFE_ISUPPORTS
  NS_DECL_NSIWEBAUTHNREGISTERRESULT

  WebAuthnRegisterResult(const nsTArray<uint8_t>& aAttestationObject,
                         const Maybe<nsCString>& aClientDataJSON,
                         const nsTArray<uint8_t>& aCredentialId,
                         const nsTArray<nsString>& aTransports,
                         const Maybe<nsString>& aAuthenticatorAttachment,
                         const Maybe<bool>& aLargeBlobSupported,
                         const Maybe<bool>& aPrfSupported,
                         const Maybe<nsTArray<uint8_t>>& aPrfFirst,
                         const Maybe<nsTArray<uint8_t>>& aPrfSecond)
      : mAttestationConsentPromptShown(false),
        mClientDataJSON(aClientDataJSON),
        mCredPropsRk(Nothing()),
        mAuthenticatorAttachment(aAuthenticatorAttachment),
        mLargeBlobSupported(aLargeBlobSupported),
        mPrfSupported(aPrfSupported) {
    mAttestationObject.AppendElements(aAttestationObject);
    mCredentialId.AppendElements(aCredentialId);
    mTransports.AppendElements(aTransports);
    if (aPrfFirst.isSome()) {
      mPrfFirst.emplace(aPrfFirst->Length());
      mPrfFirst->Assign(aPrfFirst.ref());
    }
    if (aPrfSecond.isSome()) {
      mPrfSecond.emplace(aPrfSecond->Length());
      mPrfSecond->Assign(aPrfSecond.ref());
    }
  }

#ifdef MOZ_WIDGET_ANDROID
  explicit WebAuthnRegisterResult(
      const java::WebAuthnUtils::MakeCredentialResponse::LocalRef& aResponse) {
    mAttestationObject.AppendElements(
        reinterpret_cast<uint8_t*>(
            aResponse->AttestationObject()->GetElements().Elements()),
        aResponse->AttestationObject()->Length());
    mAttestationConsentPromptShown = false;
    if (aResponse->ClientDataJson()) {
      mClientDataJSON = Some(nsAutoCString(
          reinterpret_cast<const char*>(
              aResponse->ClientDataJson()->GetElements().Elements()),
          aResponse->ClientDataJson()->Length()));
    }
    mCredentialId.AppendElements(
        reinterpret_cast<uint8_t*>(
            aResponse->KeyHandle()->GetElements().Elements()),
        aResponse->KeyHandle()->Length());
    auto transports = aResponse->Transports();
    for (size_t i = 0; i < transports->Length(); i++) {
      mTransports.AppendElement(
          jni::String::LocalRef(transports->GetElement(i))->ToString());
    }
    if (aResponse->AuthenticatorAttachment()) {
      mAuthenticatorAttachment =
          Some(aResponse->AuthenticatorAttachment()->ToString());
    }
    if (aResponse->CredProps()) {
      mCredPropsRk = Some(java::sdk::Boolean::Ref::From(aResponse->CredProps())
                              ->BooleanValue());
    }
    if (aResponse->PrfEnabled()) {
      mPrfSupported =
          Some(java::sdk::Boolean::Ref::From(aResponse->PrfEnabled())
                   ->BooleanValue());
    }
    if (aResponse->PrfFirst() && aResponse->PrfFirst()->Length() > 0) {
      mPrfFirst.emplace();
      mPrfFirst->AppendElements(
          reinterpret_cast<uint8_t*>(
              aResponse->PrfFirst()->GetElements().Elements()),
          aResponse->PrfFirst()->Length());
    }
    if (aResponse->PrfSecond() && aResponse->PrfSecond()->Length() > 0) {
      mPrfSecond.emplace();
      mPrfSecond->AppendElements(
          reinterpret_cast<uint8_t*>(
              aResponse->PrfSecond()->GetElements().Elements()),
          aResponse->PrfSecond()->Length());
    }
    if (aResponse->LargeBlobSupported()) {
      mLargeBlobSupported =
          Some(java::sdk::Boolean::Ref::From(aResponse->LargeBlobSupported())
                   ->BooleanValue());
    }
  }
#endif

#ifdef XP_WIN
  WebAuthnRegisterResult(nsCString& aClientDataJSON,
                         PCWEBAUTHN_CREDENTIAL_ATTESTATION aResponse)
      : mClientDataJSON(Some(aClientDataJSON)) {
    mCredentialId.AppendElements(aResponse->pbCredentialId,
                                 aResponse->cbCredentialId);

    mAttestationObject.AppendElements(aResponse->pbAttestationObject,
                                      aResponse->cbAttestationObject);
    mAttestationConsentPromptShown = true;

    nsTArray<WebAuthnExtensionResult> extensions;
    if (aResponse->dwVersion >= WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_2) {
      PCWEBAUTHN_EXTENSIONS pExtensionList = &aResponse->Extensions;
      if (pExtensionList->cExtensions != 0 &&
          pExtensionList->pExtensions != NULL) {
        for (DWORD dwIndex = 0; dwIndex < pExtensionList->cExtensions;
             dwIndex++) {
          PWEBAUTHN_EXTENSION pExtension =
              &pExtensionList->pExtensions[dwIndex];
          if (pExtension->pwszExtensionIdentifier &&
              (0 == _wcsicmp(pExtension->pwszExtensionIdentifier,
                             WEBAUTHN_EXTENSIONS_IDENTIFIER_HMAC_SECRET)) &&
              pExtension->cbExtension == sizeof(BOOL)) {
            BOOL* pCredentialCreatedWithHmacSecret =
                (BOOL*)pExtension->pvExtension;
            if (*pCredentialCreatedWithHmacSecret) {
              mHmacCreateSecret = Some(true);
              mPrfSupported = Some(true);
            }
          }
        }
      }
    }

    if (aResponse->dwVersion >= WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_7) {
      if (aResponse->pHmacSecret) {
        if (aResponse->pHmacSecret->cbFirst > 0) {
          mPrfFirst.emplace();
          mPrfFirst->AppendElements(aResponse->pHmacSecret->pbFirst,
                                    aResponse->pHmacSecret->cbFirst);
        }
        if (aResponse->pHmacSecret->cbSecond > 0) {
          mPrfSecond.emplace();
          mPrfSecond->AppendElements(aResponse->pHmacSecret->pbSecond,
                                     aResponse->pHmacSecret->cbSecond);
        }
      }
    }

    DWORD transports = 0;
    if (aResponse->dwVersion >= WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_8) {
      // The dwTransports field added in version 8 lists all supported
      // transports, whereas the dwUsedTransport available since version 3 only
      // returns the transport that was used.
      transports = aResponse->dwTransports;
    } else if (aResponse->dwVersion >=
               WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_3) {
      transports = aResponse->dwUsedTransport;
    }

    if (transports & WEBAUTHN_CTAP_TRANSPORT_USB) {
      mTransports.AppendElement(u"usb"_ns);
    }
    if (transports & WEBAUTHN_CTAP_TRANSPORT_NFC) {
      mTransports.AppendElement(u"nfc"_ns);
    }
    if (transports & WEBAUTHN_CTAP_TRANSPORT_BLE) {
      mTransports.AppendElement(u"ble"_ns);
    }
    if (transports & WEBAUTHN_CTAP_TRANSPORT_INTERNAL) {
      mTransports.AppendElement(u"internal"_ns);
    }
    // WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_5 corresponds to
    // WEBAUTHN_API_VERSION_6 which is where WEBAUTHN_CTAP_TRANSPORT_HYBRID was
    // defined.
    if (aResponse->dwVersion >= WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_5) {
      if (transports & WEBAUTHN_CTAP_TRANSPORT_HYBRID) {
        mTransports.AppendElement(u"hybrid"_ns);
      }
    }

    if (aResponse->dwVersion >= WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_3) {
      if (aResponse->dwUsedTransport & WEBAUTHN_CTAP_TRANSPORT_INTERNAL) {
        mAuthenticatorAttachment = Some(u"platform"_ns);
      } else {
        mAuthenticatorAttachment = Some(u"cross-platform"_ns);
      }
    }

    if (aResponse->dwVersion >= WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_4) {
      if (aResponse->bLargeBlobSupported) {
        mLargeBlobSupported = Some(true);
      }
    }

    if (aResponse->dwVersion >= WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_5) {
      if (aResponse->bPrfEnabled) {
        mPrfSupported = Some(true);
      }
    }
  }
#endif

 private:
  ~WebAuthnRegisterResult() = default;

  nsTArray<uint8_t> mAttestationObject;
  bool mAttestationConsentPromptShown;
  nsTArray<uint8_t> mCredentialId;
  nsTArray<nsString> mTransports;
  Maybe<nsCString> mClientDataJSON;
  Maybe<bool> mCredPropsRk;
  Maybe<bool> mHmacCreateSecret;
  Maybe<nsString> mAuthenticatorAttachment;
  Maybe<bool> mLargeBlobSupported;
  Maybe<bool> mPrfSupported;
  Maybe<nsTArray<uint8_t>> mPrfFirst;
  Maybe<nsTArray<uint8_t>> mPrfSecond;
};

class WebAuthnSignResult final : public nsIWebAuthnSignResult {
 public:
  NS_DECL_THREADSAFE_ISUPPORTS
  NS_DECL_NSIWEBAUTHNSIGNRESULT

  WebAuthnSignResult(const nsTArray<uint8_t>& aAuthenticatorData,
                     const Maybe<nsCString>& aClientDataJSON,
                     const nsTArray<uint8_t>& aCredentialId,
                     const nsTArray<uint8_t>& aSignature,
                     const nsTArray<uint8_t>& aUserHandle,
                     const Maybe<nsString>& aAuthenticatorAttachment,
                     const Maybe<bool>& aUsedAppId,
                     const Maybe<nsTArray<uint8_t>>& aLargeBlobValue,
                     const Maybe<bool>& aLargeBlobWritten,
                     const Maybe<nsTArray<uint8_t>>& aPrfFirst,
                     const Maybe<nsTArray<uint8_t>>& aPrfSecond)
      : mClientDataJSON(aClientDataJSON),
        mAuthenticatorAttachment(aAuthenticatorAttachment),
        mUsedAppId(aUsedAppId),
        mLargeBlobWritten(aLargeBlobWritten) {
    mAuthenticatorData.AppendElements(aAuthenticatorData);
    mCredentialId.AppendElements(aCredentialId);
    mSignature.AppendElements(aSignature);
    mUserHandle.AppendElements(aUserHandle);
    if (aLargeBlobValue.isSome()) {
      mLargeBlobValue.emplace(aLargeBlobValue->Length());
      mLargeBlobValue->Assign(aLargeBlobValue.ref());
    }
    if (aPrfFirst.isSome()) {
      mPrfFirst.emplace(aPrfFirst.ref().Length());
      mPrfFirst->Assign(aPrfFirst.ref());
    }
    if (aPrfSecond.isSome()) {
      mPrfSecond.emplace(aPrfSecond.ref().Length());
      mPrfSecond->Assign(aPrfSecond.ref());
    }
  }

#ifdef MOZ_WIDGET_ANDROID
  explicit WebAuthnSignResult(
      const java::WebAuthnUtils::GetAssertionResponse::LocalRef& aResponse) {
    mAuthenticatorData.AppendElements(
        reinterpret_cast<uint8_t*>(
            aResponse->AuthData()->GetElements().Elements()),
        aResponse->AuthData()->Length());
    if (aResponse->ClientDataJson()) {
      mClientDataJSON = Some(nsAutoCString(
          reinterpret_cast<const char*>(
              aResponse->ClientDataJson()->GetElements().Elements()),
          aResponse->ClientDataJson()->Length()));
    }
    mCredentialId.AppendElements(
        reinterpret_cast<uint8_t*>(
            aResponse->KeyHandle()->GetElements().Elements()),
        aResponse->KeyHandle()->Length());
    mSignature.AppendElements(
        reinterpret_cast<uint8_t*>(
            aResponse->Signature()->GetElements().Elements()),
        aResponse->Signature()->Length());
    mUserHandle.AppendElements(
        reinterpret_cast<uint8_t*>(
            aResponse->UserHandle()->GetElements().Elements()),
        aResponse->UserHandle()->Length());
    if (aResponse->AuthenticatorAttachment()) {
      mAuthenticatorAttachment =
          Some(aResponse->AuthenticatorAttachment()->ToString());
    }
    if (aResponse->PrfFirst() && aResponse->PrfFirst()->Length() > 0) {
      mPrfFirst.emplace();
      mPrfFirst->AppendElements(
          reinterpret_cast<uint8_t*>(
              aResponse->PrfFirst()->GetElements().Elements()),
          aResponse->PrfFirst()->Length());
    }
    if (aResponse->PrfSecond() && aResponse->PrfSecond()->Length() > 0) {
      mPrfSecond.emplace();
      mPrfSecond->AppendElements(
          reinterpret_cast<uint8_t*>(
              aResponse->PrfSecond()->GetElements().Elements()),
          aResponse->PrfSecond()->Length());
    }
    if (aResponse->LargeBlobBlob() &&
        aResponse->LargeBlobBlob()->Length() > 0) {
      mLargeBlobValue.emplace();
      mLargeBlobValue->AppendElements(
          reinterpret_cast<uint8_t*>(
              aResponse->LargeBlobBlob()->GetElements().Elements()),
          aResponse->LargeBlobBlob()->Length());
    }
    if (aResponse->LargeBlobWritten()) {
      mLargeBlobWritten =
          Some(java::sdk::Boolean::Ref::From(aResponse->LargeBlobWritten())
                   ->BooleanValue());
    }
  }
#endif

#ifdef XP_WIN
  WebAuthnSignResult(nsCString& aClientDataJSON, DWORD aCredLargeBlobOperation,
                     PCWEBAUTHN_ASSERTION aResponse)
      : mClientDataJSON(Some(aClientDataJSON)) {
    mSignature.AppendElements(aResponse->pbSignature, aResponse->cbSignature);

    mCredentialId.AppendElements(aResponse->Credential.pbId,
                                 aResponse->Credential.cbId);

    mUserHandle.AppendElements(aResponse->pbUserId, aResponse->cbUserId);

    mAuthenticatorData.AppendElements(aResponse->pbAuthenticatorData,
                                      aResponse->cbAuthenticatorData);

    mAuthenticatorAttachment = Nothing();  // not available

    if (aCredLargeBlobOperation == WEBAUTHN_CRED_LARGE_BLOB_OPERATION_GET) {
      if (aResponse->dwVersion >= WEBAUTHN_ASSERTION_VERSION_2 &&
          aResponse->dwCredLargeBlobStatus ==
              WEBAUTHN_CRED_LARGE_BLOB_STATUS_SUCCESS) {
        mLargeBlobValue.emplace();
        mLargeBlobValue->AppendElements(aResponse->pbCredLargeBlob,
                                        aResponse->cbCredLargeBlob);
      }
    } else if (aCredLargeBlobOperation ==
               WEBAUTHN_CRED_LARGE_BLOB_OPERATION_SET) {
      if (aResponse->dwVersion >= WEBAUTHN_ASSERTION_VERSION_2 &&
          aResponse->dwCredLargeBlobStatus ==
              WEBAUTHN_CRED_LARGE_BLOB_STATUS_SUCCESS) {
        mLargeBlobWritten.emplace(true);
      } else {
        mLargeBlobWritten.emplace(false);
      }
    }

    if (aResponse->dwVersion >= WEBAUTHN_ASSERTION_VERSION_3) {
      if (aResponse->pHmacSecret) {
        if (aResponse->pHmacSecret->cbFirst > 0) {
          mPrfFirst.emplace();
          mPrfFirst->AppendElements(aResponse->pHmacSecret->pbFirst,
                                    aResponse->pHmacSecret->cbFirst);
        }
        if (aResponse->pHmacSecret->cbSecond > 0) {
          mPrfSecond.emplace();
          mPrfSecond->AppendElements(aResponse->pHmacSecret->pbSecond,
                                     aResponse->pHmacSecret->cbSecond);
        }
      }
    }
  }
#endif

 private:
  ~WebAuthnSignResult() = default;

  nsTArray<uint8_t> mAuthenticatorData;
  Maybe<nsCString> mClientDataJSON;
  nsTArray<uint8_t> mCredentialId;
  nsTArray<uint8_t> mSignature;
  nsTArray<uint8_t> mUserHandle;
  Maybe<nsString> mAuthenticatorAttachment;
  Maybe<bool> mUsedAppId;
  Maybe<nsTArray<uint8_t>> mLargeBlobValue;
  Maybe<bool> mLargeBlobWritten;
  Maybe<nsTArray<uint8_t>> mPrfFirst;
  Maybe<nsTArray<uint8_t>> mPrfSecond;
};

}  // namespace mozilla::dom
#endif  // mozilla_dom_WebAuthnResult_h
