-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathReaderGKey.c
More file actions
402 lines (343 loc) · 12.5 KB
/
Copy pathReaderGKey.c
File metadata and controls
402 lines (343 loc) · 12.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
/*
* StreamLib: Gordon Key compressed file reader
* Copyright (C) 2018 Christopher Bazley
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/* History:
CJB: 07-Aug-18: Copied this source file from SF3KtoObj.
CJB: 15-Aug-19: Fixed broken backward-seek in compressed file, which
only appeared to work for small offsets and files.
Decompressed size is now read in a separate function.
CJB: 12-Nov-19: Read the file header lazily instead of in the constructor.
CJB: 07-Jun-20: Debugging output is less verbose by default.
CJB: 28-Nov-20: Initialize struct using compound literal assignment.
CJB: 09-Apr-25: Dogfooding the _Optional qualifier.
CJB: 29-Apr-26: Stop dereferencing a pointer of type void *.
CJB: 21-May-26: Refactored read_core to use long int for byte counts.
*/
/* ISO library header files */
#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
/* GKey library files */
#include "GKeyDecomp.h"
/* Local headers */
#include "Internal/StreamMisc.h"
#include "ReaderGKey.h"
#include "ReaderRaw.h"
enum {
BUFFER_SIZE = 256, /* No. of bytes to decompress at a time */
};
typedef struct {
bool read_hdr, owns_backend;
const char *out_ptr; /* remaining data within out_buffer */
long int out_total, out_len;
GKeyDecomp *decomp;
GKeyParameters params;
Reader *backend;
} ReaderGKeyState;
typedef struct {
ReaderGKeyState state;
struct {
char in[BUFFER_SIZE];
char out[BUFFER_SIZE];
} buffer;
} ReaderGKeyData;
static void prepare_for_output(ReaderGKeyData *const data)
{
assert(data != NULL);
data->state.out_ptr = data->buffer.out;
data->state.params.out_buffer = data->buffer.out;
}
static void rewind_reinit(ReaderGKeyData *const data)
{
assert(data != NULL);
data->state.out_total = 0;
prepare_for_output(data);
data->state.params.in_size = 0;
}
static long int read_core(_Optional char *ptr,
long int const bytes_to_read,
Reader *const reader)
{
long int bytes_read = 0;
assert(reader != NULL);
ReaderGKeyData *const data = reader->data;
assert(data != NULL);
assert(reader->fpos >= 0);
while (!reader->error && (bytes_read < bytes_to_read)) {
/* If there is already decompressed data in the output buffer
then copy that to the caller's buffer. */
assert((const char *)data->state.params.out_buffer >= data->state.out_ptr);
long int const n = bytes_to_read - bytes_read;
const ptrdiff_t bytes_avail =
(const char *)data->state.params.out_buffer - data->state.out_ptr;
assert(bytes_avail >= 0);
assert(bytes_avail <= BUFFER_SIZE);
DEBUG_VERBOSEF("%td bytes are available (need %ld)\n", bytes_avail, n);
assert(bytes_avail == (long)bytes_avail);
const long int copy_size = n > bytes_avail ? (long)bytes_avail : n;
if (copy_size) {
if (ptr) {
DEBUG_VERBOSEF("Copying %ld of %td bytes from output buffer\n",
copy_size, bytes_avail);
assert((unsigned long)copy_size == (size_t)copy_size);
memcpy(&*ptr, data->state.out_ptr, (size_t)copy_size);
ptr = ptr + copy_size;
}
data->state.out_ptr += copy_size;
bytes_read += copy_size;
}
/* If we didn't get enough data yet then decompress some more. */
if (bytes_read < bytes_to_read) {
DEBUG_VERBOSEF(
"Need to refill output buffer (only got %ld of %ld bytes)\n",
bytes_read, bytes_to_read);
bool in_pending = false;
GKeyStatus status = GKeyStatus_OK;
assert(data->state.out_ptr == data->state.params.out_buffer);
prepare_for_output(data);
data->state.params.out_size = sizeof(data->buffer.out);
do {
/* Is the input buffer empty? */
if (data->state.params.in_size == 0) {
/* Fill the input buffer by reading from file */
data->state.params.in_buffer = data->buffer.in;
data->state.params.in_size = reader_fread(
data->buffer.in, 1, sizeof(data->buffer.in), data->state.backend);
DEBUG_VERBOSEF(
"Filled input buffer with %zu bytes of compressed data\n",
data->state.params.in_size);
if (data->state.params.in_size != sizeof(data->buffer.in) &&
reader_ferror(data->state.backend)) {
/* Read error not end of file */
DEBUGF("Failed to read compressed data from file\n");
reader->error = 1;
break;
}
}
/* Decompress the data from the input buffer to the output buffer */
status = gkeydecomp_decompress(data->state.decomp, &data->state.params);
/* If the input buffer is empty and it cannot be (re-)filled then
there is no more input pending. */
in_pending = data->state.params.in_size > 0 ||
(!reader_feof(data->state.backend) &&
!reader_ferror(data->state.backend));
if (in_pending && status == GKeyStatus_TruncatedInput) {
/* False alarm before end of input data */
status = GKeyStatus_OK;
}
} while (in_pending && status == GKeyStatus_OK);
DEBUG_VERBOSEF(
"Filled output buffer with %zu bytes of uncompressed data\n",
sizeof(data->buffer.out) - data->state.params.out_size);
if (!reader->error) {
switch (status) {
case GKeyStatus_BadInput:
DEBUGF("Compressed bitstream contains bad data\n");
reader->error = 1;
break;
case GKeyStatus_TruncatedInput:
DEBUGF("Compressed bitstream appears truncated\n");
reader->error = 1;
break;
case GKeyStatus_BufferOverflow:
/* The output buffer was filled but not all of the data in
the input buffer was used up. */
assert(data->state.params.out_size == 0);
break;
case GKeyStatus_OK:
assert(!in_pending);
if (data->state.params.out_size == sizeof(data->buffer.out)) {
DEBUGF("Compressed bitstream appears truncated\n");
reader->error = 1;
}
break;
default:
assert("Impossible state" == NULL);
break;
}
}
}
}
data->state.out_total += bytes_read;
return bytes_read;
}
static bool read_hdr(ReaderGKeyData *const data)
{
assert(data != NULL);
int32_t out_len;
if (!reader_fread_int32(&out_len, data->state.backend)) {
DEBUGF("Failed to read decompressed size: %s\n",
reader_feof(data->state.backend) ? "End of file" : "Error");
return false;
}
DEBUGF("Decompressed data size is %" PRId32 " bytes\n", out_len);
if (out_len < 0) {
DEBUGF("Bad size %" PRId32 " in compressed file\n", out_len);
return false;
}
data->state.out_len = out_len;
return true;
}
static size_t reader_gkey_fread(void *const ptr, size_t bytes_to_read,
Reader *const reader)
{
assert(ptr != NULL);
assert(reader != NULL);
ReaderGKeyData *const data = reader->data;
assert(data != NULL);
assert(reader->fpos >= 0);
/* Get size of decompressed data if we didn't already */
if (!data->state.read_hdr) {
data->state.read_hdr = true;
if (!read_hdr(data)) {
reader->error = 1;
return 0;
}
}
assert(data->state.out_len >= data->state.out_total);
/* If fseek was used since the last read then find the right
position at which to start reading. */
if (reader->fpos > data->state.out_len) {
DEBUGF("Can't seek %ld beyond end %ld\n", reader->fpos,
data->state.out_len);
reader->error = 1;
return 0;
}
if (reader->fpos != data->state.out_total) {
DEBUGF("Seeking offset %ld in file (out %ld)\n", reader->fpos,
data->state.out_total);
if (reader->fpos < data->state.out_total) {
assert(data->state.out_ptr >= data->buffer.out);
ptrdiff_t const out_buf_used = data->state.out_ptr - data->buffer.out;
DEBUGF("%td bytes of buffer were already output\n", out_buf_used);
assert(out_buf_used == (long)out_buf_used);
long int const buf_start = data->state.out_total - (long)out_buf_used;
DEBUGF("Buffer starts at offset %ld\n", buf_start);
if (reader->fpos >= buf_start) {
long int const buf_offset = reader->fpos - buf_start;
DEBUGF("Seeking offset %ld in buffer\n", buf_offset);
data->state.out_total = reader->fpos;
data->state.out_ptr = data->buffer.out + buf_offset;
} else {
/* Seeking backwards requires decompressing data
from the start of the file to the requested place again. */
DEBUGF("Seeking start of file for fread\n");
if (reader_fseek(data->state.backend, sizeof(uint32_t), SEEK_SET)) {
reader->error = 1;
return 0;
}
rewind_reinit(data);
}
}
long int const bytes_to_skip = reader->fpos - data->state.out_total;
DEBUGF("Skipping %ld bytes\n", bytes_to_skip);
long int const nskipped = read_core(NULL, bytes_to_skip, reader);
assert(nskipped <= bytes_to_skip);
if (nskipped != bytes_to_skip) {
return 0;
}
DEBUGF("Successfully repositioned to %ld\n", reader->fpos);
}
/* Don't try to read more bytes than advertised as available. */
assert(data->state.out_len >= data->state.out_total);
long int const avail = data->state.out_len - data->state.out_total;
long int actual_bytes_to_read = (long)bytes_to_read;
assert((unsigned long)actual_bytes_to_read == bytes_to_read);
if (avail < actual_bytes_to_read) {
DEBUGF("Can't read %zu bytes: end of file at %ld\n", bytes_to_read, avail);
actual_bytes_to_read = avail;
reader->eof = 1;
}
long int const nread = read_core(ptr, actual_bytes_to_read, reader);
assert(nread <= actual_bytes_to_read);
assert((unsigned long)nread == (size_t)nread);
return (size_t)nread;
}
static void reader_gkey_destroy(Reader *const reader)
{
assert(reader != NULL);
ReaderGKeyData *const data = reader->data;
assert(data != NULL);
gkeydecomp_destroy(data->state.decomp);
if (data->state.owns_backend) {
reader_destroy(data->state.backend);
free(data->state.backend);
}
free(data);
}
bool reader_gkey_init_from(Reader *const reader,
unsigned int const history_log_2, Reader *const in)
{
assert(reader != NULL);
assert(in != NULL);
assert(!reader_ferror(in));
assert(!reader_feof(in));
_Optional ReaderGKeyData *const data = malloc(sizeof(*data));
if (data == NULL) {
DEBUGF("Failed to allocate memory for a new reader\n");
return false;
}
data->state = (ReaderGKeyState){
.backend = in,
.owns_backend = false,
.read_hdr = false,
.params =
{
.prog_cb = (GKeyProgressFn *)NULL,
.cb_arg = reader,
},
};
_Optional GKeyDecomp *const decomp = gkeydecomp_make(history_log_2);
if (decomp == NULL) {
DEBUGF("Failed to create decompressor\n");
free(data);
return false;
}
data->state.decomp = &*decomp;
static ReaderFns const fns = {reader_gkey_fread, reader_gkey_destroy};
reader_internal_init(reader, &fns, &*data);
rewind_reinit(&*data);
return true;
}
bool reader_gkey_init(Reader *const reader, unsigned int const history_log_2,
FILE *const in)
{
assert(reader != NULL);
assert(in != NULL);
assert(!ferror(in));
assert(!feof(in));
_Optional Reader *const raw = malloc(sizeof(*raw));
if (raw == NULL) {
DEBUGF("Failed to allocate raw backend\n");
return false;
}
reader_raw_init(&*raw, in);
bool const success = reader_gkey_init_from(reader, history_log_2, &*raw);
if (!success) {
DEBUGF("Failed to initialize a new reader\n");
reader_destroy(&*raw);
free(raw);
} else {
ReaderGKeyData *const data = reader->data;
data->state.owns_backend = true; /* override default */
}
return success;
}