#include <linux/config.h>
#include <linux/module.h>
#include <linux/init.h>
#include <asm/byteorder.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/sysctl.h>
#include <linux/inet.h>
#include <linux/ipv6.h>
#include <linux/smp.h>
#include <linux/list.h>
#include <linux/skbuff.h>
#include <asm/uaccess.h>
#include <net/ipv6.h>
#include <net/ipcomp.h>
#include "zlib/zlib.h"
#include "zlib/zutil.h"


#if !defined(CONFIG_IPCOMP_DEBUG)
# define CONFIG_IPCOMP_DEBUG 1
#endif
#ifdef CONFIG_IPCOMP_DEBUG
# define IPCOMP_DEBUG(fmt, args...) printk(KERN_DEBUG "%s: " fmt, __FUNCTION__ , ## args)
#else
# define IPCOMP_DEBUG(fmt, args...) 
#endif 

static int deflate_compress(void *data_in, u32 len_in, void **data_out, u32 len_out);
static int deflate_decompress(void *data_in, u32 len_in, void **data_out, u32 len_out);

static const struct ipcomp_algo_func ipcomp_algo_funcs[] = {
		{ NULL, NULL, 0},                             /* reserved */
		{ NULL, NULL, 0},                             /* OUI */
	        { deflate_compress, deflate_decompress, IPCOMP_THRESHOLD_DEFLATE }, /* DEFLATE */
		{ NULL, NULL, 0},                             /* LZS */
		{ NULL, NULL, 0},                             /* LZJH */
};

struct ipcomp_algo_func *
ipcomp_algo_lookup(u32 cpi)
{
	return &ipcomp_algo_funcs[cpi];
}


/***************** deflate algorithm **********************/
static voidpf
ipcomp_zalloc(voidpf opaque, uInt items, uInt size)
{
	return (voidpf)kmalloc(items*size, GFP_ATOMIC);
}

static void 
ipcomp_zfree(voidpf opaque, voidpf address)
{
	kfree(address);
}

/* if len_out != 0, we use len_out as reference output buffer size.*/
static int deflate_compress(void *data_in, u32 len_in, void **data_out, u32 len_out)
{
	int zresult;
	__u8 *comp_data=NULL;
	z_stream zs;

	memset(&zs, 0, sizeof(zs));
	zs.zalloc = ipcomp_zalloc;
	zs.zfree = ipcomp_zfree;
	zs.opaque = 0;

	printk(KERN_ALERT "%s: called.\n", __FUNCTION__);
	zresult = deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -11, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY);
	//zresult = deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -12, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY);
	if (zresult != Z_OK) {
		printk(KERN_ERR "%s: deflateInit2() err: %d:%s\n", __FUNCTION__, zresult, zs.msg ? zs.msg : zError(zresult));
		return -ENOBUFS;
	}

	if (!len_out) {
		len_out = len_in; /* just get same size */
	}
	comp_data = kmalloc(len_out, GFP_ATOMIC);
	if (!comp_data) {
		printk(KERN_ERR "%s: can't allocate memory\n", __FUNCTION__);
		return -ENOMEM;
	}

	zs.next_in = data_in;
	zs.avail_in = len_in;
	zs.next_out = comp_data;
	zs.avail_out = len_out;

	IPCOMP_DEBUG("next_in: %p.\n", zs.next_in);
	IPCOMP_DEBUG("avail_in: %d.\n", zs.avail_in);
	IPCOMP_DEBUG("next_out: %p.\n", zs.next_out);
	IPCOMP_DEBUG("avail_out: %d.\n", zs.avail_out);

	zresult = deflate(&zs, Z_FINISH);
	IPCOMP_DEBUG("avail_in: %d.\n", zs.avail_in);
	IPCOMP_DEBUG("avail_out: %d.\n", zs.avail_out);
	if (zresult != Z_STREAM_END) {
		printk(KERN_ERR "%s: deflate() err: %d:%s\n", __FUNCTION__, zresult, zs.msg ? zs.msg : zError(zresult));
		deflateEnd(&zs);
		return -EINVAL;
	}
	
	zresult = deflateEnd(&zs);
	if (zresult != Z_OK) {
		printk(KERN_ERR "%s: deflateEnd() err: %d:%s\n", __FUNCTION__, zresult, zs.msg ? zs.msg : zError(zresult));
		return -EINVAL;
	}

	len_out -= zs.avail_out;

	IPCOMP_DEBUG("len_out: %d.\n", len_out);
	IPCOMP_DEBUG("comp_data: %p.\n", comp_data);
	IPCOMP_DEBUG("end.\n");

	*data_out = comp_data;

	return len_out;
}

static int deflate_decompress(void *data_in, u32 len_in, void **data_out, u32 len_out)
{
	z_stream zs;
	u8 *decomp_data = NULL;
	int zresult;

	memset(&zs, 0, sizeof(zs));
	zs.zalloc = ipcomp_zalloc;
	zs.zfree = ipcomp_zfree;
	zs.opaque = 0;

	zresult = inflateInit2(&zs, -15);
	if (zresult != Z_OK) {
		printk(KERN_ERR "inflateInit2() err: %d:%s\n", zresult, zs.msg ? zs.msg : zError(zresult));
		return -ENOBUFS;
	}

	if (!len_out) {
		len_out = len_in * 2; /* XXX */
	}
	decomp_data = kmalloc(len_out, GFP_ATOMIC);
	if (!decomp_data) {
		printk(KERN_ERR "%s: can't allocate memory\n", __FUNCTION__);
		return -ENOMEM;
	}

	zs.next_in = data_in;
	zs.avail_in = len_in;
	zs.next_out = decomp_data;
	zs.avail_out = len_out;

	zresult = inflate(&zs, Z_SYNC_FLUSH);
	/* work around a bug in zlib, which sometimes wants to taste an extra
	 * byte when being used in the (undocumented) raw deflate mode.
	 */
	if (zresult == Z_OK && !zs.avail_in && zs.avail_out) {
		__u8 zerostuff = 0;
		zs.next_in = &zerostuff; 
		zs.avail_in = 1; 
		zresult = inflate(&zs, Z_FINISH); 
		IPCOMP_DEBUG("rent: zresult:%d,%s\n", zresult, zs.msg ? zs.msg : zError(zresult));
	}
			
	if (zresult != Z_STREAM_END) {
		printk(KERN_ERR "inflate() err: %d:%s\n", zresult, zs.msg ? zs.msg : zError(zresult));
		kfree(decomp_data);
		decomp_data = NULL;
		return -EINVAL;
	}

	zresult = inflateEnd(&zs);
	if (zresult != Z_OK) {
		printk(KERN_ERR "inflateEnd() err: %d:%s\n", zresult, zs.msg ? zs.msg : zError(zresult));
		kfree(decomp_data);
		decomp_data = NULL;
		return -EINVAL;
	}
	len_out -= zs.avail_out;
	*data_out = decomp_data;

	return len_out;
}

