1 /*
2  * Copyright (c) 2012-2021, The OSKAR Developers.
3  * See the LICENSE file at the top-level directory of this distribution.
4  */
5 
6 #include "binary/oskar_binary.h"
7 #include "binary/oskar_endian.h"
8 #include "binary/private_binary.h"
9 #include <string.h>
10 #include <stdlib.h>
11 #include <stdio.h>
12 #ifndef _MSC_VER
13 #include <sys/types.h>
14 #endif
15 
16 #ifdef __cplusplus
17 extern "C" {
18 #endif
19 
20 #define MIN(X,Y) ((X) < (Y) ? (X) : (Y))
21 
22 static void oskar_binary_resize(oskar_Binary* handle, int m);
23 static void oskar_binary_read_header(FILE* stream, oskar_BinaryHeader* header,
24         int* status);
25 static void oskar_binary_write_header(FILE* stream, oskar_BinaryHeader* header,
26         int* status);
27 
28 #ifdef _MSC_VER
29 #define FTELL _ftelli64
30 #else
31 #define FTELL ftello
32 #endif
33 
oskar_binary_create(const char * filename,char mode,int * status)34 oskar_Binary* oskar_binary_create(const char* filename, char mode, int* status)
35 {
36     oskar_Binary* handle = 0;
37     oskar_BinaryHeader header;
38     FILE* stream = 0;
39     int i = 0;
40 
41     /* Initialise the header. This doesn't actually need to happen here,
42      * since it will be done when the header is read or written,
43      * but the linter should be happier with it. */
44     memset(&header, 0, sizeof(oskar_BinaryHeader));
45     header.bin_version = OSKAR_BINARY_FORMAT_VERSION;
46 
47     /* Open the file and check or write the header, depending on the mode. */
48     if (mode == 'r')
49     {
50         stream = fopen(filename, "rb");
51         if (!stream)
52         {
53             *status = OSKAR_ERR_BINARY_OPEN_FAIL;
54             return 0;
55         }
56         oskar_binary_read_header(stream, &header, status);
57         if (*status)
58         {
59             fclose(stream);
60             return 0;
61         }
62     }
63     else if (mode == 'w')
64     {
65         stream = fopen(filename, "wb");
66         if (!stream)
67         {
68             *status = OSKAR_ERR_BINARY_OPEN_FAIL;
69             return 0;
70         }
71         oskar_binary_write_header(stream, &header, status);
72     }
73     else if (mode == 'a')
74     {
75         stream = fopen(filename, "a+b");
76         if (!stream)
77         {
78             *status = OSKAR_ERR_BINARY_OPEN_FAIL;
79             return 0;
80         }
81 
82         /* Write header only if the file is empty. */
83         fseek(stream, 0, SEEK_END);
84         if (FTELL(stream) == 0)
85         {
86             oskar_binary_write_header(stream, &header, status);
87         }
88     }
89     else
90     {
91         *status = OSKAR_ERR_BINARY_OPEN_FAIL;
92         return 0;
93     }
94 
95     /* Allocate index and store the stream handle. */
96     handle = (oskar_Binary*) calloc(1, sizeof(oskar_Binary));
97     handle->stream = stream;
98     handle->open_mode = mode;
99 
100     /* Create the CRC lookup tables. */
101     handle->crc_data = oskar_crc_create(OSKAR_CRC_32C);
102 
103     /* Store the contents of the header for later use. */
104     handle->bin_version = header.bin_version;
105 
106     /* Finish if writing. */
107     if (mode == 'w')
108     {
109         return handle;
110     }
111 
112     /* Read all tags in the stream. */
113     for (i = 0;; ++i)
114     {
115         oskar_BinaryTag tag;
116         unsigned long crc = 0;
117         int format_version = 0, element_size = 0;
118         size_t block_size = 0, memcpy_size = 0;
119 
120         /* Try to read a tag, and end the loop if unsuccessful. */
121         if (fread(&tag, sizeof(oskar_BinaryTag), 1, stream) != 1)
122         {
123             break;
124         }
125 
126         /* If the bytes read are not a tag, or the reserved flag bits
127          * are not zero, then return an error. */
128         if (tag.magic[0] != 'T' || tag.magic[2] != 'G'
129                 || (tag.flags & 0x1F) != 0)
130         {
131             *status = OSKAR_ERR_BINARY_FILE_INVALID;
132             break;
133         }
134 
135         /* Get the binary format version. */
136         format_version = tag.magic[1] - 0x40;
137         if (format_version < 1 || format_version > OSKAR_BINARY_FORMAT_VERSION)
138         {
139             *status = OSKAR_ERR_BINARY_VERSION_UNKNOWN;
140             break;
141         }
142 
143         /* Additional checks if format version > 1. */
144         if (format_version > 1)
145         {
146             /* Check system byte order is compatible. */
147             if (oskar_endian() && !(tag.flags & (1 << 5)))
148             {
149                 *status = OSKAR_ERR_BINARY_ENDIAN_MISMATCH;
150                 break;
151             }
152 
153             /* Check data size is compatible. */
154             element_size = tag.magic[3];
155             if (tag.data_type & OSKAR_MATRIX)
156             {
157                 element_size /= 4;
158             }
159             if (tag.data_type & OSKAR_COMPLEX)
160             {
161                 element_size /= 2;
162             }
163             if (tag.data_type & OSKAR_CHAR)
164             {
165                 if (element_size != sizeof(char))
166                 {
167                     *status = OSKAR_ERR_BINARY_FORMAT_BAD;
168                 }
169             }
170             else if (tag.data_type & OSKAR_INT)
171             {
172                 if (element_size != sizeof(int))
173                 {
174                     *status = OSKAR_ERR_BINARY_INT_UNKNOWN;
175                 }
176             }
177             else if (tag.data_type & OSKAR_SINGLE)
178             {
179                 if (element_size != sizeof(float))
180                 {
181                     *status = OSKAR_ERR_BINARY_FLOAT_UNKNOWN;
182                 }
183             }
184             else if (tag.data_type & OSKAR_DOUBLE)
185             {
186                 if (element_size != sizeof(double))
187                 {
188                     *status = OSKAR_ERR_BINARY_DOUBLE_UNKNOWN;
189                 }
190             }
191             else
192             {
193                 *status = OSKAR_ERR_BINARY_TYPE_UNKNOWN;
194             }
195         }
196 
197         /* Check if we need to allocate more storage for the tag data. */
198         if (i % 10 == 0)
199         {
200             oskar_binary_resize(handle, i + 10);
201         }
202 
203         /* Initialise the tag index data. */
204         handle->extended[i] = 0;
205         handle->data_type[i] = 0;
206         handle->id_group[i] = 0;
207         handle->id_tag[i] = 0;
208         handle->name_group[i] = 0;
209         handle->name_tag[i] = 0;
210         handle->user_index[i] = 0;
211         handle->payload_offset_bytes[i] = 0;
212         handle->payload_size_bytes[i] = 0;
213         handle->crc[i] = 0;
214         handle->crc_header[i] = 0;
215 
216         /* Start computing the CRC code. */
217         crc = oskar_crc_compute(handle->crc_data, &tag,
218                 sizeof(oskar_BinaryTag));
219 
220         /* Store the data type and IDs. */
221         handle->data_type[i] = (int) tag.data_type;
222         handle->id_group[i] = (int) tag.group.id;
223         handle->id_tag[i] = (int) tag.tag.id;
224 
225         /* Store the index in native byte order. */
226         memcpy_size = MIN(sizeof(int), sizeof(tag.user_index));
227         memcpy(&handle->user_index[i], tag.user_index, memcpy_size);
228         if (oskar_endian() != OSKAR_LITTLE_ENDIAN)
229         {
230             oskar_endian_swap(&handle->user_index[i], sizeof(int));
231         }
232 
233         /* Store the number of bytes in the block in native byte order. */
234         memcpy_size = MIN(sizeof(size_t), sizeof(tag.size_bytes));
235         memcpy(&block_size, tag.size_bytes, memcpy_size);
236         if (oskar_endian() != OSKAR_LITTLE_ENDIAN)
237         {
238             oskar_endian_swap(&block_size, sizeof(size_t));
239         }
240 
241         /* Set payload size to block size, minus 4 bytes if CRC-32 present. */
242         handle->payload_size_bytes[i] = block_size;
243         handle->payload_size_bytes[i] -= (tag.flags & (1 << 6) ? 4 : 0);
244 
245         /* Check if the tag is extended. */
246         if (tag.flags & (1 << 7))
247         {
248             /* Extended tag: set the extended flag. */
249             handle->extended[i] = 1;
250 
251             /* Reduce payload size by sum of length of tag names. */
252             handle->payload_size_bytes[i] -= (tag.group.bytes + tag.tag.bytes);
253 
254             /* Allocate memory for the tag names. */
255             handle->name_group[i] = (char*) malloc(tag.group.bytes);
256             handle->name_tag[i]   = (char*) malloc(tag.tag.bytes);
257 
258             /* Store the tag names. */
259             if (fread(handle->name_group[i], tag.group.bytes, 1, stream) != 1)
260             {
261                 *status = OSKAR_ERR_BINARY_FILE_INVALID;
262             }
263             if (fread(handle->name_tag[i], tag.tag.bytes, 1, stream) != 1)
264             {
265                 *status = OSKAR_ERR_BINARY_FILE_INVALID;
266             }
267             if (*status) break;
268 
269             /* Update the CRC code. */
270             crc = oskar_crc_update(handle->crc_data, crc,
271                     handle->name_group[i], tag.group.bytes);
272             crc = oskar_crc_update(handle->crc_data, crc,
273                     handle->name_tag[i], tag.tag.bytes);
274         }
275 
276         /* Store the current stream pointer as the payload offset. */
277         const int64_t cur_pos = (int64_t) FTELL(stream);
278         if (cur_pos == -1)
279         {
280             *status = OSKAR_ERR_BINARY_READ_FAIL;
281             break;
282         }
283         handle->payload_offset_bytes[i] = cur_pos;
284 
285         /* Increment stream pointer by payload size. */
286 #ifdef _MSC_VER
287         if (_fseeki64(stream, handle->payload_size_bytes[i], SEEK_CUR))
288 #else
289         if (fseeko(stream, (off_t) handle->payload_size_bytes[i], SEEK_CUR))
290 #endif
291         {
292             *status = OSKAR_ERR_BINARY_SEEK_FAIL;
293             break;
294         }
295 
296         /* Store header CRC code and get file CRC code in native byte order. */
297         handle->crc_header[i] = crc;
298         if (tag.flags & (1 << 6))
299         {
300             if (fread(&handle->crc[i], 4, 1, stream) != 1)
301             {
302                 *status = OSKAR_ERR_BINARY_READ_FAIL;
303                 break;
304             }
305 
306             if (oskar_endian() != OSKAR_LITTLE_ENDIAN)
307             {
308                 oskar_endian_swap(&handle->crc[i], sizeof(unsigned long));
309             }
310         }
311 
312         /* Save the number of tags read from the stream. */
313         handle->num_chunks = i + 1;
314     }
315 
316     return handle;
317 }
318 
oskar_binary_resize(oskar_Binary * handle,int m)319 static void oskar_binary_resize(oskar_Binary* handle, int m)
320 {
321     handle->extended = (int*) realloc(handle->extended, m * sizeof(int));
322     handle->data_type = (int*) realloc(handle->data_type, m * sizeof(int));
323     handle->id_group = (int*) realloc(handle->id_group, m * sizeof(int));
324     handle->id_tag = (int*) realloc(handle->id_tag, m * sizeof(int));
325     handle->name_group = (char**) realloc(
326             handle->name_group, m * sizeof(char*));
327     handle->name_tag = (char**) realloc(handle->name_tag, m * sizeof(char*));
328     handle->user_index = (int*) realloc(handle->user_index, m * sizeof(int));
329     handle->payload_offset_bytes = (int64_t*) realloc(
330             handle->payload_offset_bytes, m * sizeof(int64_t));
331     handle->payload_size_bytes = (size_t*) realloc(
332             handle->payload_size_bytes, m * sizeof(size_t));
333     handle->crc = (unsigned long*) realloc(
334             handle->crc, m * sizeof(unsigned long));
335     handle->crc_header = (unsigned long*) realloc(
336             handle->crc_header, m * sizeof(unsigned long));
337 }
338 
oskar_binary_write_header(FILE * stream,oskar_BinaryHeader * header,int * status)339 static void oskar_binary_write_header(FILE* stream, oskar_BinaryHeader* header,
340         int* status)
341 {
342     const char magic[] = "OSKARBIN";
343 
344     /* Construct binary header. */
345     memset(header, 0, sizeof(oskar_BinaryHeader));
346     strcpy(header->magic, magic);
347     header->bin_version = OSKAR_BINARY_FORMAT_VERSION;
348 
349     /* Write header to stream. */
350     rewind(stream);
351     if (fwrite(header, sizeof(oskar_BinaryHeader), 1, stream) != 1)
352     {
353         *status = OSKAR_ERR_BINARY_WRITE_FAIL;
354     }
355 }
356 
357 
oskar_binary_read_header(FILE * stream,oskar_BinaryHeader * header,int * status)358 static void oskar_binary_read_header(FILE* stream, oskar_BinaryHeader* header,
359         int* status)
360 {
361     /* Read the header from the stream. */
362     rewind(stream);
363     if (fread(header, sizeof(oskar_BinaryHeader), 1, stream) != 1)
364     {
365         *status = OSKAR_ERR_BINARY_READ_FAIL;
366         return;
367     }
368 
369     /* Check if this is a valid header. */
370     if (strncmp("OSKARBIN", header->magic, 8) != 0)
371     {
372         *status = OSKAR_ERR_BINARY_FILE_INVALID;
373         return;
374     }
375 
376     /* Check if the format is compatible. */
377     if ((int)(header->bin_version) > OSKAR_BINARY_FORMAT_VERSION)
378     {
379         *status = OSKAR_ERR_BINARY_VERSION_UNKNOWN;
380         return;
381     }
382 
383     if (header->bin_version == 1)
384     {
385         /* Check if the architecture is compatible. */
386         if (oskar_endian() != (int)(header->endian))
387         {
388             *status = OSKAR_ERR_BINARY_ENDIAN_MISMATCH;
389             return;
390         }
391 
392         /* Check size of data types. */
393         if (sizeof(int) != (size_t)(header->size_int))
394         {
395             *status = OSKAR_ERR_BINARY_INT_UNKNOWN;
396         }
397         if (sizeof(float) != (size_t)(header->size_float))
398         {
399             *status = OSKAR_ERR_BINARY_FLOAT_UNKNOWN;
400         }
401         if (sizeof(double) != (size_t)(header->size_double))
402         {
403             *status = OSKAR_ERR_BINARY_DOUBLE_UNKNOWN;
404         }
405     }
406 }
407 
408 #ifdef __cplusplus
409 }
410 #endif
411