/* $USAGI: ipsec6.h,v 1.20 2002/12/18 04:16:39 takamiya Exp $ */
/*
 * Copyright (C)2001 USAGI/WIDE Project
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Authors:
 *      Kazunori MIYAZAWA <miyazawa@linux-ipv6.org> / USAGI Project
 *      Mitsuru KANDA <mk@linux-ipv6.org> / USAGI Project
 *      YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org> / USAGI Project
 */
#ifndef _LINUX_IPSEC6_H
#define _LINUX_IPSEC6_H

#include <linux/skbuff.h>
#include <net/ipv6.h>
#include <net/spd.h>

#ifdef __KERNEL__

#ifdef CONFIG_IPSEC_DEBUG
# ifdef CONFIG_SYSCTL
#  define IPSEC6_DEBUG(fmt, args...) 					\
do {									\
	if (sysctl_ipsec_debug_ipv6) {					\
		printk(KERN_DEBUG "%s: " fmt, __FUNCTION__ , ## args);	\
	}								\
} while(0)
# else
#  define IPSEC6_DEBUG(fmt, args...) printk(KERN_DEBUG "%s: " fmt, __FUNCTION__ , ## args)
# endif /* CONFIG_SYSCTL */
#else
#  define IPSEC6_DEBUG(fmt, args...)
#endif

/* Set all mutable/unpredictable fields to zero. */
static int inline zero_out_mutable_opts(struct ipv6_opt_hdr *opthdr)
{
	u8 *opt = (u8*)opthdr;
	int len = ipv6_optlen(opthdr);
	int off = 0;
	int optlen;

	off += 2;
	len -= 2;

	while(len > 0) {
		switch(opt[off]) {
		case IPV6_TLV_PAD0:
			optlen = 1;
			break;
		default:
			if (len < 2)
				goto bad;
			optlen = opt[off+1]+2;
			if (len < optlen)
				goto bad;
			if (opt[off] & 0x20)	/* mutable check */
				memset(&opt[off+2], 0, opt[off+1]);
			break;
		}
		off += optlen;
		len -= optlen;
	}
	if (len == 0)
		return 1;
bad:
	return 0;
}

/* Set all mutable/predictable fields to the destination state, and all
   mutable/unpredictable fields to zero. */
static void inline zero_out_for_ah(struct inet6_skb_parm *parm, char* packet) 
{
	struct ipv6hdr *hdr = (struct ipv6hdr*)packet;

	/* Main header */
	hdr->tclass1=0;
	hdr->tclass2_flow[0]=0;
	hdr->tclass2_flow[1]=0;
	hdr->tclass2_flow[2]=0;
	hdr->hop_limit=0;
	/* Mutable/unpredictable Option headers */
	/* AH header */

	if (parm->auth) {
		struct ipv6_auth_hdr* authhdr =
			(struct ipv6_auth_hdr*)(packet + parm->auth);
		int len = ((authhdr->hdrlen - 1)  << 2);
		memset(authhdr->auth_data,0,len);
	}

	if (parm->hop) {
		struct ipv6_hopopt_hdr* hopopthdr =
			(struct ipv6_hopopt_hdr*)(packet + parm->hop);
		if (!zero_out_mutable_opts(hopopthdr))
			printk(KERN_WARNING
				"overrun when muting hopopts\n");
	}

	if (parm->dst0) {
		struct ipv6_destopt_hdr* destopthdr0 =
			(struct ipv6_destopt_hdr*)(packet + parm->dst0);
		if (!zero_out_mutable_opts(destopthdr0))
			printk(KERN_WARNING
				"overrun when muting destopt\n");
	}

	if (parm->dst1) {
		struct ipv6_destopt_hdr* destopthdr1 =
			(struct ipv6_destopt_hdr*)(packet + parm->dst1);
		if (!zero_out_mutable_opts(destopthdr1))
			printk(KERN_WARNING
				"overrun when muting destopt\n");
	}
	if (parm->srcrt) {
		struct ipv6_rt_hdr *rthdr =
			(struct ipv6_rt_hdr*)(packet + parm->srcrt);
		rthdr->segments_left = 0;
	}
}

int ipsec6_out_get_ahsize(struct ipsec_sp *policy);
int ipsec6_out_get_espsize(struct ipsec_sp *policy);
static inline int ipsec6_out_get_hdrsize(struct ipsec_sp *policy)
{
	return ipsec6_out_get_ahsize(policy) + ipsec6_out_get_espsize(policy);
}

struct ipv6_txoptions *ipsec6_out_get_newopt(struct ipv6_txoptions *opt, struct ipsec_sp *policy);
int ipsec6_out_ah_calc(const void *data, unsigned length, 
		inet_getfrag_t getfrag, struct sk_buff *skb, 
		struct ipv6_auth_hdr *authhdr, struct ipsec_sp *policy);
void ipsec6_out_enc(const void *data, unsigned length, u8 proto, struct ipv6_txoptions *opt,
		void **newdata, unsigned *newlength, struct ipsec_sp *policy);
void ipsec6_out_finish(struct ipv6_txoptions *opt, struct ipsec_sp *policy_ptr);
int ipsec6_input_check_ah(struct sk_buff **skb, struct ipv6_auth_hdr* authhdr);
int ipsec6_input_check_esp(struct sk_buff **skb, struct ipv6_esp_hdr *esphdr, u8 *nexthdr);

int ipsec6_input_check(struct sk_buff **skb, u8 *nexthdr);
int ipsec6_output_check(struct sock *sk, struct flowi *fl, const u8* data, struct ipsec_sp **policy_ptr);
int ipsec6_ndisc_check(struct in6_addr *saddr, struct in6_addr *daddr, struct ipsec_sp **policy_ptr);

#endif /* __KERNEL__ */

#endif /* _LINUX_IPSEC6_H */
