• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) Meta Platforms, Inc. and affiliates.
3  * All rights reserved.
4  *
5  * This source code is licensed under both the BSD-style license (found in the
6  * LICENSE file in the root directory of this source tree) and the GPLv2 (found
7  * in the COPYING file in the root directory of this source tree).
8  * You may select, at your option, one of the above-listed licenses.
9  */
10 
11 /* *********************************************************
12 *  Turn on Large Files support (>4GB) for 32-bit Linux/Unix
13 ***********************************************************/
14 #if !defined(__64BIT__) || defined(__MINGW32__)       /* No point defining Large file for 64 bit but MinGW-w64 requires it */
15 #  if !defined(_FILE_OFFSET_BITS)
16 #    define _FILE_OFFSET_BITS 64                      /* turn off_t into a 64-bit type for ftello, fseeko */
17 #  endif
18 #  if !defined(_LARGEFILE_SOURCE)                     /* obsolete macro, replaced with _FILE_OFFSET_BITS */
19 #    define _LARGEFILE_SOURCE 1                       /* Large File Support extension (LFS) - fseeko, ftello */
20 #  endif
21 #  if defined(_AIX) || defined(__hpux)
22 #    define _LARGE_FILES                              /* Large file support on 32-bits AIX and HP-UX */
23 #  endif
24 #endif
25 
26 /* ************************************************************
27 *  Detect POSIX version
28 *  PLATFORM_POSIX_VERSION = 0 for non-Unix e.g. Windows
29 *  PLATFORM_POSIX_VERSION = 1 for Unix-like but non-POSIX
30 *  PLATFORM_POSIX_VERSION > 1 is equal to found _POSIX_VERSION
31 *  Value of PLATFORM_POSIX_VERSION can be forced on command line
32 ***************************************************************/
33 #ifndef PLATFORM_POSIX_VERSION
34 
35 #  if (defined(__APPLE__) && defined(__MACH__)) || defined(__SVR4) || defined(_AIX) || defined(__hpux) /* POSIX.1-2001 (SUSv3) conformant */ \
36      || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)  /* BSD distros */
37      /* exception rule : force posix version to 200112L,
38       * note: it's better to use unistd.h's _POSIX_VERSION whenever possible */
39 #    define PLATFORM_POSIX_VERSION 200112L
40 
41 /* try to determine posix version through official unistd.h's _POSIX_VERSION (https://pubs.opengroup.org/onlinepubs/7908799/xsh/unistd.h.html).
42  * note : there is no simple way to know in advance if <unistd.h> is present or not on target system,
43  * Posix specification mandates its presence and its content, but target system must respect this spec.
44  * It's necessary to _not_ #include <unistd.h> whenever target OS is not unix-like
45  * otherwise it will block preprocessing stage.
46  * The following list of build macros tries to "guess" if target OS is likely unix-like, and therefore can #include <unistd.h>
47  */
48 #  elif !defined(_WIN32) \
49      && ( defined(__unix__) || defined(__unix) \
50        || defined(__midipix__) || defined(__VMS) || defined(__HAIKU__) )
51 
52 #    if defined(__linux__) || defined(__linux) || defined(__CYGWIN__)
53 #      ifndef _POSIX_C_SOURCE
54 #        define _POSIX_C_SOURCE 200809L  /* feature test macro : https://www.gnu.org/software/libc/manual/html_node/Feature-Test-Macros.html */
55 #      endif
56 #    endif
57 #    include <unistd.h>  /* declares _POSIX_VERSION */
58 #    if defined(_POSIX_VERSION)  /* POSIX compliant */
59 #      define PLATFORM_POSIX_VERSION _POSIX_VERSION
60 #    else
61 #      define PLATFORM_POSIX_VERSION 1
62 #    endif
63 
64 #    ifdef __UCLIBC__
65 #     ifndef __USE_MISC
66 #      define __USE_MISC /* enable st_mtim on uclibc */
67 #     endif
68 #    endif
69 
70 #  else  /* non-unix target platform (like Windows) */
71 #    define PLATFORM_POSIX_VERSION 0
72 #  endif
73 
74 #endif   /* PLATFORM_POSIX_VERSION */
75 
76 
77 /* ************************************************************
78 * Avoid fseek()'s 2GiB barrier with MSVC, macOS, *BSD, MinGW
79 ***************************************************************/
80 #if defined(LIBC_NO_FSEEKO)
81 /* Some older libc implementations don't include these functions (e.g. Bionic < 24) */
82 #   define LONG_SEEK fseek
83 #elif defined(_MSC_VER) && _MSC_VER >= 1400
84 #   define LONG_SEEK _fseeki64
85 #elif !defined(__64BIT__) && (PLATFORM_POSIX_VERSION >= 200112L) /* No point defining Large file for 64 bit */
86 #   define LONG_SEEK fseeko
87 #elif defined(__MINGW32__) && !defined(__STRICT_ANSI__) && !defined(__NO_MINGW_LFS) && defined(__MSVCRT__)
88 #   define LONG_SEEK fseeko64
89 #elif defined(_WIN32) && !defined(__DJGPP__)
90 #   include <windows.h>
LONG_SEEK(FILE * file,__int64 offset,int origin)91     static int LONG_SEEK(FILE* file, __int64 offset, int origin) {
92         LARGE_INTEGER off;
93         DWORD method;
94         off.QuadPart = offset;
95         if (origin == SEEK_END)
96             method = FILE_END;
97         else if (origin == SEEK_CUR)
98             method = FILE_CURRENT;
99         else
100             method = FILE_BEGIN;
101 
102         if (SetFilePointerEx((HANDLE) _get_osfhandle(_fileno(file)), off, NULL, method))
103             return 0;
104         else
105             return -1;
106     }
107 #else
108 #   define LONG_SEEK fseek
109 #endif
110 
111 #include <stdlib.h>  /* malloc, free */
112 #include <stdio.h>   /* FILE* */
113 #include <limits.h>  /* UNIT_MAX */
114 #include <assert.h>
115 
116 #define XXH_STATIC_LINKING_ONLY
117 #include "xxhash.h"
118 
119 #define ZSTD_STATIC_LINKING_ONLY
120 #include "zstd.h"
121 #include "zstd_errors.h"
122 #include "mem.h"
123 #include "zstd_seekable.h"
124 
125 #undef ERROR
126 #define ERROR(name) ((size_t)-ZSTD_error_##name)
127 
128 #define CHECK_IO(f) { int const errcod = (f); if (errcod < 0) return ERROR(seekableIO); }
129 
130 #undef MIN
131 #undef MAX
132 #define MIN(a, b) ((a) < (b) ? (a) : (b))
133 #define MAX(a, b) ((a) > (b) ? (a) : (b))
134 
135 #define ZSTD_SEEKABLE_NO_OUTPUT_PROGRESS_MAX 16
136 
137 /* Special-case callbacks for FILE* and in-memory modes, so that we can treat
138  * them the same way as the advanced API */
ZSTD_seekable_read_FILE(void * opaque,void * buffer,size_t n)139 static int ZSTD_seekable_read_FILE(void* opaque, void* buffer, size_t n)
140 {
141     size_t const result = fread(buffer, 1, n, (FILE*)opaque);
142     if (result != n) {
143         return -1;
144     }
145     return 0;
146 }
147 
ZSTD_seekable_seek_FILE(void * opaque,long long offset,int origin)148 static int ZSTD_seekable_seek_FILE(void* opaque, long long offset, int origin)
149 {
150     int const ret = LONG_SEEK((FILE*)opaque, offset, origin);
151     if (ret) return ret;
152     return fflush((FILE*)opaque);
153 }
154 
155 typedef struct {
156     const void *ptr;
157     size_t size;
158     size_t pos;
159 } buffWrapper_t;
160 
ZSTD_seekable_read_buff(void * opaque,void * buffer,size_t n)161 static int ZSTD_seekable_read_buff(void* opaque, void* buffer, size_t n)
162 {
163     buffWrapper_t* const buff = (buffWrapper_t*)opaque;
164     assert(buff != NULL);
165     if (buff->pos + n > buff->size) return -1;
166     memcpy(buffer, (const BYTE*)buff->ptr + buff->pos, n);
167     buff->pos += n;
168     return 0;
169 }
170 
ZSTD_seekable_seek_buff(void * opaque,long long offset,int origin)171 static int ZSTD_seekable_seek_buff(void* opaque, long long offset, int origin)
172 {
173     buffWrapper_t* const buff = (buffWrapper_t*) opaque;
174     unsigned long long newOffset;
175     assert(buff != NULL);
176     switch (origin) {
177     case SEEK_SET:
178         assert(offset >= 0);
179         newOffset = (unsigned long long)offset;
180         break;
181     case SEEK_CUR:
182         newOffset = (unsigned long long)((long long)buff->pos + offset);
183         break;
184     case SEEK_END:
185         newOffset = (unsigned long long)((long long)buff->size + offset);
186         break;
187     default:
188         assert(0);  /* not possible */
189     }
190     if (newOffset > buff->size) {
191         return -1;
192     }
193     buff->pos = newOffset;
194     return 0;
195 }
196 
197 typedef struct {
198     U64 cOffset;
199     U64 dOffset;
200     U32 checksum;
201 } seekEntry_t;
202 
203 struct ZSTD_seekTable_s {
204     seekEntry_t* entries;
205     size_t tableLen;
206 
207     int checksumFlag;
208 };
209 
210 #define SEEKABLE_BUFF_SIZE ZSTD_BLOCKSIZE_MAX
211 
212 struct ZSTD_seekable_s {
213     ZSTD_DStream* dstream;
214     ZSTD_seekTable seekTable;
215     ZSTD_seekable_customFile src;
216 
217     U64 decompressedOffset;
218     U32 curFrame;
219 
220     BYTE inBuff[SEEKABLE_BUFF_SIZE]; /* need to do our own input buffering */
221     BYTE outBuff[SEEKABLE_BUFF_SIZE]; /* so we can efficiently decompress the
222                                          starts of chunks before we get to the
223                                          desired section */
224     ZSTD_inBuffer in; /* maintain continuity across ZSTD_seekable_decompress operations */
225     buffWrapper_t buffWrapper; /* for `src.opaque` in in-memory mode */
226 
227     XXH64_state_t xxhState;
228 };
229 
ZSTD_seekable_create(void)230 ZSTD_seekable* ZSTD_seekable_create(void)
231 {
232     ZSTD_seekable* const zs = (ZSTD_seekable*)malloc(sizeof(ZSTD_seekable));
233     if (zs == NULL) return NULL;
234 
235     /* also initializes stage to zsds_init */
236     memset(zs, 0, sizeof(*zs));
237 
238     zs->dstream = ZSTD_createDStream();
239     if (zs->dstream == NULL) {
240         free(zs);
241         return NULL;
242     }
243 
244     return zs;
245 }
246 
ZSTD_seekable_free(ZSTD_seekable * zs)247 size_t ZSTD_seekable_free(ZSTD_seekable* zs)
248 {
249     if (zs == NULL) return 0; /* support free on null */
250     ZSTD_freeDStream(zs->dstream);
251     free(zs->seekTable.entries);
252     free(zs);
253     return 0;
254 }
255 
ZSTD_seekTable_create_fromSeekable(const ZSTD_seekable * zs)256 ZSTD_seekTable* ZSTD_seekTable_create_fromSeekable(const ZSTD_seekable* zs)
257 {
258     assert(zs != NULL);
259     if (zs->seekTable.entries == NULL) return NULL;
260     ZSTD_seekTable* const st = (ZSTD_seekTable*)malloc(sizeof(ZSTD_seekTable));
261     if (st==NULL) return NULL;
262 
263     st->checksumFlag = zs->seekTable.checksumFlag;
264     st->tableLen = zs->seekTable.tableLen;
265 
266     /* Allocate an extra entry at the end to match logic of initial allocation */
267     size_t const entriesSize = sizeof(seekEntry_t) * (zs->seekTable.tableLen + 1);
268     seekEntry_t* const entries = (seekEntry_t*)malloc(entriesSize);
269     if (entries==NULL) {
270         free(st);
271         return NULL;
272     }
273 
274     memcpy(entries, zs->seekTable.entries, entriesSize);
275     st->entries = entries;
276     return st;
277 }
278 
ZSTD_seekTable_free(ZSTD_seekTable * st)279 size_t ZSTD_seekTable_free(ZSTD_seekTable* st)
280 {
281     if (st == NULL) return 0; /* support free on null */
282     free(st->entries);
283     free(st);
284     return 0;
285 }
286 
287 /** ZSTD_seekable_offsetToFrameIndex() :
288  *  Performs a binary search to find the last frame with a decompressed offset
289  *  <= pos
290  *  @return : the frame's index */
ZSTD_seekable_offsetToFrameIndex(const ZSTD_seekable * zs,unsigned long long pos)291 unsigned ZSTD_seekable_offsetToFrameIndex(const ZSTD_seekable* zs, unsigned long long pos)
292 {
293     return ZSTD_seekTable_offsetToFrameIndex(&zs->seekTable, pos);
294 }
295 
ZSTD_seekTable_offsetToFrameIndex(const ZSTD_seekTable * st,unsigned long long pos)296 unsigned ZSTD_seekTable_offsetToFrameIndex(const ZSTD_seekTable* st, unsigned long long pos)
297 {
298     U32 lo = 0;
299     U32 hi = (U32)st->tableLen;
300     assert(st->tableLen <= UINT_MAX);
301 
302     if (pos >= st->entries[st->tableLen].dOffset) {
303         return (unsigned)st->tableLen;
304     }
305 
306     while (lo + 1 < hi) {
307         U32 const mid = lo + ((hi - lo) >> 1);
308         if (st->entries[mid].dOffset <= pos) {
309             lo = mid;
310         } else {
311             hi = mid;
312         }
313     }
314     return lo;
315 }
316 
ZSTD_seekable_getNumFrames(const ZSTD_seekable * zs)317 unsigned ZSTD_seekable_getNumFrames(const ZSTD_seekable* zs)
318 {
319     return ZSTD_seekTable_getNumFrames(&zs->seekTable);
320 }
321 
ZSTD_seekTable_getNumFrames(const ZSTD_seekTable * st)322 unsigned ZSTD_seekTable_getNumFrames(const ZSTD_seekTable* st)
323 {
324     assert(st->tableLen <= UINT_MAX);
325     return (unsigned)st->tableLen;
326 }
327 
ZSTD_seekable_getFrameCompressedOffset(const ZSTD_seekable * zs,unsigned frameIndex)328 unsigned long long ZSTD_seekable_getFrameCompressedOffset(const ZSTD_seekable* zs, unsigned frameIndex)
329 {
330     return ZSTD_seekTable_getFrameCompressedOffset(&zs->seekTable, frameIndex);
331 }
332 
ZSTD_seekTable_getFrameCompressedOffset(const ZSTD_seekTable * st,unsigned frameIndex)333 unsigned long long ZSTD_seekTable_getFrameCompressedOffset(const ZSTD_seekTable* st, unsigned frameIndex)
334 {
335     if (frameIndex >= st->tableLen) return ZSTD_SEEKABLE_FRAMEINDEX_TOOLARGE;
336     return st->entries[frameIndex].cOffset;
337 }
338 
ZSTD_seekable_getFrameDecompressedOffset(const ZSTD_seekable * zs,unsigned frameIndex)339 unsigned long long ZSTD_seekable_getFrameDecompressedOffset(const ZSTD_seekable* zs, unsigned frameIndex)
340 {
341     return ZSTD_seekTable_getFrameDecompressedOffset(&zs->seekTable, frameIndex);
342 }
343 
ZSTD_seekTable_getFrameDecompressedOffset(const ZSTD_seekTable * st,unsigned frameIndex)344 unsigned long long ZSTD_seekTable_getFrameDecompressedOffset(const ZSTD_seekTable* st, unsigned frameIndex)
345 {
346     if (frameIndex >= st->tableLen) return ZSTD_SEEKABLE_FRAMEINDEX_TOOLARGE;
347     return st->entries[frameIndex].dOffset;
348 }
349 
ZSTD_seekable_getFrameCompressedSize(const ZSTD_seekable * zs,unsigned frameIndex)350 size_t ZSTD_seekable_getFrameCompressedSize(const ZSTD_seekable* zs, unsigned frameIndex)
351 {
352     return ZSTD_seekTable_getFrameCompressedSize(&zs->seekTable, frameIndex);
353 }
354 
ZSTD_seekTable_getFrameCompressedSize(const ZSTD_seekTable * st,unsigned frameIndex)355 size_t ZSTD_seekTable_getFrameCompressedSize(const ZSTD_seekTable* st, unsigned frameIndex)
356 {
357     if (frameIndex >= st->tableLen) return ERROR(frameIndex_tooLarge);
358     return st->entries[frameIndex + 1].cOffset -
359            st->entries[frameIndex].cOffset;
360 }
361 
ZSTD_seekable_getFrameDecompressedSize(const ZSTD_seekable * zs,unsigned frameIndex)362 size_t ZSTD_seekable_getFrameDecompressedSize(const ZSTD_seekable* zs, unsigned frameIndex)
363 {
364     return ZSTD_seekTable_getFrameDecompressedSize(&zs->seekTable, frameIndex);
365 }
366 
ZSTD_seekTable_getFrameDecompressedSize(const ZSTD_seekTable * st,unsigned frameIndex)367 size_t ZSTD_seekTable_getFrameDecompressedSize(const ZSTD_seekTable* st, unsigned frameIndex)
368 {
369     if (frameIndex > st->tableLen) return ERROR(frameIndex_tooLarge);
370     return st->entries[frameIndex + 1].dOffset -
371            st->entries[frameIndex].dOffset;
372 }
373 
ZSTD_seekable_loadSeekTable(ZSTD_seekable * zs)374 static size_t ZSTD_seekable_loadSeekTable(ZSTD_seekable* zs)
375 {
376     int checksumFlag;
377     ZSTD_seekable_customFile src = zs->src;
378     /* read the footer, fixed size */
379     CHECK_IO(src.seek(src.opaque, -(int)ZSTD_seekTableFooterSize, SEEK_END));
380     CHECK_IO(src.read(src.opaque, zs->inBuff, ZSTD_seekTableFooterSize));
381 
382     if (MEM_readLE32(zs->inBuff + 5) != ZSTD_SEEKABLE_MAGICNUMBER) {
383         return ERROR(prefix_unknown);
384     }
385 
386     {   BYTE const sfd = zs->inBuff[4];
387         checksumFlag = sfd >> 7;
388 
389         /* check reserved bits */
390         if ((sfd >> 2) & 0x1f) {
391             return ERROR(corruption_detected);
392     }   }
393 
394     {   U32 const numFrames = MEM_readLE32(zs->inBuff);
395         U32 const sizePerEntry = 8 + (checksumFlag?4:0);
396         U32 const tableSize = sizePerEntry * numFrames;
397         U32 const frameSize = tableSize + ZSTD_seekTableFooterSize + ZSTD_SKIPPABLEHEADERSIZE;
398 
399         U32 remaining = frameSize - ZSTD_seekTableFooterSize; /* don't need to re-read footer */
400         {   U32 const toRead = MIN(remaining, SEEKABLE_BUFF_SIZE);
401             CHECK_IO(src.seek(src.opaque, -(S64)frameSize, SEEK_END));
402             CHECK_IO(src.read(src.opaque, zs->inBuff, toRead));
403             remaining -= toRead;
404         }
405 
406         if (MEM_readLE32(zs->inBuff) != (ZSTD_MAGIC_SKIPPABLE_START | 0xE)) {
407             return ERROR(prefix_unknown);
408         }
409         if (MEM_readLE32(zs->inBuff+4) + ZSTD_SKIPPABLEHEADERSIZE != frameSize) {
410             return ERROR(prefix_unknown);
411         }
412 
413         {   /* Allocate an extra entry at the end so that we can do size
414              * computations on the last element without special case */
415             seekEntry_t* const entries = (seekEntry_t*)malloc(sizeof(seekEntry_t) * (numFrames + 1));
416 
417             U32 idx = 0;
418             U32 pos = 8;
419 
420             U64 cOffset = 0;
421             U64 dOffset = 0;
422 
423             if (entries == NULL) return ERROR(memory_allocation);
424 
425             /* compute cumulative positions */
426             for (; idx < numFrames; idx++) {
427                 if (pos + sizePerEntry > SEEKABLE_BUFF_SIZE) {
428                     U32 const offset = SEEKABLE_BUFF_SIZE - pos;
429                     U32 const toRead = MIN(remaining, SEEKABLE_BUFF_SIZE - offset);
430                     memmove(zs->inBuff, zs->inBuff + pos, offset); /* move any data we haven't read yet */
431                     CHECK_IO(src.read(src.opaque, zs->inBuff+offset, toRead));
432                     remaining -= toRead;
433                     pos = 0;
434                 }
435                 entries[idx].cOffset = cOffset;
436                 entries[idx].dOffset = dOffset;
437 
438                 cOffset += MEM_readLE32(zs->inBuff + pos);
439                 pos += 4;
440                 dOffset += MEM_readLE32(zs->inBuff + pos);
441                 pos += 4;
442                 if (checksumFlag) {
443                     entries[idx].checksum = MEM_readLE32(zs->inBuff + pos);
444                     pos += 4;
445                 }
446             }
447             entries[numFrames].cOffset = cOffset;
448             entries[numFrames].dOffset = dOffset;
449 
450             zs->seekTable.entries = entries;
451             zs->seekTable.tableLen = numFrames;
452             zs->seekTable.checksumFlag = checksumFlag;
453             return 0;
454         }
455     }
456 }
457 
ZSTD_seekable_initBuff(ZSTD_seekable * zs,const void * src,size_t srcSize)458 size_t ZSTD_seekable_initBuff(ZSTD_seekable* zs, const void* src, size_t srcSize)
459 {
460     zs->buffWrapper = (buffWrapper_t){src, srcSize, 0};
461     {   ZSTD_seekable_customFile srcFile = {&zs->buffWrapper,
462                                             &ZSTD_seekable_read_buff,
463                                             &ZSTD_seekable_seek_buff};
464         return ZSTD_seekable_initAdvanced(zs, srcFile); }
465 }
466 
ZSTD_seekable_initFile(ZSTD_seekable * zs,FILE * src)467 size_t ZSTD_seekable_initFile(ZSTD_seekable* zs, FILE* src)
468 {
469     ZSTD_seekable_customFile srcFile = {src, &ZSTD_seekable_read_FILE,
470                                         &ZSTD_seekable_seek_FILE};
471     return ZSTD_seekable_initAdvanced(zs, srcFile);
472 }
473 
ZSTD_seekable_initAdvanced(ZSTD_seekable * zs,ZSTD_seekable_customFile src)474 size_t ZSTD_seekable_initAdvanced(ZSTD_seekable* zs, ZSTD_seekable_customFile src)
475 {
476     zs->src = src;
477 
478     {   const size_t seekTableInit = ZSTD_seekable_loadSeekTable(zs);
479         if (ZSTD_isError(seekTableInit)) return seekTableInit; }
480 
481     zs->decompressedOffset = (U64)-1;
482     zs->curFrame = (U32)-1;
483 
484     {   const size_t dstreamInit = ZSTD_initDStream(zs->dstream);
485         if (ZSTD_isError(dstreamInit)) return dstreamInit; }
486     return 0;
487 }
488 
ZSTD_seekable_decompress(ZSTD_seekable * zs,void * dst,size_t len,unsigned long long offset)489 size_t ZSTD_seekable_decompress(ZSTD_seekable* zs, void* dst, size_t len, unsigned long long offset)
490 {
491     unsigned long long const eos = zs->seekTable.entries[zs->seekTable.tableLen].dOffset;
492     if (offset + len > eos) {
493         len = eos - offset;
494     }
495 
496     U32 targetFrame = ZSTD_seekable_offsetToFrameIndex(zs, offset);
497     U32 noOutputProgressCount = 0;
498     size_t srcBytesRead = 0;
499     do {
500         /* check if we can continue from a previous decompress job */
501         if (targetFrame != zs->curFrame || offset < zs->decompressedOffset) {
502             zs->decompressedOffset = zs->seekTable.entries[targetFrame].dOffset;
503             zs->curFrame = targetFrame;
504 
505             assert(zs->seekTable.entries[targetFrame].cOffset < LLONG_MAX);
506             CHECK_IO(zs->src.seek(zs->src.opaque,
507                                   (long long)zs->seekTable.entries[targetFrame].cOffset,
508                                   SEEK_SET));
509             zs->in = (ZSTD_inBuffer){zs->inBuff, 0, 0};
510             XXH64_reset(&zs->xxhState, 0);
511             ZSTD_DCtx_reset(zs->dstream, ZSTD_reset_session_only);
512             if (zs->buffWrapper.size && srcBytesRead > zs->buffWrapper.size) {
513                 return ERROR(seekableIO);
514             }
515         }
516 
517         while (zs->decompressedOffset < offset + len) {
518             size_t toRead;
519             ZSTD_outBuffer outTmp;
520             size_t prevOutPos;
521             size_t prevInPos;
522             size_t forwardProgress;
523             if (zs->decompressedOffset < offset) {
524                 /* dummy decompressions until we get to the target offset */
525                 outTmp = (ZSTD_outBuffer){zs->outBuff, (size_t) (MIN(SEEKABLE_BUFF_SIZE, offset - zs->decompressedOffset)), 0};
526             } else {
527                 outTmp = (ZSTD_outBuffer){dst, len, (size_t) (zs->decompressedOffset - offset)};
528             }
529 
530             prevOutPos = outTmp.pos;
531             prevInPos = zs->in.pos;
532             toRead = ZSTD_decompressStream(zs->dstream, &outTmp, &zs->in);
533             if (ZSTD_isError(toRead)) {
534                 return toRead;
535             }
536 
537             if (zs->seekTable.checksumFlag) {
538                 XXH64_update(&zs->xxhState, (BYTE*)outTmp.dst + prevOutPos,
539                              outTmp.pos - prevOutPos);
540             }
541             forwardProgress = outTmp.pos - prevOutPos;
542             if (forwardProgress == 0) {
543                 if (noOutputProgressCount++ > ZSTD_SEEKABLE_NO_OUTPUT_PROGRESS_MAX) {
544                     return ERROR(seekableIO);
545                 }
546             } else {
547                 noOutputProgressCount = 0;
548             }
549             zs->decompressedOffset += forwardProgress;
550             srcBytesRead += zs->in.pos - prevInPos;
551 
552             if (toRead == 0) {
553                 /* frame complete */
554 
555                 /* verify checksum */
556                 if (zs->seekTable.checksumFlag &&
557                     (XXH64_digest(&zs->xxhState) & 0xFFFFFFFFU) !=
558                             zs->seekTable.entries[targetFrame].checksum) {
559                     return ERROR(corruption_detected);
560                 }
561 
562                 if (zs->decompressedOffset < offset + len) {
563                     /* go back to the start and force a reset of the stream */
564                     targetFrame = ZSTD_seekable_offsetToFrameIndex(zs, zs->decompressedOffset);
565                     /* in this case it will fail later with corruption_detected, since last block does not have checksum */
566                     assert(targetFrame != zs->seekTable.tableLen);
567                 }
568                 break;
569             }
570 
571             /* read in more data if we're done with this buffer */
572             if (zs->in.pos == zs->in.size) {
573                 toRead = MIN(toRead, SEEKABLE_BUFF_SIZE);
574                 CHECK_IO(zs->src.read(zs->src.opaque, zs->inBuff, toRead));
575                 zs->in.size = toRead;
576                 zs->in.pos = 0;
577             }
578         }  /* while (zs->decompressedOffset < offset + len) */
579     } while (zs->decompressedOffset != offset + len);
580 
581     return len;
582 }
583 
ZSTD_seekable_decompressFrame(ZSTD_seekable * zs,void * dst,size_t dstSize,unsigned frameIndex)584 size_t ZSTD_seekable_decompressFrame(ZSTD_seekable* zs, void* dst, size_t dstSize, unsigned frameIndex)
585 {
586     if (frameIndex >= zs->seekTable.tableLen) {
587         return ERROR(frameIndex_tooLarge);
588     }
589 
590     {   size_t const decompressedSize =
591                 zs->seekTable.entries[frameIndex + 1].dOffset -
592                 zs->seekTable.entries[frameIndex].dOffset;
593         if (dstSize < decompressedSize) {
594             return ERROR(dstSize_tooSmall);
595         }
596         return ZSTD_seekable_decompress(
597                 zs, dst, decompressedSize,
598                 zs->seekTable.entries[frameIndex].dOffset);
599     }
600 }
601