/*
 * Copyright (c) University of British Columbia, 1984
 * Copyright (c) 1990 The Regents of the University of California.
 * All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * the Laboratory for Computation Vision and the Computer Science Department
 * of the University of British Columbia.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *	@(#)hd_subr.c	7.6 (Berkeley) 5/29/91
 */

#include <linux/config.h>
#ifdef CONFIG_AX25
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/socket.h>
#include <linux/in.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/string.h>
#include <linux/sockios.h>
#include <linux/net.h>
#include "ax25.h"
#include <linux/inet.h>
#include <linux/netdevice.h>
#include <linux/skbuff.h>
#include "sock.h"
#include <asm/segment.h>
#include <asm/system.h>
#include <linux/fcntl.h>
#include <linux/mm.h>
#include <linux/interrupt.h>

struct	Frmr_frame ax25_frmr;	/* rejected frame diagnostic info */


void ax25_init_vars(ax25_socket *sk)
{
	sk_buff *skb;
	register int i;

	/* Clear Transmit queue. */
	while ((skb = skb_dequeue(&sk->write_queue)) != NULL)
	{
		/* skb_unlink(skb); */
		kfree_skb(skb,FREE_WRITE);
	}

	/* Clear Retransmit queue. */
	i = sk->opt.ax25.lastrxnr;
	while (i != sk->opt.ax25.retxqi) {
		skb=sk->opt.ax25.retxq[i];
		if(skb==NULL)
			printk("OOPS: Double free in ax25_init_vars!.\n");
		if(skb->next && skb->prev)
			skb_unlink(skb);
		kfree_skb(skb,FREE_WRITE);
		i = (i + 1) % MODULUS;
	}
	sk->opt.ax25.retxqi = 0;

	sk->opt.ax25.vs = sk->opt.ax25.vr = 0;
	sk->opt.ax25.lasttxnr = sk->opt.ax25.lastrxnr = 0;
	sk->opt.ax25.rrtimer = 0;
	KILL_TIMER(sk);
	sk->opt.ax25.retxcnt = 0;
	sk->opt.ax25.condition = 0;
}

int ax25_decode(ax25_socket *sk, struct Hdlc_frame *frame)
{
	int frametype = ILLEGAL;
	struct Hdlc_iframe *iframe = (struct Hdlc_iframe *) frame;
	struct Hdlc_sframe *sframe = (struct Hdlc_sframe *) frame;
	struct Hdlc_uframe *uframe = (struct Hdlc_uframe *) frame;

	if (iframe -> hdlc_0 == 0) 
	{
		frametype = IFRAME;
	}

	else if (sframe -> hdlc_01 == 1) 
	{
		/* Supervisory format. */
		switch (sframe -> s2) 
		{
			case 0: 
				frametype = RR;
				break;
	
			case 1: 
				frametype = RNR;
				break;
	
			case 2: 
				frametype = REJ;
		}
	}
	else if (uframe -> hdlc_11 == 3) 
	{
		/* Unnumbered format. */
		switch (uframe -> m3) 
		{
			case 0: 
				frametype = DM;
				break;
			case 1: 
				frametype = SABM;
				break;
			case 2: 
				frametype = DISC;
				break;
			case 3: 
				frametype = UA;
				break;
			case 4: 
				frametype = FRMR;
		}
	}
	return (frametype);
}

/* 
 *  This routine is called when the HDLC layer internally  generates a
 *  command or  response  for  the remote machine ( eg. RR, UA etc. ). 
 *  Only supervisory or unnumbered frames are processed.
 */

void ax25_write_internal(ax25_socket *sk,int frametype, int pf, int type)
{
	sk_buff *skb;
	struct Hdlc_frame *frame;
	struct Hdlc_sframe *sframe;
	struct Hdlc_uframe *uframe;
	unsigned char *dptr;
	int len;
	struct protocol *proto;
	
	
	proto=ax25rtr_get_dev(&sk->opt.ax25.source_addr);
	if(proto==NULL)
		return;	/* Route died */

	skb=alloc_skb(sizeof(struct Hdlc_frame)+protocol_size(&proto_ax25),GFP_ATOMIC);
	if(skb==NULL)
		return;
	protocol_adjust(skb,&proto_ax25);

	skb->sk=sk;
	sk->wmem_alloc+=skb->mem_len;
	dptr=skb_put(skb,sizeof(struct Hdlc_frame));
	frame = (struct Hdlc_frame *)dptr;
	sframe = (struct Hdlc_sframe *)dptr;
	uframe = (struct Hdlc_uframe *)dptr;

	/* Assume a response - address structure for DTE */
	len=sizeof(struct Hdlc_sframe);		/* Normal size */
	
	switch (frametype) {
	case RR: 
		frame -> control = RR_CONTROL;
		break;

	case RNR: 
		frame -> control = RNR_CONTROL;
		break;

	case REJ: 
		frame -> control = REJ_CONTROL;
		break;

	case SABM: 
		frame -> control = SABM_CONTROL;
		break;

	case DISC: 
		frame -> control = DISC_CONTROL;
		break;

	case DM: 
		frame -> control = DM_CONTROL;
		break;

	case UA: 
		frame -> control = UA_CONTROL;
		break;

	case FRMR: 
		frame -> control = FRMR_CONTROL;
		memcpy(frame->info,&ax25_frmr,3);
		len = 5;

	}

	if (sframe -> hdlc_01 == 1) {
		/* Supervisory format - RR, REJ, or RNR. */
		sframe -> nr = sk->opt.ax25.vr;
		sframe -> pf = pf;
		sk->opt.ax25.lasttxnr = sk->opt.ax25.vr;
		sk->opt.ax25.rrtimer = 0;
	}
	else
		uframe -> pf = pf;

	skb->free=1;
	skb_trim(skb, len);
	ax25_transmit_buffer(sk,skb,type,1);
}

/*
 *	Send a 'DM' to an unknown connection attempt, or an invalid caller.
 *
 *	Note: src here is the sender, thus its the target of the DM
 */
 
void ax25_return_dm(ax25_address *src,ax25_address *dest, ax25_digi *digi)
{
	sk_buff *skb;
	struct Hdlc_frame *frame;
	struct protocol *proto;
	ax25_digi retdigi;
	int len=sizeof(struct Hdlc_frame)+protocol_size(&proto_ax25);
	if((skb=alloc_skb(len,GFP_ATOMIC))==NULL)
		return;	/* Next SABM will get DM'd */
	protocol_adjust(skb, &proto_ax25);
	ax25_digi_invert(digi,&retdigi);
	skb->sk=NULL;
	frame=(struct Hdlc_frame *)skb_push(skb,sizeof(struct Hdlc_frame));
	
	frame->control=DM_CONTROL;

	if((proto=ax25rtr_get_dev(dest))==NULL)
		return;
	build_ax25_addr(skb,dest,src,&retdigi,C_RESPONSE);
	skb->free=1;
	proto->output(proto,skb,ETH_P_AX25, 0, NULL, NULL, NULL);
}
	

/*
 *	Digipeated address processing
 */
 

/*
 *	Given an AX.25 address pull of to, from, digi list, command/response and the start of data
 *
 */

unsigned char *ax25_parse_addr(sk_buff *skb,int len, ax25_address *src, ax25_address *dest, ax25_digi *digi, int *flags)
{
	int d=0;
	unsigned char *buf;
	
	if(len<14)
		return NULL;
		
	buf=skb_pull(skb,14,NULL);	/* Destination, Source */
	
	if(flags!=NULL)
	{
		*flags=0;
	
		if(buf[6]&LAPB_C)
		{
			*flags=C_COMMAND;
		}
		if(buf[13]&LAPB_C)
		{
			*flags=C_RESPONSE;
		}
	}
		
	/* Copy to, from */
	if(dest!=NULL)
		memcpy(dest,buf,7);
	buf+=7;
	if(src!=NULL)
		memcpy(src,buf,7);
	len-=14;
	digi->lastrepeat=-1;
	digi->ndigi=0;
	
	while(!(buf[6]&LAPB_E))			/* Did last address have the end mark set */
	{
		buf=skb_pull(skb,7,NULL);	/* Pull a digipeater address */
		if(d>=6)
			return NULL;	/* Max of 6 digis */
		if(len<7)
			return NULL;	/* Short packet */
		if(digi!=NULL)
		{
			memcpy(&digi->calls[d],buf,7);
			digi->ndigi=d+1;
			if(buf[6]&AX25_REPEATED)
			{
				digi->repeated[d]=1;
				digi->lastrepeat=d;
			}
			else
				digi->repeated[d]=0;
		}
		len-=7;
		d++;
	}
	return skb_data(skb);
}

/*
 *	Assemble an AX.25 header from the bits
 */
		
int build_ax25_addr(sk_buff *skb,ax25_address *src,ax25_address *dest,ax25_digi *d,int flag)
{
	int len=0;
	int ct=0;	
	unsigned char *buf=skb_push(skb,size_ax25_addr(d));
	
	memcpy(buf,dest,7);
	
	if(flag!=C_COMMAND && flag!=C_RESPONSE)
		printk("build_ax25_addr: Bogus flag %d\n!",flag);
	buf[6]&=~(LAPB_E|LAPB_C);
	buf[6]|=SSID_SPARE;
	if(flag==C_COMMAND)
		buf[6]|=LAPB_C;

	
	buf+=7;
	len+=7;
	memcpy(buf,src,7);
	buf[6]&=~(LAPB_E|LAPB_C);
	buf[6]|=SSID_SPARE;
	if(flag==C_RESPONSE)
		buf[6]|=LAPB_C;
	/*
	 *	Fast path the normal digiless path
	 */
	if(d==NULL || d->ndigi==0)
	{
		buf[6]|=LAPB_E;
		return 14;
	}	
	
	buf+=7;
	len+=7;
	
	while(ct<d->ndigi)
	{
		memcpy(buf,&d->calls[ct],7);
		if(d->repeated[ct])
			buf[6]|=AX25_REPEATED;
		else
			buf[6]&=~AX25_REPEATED;
		buf[6]&=~LAPB_E;
		buf[6]|=SSID_SPARE;
		buf+=7;
		len+=7;
		ct++;
	}
	buf[-1]|=LAPB_E;
	
	return len;
}

int size_ax25_addr(ax25_digi *dp)
{
	if(dp==NULL)
		return 14;
	return 14+(7*dp->ndigi);
}
	
/* 
 *	Reverse Digipeat List. May not pass both parameters as same struct
 */	

void ax25_digi_invert(ax25_digi *in,ax25_digi *out)
{
	int ct=0;
	
	/* Invert the digipeaters */
	
	while(ct<in->ndigi)
	{
		out->calls[ct]=in->calls[in->ndigi-ct-1];
		out->repeated[ct]=0;
		ct++;
	}
	
	/* Copy ndigis */
	out->ndigi=in->ndigi;
	/* Finish off */
	out->lastrepeat=0;
}

		
 
#endif
