blob: b9b8488965ece877951fe0a98c38a9efcbb0f3d6 [file] [log] [blame]
// Copyright 2019 Joe Drago. All rights reserved.
// SPDX-License-Identifier: BSD-2-Clause
#include "avif/internal.h"
#include <assert.h>
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
// ---------------------------------------------------------------------------
// avifROStream
const uint8_t * avifROStreamCurrent(avifROStream * stream)
{
return stream->raw->data + stream->offset;
}
void avifROStreamStart(avifROStream * stream, avifROData * raw, avifDiagnostics * diag, const char * diagContext)
{
stream->raw = raw;
stream->offset = 0;
stream->numUsedBitsInPartialByte = 0;
stream->diag = diag;
stream->diagContext = diagContext;
// If diag is non-NULL, diagContext must also be non-NULL
assert(!stream->diag || stream->diagContext);
}
avifBool avifROStreamHasBytesLeft(const avifROStream * stream, size_t byteCount)
{
return byteCount <= (stream->raw->size - stream->offset);
}
size_t avifROStreamRemainingBytes(const avifROStream * stream)
{
return stream->raw->size - stream->offset;
}
size_t avifROStreamOffset(const avifROStream * stream)
{
return stream->offset;
}
void avifROStreamSetOffset(avifROStream * stream, size_t offset)
{
assert(stream->numUsedBitsInPartialByte == 0); // Byte alignment is required.
stream->offset = offset;
if (stream->offset > stream->raw->size) {
stream->offset = stream->raw->size;
}
}
avifBool avifROStreamSkip(avifROStream * stream, size_t byteCount)
{
assert(stream->numUsedBitsInPartialByte == 0); // Byte alignment is required.
if (!avifROStreamHasBytesLeft(stream, byteCount)) {
avifDiagnosticsPrintf(stream->diag, "%s: Failed to skip %zu bytes, truncated data?", stream->diagContext, byteCount);
return AVIF_FALSE;
}
stream->offset += byteCount;
return AVIF_TRUE;
}
avifBool avifROStreamRead(avifROStream * stream, uint8_t * data, size_t size)
{
assert(stream->numUsedBitsInPartialByte == 0); // Byte alignment is required.
if (!avifROStreamHasBytesLeft(stream, size)) {
avifDiagnosticsPrintf(stream->diag, "%s: Failed to read %zu bytes, truncated data?", stream->diagContext, size);
return AVIF_FALSE;
}
memcpy(data, stream->raw->data + stream->offset, size);
stream->offset += size;
return AVIF_TRUE;
}
avifBool avifROStreamReadUX8(avifROStream * stream, uint64_t * v, uint64_t factor)
{
assert(stream->numUsedBitsInPartialByte == 0); // Byte alignment is required.
if (factor == 0) {
// Don't read anything, just set to 0
*v = 0;
} else if (factor == 1) {
uint8_t tmp;
AVIF_CHECK(avifROStreamRead(stream, &tmp, 1));
*v = tmp;
} else if (factor == 2) {
uint16_t tmp;
AVIF_CHECK(avifROStreamReadU16(stream, &tmp));
*v = tmp;
} else if (factor == 4) {
uint32_t tmp;
AVIF_CHECK(avifROStreamReadU32(stream, &tmp));
*v = tmp;
} else if (factor == 8) {
uint64_t tmp;
AVIF_CHECK(avifROStreamReadU64(stream, &tmp));
*v = tmp;
} else {
// Unsupported factor
avifDiagnosticsPrintf(stream->diag, "%s: Failed to read UX8 value; Unsupported UX8 factor [%" PRIu64 "]", stream->diagContext, factor);
return AVIF_FALSE;
}
return AVIF_TRUE;
}
avifBool avifROStreamReadU16(avifROStream * stream, uint16_t * v)
{
assert(stream->numUsedBitsInPartialByte == 0); // Byte alignment is required.
AVIF_CHECK(avifROStreamRead(stream, (uint8_t *)v, sizeof(uint16_t)));
*v = avifNTOHS(*v);
return AVIF_TRUE;
}
avifBool avifROStreamReadU16Endianness(avifROStream * stream, uint16_t * v, avifBool littleEndian)
{
assert(stream->numUsedBitsInPartialByte == 0); // Byte alignment is required.
AVIF_CHECK(avifROStreamRead(stream, (uint8_t *)v, sizeof(uint16_t)));
*v = littleEndian ? avifCTOHS(*v) : avifNTOHS(*v);
return AVIF_TRUE;
}
avifBool avifROStreamReadU32(avifROStream * stream, uint32_t * v)
{
assert(stream->numUsedBitsInPartialByte == 0); // Byte alignment is required.
AVIF_CHECK(avifROStreamRead(stream, (uint8_t *)v, sizeof(uint32_t)));
*v = avifNTOHL(*v);
return AVIF_TRUE;
}
avifBool avifROStreamReadU32Endianness(avifROStream * stream, uint32_t * v, avifBool littleEndian)
{
assert(stream->numUsedBitsInPartialByte == 0); // Byte alignment is required.
AVIF_CHECK(avifROStreamRead(stream, (uint8_t *)v, sizeof(uint32_t)));
*v = littleEndian ? avifCTOHL(*v) : avifNTOHL(*v);
return AVIF_TRUE;
}
avifBool avifROStreamReadU64(avifROStream * stream, uint64_t * v)
{
assert(stream->numUsedBitsInPartialByte == 0); // Byte alignment is required.
AVIF_CHECK(avifROStreamRead(stream, (uint8_t *)v, sizeof(uint64_t)));
*v = avifNTOH64(*v);
return AVIF_TRUE;
}
avifBool avifROStreamSkipBits(avifROStream * stream, size_t bitCount)
{
if (stream->numUsedBitsInPartialByte != 0) {
assert(stream->numUsedBitsInPartialByte < 8);
const size_t padding = AVIF_MIN(8 - stream->numUsedBitsInPartialByte, bitCount);
stream->numUsedBitsInPartialByte = (stream->numUsedBitsInPartialByte + padding) % 8;
bitCount -= padding;
if (bitCount == 0) {
return AVIF_TRUE;
}
}
const size_t num_bytes = (bitCount + 7) / 8;
AVIF_CHECK(avifROStreamSkip(stream, num_bytes));
stream->numUsedBitsInPartialByte = bitCount % 8;
return AVIF_TRUE;
}
avifBool avifROStreamReadBitsU8(avifROStream * stream, uint8_t * v, size_t bitCount)
{
AVIF_CHECK(bitCount <= sizeof(*v) * 8);
uint32_t vU32;
AVIF_CHECK(avifROStreamReadBitsU32(stream, &vU32, bitCount));
*v = (uint8_t)vU32;
return AVIF_TRUE;
}
avifBool avifROStreamReadBitsU16(avifROStream * stream, uint16_t * v, size_t bitCount)
{
AVIF_CHECK(bitCount <= sizeof(*v) * 8);
uint32_t vU32;
AVIF_CHECK(avifROStreamReadBitsU32(stream, &vU32, bitCount));
*v = (uint16_t)vU32;
return AVIF_TRUE;
}
avifBool avifROStreamReadBitsU32(avifROStream * stream, uint32_t * v, size_t bitCount)
{
AVIF_CHECK(bitCount <= sizeof(*v) * 8);
*v = 0;
while (bitCount) {
if (stream->numUsedBitsInPartialByte == 0) {
AVIF_CHECK(avifROStreamSkip(stream, sizeof(uint8_t))); // Book a new partial byte in the stream.
}
assert(stream->offset > 0);
const uint8_t * packedBits = stream->raw->data + stream->offset - 1;
const size_t numBits = AVIF_MIN(bitCount, 8 - stream->numUsedBitsInPartialByte);
stream->numUsedBitsInPartialByte += numBits;
bitCount -= numBits;
// The stream bits are packed starting with the most significant bit of the first input byte.
// This way, packed bits can be found in the same order in the bit stream.
const uint32_t bits = (*packedBits >> (8 - stream->numUsedBitsInPartialByte)) & ((1 << numBits) - 1);
// The value bits are ordered from the most significant bit to the least significant bit.
// In the case where avifROStreamReadBitsU32() is used to parse the unsigned integer value *v
// over multiple aligned bytes, this order corresponds to big endianness.
*v |= bits << bitCount;
if (stream->numUsedBitsInPartialByte == 8) {
// Start a new partial byte the next time a bit is needed.
stream->numUsedBitsInPartialByte = 0;
}
}
return AVIF_TRUE;
}
avifBool avifROStreamReadString(avifROStream * stream, char * output, size_t outputSize)
{
assert(stream->numUsedBitsInPartialByte == 0); // Byte alignment is required.
// Check for the presence of a null terminator in the stream.
size_t remainingBytes = avifROStreamRemainingBytes(stream);
const uint8_t * p = avifROStreamCurrent(stream);
avifBool foundNullTerminator = AVIF_FALSE;
for (size_t i = 0; i < remainingBytes; ++i) {
if (p[i] == 0) {
foundNullTerminator = AVIF_TRUE;
break;
}
}
if (!foundNullTerminator) {
avifDiagnosticsPrintf(stream->diag, "%s: Failed to find a NULL terminator when reading a string", stream->diagContext);
return AVIF_FALSE;
}
const char * streamString = (const char *)p;
size_t stringLen = strlen(streamString);
stream->offset += stringLen + 1; // update the stream to have read the "whole string" in
if (output && outputSize) {
// clamp to our output buffer
if (stringLen >= outputSize) {
stringLen = outputSize - 1;
}
memcpy(output, streamString, stringLen);
output[stringLen] = 0;
}
return AVIF_TRUE;
}
avifBool avifROStreamReadBoxHeaderPartial(avifROStream * stream, avifBoxHeader * header, avifBool topLevel)
{
// Section 4.2.2 of ISO/IEC 14496-12.
size_t startOffset = stream->offset;
uint32_t smallSize;
AVIF_CHECK(avifROStreamReadU32(stream, &smallSize)); // unsigned int(32) size;
AVIF_CHECK(avifROStreamRead(stream, header->type, 4)); // unsigned int(32) type = boxtype;
uint64_t size = smallSize;
if (size == 1) {
AVIF_CHECK(avifROStreamReadU64(stream, &size)); // unsigned int(64) largesize;
}
if (!memcmp(header->type, "uuid", 4)) {
AVIF_CHECK(avifROStreamSkip(stream, 16)); // unsigned int(8) usertype[16] = extended_type;
}
size_t bytesRead = stream->offset - startOffset;
if (size == 0) {
// Section 4.2.2 of ISO/IEC 14496-12.
// if size is 0, then this box shall be in a top-level box (i.e. not contained in another
// box), and be the last box in its 'file', and its payload extends to the end of that
// enclosing 'file'. This is normally only used for a MediaDataBox ('mdat').
if (!topLevel) {
avifDiagnosticsPrintf(stream->diag, "%s: Non-top-level box with size 0", stream->diagContext);
return AVIF_FALSE;
}
// The given stream may be incomplete and there is no guarantee that sizeHint is available and accurate.
// Otherwise size could be set to avifROStreamRemainingBytes(stream) + (stream->offset - startOffset) right now.
// Wait for avifIOReadFunc() to return AVIF_RESULT_OK.
header->isSizeZeroBox = AVIF_TRUE;
header->size = 0;
return AVIF_TRUE;
}
if ((size < bytesRead) || ((size - bytesRead) > SIZE_MAX)) {
avifDiagnosticsPrintf(stream->diag, "%s: Header size overflow check failure", stream->diagContext);
return AVIF_FALSE;
}
header->isSizeZeroBox = AVIF_FALSE;
header->size = (size_t)(size - bytesRead);
return AVIF_TRUE;
}
avifBool avifROStreamReadBoxHeader(avifROStream * stream, avifBoxHeader * header)
{
AVIF_CHECK(avifROStreamReadBoxHeaderPartial(stream, header, /*topLevel=*/AVIF_FALSE));
if (header->size > avifROStreamRemainingBytes(stream)) {
avifDiagnosticsPrintf(stream->diag, "%s: Child box too large, possibly truncated data", stream->diagContext);
return AVIF_FALSE;
}
return AVIF_TRUE;
}
avifBool avifROStreamReadVersionAndFlags(avifROStream * stream, uint8_t * version, uint32_t * flags)
{
uint8_t versionAndFlags[4];
AVIF_CHECK(avifROStreamRead(stream, versionAndFlags, 4));
if (version) {
*version = versionAndFlags[0];
}
if (flags) {
*flags = (versionAndFlags[1] << 16) + (versionAndFlags[2] << 8) + (versionAndFlags[3] << 0);
}
return AVIF_TRUE;
}
avifBool avifROStreamReadAndEnforceVersion(avifROStream * stream, uint8_t enforcedVersion)
{
uint8_t version;
AVIF_CHECK(avifROStreamReadVersionAndFlags(stream, &version, NULL));
if (version != enforcedVersion) {
avifDiagnosticsPrintf(stream->diag, "%s: Expecting box version %u, got version %u", stream->diagContext, enforcedVersion, version);
return AVIF_FALSE;
}
return AVIF_TRUE;
}
// ---------------------------------------------------------------------------
// avifRWStream
#define AVIF_STREAM_BUFFER_INCREMENT (1024 * 1024)
static avifResult makeRoom(avifRWStream * stream, size_t size)
{
size_t neededSize = stream->offset + size;
size_t newSize = stream->raw->size;
while (newSize < neededSize) {
newSize += AVIF_STREAM_BUFFER_INCREMENT;
}
return avifRWDataRealloc(stream->raw, newSize);
}
void avifRWStreamStart(avifRWStream * stream, avifRWData * raw)
{
stream->raw = raw;
stream->offset = 0;
stream->numUsedBitsInPartialByte = 0;
}
size_t avifRWStreamOffset(const avifRWStream * stream)
{
return stream->offset;
}
void avifRWStreamSetOffset(avifRWStream * stream, size_t offset)
{
stream->offset = offset;
if (stream->offset > stream->raw->size) {
stream->offset = stream->raw->size;
}
}
void avifRWStreamFinishWrite(avifRWStream * stream)
{
if (stream->raw->size != stream->offset) {
if (stream->offset) {
stream->raw->size = stream->offset;
} else {
avifRWDataFree(stream->raw);
}
}
}
avifResult avifRWStreamWrite(avifRWStream * stream, const void * data, size_t size)
{
assert(stream->numUsedBitsInPartialByte == 0); // Byte alignment is required.
if (size) {
AVIF_CHECKRES(makeRoom(stream, size));
memcpy(stream->raw->data + stream->offset, data, size);
stream->offset += size;
}
return AVIF_RESULT_OK;
}
avifResult avifRWStreamWriteChars(avifRWStream * stream, const char * chars, size_t size)
{
return avifRWStreamWrite(stream, chars, size);
}
avifResult avifRWStreamWriteFullBox(avifRWStream * stream, const char * type, size_t contentSize, int version, uint32_t flags, avifBoxMarker * marker)
{
assert(stream->numUsedBitsInPartialByte == 0); // Byte alignment is required.
if (marker) {
*marker = stream->offset;
}
size_t headerSize = sizeof(uint32_t) + 4 /* size of type */;
if (version != -1) {
headerSize += 4;
}
AVIF_CHECKRES(makeRoom(stream, headerSize));
memset(stream->raw->data + stream->offset, 0, headerSize);
uint32_t noSize = avifHTONL((uint32_t)(headerSize + contentSize));
memcpy(stream->raw->data + stream->offset, &noSize, sizeof(uint32_t));
memcpy(stream->raw->data + stream->offset + 4, type, 4);
if (version != -1) {
stream->raw->data[stream->offset + 8] = (uint8_t)version;
stream->raw->data[stream->offset + 9] = (uint8_t)((flags >> 16) & 0xff);
stream->raw->data[stream->offset + 10] = (uint8_t)((flags >> 8) & 0xff);
stream->raw->data[stream->offset + 11] = (uint8_t)((flags >> 0) & 0xff);
}
stream->offset += headerSize;
return AVIF_RESULT_OK;
}
avifResult avifRWStreamWriteBox(avifRWStream * stream, const char * type, size_t contentSize, avifBoxMarker * marker)
{
return avifRWStreamWriteFullBox(stream, type, contentSize, -1, 0, marker);
}
void avifRWStreamFinishBox(avifRWStream * stream, avifBoxMarker marker)
{
assert(stream->numUsedBitsInPartialByte == 0); // Byte alignment is required.
uint32_t noSize = avifHTONL((uint32_t)(stream->offset - marker));
memcpy(stream->raw->data + marker, &noSize, sizeof(uint32_t));
}
avifResult avifRWStreamWriteU8(avifRWStream * stream, uint8_t v)
{
assert(stream->numUsedBitsInPartialByte == 0); // Byte alignment is required.
AVIF_CHECKRES(makeRoom(stream, 1));
stream->raw->data[stream->offset] = v;
stream->offset += 1;
return AVIF_RESULT_OK;
}
avifResult avifRWStreamWriteU16(avifRWStream * stream, uint16_t v)
{
assert(stream->numUsedBitsInPartialByte == 0); // Byte alignment is required.
const size_t size = sizeof(uint16_t);
AVIF_CHECKRES(makeRoom(stream, size));
v = avifHTONS(v);
memcpy(stream->raw->data + stream->offset, &v, size);
stream->offset += size;
return AVIF_RESULT_OK;
}
avifResult avifRWStreamWriteU32(avifRWStream * stream, uint32_t v)
{
assert(stream->numUsedBitsInPartialByte == 0); // Byte alignment is required.
const size_t size = sizeof(uint32_t);
AVIF_CHECKRES(makeRoom(stream, size));
v = avifHTONL(v);
memcpy(stream->raw->data + stream->offset, &v, size);
stream->offset += size;
return AVIF_RESULT_OK;
}
avifResult avifRWStreamWriteU64(avifRWStream * stream, uint64_t v)
{
assert(stream->numUsedBitsInPartialByte == 0); // Byte alignment is required.
const size_t size = sizeof(uint64_t);
AVIF_CHECKRES(makeRoom(stream, size));
v = avifHTON64(v);
memcpy(stream->raw->data + stream->offset, &v, size);
stream->offset += size;
return AVIF_RESULT_OK;
}
avifResult avifRWStreamWriteZeros(avifRWStream * stream, size_t byteCount)
{
assert(stream->numUsedBitsInPartialByte == 0); // Byte alignment is required.
AVIF_CHECKRES(makeRoom(stream, byteCount));
memset(stream->raw->data + stream->offset, 0, byteCount);
stream->offset += byteCount;
return AVIF_RESULT_OK;
}
avifResult avifRWStreamWriteBits(avifRWStream * stream, uint32_t v, size_t bitCount)
{
AVIF_CHECKERR(bitCount >= 32 || (v >> bitCount) == 0, AVIF_RESULT_INVALID_ARGUMENT);
while (bitCount) {
if (stream->numUsedBitsInPartialByte == 0) {
AVIF_CHECKRES(makeRoom(stream, 1)); // Book a new partial byte in the stream.
stream->raw->data[stream->offset] = 0;
stream->offset += 1;
}
assert(stream->offset > 0);
uint8_t * packedBits = stream->raw->data + stream->offset - 1;
const size_t numBits = AVIF_MIN(bitCount, 8 - stream->numUsedBitsInPartialByte);
stream->numUsedBitsInPartialByte += numBits;
bitCount -= numBits;
// Order the input bits from the most significant bit to the least significant bit.
// In the case where avifRWStreamWriteBits() is used to write the unsigned integer value v
// over multiple aligned bytes, this order corresponds to big endianness.
const uint32_t bits = (v >> bitCount) & ((1 << numBits) - 1);
// Pack bits starting with the most significant bit of the first output byte.
// This way, packed bits can be found in the same order in the bit stream.
*packedBits |= bits << (8 - stream->numUsedBitsInPartialByte);
if (stream->numUsedBitsInPartialByte == 8) {
// Start a new partial byte the next time a bit is needed.
stream->numUsedBitsInPartialByte = 0;
}
}
return AVIF_RESULT_OK;
}