/*
 * Copyright 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 "NuPlayer2Drm"

#include "NuPlayer2Drm.h"

#include <media/NdkWrapper.h>
#include <utils/Log.h>
#include <sstream>

namespace android {

Vector<DrmUUID> NuPlayer2Drm::parsePSSH(const void *pssh, size_t psshsize)
{
    Vector<DrmUUID> drmSchemes, empty;
    const int DATALEN_SIZE = 4;

    // the format of the buffer is 1 or more of:
    //    {
    //        16 byte uuid
    //        4 byte data length N
    //        N bytes of data
    //    }
    // Determine the number of entries in the source data.
    // Since we got the data from stagefright, we trust it is valid and properly formatted.

    const uint8_t *data = (const uint8_t*)pssh;
    size_t len = psshsize;
    size_t numentries = 0;
    while (len > 0) {
        if (len < DrmUUID::UUID_SIZE) {
            ALOGE("ParsePSSH: invalid PSSH data");
            return empty;
        }

        const uint8_t *uuidPtr = data;

        // skip uuid
        data += DrmUUID::UUID_SIZE;
        len -= DrmUUID::UUID_SIZE;

        // get data length
        if (len < DATALEN_SIZE) {
            ALOGE("ParsePSSH: invalid PSSH data");
            return empty;
        }

        uint32_t datalen = *((uint32_t*)data);
        data += DATALEN_SIZE;
        len -= DATALEN_SIZE;

        if (len < datalen) {
            ALOGE("ParsePSSH: invalid PSSH data");
            return empty;
        }

        // skip the data
        data += datalen;
        len -= datalen;

        DrmUUID _uuid(uuidPtr);
        drmSchemes.add(_uuid);

        ALOGV("ParsePSSH[%zu]: %s: %s", numentries,
                _uuid.toHexString().string(),
                DrmUUID::arrayToHex(data, datalen).string()
             );

        numentries++;
    }

    return drmSchemes;
}

Vector<DrmUUID> NuPlayer2Drm::getSupportedDrmSchemes(const void *pssh, size_t psshsize)
{
    Vector<DrmUUID> psshDRMs = parsePSSH(pssh, psshsize);

    Vector<DrmUUID> supportedDRMs;
    for (size_t i = 0; i < psshDRMs.size(); i++) {
        DrmUUID uuid = psshDRMs[i];
        if (AMediaDrmWrapper::isCryptoSchemeSupported(uuid.ptr(), NULL)) {
            supportedDRMs.add(uuid);
        }
    }

    ALOGV("getSupportedDrmSchemes: psshDRMs: %zu supportedDRMs: %zu",
            psshDRMs.size(), supportedDRMs.size());

    return supportedDRMs;
}

sp<ABuffer> NuPlayer2Drm::retrieveDrmInfo(const void *pssh, uint32_t psshsize)
{
    std::ostringstream buf;

    // 1) PSSH bytes
    buf.write(reinterpret_cast<const char *>(&psshsize), sizeof(psshsize));
    buf.write(reinterpret_cast<const char *>(pssh), psshsize);

    ALOGV("retrieveDrmInfo: MEDIA2_DRM_INFO  PSSH: size: %u %s", psshsize,
            DrmUUID::arrayToHex((uint8_t*)pssh, psshsize).string());

    // 2) supportedDRMs
    Vector<DrmUUID> supportedDRMs = getSupportedDrmSchemes(pssh, psshsize);
    uint32_t n = supportedDRMs.size();
    buf.write(reinterpret_cast<char *>(&n), sizeof(n));
    for (size_t i = 0; i < n; i++) {
        DrmUUID uuid = supportedDRMs[i];
        buf.write(reinterpret_cast<const char *>(&n), sizeof(n));
        buf.write(reinterpret_cast<const char *>(uuid.ptr()), DrmUUID::UUID_SIZE);

        ALOGV("retrieveDrmInfo: MEDIA2_DRM_INFO  supportedScheme[%zu] %s", i,
                uuid.toHexString().string());
    }

    sp<ABuffer> drmInfoBuffer = ABuffer::CreateAsCopy(buf.str().c_str(), buf.tellp());
    return drmInfoBuffer;
}

sp<ABuffer> NuPlayer2Drm::retrieveDrmInfo(PsshInfo *psshInfo)
{

    std::ostringstream pssh, drmInfo;

    // 0) Generate PSSH bytes
    for (size_t i = 0; i < psshInfo->numentries; i++) {
        PsshEntry *entry = &psshInfo->entries[i];
        uint32_t datalen = entry->datalen;
        pssh.write(reinterpret_cast<const char *>(&entry->uuid), sizeof(entry->uuid));
        pssh.write(reinterpret_cast<const char *>(&datalen), sizeof(datalen));
        pssh.write(reinterpret_cast<const char *>(entry->data), datalen);
    }

    uint32_t psshSize = pssh.tellp();
    const uint8_t* psshPtr = reinterpret_cast<const uint8_t*>(pssh.str().c_str());
    const char *psshHex = DrmUUID::arrayToHex(psshPtr, psshSize).string();
    ALOGV("retrieveDrmInfo: MEDIA_DRM_INFO  PSSH: size: %u %s", psshSize, psshHex);

    // 1) Write PSSH bytes
    drmInfo.write(reinterpret_cast<const char *>(&psshSize), sizeof(psshSize));
    drmInfo.write(reinterpret_cast<const char *>(pssh.str().c_str()), psshSize);

    // 2) Write supportedDRMs
    uint32_t numentries = psshInfo->numentries;
    drmInfo.write(reinterpret_cast<const char *>(&numentries), sizeof(numentries));
    for (size_t i = 0; i < numentries; i++) {
        PsshEntry *entry = &psshInfo->entries[i];
        drmInfo.write(reinterpret_cast<const char *>(&entry->uuid), sizeof(entry->uuid));
        ALOGV("retrieveDrmInfo: MEDIA_DRM_INFO  supportedScheme[%zu] %s", i,
                DrmUUID::arrayToHex((const uint8_t*)&entry->uuid, sizeof(AMediaUUID)).string());
    }

    sp<ABuffer> drmInfoBuf = ABuffer::CreateAsCopy(drmInfo.str().c_str(), drmInfo.tellp());
    drmInfoBuf->setRange(0, drmInfo.tellp());
    return drmInfoBuf;
}

}   // namespace android
