blob: 837c10b6d7a73f1cae853869a8fabdcd9c78b92d [file] [log] [blame]
#include "ProtoToGif.h"
using namespace gifProtoFuzzer;
using namespace std;
constexpr unsigned char ProtoConverter::m_sig[];
constexpr unsigned char ProtoConverter::m_ver89a[];
constexpr unsigned char ProtoConverter::m_ver87a[];
string ProtoConverter::gifProtoToString(GifProto const &proto)
{
visit(proto);
return m_output.str();
}
void ProtoConverter::visit(GifProto const &gif)
{
visit(gif.header());
visit(gif.lsd());
if (m_hasGCT)
visit(gif.gct());
for (auto const &chunk : gif.chunks())
visit(chunk);
visit(gif.trailer());
}
void ProtoConverter::visit(Header const &header)
{
// Signature GIF
m_output.write((const char *)m_sig, sizeof(m_sig));
switch (header.ver())
{
case Header::ENA:
m_output.write((const char *)m_ver89a, sizeof(m_ver89a));
break;
case Header::ESA:
m_output.write((const char *)m_ver87a, sizeof(m_ver87a));
break;
// We simply don't write anything if it's an invalid version
// Bytes that follow (LSD) will be interpreted as version
case Header::INV:
break;
}
}
void ProtoConverter::visit(LogicalScreenDescriptor const &lsd)
{
writeWord(extractWordFromUInt32(lsd.screenwidth()));
writeWord(extractWordFromUInt32(lsd.screenheight()));
uint8_t packedByte = extractByteFromUInt32(lsd.packed());
// If MSB of packed byte is 1, GCT follows
if (packedByte & 0x80)
{
m_hasGCT = true;
// N: 2^(N+1) colors in GCT
m_globalColorExp = packedByte & 0x07;
}
writeByte(packedByte);
writeByte(extractByteFromUInt32(lsd.backgroundcolor()));
writeByte(extractByteFromUInt32(lsd.aspectratio()));
}
void ProtoConverter::visit(GlobalColorTable const &gct)
{
//[TODO 27/04/2019 VU]: Should it really be exactly the same size? Or do we want some deterministic randomness here?
// TODO BS: We never overflow expected table size due to the use of min
uint32_t tableSize = min((uint32_t)gct.colors().size(), tableExpToTableSize(m_globalColorExp));
m_output.write(gct.colors().data(), tableSize);
}
void ProtoConverter::visit(GraphicControlExtension const &gce)
{
writeByte(0x21); // Extension Introducer
writeByte(0xF9); // Graphic Control Label
writeByte(4); // Block size
uint8_t packedByte = extractByteFromUInt32(gce.packed());
// packed byte
writeByte(packedByte);
// Delay time is 2 bytes
writeWord(extractWordFromUInt32(gce.delaytime()));
// Transparent color index is 1 byte
writeByte(extractByteFromUInt32(gce.transparentcolorindex()));
writeByte(0x0); // Block Terminator
}
void ProtoConverter::visit(ImageChunk const &chunk)
{
switch (chunk.chunk_oneof_case())
{
case ImageChunk::kBasic:
visit(chunk.basic());
break;
case ImageChunk::kPlaintext:
visit(chunk.plaintext());
break;
case ImageChunk::kAppExt:
visit(chunk.appext());
break;
case ImageChunk::kComExt:
visit(chunk.comext());
break;
case ImageChunk::CHUNK_ONEOF_NOT_SET:
break;
}
}
void ProtoConverter::visit(const BasicChunk &chunk)
{
// Visit GCExt if necessary
if (chunk.has_gcext())
visit(chunk.gcext());
visit(chunk.imdescriptor());
if (m_hasLCT)
visit(chunk.lct());
visit(chunk.img());
}
void ProtoConverter::visit(LocalColorTable const &lct)
{
//[TODO 27/04/2019 VU]: Should it really be exactly the same size? Or do we want some deterministic randomness here?
// TODO BS: We never overflow expected table size due to the use of min
uint32_t tableSize = min((uint32_t)lct.colors().size(), tableExpToTableSize(m_localColorExp));
m_output.write(lct.colors().data(), tableSize);
}
void ProtoConverter::visit(ImageDescriptor const &descriptor)
{
// TODO: Remove seperator from proto since it is always 2C
writeByte(0x2C);
writeWord(extractWordFromUInt32(descriptor.left()));
writeWord(extractWordFromUInt32(descriptor.top()));
writeWord(extractWordFromUInt32(descriptor.height()));
writeWord(extractWordFromUInt32(descriptor.width()));
uint8_t packedByte = extractByteFromUInt32(descriptor.packed());
if (packedByte & 0x80)
{
m_hasLCT = true;
m_localColorExp = packedByte & 0x07;
}
else
m_hasLCT = false;
}
void ProtoConverter::visit(SubBlock const &block)
{
uint8_t len = extractByteFromUInt32(block.len());
if (len == 0)
{
writeByte(0x00);
}
else
{
// TODO BS: We never overflow expected block size due to the use of min
uint32_t write_len = min((uint32_t)len, (uint32_t)block.data().size());
m_output.write(block.data().data(), write_len);
}
}
void ProtoConverter::visit(ImageData const &img)
{
// TODO: Verify we are writing the image data correctly
// LZW
writeByte(extractByteFromUInt32(img.lzw()));
// Sub-blocks
for (auto const &block : img.subs())
visit(block);
// NULL sub block signals end of image data
writeByte(0x00);
}
void ProtoConverter::visit(PlainTextExtension const &ptExt)
{
// Visit GCExt if necessary
if (ptExt.has_gcext())
visit(ptExt.gcext());
// First two bytes are 0x21 0x01
writeByte(0x21);
writeByte(0x01);
// Skip zero bytes
writeByte(0x00);
for (auto const &block : ptExt.subs())
visit(block);
// NULL sub block signals end
writeByte(0x00);
}
void ProtoConverter::visit(CommentExtension const &comExt)
{
// First two bytes are 0x21 0xFE
writeByte(0x21);
writeByte(0xFE);
// Sub-blocks
for (auto const &block : comExt.subs())
visit(block);
// NULL sub block signals end of image data
writeByte(0x00);
}
void ProtoConverter::visit(ApplicationExtension const &appExt)
{
// First two bytes are 0x21 0xFF
writeByte(0x21);
writeByte(0xFF);
// Next, we write "11" decimal or 0x0B
writeByte(0x0B);
writeLong(appExt.appid());
// We hardcode the auth code to 1.0 or 0x31 0x2E 0x30
writeByte(0x31);
writeByte(0x2E);
writeByte(0x30);
// Sub-blocks
for (auto const &block : appExt.subs())
visit(block);
// NULL sub block signals end of image data
writeByte(0x00);
}
void ProtoConverter::visit(Trailer const &)
{
writeByte(0x3B);
}
// =============================================================
// Utility functions
// =============================================================
void ProtoConverter::writeByte(uint8_t x)
{
m_output.write((char *)&x, sizeof(x));
}
void ProtoConverter::writeWord(uint16_t x)
{
m_output.write((char *)&x, sizeof(x));
}
void ProtoConverter::writeInt(uint32_t x)
{
m_output.write((char *)&x, sizeof(x));
}
void ProtoConverter::writeLong(uint64_t x)
{
m_output.write((char *)&x, sizeof(x));
}
uint16_t ProtoConverter::extractWordFromUInt32(uint32_t a)
{
uint16_t first_byte = (a & 0xFF);
uint16_t second_byte = ((a >> 8) & 0xFF) << 8;
return first_byte | second_byte;
}
uint8_t ProtoConverter::extractByteFromUInt32(uint32_t a)
{
uint8_t byte = a & 0x80;
return byte;
}
/**
* Given an exponent, returns the global/local color table size, given by 3*2^(exp+1)
* @param tableExp The exponent
* @return The actual color table size
*/
uint32_t ProtoConverter::tableExpToTableSize(uint32_t tableExp)
{
// 0 <= tableExp <= 7
// 6 <= tableSize <= 768
uint32_t tableSize = 3 * (pow(2, tableExp + 1));
return tableSize;
}