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