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