예제 #1
0
        private static void LZ4_memcpy_using_offset_base(
            byte *dstPtr, byte *srcPtr, byte *dstEnd, uint offset)
        {
            if (offset < 8)
            {
                dstPtr[0] = srcPtr[0];
                dstPtr[1] = srcPtr[1];
                dstPtr[2] = srcPtr[2];
                dstPtr[3] = srcPtr[3];
                srcPtr   += inc32table[offset];
                Mem.Copy4(dstPtr + 4, srcPtr);
                srcPtr -= dec64table[offset];
                dstPtr += 8;
            }
            else
            {
                Mem.Copy8(dstPtr, srcPtr);
                dstPtr += 8;
                srcPtr += 8;
            }

            Mem.WildCopy8(dstPtr, srcPtr, dstEnd);
        }
예제 #2
0
        protected static int LZ4_compress_generic(
            LZ4_stream_t *cctx,
            byte *source,
            byte *dest,
            int inputSize,
            int *inputConsumed,             /* only written when outputDirective == fillOutput */
            int maxOutputSize,
            limitedOutput_directive outputDirective,
            tableType_t tableType,
            dict_directive dictDirective,
            dictIssue_directive dictIssue,
            int acceleration)
        {
            int   result;
            byte *ip = (byte *)source;

            uint   startIndex = cctx->currentOffset;
            byte * @base      = (byte *)source - startIndex;
            byte * lowLimit;

            LZ4_stream_t *dictCtx    = (LZ4_stream_t *)cctx->dictCtx;
            byte *        dictionary =
                dictDirective == dict_directive.usingDictCtx ? dictCtx->dictionary
                                        : cctx->dictionary;
            uint dictSize =
                dictDirective == dict_directive.usingDictCtx ? dictCtx->dictSize : cctx->dictSize;
            uint dictDelta = (dictDirective == dict_directive.usingDictCtx)
                                ? startIndex - dictCtx->currentOffset : 0;

            bool maybe_extMem = (dictDirective == dict_directive.usingExtDict) ||
                                (dictDirective == dict_directive.usingDictCtx);
            uint  prefixIdxLimit = startIndex - dictSize;
            byte *dictEnd        = dictionary + dictSize;
            byte *anchor         = (byte *)source;
            byte *iend           = ip + inputSize;
            byte *mflimitPlusOne = iend - MFLIMIT + 1;
            byte *matchlimit     = iend - LASTLITERALS;

            /* the dictCtx currentOffset is indexed on the start of the dictionary,
             * while a dictionary in the current context precedes the currentOffset */
            byte *dictBase = (dictDirective == dict_directive.usingDictCtx) ?
                             dictionary + dictSize - dictCtx->currentOffset :
                             dictionary + dictSize - startIndex;

            byte *op     = (byte *)dest;
            byte *olimit = op + maxOutputSize;

            uint offset = 0;
            uint forwardH;

            if (outputDirective == limitedOutput_directive.fillOutput && maxOutputSize < 1)
            {
                return(0);
            }

            if ((uint)inputSize > (uint)LZ4_MAX_INPUT_SIZE)
            {
                return(0);
            }

            if ((tableType == tableType_t.byU16) && (inputSize >= LZ4_64Klimit))
            {
                return(0);
            }

            if (tableType == tableType_t.byPtr)
            {
                Assert(dictDirective == dict_directive.noDict);
            }
            Assert(acceleration >= 1);

            lowLimit = (byte *)source
                       - (dictDirective == dict_directive.withPrefix64k ? dictSize : 0);

            /* Update context state */
            if (dictDirective == dict_directive.usingDictCtx)
            {
                /* Subsequent linked blocks can't use the dictionary. */
                /* Instead, they use the block we just compressed. */
                cctx->dictCtx  = null;
                cctx->dictSize = (uint)inputSize;
            }
            else
            {
                cctx->dictSize += (uint)inputSize;
            }

            cctx->currentOffset += (uint)inputSize;
            cctx->tableType      = tableType;

            if (inputSize < LZ4_minLength)
            {
                goto _last_literals;
            }

            /* First Byte */
            LZ4_putPosition(ip, cctx->hashTable, tableType, @base);
            ip++;
            forwardH = LZ4_hashPosition(ip, tableType);

            /* Main Loop */
            for (;;)
            {
                byte *match;
                byte *token;
                byte *filledIp;

                /* Find a match */
                if (tableType == tableType_t.byPtr)
                {
                    byte *forwardIp     = ip;
                    int   step          = 1;
                    int   searchMatchNb = acceleration << LZ4_skipTrigger;
                    do
                    {
                        uint h = forwardH;
                        ip         = forwardIp;
                        forwardIp += step;
                        step       = (searchMatchNb++ >> LZ4_skipTrigger);

                        if ((forwardIp > mflimitPlusOne))
                        {
                            goto _last_literals;
                        }

                        Assert(ip < mflimitPlusOne);

                        match    = LZ4_getPositionOnHash(h, cctx->hashTable, tableType, @base);
                        forwardH = LZ4_hashPosition(forwardIp, tableType);
                        LZ4_putPositionOnHash(ip, h, cctx->hashTable, tableType, @base);
                    }while ((match + LZ4_DISTANCE_MAX < ip) || (Mem.Peek4(match) != Mem.Peek4(ip)));
                }
                else
                {
                    /* byU32, byU16 */

                    byte *forwardIp     = ip;
                    int   step          = 1;
                    int   searchMatchNb = acceleration << LZ4_skipTrigger;
                    do
                    {
                        uint h          = forwardH;
                        uint current    = (uint)(forwardIp - @base);
                        uint matchIndex = LZ4_getIndexOnHash(h, cctx->hashTable, tableType);
                        Assert(matchIndex <= current);
                        Assert(forwardIp - @base < (2 * GB - 1));
                        ip         = forwardIp;
                        forwardIp += step;
                        step       = (searchMatchNb++ >> LZ4_skipTrigger);

                        if ((forwardIp > mflimitPlusOne))
                        {
                            goto _last_literals;
                        }

                        Assert(ip < mflimitPlusOne);

                        if (dictDirective == dict_directive.usingDictCtx)
                        {
                            if (matchIndex < startIndex)
                            {
                                Assert(tableType == tableType_t.byU32);
                                matchIndex = LZ4_getIndexOnHash(
                                    h, dictCtx->hashTable, tableType_t.byU32);
                                match       = dictBase + matchIndex;
                                matchIndex += dictDelta;
                                lowLimit    = dictionary;
                            }
                            else
                            {
                                match    = @base + matchIndex;
                                lowLimit = (byte *)source;
                            }
                        }
                        else if (dictDirective == dict_directive.usingExtDict)
                        {
                            if (matchIndex < startIndex)
                            {
                                Assert(startIndex - matchIndex >= MINMATCH);
                                match    = dictBase + matchIndex;
                                lowLimit = dictionary;
                            }
                            else
                            {
                                match    = @base + matchIndex;
                                lowLimit = (byte *)source;
                            }
                        }
                        else
                        {
                            match = @base + matchIndex;
                        }

                        forwardH = LZ4_hashPosition(forwardIp, tableType);
                        LZ4_putIndexOnHash(current, h, cctx->hashTable, tableType);

                        if ((dictIssue == dictIssue_directive.dictSmall) &&
                            (matchIndex < prefixIdxLimit))
                        {
                            continue;
                        }

                        Assert(matchIndex < current);
                        if (((tableType != tableType_t.byU16) ||
                             (LZ4_DISTANCE_MAX < LZ4_DISTANCE_ABSOLUTE_MAX)) &&
                            (matchIndex + LZ4_DISTANCE_MAX < current))
                        {
                            continue;
                        }

                        Assert((current - matchIndex) <= LZ4_DISTANCE_MAX);

                        if (Mem.Peek4(match) == Mem.Peek4(ip))
                        {
                            if (maybe_extMem)
                            {
                                offset = current - matchIndex;
                            }
                            break;
                        }
                    }while (true);
                }

                filledIp = ip;
                while (((ip > anchor) & (match > lowLimit)) && ((ip[-1] == match[-1])))
                {
                    ip--;
                    match--;
                }

                {
                    var litLength = (uint)(ip - anchor);
                    token = op++;
                    if ((outputDirective == limitedOutput_directive.limitedOutput) &&
                        ((op + litLength + (2 + 1 + LASTLITERALS) + (litLength / 255) > olimit)))
                    {
                        return(0);
                    }

                    if ((outputDirective == limitedOutput_directive.fillOutput) &&
                        ((op + (litLength + 240) / 255 + litLength + 2 + 1 + MFLIMIT - MINMATCH
                          > olimit)))
                    {
                        op--;
                        goto _last_literals;
                    }

                    if (litLength >= RUN_MASK)
                    {
                        int len = (int)(litLength - RUN_MASK);
                        *token = (byte)(RUN_MASK << ML_BITS);
                        for (; len >= 255; len -= 255)
                        {
                            *op++ = 255;
                        }
                        *op++ = (byte)len;
                    }
                    else
                    {
                        *token = (byte)(litLength << ML_BITS);
                    }

                    Mem.WildCopy8(op, anchor, op + litLength);
                    op += litLength;
                }

_next_match:

                /* at this stage, the following variables must be correctly set :
                 * - ip : at start of LZ operation
                 * - match : at start of previous pattern occurence; can be within current prefix, or within extDict
                 * - offset : if maybe_ext_memSegment==1 (constant)
                 * - lowLimit : must be == dictionary to mean "match is within extDict"; must be == source otherwise
                 * - token and *token : position to write 4-bits for match length; higher 4-bits for literal length supposed already written
                 */

                if ((outputDirective == limitedOutput_directive.fillOutput) &&
                    (op + 2 + 1 + MFLIMIT - MINMATCH > olimit))
                {
                    /* the match was too close to the end, rewind and go to last literals */
                    op = token;
                    goto _last_literals;
                }

                /* Encode Offset */
                if (maybe_extMem)
                {
                    /* static test */
                    Assert(offset <= LZ4_DISTANCE_MAX && offset > 0);
                    Mem.Poke2(op, (ushort)offset);
                    op += 2;
                }
                else
                {
                    Assert(ip - match <= LZ4_DISTANCE_MAX);
                    Mem.Poke2(op, (ushort)(ip - match));
                    op += 2;
                }

                /* Encode MatchLength */
                {
                    uint matchCode;

                    if ((dictDirective == dict_directive.usingExtDict ||
                         dictDirective == dict_directive.usingDictCtx) &&
                        (lowLimit == dictionary) /* match within extDict */)
                    {
                        byte *limit = ip + (dictEnd - match);
                        Assert(dictEnd > match);
                        if (limit > matchlimit)
                        {
                            limit = matchlimit;
                        }
                        matchCode = LZ4_count(ip + MINMATCH, match + MINMATCH, limit);
                        ip       += (uint)matchCode + MINMATCH;
                        if (ip == limit)
                        {
                            uint more = LZ4_count(limit, (byte *)source, matchlimit);
                            matchCode += more;
                            ip        += more;
                        }
                    }
                    else
                    {
                        matchCode = LZ4_count(ip + MINMATCH, match + MINMATCH, matchlimit);
                        ip       += (uint)matchCode + MINMATCH;
                    }

                    if ((outputDirective != 0) &&
                        ((op + (1 + LASTLITERALS) + (matchCode + 240) / 255 > olimit)))
                    {
                        if (outputDirective == limitedOutput_directive.fillOutput)
                        {
                            /* Match description too long : reduce it */
                            uint newMatchCode =
                                15 - 1 + ((uint)(olimit - op) - 1 - LASTLITERALS) * 255;
                            ip -= matchCode - newMatchCode;
                            Assert(newMatchCode < matchCode);
                            matchCode = newMatchCode;
                            if ((ip <= filledIp))
                            {
                                /* We have already filled up to filledIp so if ip ends up less than filledIp
                                 * we have positions in the hash table beyond the current position. This is
                                 * a problem if we reuse the hash table. So we have to remove these positions
                                 * from the hash table.
                                 */
                                byte *ptr;
                                for (ptr = ip; ptr <= filledIp; ++ptr)
                                {
                                    uint h = LZ4_hashPosition(ptr, tableType);
                                    LZ4_clearHash(h, cctx->hashTable, tableType);
                                }
                            }
                        }
                        else
                        {
                            Assert(outputDirective == limitedOutput_directive.limitedOutput);
                            return(0);
                        }
                    }

                    if (matchCode >= ML_MASK)
                    {
                        *token    += (byte)ML_MASK;                       //!!!
                        matchCode -= ML_MASK;
                        Mem.Poke4(op, 0xFFFFFFFF);
                        while (matchCode >= 4 * 255)
                        {
                            op += 4;
                            Mem.Poke4(op, 0xFFFFFFFF);
                            matchCode -= 4 * 255;
                        }

                        op   += matchCode / 255;
                        *op++ = (byte)(matchCode % 255);
                    }
                    else
                    {
                        *token += (byte)(matchCode);
                    }
                }
                /* Ensure we have enough space for the last literals. */
                Assert(
                    !(outputDirective == limitedOutput_directive.fillOutput &&
                      op + 1 + LASTLITERALS > olimit));

                anchor = ip;

                /* Test end of chunk */
                if (ip >= mflimitPlusOne)
                {
                    break;
                }

                /* Fill table */
                LZ4_putPosition(ip - 2, cctx->hashTable, tableType, @base);

                /* Test next position */
                if (tableType == tableType_t.byPtr)
                {
                    match = LZ4_getPosition(ip, cctx->hashTable, tableType, @base);
                    LZ4_putPosition(ip, cctx->hashTable, tableType, @base);
                    if ((match + LZ4_DISTANCE_MAX >= ip) && (Mem.Peek4(match) == Mem.Peek4(ip)))
                    {
                        token  = op++;
                        *token = 0;
                        goto _next_match;
                    }
                }
                else
                {
                    /* byU32, byU16 */

                    uint h          = LZ4_hashPosition(ip, tableType);
                    uint current    = (uint)(ip - @base);
                    uint matchIndex = LZ4_getIndexOnHash(h, cctx->hashTable, tableType);
                    Assert(matchIndex < current);
                    if (dictDirective == dict_directive.usingDictCtx)
                    {
                        if (matchIndex < startIndex)
                        {
                            matchIndex = LZ4_getIndexOnHash(
                                h, dictCtx->hashTable, tableType_t.byU32);
                            match       = dictBase + matchIndex;
                            lowLimit    = dictionary;
                            matchIndex += dictDelta;
                        }
                        else
                        {
                            match    = @base + matchIndex;
                            lowLimit = (byte *)source;
                        }
                    }
                    else if (dictDirective == dict_directive.usingExtDict)
                    {
                        if (matchIndex < startIndex)
                        {
                            match    = dictBase + matchIndex;
                            lowLimit = dictionary;
                        }
                        else
                        {
                            match    = @base + matchIndex;
                            lowLimit = (byte *)source;
                        }
                    }
                    else
                    {
                        match = @base + matchIndex;
                    }

                    LZ4_putIndexOnHash(current, h, cctx->hashTable, tableType);
                    Assert(matchIndex < current);
                    if (((dictIssue != dictIssue_directive.dictSmall) ||
                         (matchIndex >= prefixIdxLimit)) &&
                        (((tableType == tableType_t.byU16) &&
                          (LZ4_DISTANCE_MAX == LZ4_DISTANCE_ABSOLUTE_MAX)) ||
                         (matchIndex + LZ4_DISTANCE_MAX >= current)) &&
                        (Mem.Peek4(match) == Mem.Peek4(ip)))
                    {
                        token  = op++;
                        *token = 0;
                        if (maybe_extMem)
                        {
                            offset = current - matchIndex;
                        }
                        goto _next_match;
                    }
                }

                forwardH = LZ4_hashPosition(++ip, tableType);
            }

_last_literals:
            {
                var lastRun = (size_t)(iend - anchor);
                if ((outputDirective != 0) &&
                    (op + lastRun + 1 + ((lastRun + 255 - RUN_MASK) / 255) > olimit))
                {
                    if (outputDirective == limitedOutput_directive.fillOutput)
                    {
                        Assert(olimit >= op);
                        lastRun  = (size_t)(olimit - op) - 1;
                        lastRun -= (lastRun + 240) / 255;
                    }
                    else
                    {
                        Assert(outputDirective == limitedOutput_directive.limitedOutput);
                        return(0);
                    }
                }

                if (lastRun >= RUN_MASK)
                {
                    var accumulator = (size_t)(lastRun - RUN_MASK);
                    *op++ = (byte)(RUN_MASK << ML_BITS);
                    for (; accumulator >= 255; accumulator -= 255)
                    {
                        *op++ = 255;
                    }
                    *op++ = (byte)accumulator;
                }
                else
                {
                    *op++ = (byte)(lastRun << ML_BITS);
                }

                Mem.Copy(op, anchor, (int)lastRun);
                ip  = anchor + lastRun;
                op += lastRun;
            }

            if (outputDirective == limitedOutput_directive.fillOutput)
            {
                *inputConsumed = (int)(((byte *)ip) - source);
            }

            result = (int)(((byte *)op) - dest);
            Assert(result > 0);
            return(result);
        }
예제 #3
0
        public static int LZ4_decompress_generic(
            byte *src,
            byte *dst,
            int srcSize,
            int outputSize,
            bool endOnInput,             // endCondition_directive
            bool partialDecoding,        // earlyEnd_directive
            dict_directive dict,
            byte *lowPrefix,
            byte *dictStart,
            size_t dictSize)
        {
            if (src == null)
            {
                return(-1);
            }

            {
                byte *ip   = (byte *)src;
                byte *iend = ip + srcSize;

                byte *op   = (byte *)dst;
                byte *oend = op + outputSize;
                byte *cpy;

                byte *dictEnd = (dictStart == null) ? null : dictStart + dictSize;

                bool safeDecode  = endOnInput;
                bool checkOffset = ((safeDecode) && (dictSize < (int)(64 * KB)));

                /* Set up the "end" pointers for the shortcut. */
                byte *shortiend = iend - (endOnInput ? 14 : 8) /*maxLL*/ - 2 /*offset*/;
                byte *shortoend = oend - (endOnInput ? 14 : 8) /*maxLL*/ - 18 /*maxML*/;

                byte * match;
                size_t offset;
                uint   token;
                size_t length;

                /* Special cases */
                Assert(lowPrefix <= op);
                if ((endOnInput) && ((outputSize == 0)))
                {
                    /* Empty output buffer */
                    if (partialDecoding)
                    {
                        return(0);
                    }

                    return(((srcSize == 1) && (*ip == 0)) ? 0 : -1);
                }

                if ((!endOnInput) && ((outputSize == 0)))
                {
                    return(*ip == 0 ? 1 : -1);
                }

                if ((endOnInput) && (srcSize == 0))
                {
                    return(-1);
                }

                /* Main Loop : decode remaining sequences where output < FASTLOOP_SAFE_DISTANCE */
                while (true)
                {
                    token  = *ip++;
                    length = (size_t)(token >> ML_BITS);                   /* literal length */

                    Assert(!endOnInput || ip <= iend);                     /* ip < iend before the increment */

                    /* A two-stage shortcut for the most common case:
                     * 1) If the literal length is 0..14, and there is enough space,
                     * enter the shortcut and copy 16 bytes on behalf of the literals
                     * (in the fast mode, only 8 bytes can be safely copied this way).
                     * 2) Further if the match length is 4..18, copy 18 bytes in a similar
                     * manner; but we ensure that there's enough space in the output for
                     * those 18 bytes earlier, upon entering the shortcut (in other words,
                     * there is a combined check for both stages).
                     */
                    if ((endOnInput ? length != RUN_MASK : length <= 8)
                        /* strictly "less than" on input, to re-enter the loop with at least one byte */
                        && ((!endOnInput || ip < shortiend) & (op <= shortoend)))
                    {
                        /* Copy the literals */
                        if (endOnInput)
                        {
                            Mem.Copy16(op, ip);
                        }
                        else
                        {
                            Mem.Copy8(op, ip);
                        }
                        // Mem.Copy(op, ip, endOnInput ? 16 : 8);
                        op += length;
                        ip += length;

                        /* The second stage: prepare for match copying, decode full info.
                         * If it doesn't work out, the info won't be wasted. */
                        length = token & ML_MASK;                         /* match length */
                        offset = Mem.Peek2(ip);
                        ip    += 2;
                        match  = op - offset;
                        Assert(match <= op);                         /* check overflow */

                        /* Do not deal with overlapping matches. */
                        if ((length != ML_MASK) &&
                            (offset >= 8) &&
                            (dict == dict_directive.withPrefix64k || match >= lowPrefix))
                        {
                            /* Copy the match. */
                            Mem.Copy18(op, match);
                            op += length + MINMATCH;
                            /* Both stages worked, load the next token. */
                            continue;
                        }

                        /* The second stage didn't work out, but the info is ready.
                         * Propel it right to the point of match copying. */
                        goto _copy_match;
                    }

                    /* decode literal length */
                    if (length == RUN_MASK)
                    {
                        variable_length_error error = variable_length_error.ok;
                        length += LZ4_readVLE(&ip, iend - RUN_MASK, endOnInput, endOnInput, &error);
                        if (error == variable_length_error.initial_error)
                        {
                            goto _output_error;
                        }

                        if ((safeDecode) && ((op) + length < (op)))
                        {
                            goto _output_error;
                        }                         /* overflow detection */

                        if ((safeDecode) && ((ip) + length < (ip)))
                        {
                            goto _output_error;
                        }                         /* overflow detection */
                    }

                    /* copy literals */
                    cpy = op + length;
                    if (((endOnInput) && ((cpy > oend - MFLIMIT) ||
                                          (ip + length > iend - (2 + 1 + LASTLITERALS)))) ||
                        ((!endOnInput) && (cpy > oend - WILDCOPYLENGTH)))
                    {
                        /* We've either hit the input parsing restriction or the output parsing restriction.
                         * If we've hit the input parsing condition then this must be the last sequence.
                         * If we've hit the output parsing condition then we are either using partialDecoding
                         * or we've hit the output parsing condition.
                         */
                        if (partialDecoding)
                        {
                            /* Since we are partial decoding we may be in this block because of the output parsing
                             * restriction, which is not valid since the output buffer is allowed to be undersized.
                             */
                            Assert(endOnInput);

                            /* If we're in this block because of the input parsing condition, then we must be on the
                             * last sequence (or invalid), so we must check that we exactly consume the input.
                             */
                            if ((ip + length > iend - (2 + 1 + LASTLITERALS)) &&
                                (ip + length != iend))
                            {
                                goto _output_error;
                            }

                            Assert(ip + length <= iend);

                            /* We are finishing in the middle of a literals segment.
                             * Break after the copy.
                             */
                            if (cpy > oend)
                            {
                                cpy = oend;
                                Assert(op <= oend);
                                length = (size_t)(oend - op);
                            }

                            Assert(ip + length <= iend);
                        }
                        else
                        {
                            /* We must be on the last sequence because of the parsing limitations so check
                             * that we exactly regenerate the original size (must be exact when !endOnInput).
                             */
                            if ((!endOnInput) && (cpy != oend))
                            {
                                goto _output_error;
                            }

                            /* We must be on the last sequence (or invalid) because of the parsing limitations
                             * so check that we exactly consume the input and don't overrun the output buffer.
                             */
                            if ((endOnInput) && ((ip + length != iend) || (cpy > oend)))
                            {
                                goto _output_error;
                            }
                        }

                        Mem.Move(op, ip, (int)length);                          /* supports overlapping memory regions, which only matters for in-place decompression scenarios */
                        ip += length;
                        op += length;

                        /* Necessarily EOF when !partialDecoding. When partialDecoding
                         * it is EOF if we've either filled the output buffer or hit
                         * the input parsing restriction.
                         */
                        if (!partialDecoding || (cpy == oend) || (ip == iend))
                        {
                            break;
                        }
                    }
                    else
                    {
                        Mem.WildCopy8(
                            op, ip, cpy);                             /* may overwrite up to WILDCOPYLENGTH beyond cpy */
                        ip += length;
                        op  = cpy;
                    }

                    /* get offset */
                    offset = Mem.Peek2(ip);
                    ip    += 2;
                    match  = op - offset;

                    /* get matchlength */
                    length = token & ML_MASK;

_copy_match:
                    if (length == ML_MASK)
                    {
                        variable_length_error error = variable_length_error.ok;
                        length += LZ4_readVLE(
                            &ip, iend - LASTLITERALS + 1, endOnInput, false, &error);
                        if (error != variable_length_error.ok)
                        {
                            goto _output_error;
                        }
                        if ((safeDecode) && ((op) + length < op))
                        {
                            goto _output_error;                             /* overflow detection */
                        }
                    }

                    length += MINMATCH;

                    if ((checkOffset) && ((match + dictSize < lowPrefix)))
                    {
                        goto _output_error;                         /* Error : offset outside buffers */
                    }
                    /* match starting within external dictionary */
                    if ((dict == dict_directive.usingExtDict) && (match < lowPrefix))
                    {
                        if ((op + length > oend - LASTLITERALS))
                        {
                            if (partialDecoding)
                            {
                                length = MIN(length, (size_t)(oend - op));
                            }
                            else
                            {
                                goto _output_error;                              /* doesn't respect parsing restriction */
                            }
                        }

                        if (length <= (size_t)(lowPrefix - match))
                        {
                            /* match fits entirely within external dictionary : just copy */
                            Mem.Move(op, dictEnd - (lowPrefix - match), (int)length);
                            op += length;
                        }
                        else
                        {
                            /* match stretches into both external dictionary and current block */
                            size_t copySize = (size_t)(lowPrefix - match);
                            size_t restSize = length - copySize;
                            Mem.Copy(op, dictEnd - copySize, (int)copySize);
                            op += copySize;
                            if (restSize > (size_t)(op - lowPrefix))
                            {
                                /* overlap copy */
                                byte *endOfMatch = op + restSize;
                                byte *copyFrom   = lowPrefix;
                                while (op < endOfMatch)
                                {
                                    *op++ = *copyFrom++;
                                }
                            }
                            else
                            {
                                Mem.Copy(op, lowPrefix, (int)restSize);
                                op += restSize;
                            }
                        }

                        continue;
                    }

                    Assert(match >= lowPrefix);

                    /* copy match within block */
                    cpy = op + length;

                    /* partialDecoding : may end anywhere within the block */
                    Assert(op <= oend);
                    if (partialDecoding && (cpy > oend - MATCH_SAFEGUARD_DISTANCE))
                    {
                        size_t mlen     = MIN(length, (size_t)(oend - op));
                        byte * matchEnd = match + mlen;
                        byte * copyEnd  = op + mlen;
                        if (matchEnd > op)
                        {
                            /* overlap copy */
                            while (op < copyEnd)
                            {
                                *op++ = *match++;
                            }
                        }
                        else
                        {
                            Mem.Copy(op, match, (int)mlen);
                        }

                        op = copyEnd;
                        if (op == oend)
                        {
                            break;
                        }

                        continue;
                    }

                    if ((offset < 8))
                    {
                        // Mem.Poke4(op, 0); /* silence msan warning when offset==0 */
                        op[0]  = match[0];
                        op[1]  = match[1];
                        op[2]  = match[2];
                        op[3]  = match[3];
                        match += inc32table[offset];
                        Mem.Copy4(op + 4, match);
                        match -= dec64table[offset];
                    }
                    else
                    {
                        Mem.Copy8(op, match);
                        match += 8;
                    }

                    op += 8;

                    if ((cpy > oend - MATCH_SAFEGUARD_DISTANCE))
                    {
                        byte *oCopyLimit = oend - (WILDCOPYLENGTH - 1);
                        if (cpy > oend - LASTLITERALS)
                        {
                            goto _output_error;
                        }                         /* Error : last LASTLITERALS bytes must be literals (uncompressed) */

                        if (op < oCopyLimit)
                        {
                            Mem.WildCopy8(op, match, oCopyLimit);
                            match += oCopyLimit - op;
                            op     = oCopyLimit;
                        }

                        while (op < cpy)
                        {
                            *op++ = *match++;
                        }
                    }
                    else
                    {
                        Mem.Copy8(op, match);
                        if (length > 16)
                        {
                            Mem.WildCopy8(op + 8, match + 8, cpy);
                        }
                    }

                    op = cpy;                     /* wildcopy correction */
                }

                /* end of decoding */
                if (endOnInput)
                {
                    return((int)(((byte *)op) - dst));                     /* Nb of output bytes decoded */
                }
                else
                {
                    return((int)(((byte *)ip) - src));                     /* Nb of input bytes read */
                }

                /* Overflow error detected */
_output_error:
                return((int)(-(((byte *)ip) - src)) - 1);
            }
        }