/* 7zDec.c -- Decoding from 7z folder
2015-11-18 : Igor Pavlov : Public domain */

#include "Precomp.h"

#include <string.h>

/* #define _7ZIP_PPMD_SUPPPORT */

#include "7z.h"
#include "7zCrc.h"

#include "Bcj2.h"
#include "Bra.h"
#include "CpuArch.h"
#include "Delta.h"
#include "LzmaDec.h"
#include "Lzma2Dec.h"
#ifdef _7ZIP_PPMD_SUPPPORT
#include "Ppmd7.h"
#endif

#define k_Copy 0
#define k_Delta 3
#define k_LZMA2 0x21
#define k_LZMA  0x30101
#define k_BCJ   0x3030103
#define k_BCJ2  0x303011B
#define k_PPC   0x3030205
#define k_IA64  0x3030401
#define k_ARM   0x3030501
#define k_ARMT  0x3030701
#define k_SPARC 0x3030805


#ifdef _7ZIP_PPMD_SUPPPORT

#define k_PPMD 0x30401

typedef struct
{
    IByteIn p;
    const Byte* cur;
    const Byte* end;
    const Byte* begin;
    UInt64 processed;
    Bool extra;
    SRes res;
    ILookInStream* inStream;
} CByteInToLook;

static Byte ReadByte(void* pp)
{
    CByteInToLook* p = (CByteInToLook*)pp;
    if (p->cur != p->end)
        return *p->cur++;
    if (p->res == SZ_OK)
    {
        size_t size = p->cur - p->begin;
        p->processed += size;
        p->res = p->inStream->Skip(p->inStream, size);
        size = (1 << 25);
        p->res = p->inStream->Look(p->inStream, (const void**)&p->begin, &size);
        p->cur = p->begin;
        p->end = p->begin + size;
        if (size != 0)
            return *p->cur++;;
    }
    p->extra = True;
    return 0;
}

static SRes SzDecodePpmd(const Byte* props, unsigned propsSize, UInt64 inSize, ILookInStream* inStream,
    Byte* outBuffer, SizeT outSize, ISzAlloc* allocMain)
{
    CPpmd7 ppmd;
    CByteInToLook s;
    SRes res = SZ_OK;

    s.p.Read = ReadByte;
    s.inStream = inStream;
    s.begin = s.end = s.cur = NULL;
    s.extra = False;
    s.res = SZ_OK;
    s.processed = 0;

    if (propsSize != 5)
        return SZ_ERROR_UNSUPPORTED;

    {
        unsigned order = props[0];
        UInt32 memSize = GetUi32(props + 1);
        if (order < PPMD7_MIN_ORDER ||
            order > PPMD7_MAX_ORDER ||
            memSize < PPMD7_MIN_MEM_SIZE ||
            memSize > PPMD7_MAX_MEM_SIZE)
            return SZ_ERROR_UNSUPPORTED;
        Ppmd7_Construct(&ppmd);
        if (!Ppmd7_Alloc(&ppmd, memSize, allocMain))
            return SZ_ERROR_MEM;
        Ppmd7_Init(&ppmd, order);
    }
    {
        CPpmd7z_RangeDec rc;
        Ppmd7z_RangeDec_CreateVTable(&rc);
        rc.Stream = &s.p;
        if (!Ppmd7z_RangeDec_Init(&rc))
            res = SZ_ERROR_DATA;
        else if (s.extra)
            res = (s.res != SZ_OK ? s.res : SZ_ERROR_DATA);
        else
        {
            SizeT i;
            for (i = 0; i < outSize; i++)
            {
                int sym = Ppmd7_DecodeSymbol(&ppmd, &rc.p);
                if (s.extra || sym < 0)
                    break;
                outBuffer[i] = (Byte)sym;
            }
            if (i != outSize)
                res = (s.res != SZ_OK ? s.res : SZ_ERROR_DATA);
            else if (s.processed + (s.cur - s.begin) != inSize || !Ppmd7z_RangeDec_IsFinishedOK(&rc))
                res = SZ_ERROR_DATA;
        }
    }
    Ppmd7_Free(&ppmd, allocMain);
    return res;
}

#endif


static SRes SzDecodeLzma(const Byte* props, unsigned propsSize, UInt64 inSize, ILookInStream* inStream,
    Byte* outBuffer, SizeT outSize, ISzAlloc* allocMain)
{
    CLzmaDec state;
    SRes res = SZ_OK;

    LzmaDec_Construct(&state);
    RINOK(LzmaDec_AllocateProbs(&state, props, propsSize, allocMain));
    state.dic = outBuffer;
    state.dicBufSize = outSize;
    LzmaDec_Init(&state);

    for (;;)
    {
        const void* inBuf = NULL;
        size_t lookahead = (1 << 18);
        if (lookahead > inSize)
            lookahead = (size_t)inSize;
        res = inStream->Look(inStream, &inBuf, &lookahead);
        if (res != SZ_OK)
            break;

        {
            SizeT inProcessed = (SizeT)lookahead, dicPos = state.dicPos;
            ELzmaStatus status;
            res = LzmaDec_DecodeToDic(&state, outSize, inBuf, &inProcessed, LZMA_FINISH_END, &status);
            lookahead -= inProcessed;
            inSize -= inProcessed;
            if (res != SZ_OK)
                break;

            if (status == LZMA_STATUS_FINISHED_WITH_MARK)
            {
                if (outSize != state.dicPos || inSize != 0)
                    res = SZ_ERROR_DATA;
                break;
            }

            if (outSize == state.dicPos && inSize == 0 && status == LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK)
                break;

            if (inProcessed == 0 && dicPos == state.dicPos)
            {
                res = SZ_ERROR_DATA;
                break;
            }

            res = inStream->Skip((void*)inStream, inProcessed);
            if (res != SZ_OK)
                break;
        }
    }

    LzmaDec_FreeProbs(&state, allocMain);
    return res;
}


#ifndef _7Z_NO_METHOD_LZMA2

static SRes SzDecodeLzma2(const Byte* props, unsigned propsSize, UInt64 inSize, ILookInStream* inStream,
    Byte* outBuffer, SizeT outSize, ISzAlloc* allocMain)
{
    CLzma2Dec state;
    SRes res = SZ_OK;

    Lzma2Dec_Construct(&state);
    if (propsSize != 1)
        return SZ_ERROR_DATA;
    RINOK(Lzma2Dec_AllocateProbs(&state, props[0], allocMain));
    state.decoder.dic = outBuffer;
    state.decoder.dicBufSize = outSize;
    Lzma2Dec_Init(&state);

    for (;;)
    {
        const void* inBuf = NULL;
        size_t lookahead = (1 << 18);
        if (lookahead > inSize)
            lookahead = (size_t)inSize;
        res = inStream->Look(inStream, &inBuf, &lookahead);
        if (res != SZ_OK)
            break;

        {
            SizeT inProcessed = (SizeT)lookahead, dicPos = state.decoder.dicPos;
            ELzmaStatus status;
            res = Lzma2Dec_DecodeToDic(&state, outSize, inBuf, &inProcessed, LZMA_FINISH_END, &status);
            lookahead -= inProcessed;
            inSize -= inProcessed;
            if (res != SZ_OK)
                break;

            if (status == LZMA_STATUS_FINISHED_WITH_MARK)
            {
                if (outSize != state.decoder.dicPos || inSize != 0)
                    res = SZ_ERROR_DATA;
                break;
            }

            if (inProcessed == 0 && dicPos == state.decoder.dicPos)
            {
                res = SZ_ERROR_DATA;
                break;
            }

            res = inStream->Skip((void*)inStream, inProcessed);
            if (res != SZ_OK)
                break;
        }
    }

    Lzma2Dec_FreeProbs(&state, allocMain);
    return res;
}

#endif


static SRes SzDecodeCopy(UInt64 inSize, ILookInStream* inStream, Byte* outBuffer)
{
    while (inSize > 0)
    {
        const void* inBuf;
        size_t curSize = (1 << 18);
        if (curSize > inSize)
            curSize = (size_t)inSize;
        RINOK(inStream->Look(inStream, &inBuf, &curSize));
        if (curSize == 0)
            return SZ_ERROR_INPUT_EOF;
        memcpy(outBuffer, inBuf, curSize);
        outBuffer += curSize;
        inSize -= curSize;
        RINOK(inStream->Skip((void*)inStream, curSize));
    }
    return SZ_OK;
}

static Bool IS_MAIN_METHOD(UInt32 m)
{
    switch (m)
    {
    case k_Copy:
    case k_LZMA:
#ifndef _7Z_NO_METHOD_LZMA2
    case k_LZMA2:
#endif
#ifdef _7ZIP_PPMD_SUPPPORT
    case k_PPMD:
#endif
        return True;
    }
    return False;
}

static Bool IS_SUPPORTED_CODER(const CSzCoderInfo* c)
{
    return
        c->NumStreams == 1
        /* && c->MethodID <= (UInt32)0xFFFFFFFF */
        && IS_MAIN_METHOD((UInt32)c->MethodID);
}

#define IS_BCJ2(c) ((c)->MethodID == k_BCJ2 && (c)->NumStreams == 4)

static SRes CheckSupportedFolder(const CSzFolder* f)
{
    if (f->NumCoders < 1 || f->NumCoders > 4)
        return SZ_ERROR_UNSUPPORTED;
    if (!IS_SUPPORTED_CODER(&f->Coders[0]))
        return SZ_ERROR_UNSUPPORTED;
    if (f->NumCoders == 1)
    {
        if (f->NumPackStreams != 1 || f->PackStreams[0] != 0 || f->NumBonds != 0)
            return SZ_ERROR_UNSUPPORTED;
        return SZ_OK;
    }


#ifndef _7Z_NO_METHODS_FILTERS

    if (f->NumCoders == 2)
    {
        const CSzCoderInfo* c = &f->Coders[1];
        if (
            /* c->MethodID > (UInt32)0xFFFFFFFF || */
            c->NumStreams != 1
            || f->NumPackStreams != 1
            || f->PackStreams[0] != 0
            || f->NumBonds != 1
            || f->Bonds[0].InIndex != 1
            || f->Bonds[0].OutIndex != 0)
            return SZ_ERROR_UNSUPPORTED;
        switch ((UInt32)c->MethodID)
        {
        case k_Delta:
        case k_BCJ:
        case k_PPC:
        case k_IA64:
        case k_SPARC:
        case k_ARM:
        case k_ARMT:
            break;
        default:
            return SZ_ERROR_UNSUPPORTED;
        }
        return SZ_OK;
    }

#endif


    if (f->NumCoders == 4)
    {
        if (!IS_SUPPORTED_CODER(&f->Coders[1])
            || !IS_SUPPORTED_CODER(&f->Coders[2])
            || !IS_BCJ2(&f->Coders[3]))
            return SZ_ERROR_UNSUPPORTED;
        if (f->NumPackStreams != 4
            || f->PackStreams[0] != 2
            || f->PackStreams[1] != 6
            || f->PackStreams[2] != 1
            || f->PackStreams[3] != 0
            || f->NumBonds != 3
            || f->Bonds[0].InIndex != 5 || f->Bonds[0].OutIndex != 0
            || f->Bonds[1].InIndex != 4 || f->Bonds[1].OutIndex != 1
            || f->Bonds[2].InIndex != 3 || f->Bonds[2].OutIndex != 2)
            return SZ_ERROR_UNSUPPORTED;
        return SZ_OK;
    }

    return SZ_ERROR_UNSUPPORTED;
}

#define CASE_BRA_CONV(isa) case k_ ## isa: isa ## _Convert(outBuffer, outSize, 0, 0); break;

static SRes SzFolder_Decode2(const CSzFolder* folder,
    const Byte* propsData,
    const UInt64* unpackSizes,
    const UInt64* packPositions,
    ILookInStream* inStream, UInt64 startPos,
    Byte* outBuffer, SizeT outSize, ISzAlloc* allocMain,
    Byte* tempBuf[])
{
    UInt32 ci;
    SizeT tempSizes[3] = { 0, 0, 0 };
    SizeT tempSize3 = 0;
    Byte* tempBuf3 = 0;

    RINOK(CheckSupportedFolder(folder));

    for (ci = 0; ci < folder->NumCoders; ci++)
    {
        const CSzCoderInfo* coder = &folder->Coders[ci];

        if (IS_MAIN_METHOD((UInt32)coder->MethodID))
        {
            UInt32 si = 0;
            UInt64 offset;
            UInt64 inSize;
            Byte* outBufCur = outBuffer;
            SizeT outSizeCur = outSize;
            if (folder->NumCoders == 4)
            {
                UInt32 indices[] = { 3, 2, 0 };
                UInt64 unpackSize = unpackSizes[ci];
                si = indices[ci];
                if (ci < 2)
                {
                    Byte* temp;
                    outSizeCur = (SizeT)unpackSize;
                    if (outSizeCur != unpackSize)
                        return SZ_ERROR_MEM;
                    temp = (Byte*)IAlloc_Alloc(allocMain, outSizeCur);
                    if (!temp && outSizeCur != 0)
                        return SZ_ERROR_MEM;
                    outBufCur = tempBuf[1 - ci] = temp;
                    tempSizes[1 - ci] = outSizeCur;
                }
                else if (ci == 2)
                {
                    if (unpackSize > outSize) /* check it */
                        return SZ_ERROR_PARAM;
                    tempBuf3 = outBufCur = outBuffer + (outSize - (size_t)unpackSize);
                    tempSize3 = outSizeCur = (SizeT)unpackSize;
                }
                else
                    return SZ_ERROR_UNSUPPORTED;
            }
            offset = packPositions[si];
            inSize = packPositions[si + 1] - offset;
            RINOK(LookInStream_SeekTo(inStream, startPos + offset));

            if (coder->MethodID == k_Copy)
            {
                if (inSize != outSizeCur) /* check it */
                    return SZ_ERROR_DATA;
                RINOK(SzDecodeCopy(inSize, inStream, outBufCur));
            }
            else if (coder->MethodID == k_LZMA)
            {
                RINOK(SzDecodeLzma(propsData + coder->PropsOffset, coder->PropsSize, inSize, inStream, outBufCur, outSizeCur, allocMain));
            }
#ifndef _7Z_NO_METHOD_LZMA2
            else if (coder->MethodID == k_LZMA2)
            {
                RINOK(SzDecodeLzma2(propsData + coder->PropsOffset, coder->PropsSize, inSize, inStream, outBufCur, outSizeCur, allocMain));
            }
#endif
#ifdef _7ZIP_PPMD_SUPPPORT
            else if (coder->MethodID == k_PPMD)
            {
                RINOK(SzDecodePpmd(propsData + coder->PropsOffset, coder->PropsSize, inSize, inStream, outBufCur, outSizeCur, allocMain));
            }
#endif
            else
                return SZ_ERROR_UNSUPPORTED;
        }
        else if (coder->MethodID == k_BCJ2)
        {
            UInt64 offset = packPositions[1];
            UInt64 s3Size = packPositions[2] - offset;

            if (ci != 3)
                return SZ_ERROR_UNSUPPORTED;

            tempSizes[2] = (SizeT)s3Size;
            if (tempSizes[2] != s3Size)
                return SZ_ERROR_MEM;
            tempBuf[2] = (Byte*)IAlloc_Alloc(allocMain, tempSizes[2]);
            if (!tempBuf[2] && tempSizes[2] != 0)
                return SZ_ERROR_MEM;

            RINOK(LookInStream_SeekTo(inStream, startPos + offset));
            RINOK(SzDecodeCopy(s3Size, inStream, tempBuf[2]));

            if ((tempSizes[0] & 3) != 0 ||
                (tempSizes[1] & 3) != 0 ||
                tempSize3 + tempSizes[0] + tempSizes[1] != outSize)
                return SZ_ERROR_DATA;

            {
                CBcj2Dec p;

                p.bufs[0] = tempBuf3;   p.lims[0] = tempBuf3 + tempSize3;
                p.bufs[1] = tempBuf[0]; p.lims[1] = tempBuf[0] + tempSizes[0];
                p.bufs[2] = tempBuf[1]; p.lims[2] = tempBuf[1] + tempSizes[1];
                p.bufs[3] = tempBuf[2]; p.lims[3] = tempBuf[2] + tempSizes[2];

                p.dest = outBuffer;
                p.destLim = outBuffer + outSize;

                Bcj2Dec_Init(&p);
                RINOK(Bcj2Dec_Decode(&p));

                {
                    unsigned i;
                    for (i = 0; i < 4; i++)
                        if (p.bufs[i] != p.lims[i])
                            return SZ_ERROR_DATA;

                    if (!Bcj2Dec_IsFinished(&p))
                        return SZ_ERROR_DATA;

                    if (p.dest != p.destLim
                        || p.state != BCJ2_STREAM_MAIN)
                        return SZ_ERROR_DATA;
                }
            }
        }
#ifndef _7Z_NO_METHODS_FILTERS
        else if (ci == 1)
        {
            if (coder->MethodID == k_Delta)
            {
                if (coder->PropsSize != 1)
                    return SZ_ERROR_UNSUPPORTED;
                {
                    Byte state[DELTA_STATE_SIZE];
                    Delta_Init(state);
                    Delta_Decode(state, (unsigned)(propsData[coder->PropsOffset]) + 1, outBuffer, outSize);
                }
            }
            else
            {
                if (coder->PropsSize != 0)
                    return SZ_ERROR_UNSUPPORTED;
                switch (coder->MethodID)
                {
                case k_BCJ:
                {
                    UInt32 state;
                    x86_Convert_Init(state);
                    x86_Convert(outBuffer, outSize, 0, &state, 0);
                    break;
                }
                CASE_BRA_CONV(PPC)
                    CASE_BRA_CONV(IA64)
                    CASE_BRA_CONV(SPARC)
                    CASE_BRA_CONV(ARM)
                    CASE_BRA_CONV(ARMT)
                default:
                    return SZ_ERROR_UNSUPPORTED;
                }
            }
        }
#endif
        else
            return SZ_ERROR_UNSUPPORTED;
    }

    return SZ_OK;
}


SRes SzAr_DecodeFolder(const CSzAr* p, UInt32 folderIndex,
    ILookInStream* inStream, UInt64 startPos,
    Byte* outBuffer, size_t outSize,
    ISzAlloc* allocMain)
{
    SRes res;
    CSzFolder folder;
    CSzData sd;

    const Byte* data = p->CodersData + p->FoCodersOffsets[folderIndex];
    sd.Data = data;
    sd.Size = p->FoCodersOffsets[folderIndex + 1] - p->FoCodersOffsets[folderIndex];

    res = SzGetNextFolderItem(&folder, &sd);

    if (res != SZ_OK)
        return res;

    if (sd.Size != 0
        || folder.UnpackStream != p->FoToMainUnpackSizeIndex[folderIndex]
        || outSize != SzAr_GetFolderUnpackSize(p, folderIndex))
        return SZ_ERROR_FAIL;
    {
        unsigned i;
        Byte* tempBuf[3] = { 0, 0, 0 };

        res = SzFolder_Decode2(&folder, data,
            &p->CoderUnpackSizes[p->FoToCoderUnpackSizes[folderIndex]],
            p->PackPositions + p->FoStartPackStreamIndex[folderIndex],
            inStream, startPos,
            outBuffer, (SizeT)outSize, allocMain, tempBuf);

        for (i = 0; i < 3; i++)
            IAlloc_Free(allocMain, tempBuf[i]);

        if (res == SZ_OK)
            if (SzBitWithVals_Check(&p->FolderCRCs, folderIndex))
                if (CrcCalc(outBuffer, outSize) != p->FolderCRCs.Vals[folderIndex])
                    res = SZ_ERROR_CRC;

        return res;
    }
}
