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

#include "otapreopt_parameters.h"

#include <android-base/logging.h>

#include "dexopt.h"
#include "installd_constants.h"
#include "otapreopt_utils.h"

#ifndef LOG_TAG
#define LOG_TAG "otapreopt"
#endif

namespace android {
namespace installd {

static bool ParseBool(const char* in) {
    if (strcmp(in, "true") == 0) {
        return true;
    }
    return false;
}

static const char* ParseNull(const char* arg) {
    return (strcmp(arg, "!") == 0) ? nullptr : arg;
}

static bool ParseUInt(const char* in, uint32_t* out) {
    char* end;
    long long int result = strtoll(in, &end, 0);
    if (in == end || *end != '\0') {
        return false;
    }
    if (result < std::numeric_limits<uint32_t>::min() ||
            std::numeric_limits<uint32_t>::max() < result) {
        return false;
    }
    *out = static_cast<uint32_t>(result);
    return true;
}

bool OTAPreoptParameters::ReadArguments(int argc, const char** argv) {
    // Expected command line:
    //   target-slot [version] dexopt {DEXOPT_PARAMETERS}

    const char* target_slot_arg = argv[1];
    if (target_slot_arg == nullptr) {
        LOG(ERROR) << "Missing parameters";
        return false;
    }
    // Sanitize value. Only allow (a-zA-Z0-9_)+.
    target_slot = target_slot_arg;
    if (!ValidateTargetSlotSuffix(target_slot)) {
        LOG(ERROR) << "Target slot suffix not legal: " << target_slot;
        return false;
    }

    // Check for version or "dexopt" next.
    if (argv[2] == nullptr) {
        LOG(ERROR) << "Missing parameters";
        return false;
    }

    if (std::string("dexopt").compare(argv[2]) == 0) {
        // This is version 1 (N) or pre-versioning version 2.
        constexpr int kV2ArgCount =   1   // "otapreopt"
                                    + 1   // slot
                                    + 1   // "dexopt"
                                    + 1   // apk_path
                                    + 1   // uid
                                    + 1   // pkg
                                    + 1   // isa
                                    + 1   // dexopt_needed
                                    + 1   // oat_dir
                                    + 1   // dexopt_flags
                                    + 1   // filter
                                    + 1   // volume
                                    + 1   // libs
                                    + 1;  // seinfo
        if (argc == kV2ArgCount) {
            return ReadArgumentsPostV1(2, argv, false);
        } else {
            return ReadArgumentsV1(argv);
        }
    }

    uint32_t version;
    if (!ParseUInt(argv[2], &version)) {
        LOG(ERROR) << "Could not parse version: " << argv[2];
        return false;
    }

    return ReadArgumentsPostV1(version, argv, true);
}

static int ReplaceMask(int input, int old_mask, int new_mask) {
    return (input & old_mask) != 0 ? new_mask : 0;
}

void OTAPreoptParameters::SetDefaultsForPostV1Arguments() {
    // Set se_info to null. It is only relevant for secondary dex files, which we won't
    // receive from a v1 A side.
    se_info = nullptr;

    // Set downgrade to false. It is only relevant when downgrading compiler
    // filter, which is not the case during ota.
    downgrade = false;

    // Set target_sdk_version to 0, ie the platform SDK version. This is
    // conservative and may force some classes to verify at runtime.
    target_sdk_version = 0;

    // Set the profile name to the primary apk profile.
    profile_name = "primary.prof";

    // By default we don't have a dex metadata file.
    dex_metadata_path = nullptr;

    // The compilation reason is ab-ota (match the system property pm.dexopt.ab-ota)
    compilation_reason = "ab-ota";

    // Flag is enabled by default for A/B otas.
    dexopt_flags = DEXOPT_GENERATE_COMPACT_DEX;
}

bool OTAPreoptParameters::ReadArgumentsV1(const char** argv) {
    // Check for "dexopt".
    if (argv[2] == nullptr) {
        LOG(ERROR) << "Missing parameters";
        return false;
    }
    if (std::string("dexopt").compare(argv[2]) != 0) {
        LOG(ERROR) << "Expected \"dexopt\" but found: " << argv[2];
        return false;
    }

    SetDefaultsForPostV1Arguments();

    size_t param_index = 0;
    for (;; ++param_index) {
        const char* param = argv[3 + param_index];
        if (param == nullptr) {
            break;
        }

        switch (param_index) {
            case 0:
                apk_path = param;
                break;

            case 1:
                uid = atoi(param);
                break;

            case 2:
                pkgName = param;
                break;

            case 3:
                instruction_set = param;
                break;

            case 4: {
                // Version 1 had:
                //   DEXOPT_DEX2OAT_NEEDED       = 1
                //   DEXOPT_PATCHOAT_NEEDED      = 2
                //   DEXOPT_SELF_PATCHOAT_NEEDED = 3
                // We will simply use DEX2OAT_FROM_SCRATCH.
                dexopt_needed = DEX2OAT_FROM_SCRATCH;
                break;
            }

            case 5:
                oat_dir = param;
                break;

            case 6: {
                // Version 1 had:
                constexpr int OLD_DEXOPT_PUBLIC         = 1 << 1;
                // Note: DEXOPT_SAFEMODE has been removed.
                // constexpr int OLD_DEXOPT_SAFEMODE       = 1 << 2;
                constexpr int OLD_DEXOPT_DEBUGGABLE     = 1 << 3;
                constexpr int OLD_DEXOPT_BOOTCOMPLETE   = 1 << 4;
                constexpr int OLD_DEXOPT_PROFILE_GUIDED = 1 << 5;
                constexpr int OLD_DEXOPT_OTA            = 1 << 6;
                static_assert(DEXOPT_GENERATE_COMPACT_DEX > OLD_DEXOPT_OTA, "must not overlap");
                int input = atoi(param);
                dexopt_flags |=
                        ReplaceMask(input, OLD_DEXOPT_PUBLIC, DEXOPT_PUBLIC) |
                        ReplaceMask(input, OLD_DEXOPT_DEBUGGABLE, DEXOPT_DEBUGGABLE) |
                        ReplaceMask(input, OLD_DEXOPT_BOOTCOMPLETE, DEXOPT_BOOTCOMPLETE) |
                        ReplaceMask(input, OLD_DEXOPT_PROFILE_GUIDED, DEXOPT_PROFILE_GUIDED) |
                        ReplaceMask(input, OLD_DEXOPT_OTA, 0);
                break;
            }

            case 7:
                compiler_filter = param;
                break;

            case 8:
                volume_uuid = ParseNull(param);
                break;

            case 9:
                shared_libraries = ParseNull(param);
                break;

            default:
                LOG(ERROR) << "Too many arguments, got " << param;
                return false;
        }
    }

    if (param_index != 10) {
        LOG(ERROR) << "Not enough parameters";
        return false;
    }

    return true;
}

bool OTAPreoptParameters::ReadArgumentsPostV1(uint32_t version, const char** argv, bool versioned) {
    size_t num_args_expected = 0;
    switch (version) {
        case 2: num_args_expected = 11; break;
        case 3: num_args_expected = 12; break;
        case 4: num_args_expected = 13; break;
        case 5: num_args_expected = 14; break;
        case 6: num_args_expected = 15; break;
        case 7:
        // Version 8 adds a new dexopt flag: DEXOPT_GENERATE_COMPACT_DEX
        case 8: num_args_expected = 16; break;
        // Version 9 adds a new dexopt flag: DEXOPT_GENERATE_APP_IMAGE
        case 9: num_args_expected = 16; break;
        default:
            LOG(ERROR) << "Don't know how to read arguments for version " << version;
            return false;
    }
    size_t dexopt_index = versioned ? 3 : 2;

    // Check for "dexopt".
    if (argv[dexopt_index] == nullptr) {
        LOG(ERROR) << "Missing parameters";
        return false;
    }
    if (std::string("dexopt").compare(argv[dexopt_index]) != 0) {
        LOG(ERROR) << "Expected \"dexopt\" but found: " << argv[dexopt_index];
        return false;
    }

    // Validate the number of arguments.
    size_t num_args_actual = 0;
    while (argv[dexopt_index + 1 + num_args_actual] != nullptr) {
        num_args_actual++;
    }

    if (num_args_actual != num_args_expected) {
        LOG(ERROR) << "Invalid number of arguments. expected="
                << num_args_expected << " actual=" << num_args_actual;
        return false;
    }

    // The number of arguments is OK.
    // Configure the default values for the parameters that were added after V1.
    // The default values will be overwritten in case they are passed as arguments.
    SetDefaultsForPostV1Arguments();

    for (size_t param_index = 0; param_index < num_args_actual; ++param_index) {
        const char* param = argv[dexopt_index + 1 + param_index];
        switch (param_index) {
            case 0:
                apk_path = param;
                break;

            case 1:
                uid = atoi(param);
                break;

            case 2:
                pkgName = param;
                break;

            case 3:
                instruction_set = param;
                break;

            case 4:
                dexopt_needed = atoi(param);
                break;

            case 5:
                oat_dir = param;
                break;

            case 6:
                dexopt_flags = atoi(param);
                // Add CompactDex generation flag for versions less than 8 since it wasn't passed
                // from the package manager. Only conditionally set the flag here so that it can
                // be fully controlled by the package manager.
                dexopt_flags |= (version < 8) ? DEXOPT_GENERATE_COMPACT_DEX : 0u;
                break;

            case 7:
                compiler_filter = param;
                break;

            case 8:
                volume_uuid = ParseNull(param);
                break;

            case 9:
                shared_libraries = ParseNull(param);
                break;

            case 10:
                se_info = ParseNull(param);
                break;

            case 11:
                downgrade = ParseBool(param);
                break;

            case 12:
                target_sdk_version = atoi(param);
                break;

            case 13:
                profile_name = ParseNull(param);
                break;

            case 14:
                dex_metadata_path = ParseNull(param);
                break;

            case 15:
                compilation_reason = ParseNull(param);
                break;

            default:
                LOG(FATAL) << "Should not get here. Did you call ReadArguments "
                        << "with the right expectation? index=" << param_index
                        << " num_args=" << num_args_actual;
                return false;
        }
    }

    return true;
}

}  // namespace installd
}  // namespace android
