/*
 * Copyright (C) 2017 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_NDEBUG 0
#define LOG_TAG "ClearKeyFetcher"

#include <algorithm>
#include <inttypes.h>
#include <string>

#include "ClearKeyFetcher.h"
#include "ecm.h"
#include "LicenseFetcher.h"

#include <media/stagefright/foundation/ADebug.h>
#include <utils/Log.h>

namespace android {
namespace clearkeycas {

ClearKeyFetcher::ClearKeyFetcher(
        std::unique_ptr<LicenseFetcher> license_fetcher) :
    initialized_(false),
    license_fetcher_(std::move(license_fetcher)) {
    CHECK(license_fetcher_);
}

ClearKeyFetcher::~ClearKeyFetcher() {}

// This is a no-op but other KeyFetcher subclasses require initialization
// so this is necessary to preserve the contract.
status_t ClearKeyFetcher::Init() {
    initialized_ = true;
    return OK;
}

status_t ClearKeyFetcher::ObtainKey(const sp<ABuffer>& buffer,
        uint64_t* asset_id, std::vector<KeyInfo>* keys) {
    CHECK(asset_id);
    CHECK(keys);
    CHECK(initialized_);
    *asset_id = 0;
    keys->clear();

    EcmContainer container;
    status_t status = container.Parse(buffer);
    if (status != OK) {
        return status;
    }
    ALOGV("descriptor_size=%zu", container.descriptor_size());

    // Validate that the BroadcastEncryptor is sending a properly formed
    // EcmContainer. If it contains two Ecms, the ids should have different
    // parity (one odd, one even). This does not necessarily affect decryption
    // but indicates a problem with Ecm generation.
    if (container.descriptor_size() == 2) {
        // XOR the least significant bits to verify different parity.
        bool same_parity = (((container.descriptor(0).id() & 0x01) ^
                (container.descriptor(1).id() & 0x01)) == 0);
        if (same_parity) {
            ALOGW("asset_id=%" PRIu64 ": malformed Ecm, "
                    "content keys have same parity, id0=%d, id1=%d",
                    container.descriptor(0).ecm().asset_id(),
                    container.descriptor(0).id(),
                    container.descriptor(1).id());
        }
    }

    *asset_id = container.descriptor(0).ecm().asset_id();

    // Detect asset_id change. This could be caused by a configuration change
    // in the BroadcastEncryptor. This is unusual so log it in case it is an
    // operational mistake. This invalidates the current asset_key causing a
    // new license to be fetched.
    // TODO(rkint): test against BroadcastEncryptor to verify what BE sends on
    // asset_id change. If it sends an EcmContainer with 2 Ecms with different
    // asset_ids (old and new) then it might be best to prefetch the Emm.
    if ((asset_.id() != 0) && (*asset_id != asset_.id())) {
        ALOGW("Asset_id change from %" PRIu64 " to %" PRIu64, asset_.id(), *asset_id);
        asset_.Clear();
    }

    // Fetch license to get asset_id
    if (!asset_.has_id()) {
        status = license_fetcher_->FetchLicense(*asset_id, &asset_);
        if (status != OK) {
            *asset_id = 0;
            return status;
        }
        ALOGV("FetchLicense succeeded, has_id=%d", asset_.has_id());
    }
    keys->resize(container.descriptor_size());

    for (size_t i = 0; i < container.descriptor_size(); ++i) {
        status = container.mutable_descriptor(i)->mutable_ecm()->Decrypt(
                container.descriptor(i).ecm().buffer(), asset_);
        if (status != OK) {
            *asset_id = 0;
            keys->clear();
            return status;
        }
        // TODO: if 2 Ecms have same parity, key from Ecm with higher id
        // should be keys[1].
        KeyInfo key;
        key.key_id = container.descriptor(i).id();
        key.key_bytes = container.descriptor(i).ecm().content_key();

        keys->at(key.key_id & 1) = key;
    }
    return OK;
}

}  // namespace clearkeycas
}  // namespace android
