/*
 * Copyright 2021, 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.
 */

#define LOG_TAG "PresentationSession"

#include "PresentationSession.h"
#include "IdentityCredentialStore.h"

#include <android/hardware/identity/support/IdentityCredentialSupport.h>

#include <string.h>

#include <android-base/logging.h>
#include <android-base/stringprintf.h>

#include <cppbor.h>
#include <cppbor_parse.h>

#include "FakeSecureHardwareProxy.h"
#include "IdentityCredential.h"
#include "PresentationSession.h"

namespace aidl::android::hardware::identity {

using ::std::optional;

using namespace ::android::hardware::identity;

PresentationSession::~PresentationSession() {}

int PresentationSession::initialize() {
    if (!hwProxy_->initialize()) {
        LOG(ERROR) << "hwProxy->initialize failed";
        return IIdentityCredentialStore::STATUS_FAILED;
    }

    optional<uint64_t> id = hwProxy_->getId();
    if (!id) {
        LOG(ERROR) << "Error getting id for session";
        return IIdentityCredentialStore::STATUS_FAILED;
    }
    id_ = id.value();

    optional<uint64_t> authChallenge = hwProxy_->getAuthChallenge();
    if (!authChallenge) {
        LOG(ERROR) << "Error getting authChallenge for session";
        return IIdentityCredentialStore::STATUS_FAILED;
    }
    authChallenge_ = authChallenge.value();

    return IIdentityCredentialStore::STATUS_OK;
}

ndk::ScopedAStatus PresentationSession::getEphemeralKeyPair(vector<uint8_t>* outKeyPair) {
    if (ephemeralKeyPair_.size() == 0) {
        optional<vector<uint8_t>> ephemeralKeyPriv = hwProxy_->getEphemeralKeyPair();
        if (!ephemeralKeyPriv) {
            LOG(ERROR) << "Error getting ephemeral private key for session";
            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
                    IIdentityCredentialStore::STATUS_FAILED,
                    "Error getting ephemeral private key for session"));
        }
        optional<vector<uint8_t>> ephemeralKeyPair =
                support::ecPrivateKeyToKeyPair(ephemeralKeyPriv.value());
        if (!ephemeralKeyPair) {
            LOG(ERROR) << "Error creating ephemeral key-pair";
            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
                    IIdentityCredentialStore::STATUS_FAILED, "Error creating ephemeral key-pair"));
        }
        ephemeralKeyPair_ = ephemeralKeyPair.value();
    }
    *outKeyPair = ephemeralKeyPair_;
    return ndk::ScopedAStatus::ok();
}

ndk::ScopedAStatus PresentationSession::getAuthChallenge(int64_t* outChallenge) {
    *outChallenge = authChallenge_;
    return ndk::ScopedAStatus::ok();
}

ndk::ScopedAStatus PresentationSession::setReaderEphemeralPublicKey(
        const vector<uint8_t>& publicKey) {
    // We expect the reader ephemeral public key to be same size and curve
    // as the ephemeral key we generated (e.g. P-256 key), otherwise ECDH
    // won't work. So its length should be 65 bytes and it should be
    // starting with 0x04.
    if (publicKey.size() != 65 || publicKey[0] != 0x04) {
        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
                IIdentityCredentialStore::STATUS_FAILED,
                "Reader public key is not in expected format"));
    }
    readerPublicKey_ = publicKey;
    vector<uint8_t> pubKeyP256(publicKey.begin() + 1, publicKey.end());
    if (!hwProxy_->setReaderEphemeralPublicKey(pubKeyP256)) {
        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
                IIdentityCredentialStore::STATUS_FAILED,
                "Error setting readerEphemeralPublicKey for session"));
    }
    return ndk::ScopedAStatus::ok();
}

ndk::ScopedAStatus PresentationSession::setSessionTranscript(
        const vector<uint8_t>& sessionTranscript) {
    sessionTranscript_ = sessionTranscript;
    if (!hwProxy_->setSessionTranscript(sessionTranscript)) {
        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
                IIdentityCredentialStore::STATUS_FAILED,
                "Error setting SessionTranscript for session"));
    }
    return ndk::ScopedAStatus::ok();
}

ndk::ScopedAStatus PresentationSession::getCredential(
        const vector<uint8_t>& credentialData, shared_ptr<IIdentityCredential>* outCredential) {
    shared_ptr<PresentationSession> p = ref<PresentationSession>();
    shared_ptr<IdentityCredential> credential = ndk::SharedRefBase::make<IdentityCredential>(
            hwProxyFactory_, credentialData, p, hardwareInformation_);
    int ret = credential->initialize();
    if (ret != IIdentityCredentialStore::STATUS_OK) {
        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
                ret, "Error initializing IdentityCredential"));
    }
    *outCredential = std::move(credential);

    return ndk::ScopedAStatus::ok();
}

uint64_t PresentationSession::getSessionId() {
    return id_;
}

vector<uint8_t> PresentationSession::getSessionTranscript() {
    return sessionTranscript_;
}

vector<uint8_t> PresentationSession::getReaderEphemeralPublicKey() {
    return readerPublicKey_;
}

}  // namespace aidl::android::hardware::identity