You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
274 lines
6.8 KiB
274 lines
6.8 KiB
#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;
|
|
}
|