#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; }