Coverage Report

Created: 2026-06-15 06:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ProtoToGif.cpp
Line
Count
Source
1
#include "ProtoToGif.h"
2
3
using namespace gifProtoFuzzer;
4
using namespace std;
5
6
constexpr unsigned char ProtoConverter::m_sig[];
7
constexpr unsigned char ProtoConverter::m_ver89a[];
8
constexpr unsigned char ProtoConverter::m_ver87a[];
9
10
string ProtoConverter::gifProtoToString(GifProto const &proto)
11
1.04k
{
12
1.04k
  visit(proto);
13
1.04k
  return m_output.str();
14
1.04k
}
15
16
void ProtoConverter::visit(GifProto const &gif)
17
1.04k
{
18
1.04k
  visit(gif.header());
19
1.04k
  visit(gif.lsd());
20
1.04k
  if (m_hasGCT)
21
327
    visit(gif.gct());
22
1.04k
  for (auto const &chunk : gif.chunks())
23
6.48k
    visit(chunk);
24
1.04k
  visit(gif.trailer());
25
1.04k
}
26
27
void ProtoConverter::visit(Header const &header)
28
1.04k
{
29
  // Signature GIF
30
1.04k
  m_output.write((const char *)m_sig, sizeof(m_sig));
31
32
1.04k
  switch (header.ver())
33
1.04k
  {
34
515
  case Header::ENA:
35
515
    m_output.write((const char *)m_ver89a, sizeof(m_ver89a));
36
515
    break;
37
141
  case Header::ESA:
38
141
    m_output.write((const char *)m_ver87a, sizeof(m_ver87a));
39
141
    break;
40
  // We simply don't write anything if it's an invalid version
41
  // Bytes that follow (LSD) will be interpreted as version
42
390
  case Header::INV:
43
390
    break;
44
1.04k
  }
45
1.04k
}
46
47
void ProtoConverter::visit(LogicalScreenDescriptor const &lsd)
48
1.04k
{
49
1.04k
  writeWord(extractWordFromUInt32(lsd.screenwidth()));
50
1.04k
  writeWord(extractWordFromUInt32(lsd.screenheight()));
51
52
1.04k
  uint8_t packedByte = extractByteFromUInt32(lsd.packed());
53
  // If MSB of packed byte is 1, GCT follows
54
1.04k
  if (packedByte & 0x80)
55
327
  {
56
327
    m_hasGCT = true;
57
    // N: 2^(N+1) colors in GCT
58
327
    m_globalColorExp = packedByte & 0x07;
59
327
  }
60
1.04k
  writeByte(packedByte);
61
1.04k
  writeByte(extractByteFromUInt32(lsd.backgroundcolor()));
62
1.04k
  writeByte(extractByteFromUInt32(lsd.aspectratio()));
63
1.04k
}
64
65
void ProtoConverter::visit(GlobalColorTable const &gct)
66
327
{
67
  //[TODO 27/04/2019 VU]: Should it really be exactly the same size? Or do we want some deterministic randomness here?
68
  // TODO BS: We never overflow expected table size due to the use of min
69
327
  uint32_t tableSize = min((uint32_t)gct.colors().size(), tableExpToTableSize(m_globalColorExp));
70
327
  m_output.write(gct.colors().data(), tableSize);
71
327
}
72
73
void ProtoConverter::visit(GraphicControlExtension const &gce)
74
692
{
75
692
  writeByte(0x21); // Extension Introducer
76
692
  writeByte(0xF9); // Graphic Control Label
77
692
  writeByte(4); // Block size
78
692
  uint8_t packedByte = extractByteFromUInt32(gce.packed());
79
  // packed byte
80
692
  writeByte(packedByte);
81
  // Delay time is 2 bytes
82
692
  writeWord(extractWordFromUInt32(gce.delaytime()));
83
  // Transparent color index is 1 byte
84
692
  writeByte(extractByteFromUInt32(gce.transparentcolorindex()));
85
692
  writeByte(0x0); // Block Terminator
86
692
}
87
88
void ProtoConverter::visit(ImageChunk const &chunk)
89
6.48k
{
90
6.48k
  switch (chunk.chunk_oneof_case())
91
6.48k
  {
92
1.66k
  case ImageChunk::kBasic:
93
1.66k
    visit(chunk.basic());
94
1.66k
    break;
95
1.45k
  case ImageChunk::kPlaintext:
96
1.45k
    visit(chunk.plaintext());
97
1.45k
    break;
98
1.53k
  case ImageChunk::kAppExt:
99
1.53k
    visit(chunk.appext());
100
1.53k
    break;
101
751
  case ImageChunk::kComExt:
102
751
    visit(chunk.comext());
103
751
    break;
104
1.07k
  case ImageChunk::CHUNK_ONEOF_NOT_SET:
105
1.07k
    break;
106
6.48k
  }
107
6.48k
}
108
109
void ProtoConverter::visit(const BasicChunk &chunk)
110
1.66k
{
111
  // Visit GCExt if necessary
112
1.66k
  if (chunk.has_gcext())
113
208
    visit(chunk.gcext());
114
1.66k
  visit(chunk.imdescriptor());
115
1.66k
  if (m_hasLCT)
116
644
    visit(chunk.lct());
117
1.66k
  visit(chunk.img());
118
1.66k
}
119
120
void ProtoConverter::visit(LocalColorTable const &lct)
121
644
{
122
  //[TODO 27/04/2019 VU]: Should it really be exactly the same size? Or do we want some deterministic randomness here?
123
  // TODO BS: We never overflow expected table size due to the use of min
124
644
  uint32_t tableSize = min((uint32_t)lct.colors().size(), tableExpToTableSize(m_localColorExp));
125
644
  m_output.write(lct.colors().data(), tableSize);
126
644
}
127
128
void ProtoConverter::visit(ImageDescriptor const &descriptor)
129
1.66k
{
130
  // TODO: Remove seperator from proto since it is always 2C
131
1.66k
  writeByte(0x2C);
132
1.66k
  writeWord(extractWordFromUInt32(descriptor.left()));
133
1.66k
  writeWord(extractWordFromUInt32(descriptor.top()));
134
1.66k
  writeWord(extractWordFromUInt32(descriptor.height()));
135
1.66k
  writeWord(extractWordFromUInt32(descriptor.width()));
136
1.66k
  uint8_t packedByte = extractByteFromUInt32(descriptor.packed());
137
1.66k
  if (packedByte & 0x80)
138
644
  {
139
644
    m_hasLCT = true;
140
644
    m_localColorExp = packedByte & 0x07;
141
644
  }
142
1.02k
  else
143
1.02k
    m_hasLCT = false;
144
1.66k
}
145
146
void ProtoConverter::visit(SubBlock const &block)
147
25.4k
{
148
25.4k
  uint8_t len = extractByteFromUInt32(block.len());
149
25.4k
  if (len == 0)
150
3.54k
  {
151
3.54k
    writeByte(0x00);
152
3.54k
  }
153
21.8k
  else
154
21.8k
  {
155
    // TODO BS: We never overflow expected block size due to the use of min
156
21.8k
    uint32_t write_len = min((uint32_t)len, (uint32_t)block.data().size());
157
21.8k
    m_output.write(block.data().data(), write_len);
158
21.8k
  }
159
25.4k
}
160
161
void ProtoConverter::visit(ImageData const &img)
162
1.66k
{
163
  // TODO: Verify we are writing the image data correctly
164
  // LZW
165
1.66k
  writeByte(extractByteFromUInt32(img.lzw()));
166
  // Sub-blocks
167
1.66k
  for (auto const &block : img.subs())
168
1.21k
    visit(block);
169
  // NULL sub block signals end of image data
170
1.66k
  writeByte(0x00);
171
1.66k
}
172
173
void ProtoConverter::visit(PlainTextExtension const &ptExt)
174
1.45k
{
175
  // Visit GCExt if necessary
176
1.45k
  if (ptExt.has_gcext())
177
484
    visit(ptExt.gcext());
178
179
  // First two bytes are 0x21 0x01
180
1.45k
  writeByte(0x21);
181
1.45k
  writeByte(0x01);
182
  // Skip zero bytes
183
1.45k
  writeByte(0x00);
184
1.45k
  for (auto const &block : ptExt.subs())
185
8.57k
    visit(block);
186
  // NULL sub block signals end
187
1.45k
  writeByte(0x00);
188
1.45k
}
189
190
void ProtoConverter::visit(CommentExtension const &comExt)
191
751
{
192
  // First two bytes are 0x21 0xFE
193
751
  writeByte(0x21);
194
751
  writeByte(0xFE);
195
  // Sub-blocks
196
751
  for (auto const &block : comExt.subs())
197
12.9k
    visit(block);
198
  // NULL sub block signals end of image data
199
751
  writeByte(0x00);
200
751
}
201
202
void ProtoConverter::visit(ApplicationExtension const &appExt)
203
1.53k
{
204
  // First two bytes are 0x21 0xFF
205
1.53k
  writeByte(0x21);
206
1.53k
  writeByte(0xFF);
207
  // Next, we write "11" decimal or 0x0B
208
1.53k
  writeByte(0x0B);
209
1.53k
  writeLong(appExt.appid());
210
  // We hardcode the auth code to 1.0 or 0x31 0x2E 0x30
211
1.53k
  writeByte(0x31);
212
1.53k
  writeByte(0x2E);
213
1.53k
  writeByte(0x30);
214
  // Sub-blocks
215
1.53k
  for (auto const &block : appExt.subs())
216
2.63k
    visit(block);
217
  // NULL sub block signals end of image data
218
1.53k
  writeByte(0x00);
219
1.53k
}
220
221
void ProtoConverter::visit(Trailer const &)
222
1.04k
{
223
1.04k
  writeByte(0x3B);
224
1.04k
}
225
226
// =============================================================
227
// Utility functions
228
// =============================================================
229
void ProtoConverter::writeByte(uint8_t x)
230
35.7k
{
231
35.7k
  m_output.write((char *)&x, sizeof(x));
232
35.7k
}
233
234
void ProtoConverter::writeWord(uint16_t x)
235
9.44k
{
236
9.44k
  m_output.write((char *)&x, sizeof(x));
237
9.44k
}
238
239
void ProtoConverter::writeInt(uint32_t x)
240
0
{
241
0
  m_output.write((char *)&x, sizeof(x));
242
0
}
243
244
void ProtoConverter::writeLong(uint64_t x)
245
1.53k
{
246
1.53k
  m_output.write((char *)&x, sizeof(x));
247
1.53k
}
248
249
uint16_t ProtoConverter::extractWordFromUInt32(uint32_t a)
250
9.44k
{
251
9.44k
  uint16_t first_byte = (a & 0xFF);
252
9.44k
  uint16_t second_byte = ((a >> 8) & 0xFF) << 8;
253
9.44k
  return first_byte | second_byte;
254
9.44k
}
255
256
uint8_t ProtoConverter::extractByteFromUInt32(uint32_t a)
257
33.2k
{
258
33.2k
  uint8_t byte = a & 0x80;
259
33.2k
  return byte;
260
33.2k
}
261
262
/**
263
 * Given an exponent, returns the global/local color table size, given by 3*2^(exp+1)
264
 * @param tableExp The exponent
265
 * @return The actual color table size
266
 */
267
uint32_t ProtoConverter::tableExpToTableSize(uint32_t tableExp)
268
971
{
269
  // 0 <= tableExp <= 7
270
  // 6 <= tableSize <= 768
271
971
  uint32_t tableSize = 3 * (pow(2, tableExp + 1));
272
971
  return tableSize;
273
971
}