/*
 **
 ** Copyright 2020, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
 ** You may obtain a copy of the License at
 **
 **     http://www.apache.org/licenses/LICENSE-2.0
 **
 ** Unless required by applicable law or agreed to in writing, software
 ** distributed under the License is distributed on an "AS IS" BASIS,
 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 ** See the License for the specific language governing permissions and
 ** limitations under the License.
 */
/******************************************************************************
 *
 *  The original Work has been changed by NXP.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *  Copyright 2023 NXP
 *
 ******************************************************************************/
#pragma once
#include <aidl/android/hardware/security/keymint/Certificate.h>
#include <aidl/android/hardware/security/keymint/IKeyMintDevice.h>
#include <aidl/android/hardware/security/secureclock/TimeStampToken.h>
#include <aidl/android/hardware/security/sharedsecret/ISharedSecret.h>
#include <cppbor.h>
#include <cppbor_parse.h>
#include <iostream>
#include <keymaster/android_keymaster_messages.h>
#include <memory>
#include <numeric>
#include <vector>

namespace keymint::javacard {
using aidl::android::hardware::security::keymint::AttestationKey;
using aidl::android::hardware::security::keymint::Certificate;
using aidl::android::hardware::security::keymint::HardwareAuthToken;
using aidl::android::hardware::security::keymint::KeyCharacteristics;
using aidl::android::hardware::security::keymint::KeyParameter;
using aidl::android::hardware::security::secureclock::TimeStampToken;
using aidl::android::hardware::security::sharedsecret::SharedSecretParameters;
using cppbor::Array;
using cppbor::Bstr;
using cppbor::Item;
using cppbor::MajorType;
using cppbor::Map;
using cppbor::Nint;
using cppbor::parse;
using cppbor::Uint;
using std::string;
using std::unique_ptr;
using std::vector;

class CborConverter {
  public:
    CborConverter() = default;
    ~CborConverter() = default;
    std::tuple<std::unique_ptr<Item>, keymaster_error_t>
    decodeData(const std::vector<uint8_t>& response);

    template <typename T>
    bool getUint64(const std::unique_ptr<Item>& item, const uint32_t pos, T& value);

    template <typename T> bool getUint64(const std::unique_ptr<Item>& item, T& value);

    bool getSharedSecretParameters(const std::unique_ptr<Item>& item, const uint32_t pos,
                                   SharedSecretParameters& params);
    bool getBinaryArray(const std::unique_ptr<Item>& item, const uint32_t pos, string& value);

    bool getBinaryArray(const std::unique_ptr<Item>& item, const uint32_t pos,
                        vector<uint8_t>& value);

    bool getHardwareAuthToken(const std::unique_ptr<Item>& item, const uint32_t pos,
                              HardwareAuthToken& authType);

    bool getKeyParameters(const std::unique_ptr<Item>& item, const uint32_t pos,
                          vector<KeyParameter>& keyParams);

    bool addKeyparameters(Array& array, const vector<KeyParameter>& keyParams);

    bool addAttestationKey(Array& array, const std::optional<AttestationKey>& attestationKey);

    bool addHardwareAuthToken(Array& array, const HardwareAuthToken& authToken);

    bool addSharedSecretParameters(Array& array, const vector<SharedSecretParameters>& params);

    bool getTimeStampToken(const std::unique_ptr<Item>& item, const uint32_t pos,
                           TimeStampToken& token);

    bool getKeyCharacteristics(const std::unique_ptr<Item>& item, const uint32_t pos,
                               vector<KeyCharacteristics>& keyCharacteristics);

    bool getCertificateChain(const std::unique_ptr<Item>& item, const uint32_t pos,
                             vector<Certificate>& keyCharacteristics);

    bool getMultiBinaryArray(const std::unique_ptr<Item>& item, const uint32_t pos,
                             vector<vector<uint8_t>>& data);

    bool addTimeStampToken(Array& array, const TimeStampToken& token);

    bool getMapItem(const std::unique_ptr<Item>& item, const uint32_t pos,
                             Map& map);

    bool getArrayItem(const std::unique_ptr<Item>& item, const uint32_t pos,
                             Array& array);

    inline bool getErrorCode(const std::unique_ptr<Item>& item, const uint32_t pos,
                             keymaster_error_t& errorCode) {
        uint64_t errorVal;
        if (!getUint64<uint64_t>(item, pos, errorVal)) {
            return false;
        }
        errorCode = static_cast<keymaster_error_t>(0 - errorVal);
        return true;
    }

  private:
    /**
     * Returns the negative value of the same number.
     */
    inline int32_t get2sCompliment(uint32_t value) { return static_cast<int32_t>(~value + 1); }

    /**
     * Get the type of the Item pointer.
     */
    inline MajorType getType(const unique_ptr<Item>& item) { return item.get()->type(); }

    /**
     * Construct Keyparameter structure from the pair of key and value. If TagType is  ENUM_REP the
     * value contains binary string. If TagType is UINT_REP or ULONG_REP the value contains Array of
     * unsigned integers.
     */
    bool getKeyParameter(const std::pair<const unique_ptr<Item>&, const unique_ptr<Item>&> pair,
                         vector<KeyParameter>& keyParam);

    /**
     * Get the sub item pointer from the root item pointer at the given position.
     */
    inline void getItemAtPos(const unique_ptr<Item>& item, const uint32_t pos,
                             unique_ptr<Item>& subItem) {
        Array* arr = nullptr;

        if (MajorType::ARRAY != getType(item)) {
            return;
        }
        arr = const_cast<Array*>(item.get()->asArray());
        if (arr->size() < (pos + 1)) {
            return;
        }
        subItem = std::move((*arr)[pos]);
    }
};

template <typename T> bool CborConverter::getUint64(const unique_ptr<Item>& item, T& value) {
    bool ret = false;
    if ((item == nullptr) || (std::is_unsigned<T>::value && (MajorType::UINT != getType(item))) ||
        ((std::is_signed<T>::value && (MajorType::NINT != getType(item))))) {
        return ret;
    }

    if (std::is_unsigned<T>::value) {
        const Uint* uintVal = item.get()->asUint();
        value = static_cast<T>(uintVal->value());
    } else {
        const Nint* nintVal = item.get()->asNint();
        value = static_cast<T>(nintVal->value());
    }
    ret = true;
    return ret;  // success
}

template <typename T>
bool CborConverter::getUint64(const unique_ptr<Item>& item, const uint32_t pos, T& value) {
    unique_ptr<Item> intItem(nullptr);
    getItemAtPos(item, pos, intItem);
    return getUint64(intItem, value);
}
}  // namespace keymint::javacard
