/*
 * Copyright (C) 2018 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 "KeystoreOperation"

#include "operation_proto_handler.h"

#include <android/os/DropBoxManager.h>
#include <google/protobuf/message_lite.h>
#include <keymasterV4_0/Keymaster.h>
#include <keystore/keymaster_types.h>
#include <keystore/keystore_hidl_support.h>
#include <utils/String16.h>
#include <utils/StrongPointer.h>

using namespace std::chrono;

namespace keystore {

constexpr auto kCollectionTime = 1h;

void determinePurpose(KeyPurpose purpose, OperationConfig* operationConfig) {
    switch (purpose) {
    case KeyPurpose::VERIFY:
        operationConfig->set_purpose("verify");
        break;
    case KeyPurpose::ENCRYPT:
        operationConfig->set_purpose("encrypt");
        break;
    case KeyPurpose::SIGN:
        operationConfig->set_purpose("sign");
        break;
    case KeyPurpose::DECRYPT:
        operationConfig->set_purpose("decrypt");
        break;
    case KeyPurpose::WRAP_KEY:
        operationConfig->set_purpose("wrap");
        break;
    default:
        break;
    }
}

void checkKeyCharacteristics(const hidl_vec<KeyParameter>& characteristics,
                             OperationConfig* operationConfig) {
    for (auto& opParam : characteristics) {
        switch (opParam.tag) {
        case Tag::ALGORITHM:
            operationConfig->set_algorithm(toString(accessTagValue(TAG_ALGORITHM, opParam)));
            break;
        case Tag::KEY_SIZE:
            operationConfig->set_key_size(accessTagValue(TAG_KEY_SIZE, opParam));
            break;
        case Tag::EC_CURVE:
            operationConfig->set_ec_curve(toString(accessTagValue(TAG_EC_CURVE, opParam)));
            break;
        case Tag::AUTH_TIMEOUT:
            operationConfig->set_user_auth_key_timeout(accessTagValue(TAG_AUTH_TIMEOUT, opParam));
            break;
        case Tag::ORIGIN:
            operationConfig->set_origin(toString(accessTagValue(TAG_ORIGIN, opParam)));
            break;
        case Tag::BLOB_USAGE_REQUIREMENTS:
            operationConfig->set_key_blob_usage_reqs(
                toString(accessTagValue(TAG_BLOB_USAGE_REQUIREMENTS, opParam)));
            break;
        case Tag::USER_AUTH_TYPE:
            operationConfig->set_user_auth_type(
                toString(accessTagValue(TAG_USER_AUTH_TYPE, opParam)));
            break;
        default:
            break;
        }
    }
}

void checkOpCharacteristics(const hidl_vec<KeyParameter>& characteristics,
                            OperationConfig* operationConfig) {
    for (auto& opParam : characteristics) {
        switch (opParam.tag) {
        case Tag::BLOCK_MODE:
            operationConfig->set_block_mode(toString(accessTagValue(TAG_BLOCK_MODE, opParam)));
            break;
        case Tag::PADDING:
            operationConfig->set_padding(toString(accessTagValue(TAG_PADDING, opParam)));
            break;
        case Tag::DIGEST:
            operationConfig->set_digest(toString(accessTagValue(TAG_DIGEST, opParam)));
            break;
        default:
            break;
        }
    }
}

void OperationProtoHandler::uploadOpAsProto(Operation& op, bool wasOpSuccessful) {
    std::lock_guard<std::mutex> lock(op_upload_mutex);
    OperationConfig operationConfig;
    determinePurpose(op.purpose, &operationConfig);
    checkKeyCharacteristics(op.characteristics.softwareEnforced, &operationConfig);
    checkKeyCharacteristics(op.characteristics.hardwareEnforced, &operationConfig);
    checkOpCharacteristics(op.params, &operationConfig);
    operationConfig.set_was_op_successful(wasOpSuccessful);
    // Only bother with counting an hour out when an operation entry is actually
    // added
    if (protoMap.empty()) {
        start_time = std::chrono::steady_clock::now();
    }
    auto cur_time = std::chrono::steady_clock::now();

    // Add operations to a map within the time duration of an hour. Deduplicate
    // repeated ops by incrementing the counter of the original one stored and
    // discarding the new one.
    protoMap[operationConfig.SerializeAsString()]++;

    if (cur_time - start_time >= kCollectionTime) {
        // Iterate through the unordered map and dump all the operation protos
        // accumulated over the hour into the holding list proto after setting
        // their counts.
        OperationConfigEvents opConfigEvents;
        for (auto elem : protoMap) {
            OperationConfigEvent* event = opConfigEvents.add_op_config_events();
            event->mutable_op_config()->ParseFromString(elem.first);
            event->set_count(elem.second);
        }
        android::sp<android::os::DropBoxManager> dropbox(new android::os::DropBoxManager);
        size_t size = opConfigEvents.ByteSize();
        auto data = std::make_unique<uint8_t[]>(size);
        opConfigEvents.SerializeWithCachedSizesToArray(data.get());
        dropbox->addData(android::String16("keymaster"), data.get(), size, 0);
        protoMap.clear();
    }
}

}  // namespace keystore