/*
 *  Copyright (C) 2015 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
 *  Copyright (C) 2009 Sourcefire, Inc.
 *
 *  Authors: aCaB
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 *  MA 02110-1301, USA.
 */

/* zlib-alike state interface to LZMA */

#if HAVE_CONFIG_H
#include "clamav-config.h"
#endif

#include "clamav.h"
#include "lzma_iface.h"

void *__lzma_wrap_alloc(void *unused, size_t size) { 
    UNUSEDPARAM(unused);
    if(!size || size > CLI_MAX_ALLOCATION)
	return NULL;
    if(!size || size > CLI_MAX_ALLOCATION) {
	cli_dbgmsg("lzma_wrap_alloc(): Attempt to allocate %lu bytes.\n", (unsigned long int) size);
	return NULL;
    }

    return cli_calloc(1, size);
}
void __lzma_wrap_free(void *unused, void *freeme) {
    UNUSEDPARAM(unused);
    free(freeme);
}
static ISzAlloc g_Alloc = { __lzma_wrap_alloc, __lzma_wrap_free };


static unsigned char lzma_getbyte(struct CLI_LZMA *L, int *fail) {
    unsigned char c;
    if(!L->next_in || !L->avail_in) {
	*fail = 1;
	return 0;
    }
    *fail = 0;
    c = L->next_in[0];
    L->next_in++;
    L->avail_in--;
    return c;
}
    

int cli_LzmaInit(struct CLI_LZMA *L, uint64_t size_override) {
    int fail;

    if(!L->init) {
	L->p_cnt = LZMA_PROPS_SIZE;
	if(size_override)
	    L->usize = size_override;
	else
	    L->s_cnt = 8;
	L->init = 1;
    } else if(size_override)
	cli_warnmsg("cli_LzmaInit: ignoring late size override\n");

    if(L->freeme) return LZMA_RESULT_OK;

    while(L->p_cnt) {
	L->header[LZMA_PROPS_SIZE - L->p_cnt] = lzma_getbyte(L, &fail);
	if(fail) return LZMA_RESULT_OK;
	L->p_cnt--;
    }

    while(L->s_cnt) {
	uint64_t c = (uint64_t)lzma_getbyte(L, &fail);
	if(fail) return LZMA_RESULT_OK;
	L->usize = c << (8 * (8 - L->s_cnt));
	L->s_cnt--;
    }

    LzmaDec_Construct(&L->state);
    if(LzmaDec_Allocate(&L->state, L->header, LZMA_PROPS_SIZE, &g_Alloc) != SZ_OK)
	return LZMA_RESULT_DATA_ERROR;
    LzmaDec_Init(&L->state);

    L->freeme = 1;
    return LZMA_RESULT_OK;
}
	

void cli_LzmaShutdown(struct CLI_LZMA *L) {
    if(L->freeme)
	LzmaDec_Free(&L->state, &g_Alloc);
    return;
}


int cli_LzmaDecode(struct CLI_LZMA *L) {
    SRes res;
    SizeT outbytes, inbytes;
    ELzmaStatus status;
    ELzmaFinishMode finish;

    if(!L->freeme) return cli_LzmaInit(L, 0);

    inbytes = L->avail_in;
    if(~L->usize && L->avail_out > L->usize) {
	outbytes = L->usize;
	finish = LZMA_FINISH_END;
    } else {
	outbytes = L->avail_out;
	finish = LZMA_FINISH_ANY;
    }
    res = LzmaDec_DecodeToBuf(&L->state, L->next_out, &outbytes, L->next_in, &inbytes, finish, &status);
    L->avail_in -= inbytes;
    L->next_in += inbytes;
    L->avail_out -= outbytes;
    L->next_out += outbytes;
    if(~L->usize) L->usize -= outbytes;
    if(res != SZ_OK)
	return LZMA_RESULT_DATA_ERROR;
    if(!L->usize || status == LZMA_STATUS_FINISHED_WITH_MARK)
	return LZMA_STREAM_END;
    return LZMA_RESULT_OK;
}
