274 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			274 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			C++
		
	
	
	
| #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;
 | |
| }
 |