/*
 * Copyright (C) 2010 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 <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define LOG_TAG "ObbFile"

#include <androidfw/ObbFile.h>
#include <utils/Compat.h>
#include <utils/Log.h>

//#define DEBUG 1

#define kFooterTagSize 8  /* last two 32-bit integers */

#define kFooterMinSize 33 /* 32-bit signature version (4 bytes)
                           * 32-bit package version (4 bytes)
                           * 32-bit flags (4 bytes)
                           * 64-bit salt (8 bytes)
                           * 32-bit package name size (4 bytes)
                           * >=1-character package name (1 byte)
                           * 32-bit footer size (4 bytes)
                           * 32-bit footer marker (4 bytes)
                           */

#define kMaxBufSize    32768 /* Maximum file read buffer */

#define kSignature     0x01059983U /* ObbFile signature */

#define kSigVersion    1 /* We only know about signature version 1 */

/* offsets in version 1 of the header */
#define kPackageVersionOffset 4
#define kFlagsOffset          8
#define kSaltOffset           12
#define kPackageNameLenOffset 20
#define kPackageNameOffset    24

/*
 * TEMP_FAILURE_RETRY is defined by some, but not all, versions of
 * <unistd.h>. (Alas, it is not as standard as we'd hoped!) So, if it's
 * not already defined, then define it here.
 */
#ifndef TEMP_FAILURE_RETRY
/* Used to retry syscalls that can return EINTR. */
#define TEMP_FAILURE_RETRY(exp) ({         \
    typeof (exp) _rc;                      \
    do {                                   \
        _rc = (exp);                       \
    } while (_rc == -1 && errno == EINTR); \
    _rc; })
#endif


namespace android {

ObbFile::ObbFile()
        : mPackageName("")
        , mVersion(-1)
        , mFlags(0)
{
    memset(mSalt, 0, sizeof(mSalt));
}

ObbFile::~ObbFile() {
}

bool ObbFile::readFrom(const char* filename)
{
    int fd;
    bool success = false;

    fd = ::open(filename, O_RDONLY);
    if (fd < 0) {
        ALOGW("couldn't open file %s: %s", filename, strerror(errno));
        goto out;
    }
    success = readFrom(fd);
    close(fd);

    if (!success) {
        ALOGW("failed to read from %s (fd=%d)\n", filename, fd);
    }

out:
    return success;
}

bool ObbFile::readFrom(int fd)
{
    if (fd < 0) {
        ALOGW("attempt to read from invalid fd\n");
        return false;
    }

    return parseObbFile(fd);
}

bool ObbFile::parseObbFile(int fd)
{
    off64_t fileLength = lseek64(fd, 0, SEEK_END);

    if (fileLength < kFooterMinSize) {
        if (fileLength < 0) {
            ALOGW("error seeking in ObbFile: %s\n", strerror(errno));
        } else {
            ALOGW("file is only %lld (less than %d minimum)\n", (long long int)fileLength, kFooterMinSize);
        }
        return false;
    }

    ssize_t actual;
    size_t footerSize;

    {
        lseek64(fd, fileLength - kFooterTagSize, SEEK_SET);

        char footer[kFooterTagSize];
        actual = TEMP_FAILURE_RETRY(read(fd, footer, kFooterTagSize));
        if (actual != kFooterTagSize) {
            ALOGW("couldn't read footer signature: %s\n", strerror(errno));
            return false;
        }

        unsigned int fileSig = get4LE((unsigned char*)footer + sizeof(int32_t));
        if (fileSig != kSignature) {
            ALOGW("footer didn't match magic string (expected 0x%08x; got 0x%08x)\n",
                    kSignature, fileSig);
            return false;
        }

        footerSize = get4LE((unsigned char*)footer);
        if (footerSize > (size_t)fileLength - kFooterTagSize
                || footerSize > kMaxBufSize) {
            ALOGW("claimed footer size is too large (0x%08zx; file size is 0x%08lld)\n",
                    footerSize, (long long int)fileLength);
            return false;
        }

        if (footerSize < (kFooterMinSize - kFooterTagSize)) {
            ALOGW("claimed footer size is too small (0x%zx; minimum size is 0x%x)\n",
                    footerSize, kFooterMinSize - kFooterTagSize);
            return false;
        }
    }

    off64_t fileOffset = fileLength - footerSize - kFooterTagSize;
    if (lseek64(fd, fileOffset, SEEK_SET) != fileOffset) {
        ALOGW("seek %lld failed: %s\n", (long long int)fileOffset, strerror(errno));
        return false;
    }

    mFooterStart = fileOffset;

    char* scanBuf = (char*)malloc(footerSize);
    if (scanBuf == NULL) {
        ALOGW("couldn't allocate scanBuf: %s\n", strerror(errno));
        return false;
    }

    actual = TEMP_FAILURE_RETRY(read(fd, scanBuf, footerSize));
    // readAmount is guaranteed to be less than kMaxBufSize
    if (actual != (ssize_t)footerSize) {
        ALOGI("couldn't read ObbFile footer: %s\n", strerror(errno));
        free(scanBuf);
        return false;
    }

#ifdef DEBUG
    for (int i = 0; i < footerSize; ++i) {
        ALOGI("char: 0x%02x\n", scanBuf[i]);
    }
#endif

    uint32_t sigVersion = get4LE((unsigned char*)scanBuf);
    if (sigVersion != kSigVersion) {
        ALOGW("Unsupported ObbFile version %d\n", sigVersion);
        free(scanBuf);
        return false;
    }

    mVersion = (int32_t) get4LE((unsigned char*)scanBuf + kPackageVersionOffset);
    mFlags = (int32_t) get4LE((unsigned char*)scanBuf + kFlagsOffset);

    memcpy(&mSalt, (unsigned char*)scanBuf + kSaltOffset, sizeof(mSalt));

    size_t packageNameLen = get4LE((unsigned char*)scanBuf + kPackageNameLenOffset);
    if (packageNameLen == 0
            || packageNameLen > (footerSize - kPackageNameOffset)) {
        ALOGW("bad ObbFile package name length (0x%04zx; 0x%04zx possible)\n",
                packageNameLen, footerSize - kPackageNameOffset);
        free(scanBuf);
        return false;
    }

    char* packageName = reinterpret_cast<char*>(scanBuf + kPackageNameOffset);
    mPackageName = String8(const_cast<char*>(packageName), packageNameLen);

    free(scanBuf);

#ifdef DEBUG
    ALOGI("Obb scan succeeded: packageName=%s, version=%d\n", mPackageName.string(), mVersion);
#endif

    return true;
}

bool ObbFile::writeTo(const char* filename)
{
    int fd;
    bool success = false;

    fd = ::open(filename, O_WRONLY);
    if (fd < 0) {
        goto out;
    }
    success = writeTo(fd);
    close(fd);

out:
    if (!success) {
        ALOGW("failed to write to %s: %s\n", filename, strerror(errno));
    }
    return success;
}

bool ObbFile::writeTo(int fd)
{
    if (fd < 0) {
        return false;
    }

    lseek64(fd, 0, SEEK_END);

    if (mPackageName.size() == 0 || mVersion == -1) {
        ALOGW("tried to write uninitialized ObbFile data\n");
        return false;
    }

    unsigned char intBuf[sizeof(uint32_t)+1];
    memset(&intBuf, 0, sizeof(intBuf));

    put4LE(intBuf, kSigVersion);
    if (write(fd, &intBuf, sizeof(uint32_t)) != (ssize_t)sizeof(uint32_t)) {
        ALOGW("couldn't write signature version: %s\n", strerror(errno));
        return false;
    }

    put4LE(intBuf, mVersion);
    if (write(fd, &intBuf, sizeof(uint32_t)) != (ssize_t)sizeof(uint32_t)) {
        ALOGW("couldn't write package version\n");
        return false;
    }

    put4LE(intBuf, mFlags);
    if (write(fd, &intBuf, sizeof(uint32_t)) != (ssize_t)sizeof(uint32_t)) {
        ALOGW("couldn't write package version\n");
        return false;
    }

    if (write(fd, mSalt, sizeof(mSalt)) != (ssize_t)sizeof(mSalt)) {
        ALOGW("couldn't write salt: %s\n", strerror(errno));
        return false;
    }

    size_t packageNameLen = mPackageName.size();
    put4LE(intBuf, packageNameLen);
    if (write(fd, &intBuf, sizeof(uint32_t)) != (ssize_t)sizeof(uint32_t)) {
        ALOGW("couldn't write package name length: %s\n", strerror(errno));
        return false;
    }

    if (write(fd, mPackageName.string(), packageNameLen) != (ssize_t)packageNameLen) {
        ALOGW("couldn't write package name: %s\n", strerror(errno));
        return false;
    }

    put4LE(intBuf, kPackageNameOffset + packageNameLen);
    if (write(fd, &intBuf, sizeof(uint32_t)) != (ssize_t)sizeof(uint32_t)) {
        ALOGW("couldn't write footer size: %s\n", strerror(errno));
        return false;
    }

    put4LE(intBuf, kSignature);
    if (write(fd, &intBuf, sizeof(uint32_t)) != (ssize_t)sizeof(uint32_t)) {
        ALOGW("couldn't write footer magic signature: %s\n", strerror(errno));
        return false;
    }

    return true;
}

bool ObbFile::removeFrom(const char* filename)
{
    int fd;
    bool success = false;

    fd = ::open(filename, O_RDWR);
    if (fd < 0) {
        goto out;
    }
    success = removeFrom(fd);
    close(fd);

out:
    if (!success) {
        ALOGW("failed to remove signature from %s: %s\n", filename, strerror(errno));
    }
    return success;
}

bool ObbFile::removeFrom(int fd)
{
    if (fd < 0) {
        return false;
    }

    if (!readFrom(fd)) {
        return false;
    }

    if (ftruncate(fd, mFooterStart) == -1) {
        return false;
    }

    return true;
}

}
