1 // Copyright 2020 Joe Drago. All rights reserved.
2 // SPDX-License-Identifier: BSD-2-Clause
3
4 // #define WIN32_MEMORY_LEAK_DETECTION
5 #ifdef WIN32_MEMORY_LEAK_DETECTION
6 #define _CRTDBG_MAP_ALLOC
7 #include <crtdbg.h>
8 #endif
9
10 #include "avif/avif.h"
11 #include "avif/libavif_compat.h"
12
13 #include "aviftest_helpers.h"
14
15 #include <inttypes.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19
20 #define AVIF_DATA_EMPTY { NULL, 0 }
21
22 #if defined(_WIN32)
23
24 #include <windows.h>
25
26 typedef struct NextFilenameData
27 {
28 int didFirstFile;
29 HANDLE handle;
30 WIN32_FIND_DATA wfd;
31 } NextFilenameData;
32
nextFilename(const char * parentDir,const char * extension,NextFilenameData * nfd)33 static const char * nextFilename(const char * parentDir, const char * extension, NextFilenameData * nfd)
34 {
35 for (;;) {
36 if (nfd->didFirstFile) {
37 if (FindNextFile(nfd->handle, &nfd->wfd) == 0) {
38 // No more files
39 break;
40 }
41 } else {
42 char filenameBuffer[2048];
43 snprintf(filenameBuffer, sizeof(filenameBuffer), "%s\\*", parentDir);
44 filenameBuffer[sizeof(filenameBuffer) - 1] = 0;
45 nfd->handle = FindFirstFile(filenameBuffer, &nfd->wfd);
46 if (nfd->handle == INVALID_HANDLE_VALUE) {
47 return NULL;
48 }
49 nfd->didFirstFile = 1;
50 }
51
52 // If we get here, we should have a valid wfd
53 const char * dot = strrchr(nfd->wfd.cFileName, '.');
54 if (dot) {
55 ++dot;
56 if (!strcmp(dot, extension)) {
57 return nfd->wfd.cFileName;
58 }
59 }
60 }
61
62 FindClose(nfd->handle);
63 nfd->handle = INVALID_HANDLE_VALUE;
64 nfd->didFirstFile = 0;
65 return NULL;
66 }
67
68 #else
69 #include <dirent.h>
70 typedef struct NextFilenameData
71 {
72 DIR * dir;
73 } NextFilenameData;
74
nextFilename(const char * parentDir,const char * extension,NextFilenameData * nfd)75 static const char * nextFilename(const char * parentDir, const char * extension, NextFilenameData * nfd)
76 {
77 if (!nfd->dir) {
78 nfd->dir = opendir(parentDir);
79 if (!nfd->dir) {
80 return NULL;
81 }
82 }
83
84 struct dirent * entry;
85 while ((entry = readdir(nfd->dir)) != NULL) {
86 const char * dot = strrchr(entry->d_name, '.');
87 if (dot) {
88 ++dot;
89 if (!strcmp(dot, extension)) {
90 return entry->d_name;
91 }
92 }
93 }
94
95 closedir(nfd->dir);
96 nfd->dir = NULL;
97 return NULL;
98 }
99 #endif
100
101 typedef struct avifIOMeta {
102 avifROData rodata;
103 size_t availableBytes;
104 } avifIOMeta ;
105
avifIOTestReaderRead(struct avifIO * io,uint32_t readFlags,uint64_t offset,size_t size,avifROData * out)106 static avifResult avifIOTestReaderRead(struct avifIO * io, uint32_t readFlags, uint64_t offset, size_t size, avifROData * out)
107 {
108 //printf("### avifIOTestReaderRead offset %" PRIu64 " size %zu\n", offset, size);
109
110 if (readFlags != 0) {
111 // Unsupported readFlags
112 return AVIF_RESULT_IO_ERROR;
113 }
114
115 avifIOMeta * reader = (avifIOMeta *)io->data;
116
117 // Sanitize/clamp incoming request
118 if (offset > reader->rodata.size) {
119 // The offset is past the end of the buffer.
120 return AVIF_RESULT_IO_ERROR;
121 }
122 if (offset == reader->rodata.size) {
123 // The parser is *exactly* at EOF: return a 0-size pointer to any valid buffer
124 offset = 0;
125 size = 0;
126 }
127 uint64_t availableSize = reader->rodata.size - offset;
128 if (size > availableSize) {
129 size = (size_t)availableSize;
130 }
131
132 if (offset > reader->availableBytes) {
133 return AVIF_RESULT_WAITING_ON_IO;
134 }
135 if (size > (reader->availableBytes - offset)) {
136 return AVIF_RESULT_WAITING_ON_IO;
137 }
138
139 out->data = reader->rodata.data + offset;
140 out->size = size;
141 return AVIF_RESULT_OK;
142 }
143
avifIOTestReaderDestroy(struct avifIO * io)144 static void avifIOTestReaderDestroy(struct avifIO * io)
145 {
146 avifIOMeta* meta = (avifIOMeta*) io->data;
147 free(meta);
148 free(io);
149 }
150
avifIOCreateTestReader(const uint8_t * data,size_t size)151 static avifIO * avifIOCreateTestReader(const uint8_t * data, size_t size)
152 {
153 printf("### creating reader of size: %zu\n", size);
154 avifIO * io = reinterpret_cast<avifIO *>(malloc(sizeof(avifIO)));
155 memset(io, 0, sizeof(avifIO));
156 io->destroy = &avifIOTestReaderDestroy;
157 io->read = &avifIOTestReaderRead;
158 io->sizeHint = size;
159 io->persistent = AVIF_TRUE;
160 avifIOMeta * meta = reinterpret_cast<avifIOMeta *>(malloc(sizeof(avifIOMeta)));
161 meta->rodata.data = data;
162 meta->rodata.size = size;
163 meta->availableBytes = 0;
164 io->data = meta;
165 return io;
166 }
167
168 #define FILENAME_MAX_LENGTH 2047
169
runIOTests(const char * dataDir)170 static int runIOTests(const char * dataDir)
171 {
172 printf("AVIF Test Suite: Running IO Tests...\n");
173
174 static const char * ioSuffix = "/io/";
175
176 char ioDir[FILENAME_MAX_LENGTH + 1];
177 size_t dataDirLen = strlen(dataDir);
178 size_t ioSuffixLen = strlen(ioSuffix);
179
180 if ((dataDirLen + ioSuffixLen) > FILENAME_MAX_LENGTH) {
181 printf("Path too long: %s\n", dataDir);
182 return 1;
183 }
184 strcpy(ioDir, dataDir);
185 strcat(ioDir, ioSuffix);
186 size_t ioDirLen = strlen(ioDir);
187
188 int retCode = 0;
189
190 NextFilenameData nfd;
191 memset(&nfd, 0, sizeof(nfd));
192 avifRWData fileBuffer = AVIF_DATA_EMPTY;
193 const char * filename = nextFilename(ioDir, "avif", &nfd);
194 for (; filename != NULL; filename = nextFilename(ioDir, "avif", &nfd)) {
195 char fullFilename[FILENAME_MAX_LENGTH + 1];
196 size_t filenameLen = strlen(filename);
197 if ((ioDirLen + filenameLen) > FILENAME_MAX_LENGTH) {
198 printf("Path too long: %s\n", filename);
199 retCode = 1;
200 break;
201 }
202 strcpy(fullFilename, ioDir);
203 strcat(fullFilename, filename);
204
205 FILE * f = fopen(fullFilename, "rb");
206 if (!f) {
207 printf("Can't open for read: %s\n", filename);
208 retCode = 1;
209 break;
210 }
211 fseek(f, 0, SEEK_END);
212 size_t fileSize = ftell(f);
213 fseek(f, 0, SEEK_SET);
214 if (avifRWDataRealloc(&fileBuffer, fileSize) != AVIF_RESULT_OK) {
215 printf("Out of memory when allocating buffer to read file: %s\n", filename);
216 fclose(f);
217 retCode = 1;
218 break;
219 }
220 if (fread(fileBuffer.data, 1, fileSize, f) != fileSize) {
221 printf("Can't read entire file: %s\n", filename);
222 fclose(f);
223 retCode = 1;
224 break;
225 }
226 fclose(f);
227
228 avifDecoder * decoder = avifDecoderCreate();
229 if (decoder == NULL) {
230 printf("Memory allocation failure\n");
231 retCode = 1;
232 break;
233 }
234 avifIO * io = avifIOCreateTestReader(fileBuffer.data, fileBuffer.size);
235 avifIOMeta * meta = (avifIOMeta*) io->data;
236 avifDecoderSetIO(decoder, (avifIO *)io);
237
238 for (int pass = 0; pass < 4; ++pass) {
239 io->persistent = ((pass % 2) == 0);
240 decoder->ignoreExif = decoder->ignoreXMP = (pass < 2);
241
242 // Slowly pretend to have streamed-in / downloaded more and more bytes
243 avifResult parseResult = AVIF_RESULT_UNKNOWN_ERROR;
244 for (meta->availableBytes = 0; meta->availableBytes <= io->sizeHint; ++meta->availableBytes) {
245 parseResult = avifDecoderParse(decoder);
246 if (parseResult == AVIF_RESULT_WAITING_ON_IO) {
247 continue;
248 }
249 if (parseResult != AVIF_RESULT_OK) {
250 retCode = 1;
251 }
252
253 printf("File: [%s @ %zu / %" PRIu64 " bytes, %s, %s] parse returned: (%d) %s\n",
254 filename,
255 meta->availableBytes,
256 io->sizeHint,
257 io->persistent ? "Persistent" : "NonPersistent",
258 decoder->ignoreExif ? "IgnoreMetadata" : "Metadata",
259 parseResult,
260 avifResultToString(parseResult));
261 break;
262 }
263
264 if (parseResult == AVIF_RESULT_OK) {
265 for (; meta->availableBytes <= io->sizeHint; ++meta->availableBytes) {
266 avifExtent extent;
267 avifResult extentResult = avifDecoderNthImageMaxExtent(decoder, 0, &extent);
268 if (extentResult != AVIF_RESULT_OK) {
269 retCode = 1;
270
271 printf("File: [%s @ %zu / %" PRIu64 " bytes, %s, %s] maxExtent returned: %s\n",
272 filename,
273 meta->availableBytes,
274 io->sizeHint,
275 io->persistent ? "Persistent" : "NonPersistent",
276 decoder->ignoreExif ? "IgnoreMetadata" : "Metadata",
277 avifResultToString(extentResult));
278 } else {
279 avifResult nextImageResult = avifDecoderNextImage(decoder);
280 if (nextImageResult == AVIF_RESULT_WAITING_ON_IO) {
281 continue;
282 }
283 if (nextImageResult != AVIF_RESULT_OK) {
284 retCode = 1;
285 }
286
287 printf("File: [%s @ %zu / %" PRIu64 " bytes, %s, %s] nextImage [MaxExtent off %" PRIu64 ", size %zu] returned: (%d) %s\n",
288 filename,
289 meta->availableBytes,
290 io->sizeHint,
291 io->persistent ? "Persistent" : "NonPersistent",
292 decoder->ignoreExif ? "IgnoreMetadata" : "Metadata",
293 extent.offset,
294 extent.size,
295 nextImageResult,
296 avifResultToString(nextImageResult));
297 }
298 break;
299 }
300 }
301 }
302
303 avifDecoderDestroy(decoder);
304 }
305
306 avifRWDataFree(&fileBuffer);
307 return retCode;
308 }
309
syntax(void)310 static void syntax(void)
311 {
312 fprintf(stderr, "Syntax: aviftest dataDir\n");
313 }
314
main(int argc,char * argv[])315 int main(int argc, char * argv[])
316 {
317 const char * dataDir = NULL;
318
319 #ifdef WIN32_MEMORY_LEAK_DETECTION
320 _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
321 // _CrtSetBreakAlloc(2906);
322 #endif
323
324 // Parse cmdline
325 for (int i = 1; i < argc; ++i) {
326 char * arg = argv[i];
327 if (!strcmp(arg, "--io-only")) {
328 fprintf(stderr, "WARNING: --io-only is deprecated; ignoring.\n");
329 } else if (dataDir == NULL) {
330 dataDir = arg;
331 } else {
332 fprintf(stderr, "Too many positional arguments: %s\n", arg);
333 syntax();
334 return 1;
335 }
336 }
337
338 // Verify all required args were set
339 if (dataDir == NULL) {
340 fprintf(stderr, "dataDir is required, bailing out.\n");
341 syntax();
342 return 1;
343 }
344
345 setbuf(stdout, NULL);
346
347 char codecVersions[256] = {0};
348 //avifCodecVersions(codecVersions);
349 printf("Codec Versions: %s\n", codecVersions);
350 printf("Test Data Dir : %s\n", dataDir);
351
352 int retCode = runIOTests(dataDir);
353 if (retCode == 0) {
354 printf("AVIF Test Suite: Complete.\n");
355 } else {
356 printf("AVIF Test Suite: Failed.\n");
357 }
358 return retCode;
359 }
360