/*
 * 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.
 */

//#define LOG_NDEBUG 0
#define LOG_TAG "ID3"
#include <utils/Log.h>

#include "../include/ID3.h"

#include <media/DataSource.h>
#include <media/MediaExtractorPluginApi.h>
#include <media/MediaExtractorPluginHelper.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/ByteUtils.h>
#include <utils/String8.h>
#include <byteswap.h>

namespace android {

static const size_t kMaxMetadataSize = 3 * 1024 * 1024;

struct ID3::MemorySource : public DataSourceBase {
    MemorySource(const uint8_t *data, size_t size)
        : mData(data),
          mSize(size) {
    }

    virtual status_t initCheck() const {
        return OK;
    }

    virtual ssize_t readAt(off64_t offset, void *data, size_t size) {
        off64_t available = (offset >= (off64_t)mSize) ? 0LL : mSize - offset;

        size_t copy = (available > (off64_t)size) ? size : available;
        memcpy(data, mData + offset, copy);

        return copy;
    }

private:
    const uint8_t *mData;
    size_t mSize;

    DISALLOW_EVIL_CONSTRUCTORS(MemorySource);
};

class ID3::DataSourceUnwrapper : public DataSourceBase {

public:
    explicit DataSourceUnwrapper(DataSourceHelper *sourcehelper) {
        mSource = sourcehelper;
    }
    virtual status_t initCheck() const { return OK; }

    // Returns the number of bytes read, or -1 on failure. It's not an error if
    // this returns zero; it just means the given offset is equal to, or
    // beyond, the end of the source.
    virtual ssize_t readAt(off64_t offset, void *data, size_t size) {
        return mSource->readAt(offset, data, size);
    }

    // May return ERROR_UNSUPPORTED.
    virtual status_t getSize(off64_t *size) {
        return mSource->getSize(size);
    }

    virtual bool getUri(char * /*uriString*/, size_t /*bufferSize*/) {
        return false;
    }

    virtual uint32_t flags() {
        return 0;
    }

    virtual void close() {};
private:
    DataSourceHelper *mSource;
};


ID3::ID3(DataSourceHelper *sourcehelper, bool ignoreV1, off64_t offset)
    : mIsValid(false),
      mData(NULL),
      mSize(0),
      mFirstFrameOffset(0),
      mVersion(ID3_UNKNOWN),
      mRawSize(0) {
    DataSourceUnwrapper source(sourcehelper);
    mIsValid = parseV2(&source, offset);

    if (!mIsValid && !ignoreV1) {
        mIsValid = parseV1(&source);
    }
}

ID3::ID3(const uint8_t *data, size_t size, bool ignoreV1)
    : mIsValid(false),
      mData(NULL),
      mSize(0),
      mFirstFrameOffset(0),
      mVersion(ID3_UNKNOWN),
      mRawSize(0) {
    MemorySource *source = new (std::nothrow) MemorySource(data, size);

    if (source == NULL)
        return;

    mIsValid = parseV2(source, 0);

    if (!mIsValid && !ignoreV1) {
        mIsValid = parseV1(source);
    }
    delete source;
}

ID3::~ID3() {
    if (mData) {
        free(mData);
        mData = NULL;
    }
}

bool ID3::isValid() const {
    return mIsValid;
}

ID3::Version ID3::version() const {
    return mVersion;
}

// static
bool ID3::ParseSyncsafeInteger(const uint8_t encoded[4], size_t *x) {
    *x = 0;
    for (int32_t i = 0; i < 4; ++i) {
        if (encoded[i] & 0x80) {
            return false;
        }

        *x = ((*x) << 7) | encoded[i];
    }

    return true;
}

bool ID3::parseV2(DataSourceBase *source, off64_t offset) {
struct id3_header {
    char id[3];
    uint8_t version_major;
    uint8_t version_minor;
    uint8_t flags;
    uint8_t enc_size[4];
    };

    id3_header header;
    if (source->readAt(
                offset, &header, sizeof(header)) != (ssize_t)sizeof(header)) {
        return false;
    }

    if (memcmp(header.id, "ID3", 3)) {
        return false;
    }

    if (header.version_major == 0xff || header.version_minor == 0xff) {
        return false;
    }

    if (header.version_major == 2) {
        if (header.flags & 0x3f) {
            // We only support the 2 high bits, if any of the lower bits are
            // set, we cannot guarantee to understand the tag format.
            return false;
        }

        if (header.flags & 0x40) {
            // No compression scheme has been decided yet, ignore the
            // tag if compression is indicated.

            return false;
        }
    } else if (header.version_major == 3) {
        if (header.flags & 0x1f) {
            // We only support the 3 high bits, if any of the lower bits are
            // set, we cannot guarantee to understand the tag format.
            return false;
        }
    } else if (header.version_major == 4) {
        if (header.flags & 0x0f) {
            // The lower 4 bits are undefined in this spec.
            return false;
        }
    } else {
        return false;
    }

    size_t size;
    if (!ParseSyncsafeInteger(header.enc_size, &size)) {
        return false;
    }

    if (size > kMaxMetadataSize) {
        ALOGE("skipping huge ID3 metadata of size %zu", size);
        return false;
    }

    mData = (uint8_t *)malloc(size);

    if (mData == NULL) {
        return false;
    }

    mSize = size;
    mRawSize = mSize + sizeof(header);

    if (source->readAt(offset + sizeof(header), mData, mSize) != (ssize_t)mSize) {
        free(mData);
        mData = NULL;

        return false;
    }

    // first handle global unsynchronization
    bool hasGlobalUnsync = false;
    if (header.flags & 0x80) {
        ALOGV("has Global unsynchronization");
        hasGlobalUnsync = true;
        // we have to wait on applying global unsynchronization to V2.4 frames
        // if we apply it now, the length information within any V2.4 frames goes bad
        // Removing unsynchronization shrinks the buffer, but lengths (stored in safesync
        // format) stored within the frame reflect "pre-shrinking" totals.

        // we can (and should) apply the non-2.4 synch now.
        if ( header.version_major != 4) {
            ALOGV("Apply global unsync for non V2.4 frames");
            removeUnsynchronization();
        }
    }

    // handle extended header, if present
    mFirstFrameOffset = 0;
    if (header.version_major == 3 && (header.flags & 0x40)) {
        // Version 2.3 has an optional extended header.

        if (mSize < 4) {
            free(mData);
            mData = NULL;

            return false;
        }

        // v2.3 does not have syncsafe integers
        size_t extendedHeaderSize = U32_AT(&mData[0]);
        if (extendedHeaderSize > SIZE_MAX - 4) {
            free(mData);
            mData = NULL;
            ALOGE("b/24623447, extendedHeaderSize is too large");
            return false;
        }
        extendedHeaderSize += 4;

        if (extendedHeaderSize > mSize) {
            free(mData);
            mData = NULL;

            return false;
        }

        mFirstFrameOffset = extendedHeaderSize;

        uint16_t extendedFlags = 0;
        if (extendedHeaderSize >= 6) {
            extendedFlags = U16_AT(&mData[4]);

            if (extendedHeaderSize >= 10) {
                size_t paddingSize = U32_AT(&mData[6]);

                if (paddingSize > SIZE_MAX - mFirstFrameOffset) {
                    ALOGE("b/24623447, paddingSize is too large");
                }
                if (paddingSize > mSize - mFirstFrameOffset) {
                    free(mData);
                    mData = NULL;

                    return false;
                }

                mSize -= paddingSize;
            }

            if (extendedFlags & 0x8000) {
                ALOGV("have crc");
            }
        }
    } else if (header.version_major == 4 && (header.flags & 0x40)) {
        // Version 2.4 has an optional extended header, that's different
        // from Version 2.3's...

        if (mSize < 4) {
            free(mData);
            mData = NULL;

            return false;
        }

        size_t ext_size;
        if (!ParseSyncsafeInteger(mData, &ext_size)) {
            free(mData);
            mData = NULL;

            return false;
        }

        if (ext_size < 6 || ext_size > mSize) {
            free(mData);
            mData = NULL;

            return false;
        }

        mFirstFrameOffset = ext_size;
    }

    // Handle any v2.4 per-frame unsynchronization
    // The id3 spec isn't clear about what should happen if the global
    // unsynchronization flag is combined with per-frame unsynchronization,
    // or whether that's even allowed. We choose a "no more than 1 unsynchronization"
    // semantic; the V2_4 unsynchronizer gets a copy of the global flag so it can handle
    // this possible ambiquity.
    //
    if (header.version_major == 4) {
        void *copy = malloc(size);
        if (copy == NULL) {
            free(mData);
            mData = NULL;
            ALOGE("b/24623447, no more memory");
            return false;
        }

        memcpy(copy, mData, size);

        bool success = removeUnsynchronizationV2_4(false /* iTunesHack */, hasGlobalUnsync);
        if (!success) {
            memcpy(mData, copy, size);
            mSize = size;

            success = removeUnsynchronizationV2_4(true /* iTunesHack */, hasGlobalUnsync);

            if (success) {
                ALOGV("Had to apply the iTunes hack to parse this ID3 tag");
            }
        }

        free(copy);
        copy = NULL;

        if (!success) {
            free(mData);
            mData = NULL;

            return false;
        }
    }


    if (header.version_major == 2) {
        mVersion = ID3_V2_2;
    } else if (header.version_major == 3) {
        mVersion = ID3_V2_3;
    } else {
        CHECK_EQ(header.version_major, 4);
        mVersion = ID3_V2_4;
    }

    return true;
}

void ID3::removeUnsynchronization() {

    // This file has "unsynchronization", so we have to replace occurrences
    // of 0xff 0x00 with just 0xff in order to get the real data.

    size_t writeOffset = 1;
    for (size_t readOffset = 1; readOffset < mSize; ++readOffset) {
        if (mData[readOffset - 1] == 0xff && mData[readOffset] == 0x00) {
            continue;
        }
        // Only move data if there's actually something to move.
        // This handles the special case of the data being only [0xff, 0x00]
        // which should be converted to just 0xff if unsynchronization is on.
        mData[writeOffset++] = mData[readOffset];
    }

    if (writeOffset < mSize) {
        mSize = writeOffset;
    }

}

static void WriteSyncsafeInteger(uint8_t *dst, size_t x) {
    for (size_t i = 0; i < 4; ++i) {
        dst[3 - i] = (x & 0x7f);
        x >>= 7;
    }
}

bool ID3::removeUnsynchronizationV2_4(bool iTunesHack, bool hasGlobalUnsync) {
    size_t oldSize = mSize;

    size_t offset = mFirstFrameOffset;
    while (mSize >= 10 && offset <= mSize - 10) {
        if (!memcmp(&mData[offset], "\0\0\0\0", 4)) {
            break;
        }

        size_t dataSize;
        if (iTunesHack) {
            dataSize = U32_AT(&mData[offset + 4]);
        } else if (!ParseSyncsafeInteger(&mData[offset + 4], &dataSize)) {
            return false;
        }

        if (dataSize > mSize - 10 - offset) {
            return false;
        }

        uint16_t flags = U16_AT(&mData[offset + 8]);
        uint16_t prevFlags = flags;

        if (flags & 1) {
            // Strip data length indicator

            if (mSize < 14 || mSize - 14 < offset || dataSize < 4) {
                return false;
            }
            memmove(&mData[offset + 10], &mData[offset + 14], mSize - offset - 14);
            mSize -= 4;
            dataSize -= 4;

            flags &= ~1;
        }

        ALOGV("hasglobal %d  flags&2 %d", hasGlobalUnsync, flags&2);
        if (hasGlobalUnsync && !(flags & 2)) {
            ALOGV("OOPS: global unsync set, but per-frame NOT set; removing unsync anyway");
        }
        if ((hasGlobalUnsync || (flags & 2)) && (dataSize >= 2)) {
            // This frame has "unsynchronization", so we have to replace occurrences
            // of 0xff 0x00 with just 0xff in order to get the real data.

            size_t readOffset = offset + 11;
            size_t writeOffset = offset + 11;
            for (size_t i = 0; i + 1 < dataSize; ++i) {
                if (mData[readOffset - 1] == 0xff
                        && mData[readOffset] == 0x00) {
                    ++readOffset;
                    --mSize;
                    --dataSize;
                }
                if (i + 1 < dataSize) {
                    // Only move data if there's actually something to move.
                    // This handles the special case of the data being only [0xff, 0x00]
                    // which should be converted to just 0xff if unsynchronization is on.
                    mData[writeOffset++] = mData[readOffset++];
                }
            }
            // move the remaining data following this frame
            if (readOffset <= oldSize) {
                memmove(&mData[writeOffset], &mData[readOffset], oldSize - readOffset);
            } else {
                ALOGE("b/34618607 (%zu %zu %zu %zu)", readOffset, writeOffset, oldSize, mSize);
                android_errorWriteLog(0x534e4554, "34618607");
            }
        }
        flags &= ~2;
        if (flags != prevFlags || iTunesHack) {
            WriteSyncsafeInteger(&mData[offset + 4], dataSize);
            mData[offset + 8] = flags >> 8;
            mData[offset + 9] = flags & 0xff;
        }

        offset += 10 + dataSize;
    }

    memset(&mData[mSize], 0, oldSize - mSize);

    return true;
}

ID3::Iterator::Iterator(const ID3 &parent, const char *id)
    : mParent(parent),
      mID(NULL),
      mOffset(mParent.mFirstFrameOffset),
      mFrameData(NULL),
      mFrameSize(0) {
    if (id) {
        mID = strdup(id);
    }

    findFrame();
}

ID3::Iterator::~Iterator() {
    if (mID) {
        free(mID);
        mID = NULL;
    }
}

bool ID3::Iterator::done() const {
    return mFrameData == NULL;
}

void ID3::Iterator::next() {
    if (mFrameData == NULL) {
        return;
    }

    mOffset += mFrameSize;

    findFrame();
}

void ID3::Iterator::getID(String8 *id) const {
    *id = "";

    if (mFrameData == NULL) {
        return;
    }

    if (mParent.mVersion == ID3_V2_2) {
        *id = String8((const char *)&mParent.mData[mOffset], 3);
    } else if (mParent.mVersion == ID3_V2_3 || mParent.mVersion == ID3_V2_4) {
        *id = String8((const char *)&mParent.mData[mOffset], 4);
    } else {
        CHECK(mParent.mVersion == ID3_V1 || mParent.mVersion == ID3_V1_1);

        switch (mOffset) {
            case 3:
                *id = "TT2";
                break;
            case 33:
                *id = "TP1";
                break;
            case 63:
                *id = "TAL";
                break;
            case 93:
                *id = "TYE";
                break;
            case 97:
                *id = "COM";
                break;
            case 126:
                *id = "TRK";
                break;
            case 127:
                *id = "TCO";
                break;
            default:
                CHECK(!"should not be here.");
                break;
        }
    }
}


// the 2nd argument is used to get the data following the \0 in a comment field
void ID3::Iterator::getString(String8 *id, String8 *comment) const {
    getstring(id, false);
    if (comment != NULL) {
        getstring(comment, true);
    }
}

// comment fields (COM/COMM) contain an initial short descriptor, followed by \0,
// followed by more data. The data following the \0 can be retrieved by setting
// "otherdata" to true.
void ID3::Iterator::getstring(String8 *id, bool otherdata) const {
    *id = "";

    const uint8_t *frameData = mFrameData;
    if (frameData == NULL) {
        return;
    }

    uint8_t encoding = *frameData;

    if (mParent.mVersion == ID3_V1 || mParent.mVersion == ID3_V1_1) {
        if (mOffset == 126 || mOffset == 127) {
            // Special treatment for the track number and genre.
            char tmp[16];
            snprintf(tmp, sizeof(tmp), "%d", (int)*frameData);

            *id = tmp;
            return;
        }

        // this is supposed to be ISO-8859-1, but pass it up as-is to the caller, who will figure
        // out the real encoding
        *id = String8((const char*)frameData, mFrameSize);
        return;
    }

    if (mFrameSize < getHeaderLength() + 1) {
        return;
    }
    size_t n = mFrameSize - getHeaderLength() - 1;
    if (otherdata) {
        if (n < 5) {
            return;
        }
        // skip past the encoding, language, and the 0 separator
        frameData += 4;
        int32_t i = n - 4;
        while(--i >= 0 && *++frameData != 0) ;
        int skipped = (frameData - mFrameData);
        if (skipped >= (int)n) {
            return;
        }
        n -= skipped;
    }

    if (n <= 0) {
       return;
    }

    if (encoding == 0x00) {
        // supposedly ISO 8859-1
        *id = String8((const char*)frameData + 1, n);
    } else if (encoding == 0x03) {
        // supposedly UTF-8
        *id = String8((const char *)(frameData + 1), n);
    } else if (encoding == 0x02) {
        // supposedly UTF-16 BE, no byte order mark.
        // API wants number of characters, not number of bytes...
        int len = n / 2;
        const char16_t *framedata = (const char16_t *) (frameData + 1);
        char16_t *framedatacopy = NULL;
#if BYTE_ORDER == LITTLE_ENDIAN
        if (len > 0) {
            framedatacopy = new (std::nothrow) char16_t[len];
            if (framedatacopy == NULL) {
                return;
            }
            for (int i = 0; i < len; i++) {
                framedatacopy[i] = bswap_16(framedata[i]);
            }
            framedata = framedatacopy;
        }
#endif
        *id = String8(framedata, len);
        if (framedatacopy != NULL) {
            delete[] framedatacopy;
        }
    } else if (encoding == 0x01) {
        // UCS-2
        // API wants number of characters, not number of bytes...
        int len = n / 2;
        if (len == 0) {
            return;
        }
        const char16_t *framedata = (const char16_t *) (frameData + 1);
        char16_t *framedatacopy = NULL;
        if (*framedata == 0xfffe) {
            // endianness marker != host endianness, convert & skip
            if (len <= 1) {
                return;         // nothing after the marker
            }
            framedatacopy = new (std::nothrow) char16_t[len];
            if (framedatacopy == NULL) {
                return;
            }
            for (int i = 0; i < len; i++) {
                framedatacopy[i] = bswap_16(framedata[i]);
            }
            framedata = framedatacopy;
            // and skip over the marker
            framedata++;
            len--;
        } else if (*framedata == 0xfeff) {
            // endianness marker == host endianness, skip it
            if (len <= 1) {
                return;         // nothing after the marker
            }
            framedata++;
            len--;
        }

        // check if the resulting data consists entirely of 8-bit values
        bool eightBit = true;
        for (int i = 0; i < len; i++) {
            if (framedata[i] > 0xff) {
                eightBit = false;
                break;
            }
        }
        if (eightBit) {
            // collapse to 8 bit, then let the media scanner client figure out the real encoding
            char *frame8 = new (std::nothrow) char[len];
            if (frame8 != NULL) {
                for (int i = 0; i < len; i++) {
                    frame8[i] = framedata[i];
                }
                *id = String8(frame8, len);
                delete [] frame8;
            } else {
                *id = String8(framedata, len);
            }
        } else {
            *id = String8(framedata, len);
        }

        if (framedatacopy != NULL) {
            delete[] framedatacopy;
        }
    }
}

const uint8_t *ID3::Iterator::getData(size_t *length) const {
    *length = 0;

    if (mFrameData == NULL) {
        return NULL;
    }

    // Prevent integer underflow
    if (mFrameSize < getHeaderLength()) {
        return NULL;
    }

    *length = mFrameSize - getHeaderLength();

    return mFrameData;
}

size_t ID3::Iterator::getHeaderLength() const {
    if (mParent.mVersion == ID3_V2_2) {
        return 6;
    } else if (mParent.mVersion == ID3_V2_3 || mParent.mVersion == ID3_V2_4) {
        return 10;
    } else {
        CHECK(mParent.mVersion == ID3_V1 || mParent.mVersion == ID3_V1_1);
        return 0;
    }
}

void ID3::Iterator::findFrame() {
    for (;;) {
        mFrameData = NULL;
        mFrameSize = 0;

        if (mParent.mVersion == ID3_V2_2) {
            if (mOffset + 6 > mParent.mSize) {
                return;
            }

            if (!memcmp(&mParent.mData[mOffset], "\0\0\0", 3)) {
                return;
            }

            mFrameSize =
                (mParent.mData[mOffset + 3] << 16)
                | (mParent.mData[mOffset + 4] << 8)
                | mParent.mData[mOffset + 5];

            if (mFrameSize == 0) {
                return;
            }
            mFrameSize += 6; // add tag id and size field

            // Prevent integer overflow in validation
            if (SIZE_MAX - mOffset <= mFrameSize) {
                return;
            }

            if (mOffset + mFrameSize > mParent.mSize) {
                ALOGV("partial frame at offset %zu (size = %zu, bytes-remaining = %zu)",
                    mOffset, mFrameSize, mParent.mSize - mOffset - (size_t)6);
                return;
            }

            mFrameData = &mParent.mData[mOffset + 6];

            if (!mID) {
                break;
            }

            char id[4];
            memcpy(id, &mParent.mData[mOffset], 3);
            id[3] = '\0';

            if (!strcmp(id, mID)) {
                break;
            }
        } else if (mParent.mVersion == ID3_V2_3
                || mParent.mVersion == ID3_V2_4) {
            if (mOffset + 10 > mParent.mSize) {
                return;
            }

            if (!memcmp(&mParent.mData[mOffset], "\0\0\0\0", 4)) {
                return;
            }

            size_t baseSize = 0;
            if (mParent.mVersion == ID3_V2_4) {
                if (!ParseSyncsafeInteger(
                            &mParent.mData[mOffset + 4], &baseSize)) {
                    return;
                }
            } else {
                baseSize = U32_AT(&mParent.mData[mOffset + 4]);
            }

            if (baseSize == 0) {
                return;
            }

            // Prevent integer overflow when adding
            if (SIZE_MAX - 10 <= baseSize) {
                return;
            }

            mFrameSize = 10 + baseSize; // add tag id, size field and flags

            // Prevent integer overflow in validation
            if (SIZE_MAX - mOffset <= mFrameSize) {
                return;
            }

            if (mOffset + mFrameSize > mParent.mSize) {
                ALOGV("partial frame at offset %zu (size = %zu, bytes-remaining = %zu)",
                    mOffset, mFrameSize, mParent.mSize - mOffset - (size_t)10);
                return;
            }

            uint16_t flags = U16_AT(&mParent.mData[mOffset + 8]);

            if ((mParent.mVersion == ID3_V2_4 && (flags & 0x000c))
                || (mParent.mVersion == ID3_V2_3 && (flags & 0x00c0))) {
                // Compression or encryption are not supported at this time.
                // Per-frame unsynchronization and data-length indicator
                // have already been taken care of.

                ALOGV("Skipping unsupported frame (compression, encryption "
                     "or per-frame unsynchronization flagged");

                mOffset += mFrameSize;
                continue;
            }

            mFrameData = &mParent.mData[mOffset + 10];

            if (!mID) {
                break;
            }

            char id[5];
            memcpy(id, &mParent.mData[mOffset], 4);
            id[4] = '\0';

            if (!strcmp(id, mID)) {
                break;
            }
        } else {
            CHECK(mParent.mVersion == ID3_V1 || mParent.mVersion == ID3_V1_1);

            if (mOffset >= mParent.mSize) {
                return;
            }

            mFrameData = &mParent.mData[mOffset];

            switch (mOffset) {
                case 3:
                case 33:
                case 63:
                    mFrameSize = 30;
                    break;
                case 93:
                    mFrameSize = 4;
                    break;
                case 97:
                    if (mParent.mVersion == ID3_V1) {
                        mFrameSize = 30;
                    } else {
                        mFrameSize = 29;
                    }
                    break;
                case 126:
                    mFrameSize = 1;
                    break;
                case 127:
                    mFrameSize = 1;
                    break;
                default:
                    CHECK(!"Should not be here, invalid offset.");
                    break;
            }

            if (!mID) {
                break;
            }

            String8 id;
            getID(&id);

            if (id == mID) {
                break;
            }
        }

        mOffset += mFrameSize;
    }
}

// return includes terminator;  if unterminated, returns > limit
static size_t StringSize(const uint8_t *start, size_t limit, uint8_t encoding) {

    if (encoding == 0x00 || encoding == 0x03) {
        // ISO 8859-1 or UTF-8
        return strnlen((const char *)start, limit) + 1;
    }

    // UCS-2
    size_t n = 0;
    while ((n+1 < limit) && (start[n] != '\0' || start[n + 1] != '\0')) {
        n += 2;
    }
    n += 2;
    return n;
}

const void *
ID3::getAlbumArt(size_t *length, String8 *mime) const {
    *length = 0;
    *mime = "";

    Iterator it(
            *this,
            (mVersion == ID3_V2_3 || mVersion == ID3_V2_4) ? "APIC" : "PIC");

    while (!it.done()) {
        size_t size;
        const uint8_t *data = it.getData(&size);
        if (!data) {
            return NULL;
        }

        if (mVersion == ID3_V2_3 || mVersion == ID3_V2_4) {
            uint8_t encoding = data[0];
            size_t consumed = 1;

            // *always* in an 8-bit encoding
            size_t mimeLen = StringSize(&data[consumed], size - consumed, 0x00);
            if (mimeLen > size - consumed) {
                ALOGW("bogus album art size: mime");
                return NULL;
            }
            *mime = (const char *)&data[consumed];
            consumed += mimeLen;

#if 0
            uint8_t picType = data[consumed];
            if (picType != 0x03) {
                // Front Cover Art
                it.next();
                continue;
            }
#endif

            consumed++;
            if (consumed >= size) {
                ALOGW("bogus album art size: pic type");
                return NULL;
            }

            size_t descLen = StringSize(&data[consumed], size - consumed, encoding);
            consumed += descLen;

            if (consumed >= size) {
                ALOGW("bogus album art size: description");
                return NULL;
            }

            *length = size - consumed;

            return &data[consumed];
        } else {
            uint8_t encoding = data[0];

            if (size <= 5) {
                return NULL;
            }

            if (!memcmp(&data[1], "PNG", 3)) {
                *mime = "image/png";
            } else if (!memcmp(&data[1], "JPG", 3)) {
                *mime = "image/jpeg";
            } else if (!memcmp(&data[1], "-->", 3)) {
                *mime = "text/plain";
            } else {
                return NULL;
            }

#if 0
            uint8_t picType = data[4];
            if (picType != 0x03) {
                // Front Cover Art
                it.next();
                continue;
            }
#endif

            size_t descLen = StringSize(&data[5], size - 5, encoding);
            if (descLen > size - 5) {
                return NULL;
            }

            *length = size - 5 - descLen;

            return &data[5 + descLen];
        }
    }

    return NULL;
}

bool ID3::parseV1(DataSourceBase *source) {
    const size_t V1_TAG_SIZE = 128;

    off64_t size;
    if (source->getSize(&size) != OK || size < (off64_t)V1_TAG_SIZE) {
        return false;
    }

    mData = (uint8_t *)malloc(V1_TAG_SIZE);
    if (source->readAt(size - V1_TAG_SIZE, mData, V1_TAG_SIZE)
            != (ssize_t)V1_TAG_SIZE) {
        free(mData);
        mData = NULL;

        return false;
    }

    if (memcmp("TAG", mData, 3)) {
        free(mData);
        mData = NULL;

        return false;
    }

    mSize = V1_TAG_SIZE;
    mFirstFrameOffset = 3;

    if (mData[V1_TAG_SIZE - 3] != 0) {
        mVersion = ID3_V1;
    } else {
        mVersion = ID3_V1_1;
    }

    return true;
}

}  // namespace android
