/*
 * CAPI encoder/decoder for
 * Portugal Telecom CAPI 2.0
 *
 * Copyright (C) 1996 Universidade de Lisboa
 *
 * Written by Pedro Roque Marques (roque@di.fc.ul.pt)
 *
 * This software may be used and distributed according to the terms of
 * the GNU General Public License, incorporated herein by reference.
 *
 * Not compatible with the AVM Gmbh. CAPI 2.0
 *
 */

/*
 *        Documentation:
 *        - "Common ISDN API - Perfil Português - Versão 2.1",
 *           Telecom Portugal, Fev 1992.
 *        - "Common ISDN API - Especificação de protocolos para
 *           acesso aos canais B", Inesc, Jan 1994.
 */

/*
 *        TODO: better decoding of Information Elements
 *              for debug purposes mainly
 *              encode our number in CallerPN and ConnectedPN
 */

#include <linux/string.h>
#include <linux/kernel.h>

#include <linux/types.h>
#include <linux/slab.h>
#include <linux/mm.h>

#include <linux/skbuff.h>

#include <asm/io.h>
#include <asm/string.h>

#include <linux/isdnif.h>

#include "pcbit.h"
#include "edss1.h"
#include "capi.h"


/*
 *  Encoding of CAPI messages
 *
 */

int capi_conn_req(const char *calledPN, struct sk_buff **skb, int proto)
{
	ushort len;

	/*
	 * length
	 *   AppInfoMask - 2
	 *   BC0         - 3
	 *   BC1         - 1
	 *   Chan        - 2
	 *   Keypad      - 1
	 *   CPN         - 1
	 *   CPSA        - 1
	 *   CalledPN    - 2 + strlen
	 *   CalledPSA   - 1
	 *   rest...     - 4
	 *   ----------------
	 *   Total        18 + strlen
	 */

	len = 18 + strlen(calledPN);

	if (proto == ISDN_PROTO_L2_TRANS)
		len++;

	if ((*skb = dev_alloc_skb(len)) == NULL) {

		printk(KERN_WARNING "capi_conn_req: alloc_skb failed\n");
		return -1;
	}

	/* InfoElmMask */
	*((ushort *)skb_put(*skb, 2)) = AppInfoMask;

	if (proto == ISDN_PROTO_L2_TRANS)
	{
		/* Bearer Capability - Mandatory*/
		*(skb_put(*skb, 1)) = 3;        /* BC0.Length		*/
		*(skb_put(*skb, 1)) = 0x80;     /* Speech		*/
		*(skb_put(*skb, 1)) = 0x10;     /* Circuit Mode		*/
		*(skb_put(*skb, 1)) = 0x23;     /* A-law		*/
	}
	else
	{
		/* Bearer Capability - Mandatory*/
		*(skb_put(*skb, 1)) = 2;        /* BC0.Length		*/
		*(skb_put(*skb, 1)) = 0x88;     /* Digital Information	*/
		*(skb_put(*skb, 1)) = 0x90;     /* BC0.Octect4		*/
	}

	/* Bearer Capability - Optional*/
	*(skb_put(*skb, 1)) = 0;        /* BC1.Length = 0                    */

	*(skb_put(*skb, 1)) = 1;        /* ChannelID.Length = 1              */
	*(skb_put(*skb, 1)) = 0x83;     /* Basic Interface - Any Channel     */

	*(skb_put(*skb, 1)) = 0;        /* Keypad.Length = 0                 */


	*(skb_put(*skb, 1)) = 0;        /* CallingPN.Length = 0              */
	*(skb_put(*skb, 1)) = 0;        /* CallingPSA.Length = 0             */

	/* Called Party Number */
	*(skb_put(*skb, 1)) = strlen(calledPN) + 1;
	*(skb_put(*skb, 1)) = 0x81;
	memcpy(skb_put(*skb, strlen(calledPN)), calledPN, strlen(calledPN));

	/* '#' */

	*(skb_put(*skb, 1)) = 0;       /* CalledPSA.Length = 0     */

	/* LLC.Length  = 0; */
	/* HLC0.Length = 0; */
	/* HLC1.Length = 0; */
	/* UTUS.Length = 0; */
	memset(skb_put(*skb, 4), 0, 4);

	return len;
}

int capi_conn_resp(struct pcbit_chan *chan, struct sk_buff **skb)
{

	if ((*skb = dev_alloc_skb(5)) == NULL) {

		printk(KERN_WARNING "capi_conn_resp: alloc_skb failed\n");
		return -1;
	}

	*((ushort *)skb_put(*skb, 2)) = chan->callref;
	*(skb_put(*skb, 1)) = 0x01;  /* ACCEPT_CALL */
	*(skb_put(*skb, 1)) = 0;
	*(skb_put(*skb, 1)) = 0;

	return 5;
}

int capi_conn_active_req(struct pcbit_chan *chan, struct sk_buff **skb)
{
	/*
	 * 8 bytes
	 */

	if ((*skb = dev_alloc_skb(8)) == NULL) {

		printk(KERN_WARNING "capi_conn_active_req: alloc_skb failed\n");
		return -1;
	}

	*((ushort *)skb_put(*skb, 2)) = chan->callref;

#ifdef DEBUG
	printk(KERN_DEBUG "Call Reference: %04x\n", chan->callref);
#endif

	*(skb_put(*skb, 1)) = 0;       /*  BC.Length = 0;          */
	*(skb_put(*skb, 1)) = 0;       /*  ConnectedPN.Length = 0  */
	*(skb_put(*skb, 1)) = 0;       /*  PSA.Length              */
	*(skb_put(*skb, 1)) = 0;       /*  LLC.Length = 0;         */
	*(skb_put(*skb, 1)) = 0;       /*  HLC.Length = 0;         */
	*(skb_put(*skb, 1)) = 0;       /*  UTUS.Length = 0;        */

	return 8;
}

int capi_conn_active_resp(struct pcbit_chan *chan, struct sk_buff **skb)
{
	/*
	 * 2 bytes
	 */

	if ((*skb = dev_alloc_skb(2)) == NULL) {

		printk(KERN_WARNING "capi_conn_active_resp: alloc_skb failed\n");
		return -1;
	}

	*((ushort *)skb_put(*skb, 2)) = chan->callref;

	return 2;
}


int capi_select_proto_req(struct pcbit_chan *chan, struct sk_buff **skb,
			  int outgoing)
{

	/*
	 * 18 bytes
	 */

	if ((*skb = dev_alloc_skb(18)) == NULL) {

		printk(KERN_WARNING "capi_select_proto_req: alloc_skb failed\n");
		return -1;
	}

	*((ushort *)skb_put(*skb, 2)) = chan->callref;

	/* Layer2 protocol */

	switch (chan->proto) {
	case ISDN_PROTO_L2_X75I:
		*(skb_put(*skb, 1)) = 0x05;            /* LAPB */
		break;
	case ISDN_PROTO_L2_HDLC:
		*(skb_put(*skb, 1)) = 0x02;
		break;
	case ISDN_PROTO_L2_TRANS:
		/*
		 *	Voice (a-law)
		 */
		*(skb_put(*skb, 1)) = 0x06;
		break;
	default:
#ifdef DEBUG
		printk(KERN_DEBUG "Transparent\n");
#endif
		*(skb_put(*skb, 1)) = 0x03;
		break;
	}

	*(skb_put(*skb, 1)) = (outgoing ? 0x02 : 0x42);    /* Don't ask */
	*(skb_put(*skb, 1)) = 0x00;

	*((ushort *) skb_put(*skb, 2)) = MRU;


	*(skb_put(*skb, 1)) = 0x08;           /* Modulo */
	*(skb_put(*skb, 1)) = 0x07;           /* Max Window */

	*(skb_put(*skb, 1)) = 0x01;           /* No Layer3 Protocol */

	/*
	 * 2 - layer3 MTU       [10]
	 *   - Modulo           [12]
	 *   - Window
	 *   - layer1 proto     [14]
	 *   - bitrate
	 *   - sub-channel      [16]
	 *   - layer1dataformat [17]
	 */

	memset(skb_put(*skb, 8), 0, 8);

	return 18;
}


int capi_activate_transp_req(struct pcbit_chan *chan, struct sk_buff **skb)
{

	if ((*skb = dev_alloc_skb(7)) == NULL) {

		printk(KERN_WARNING "capi_activate_transp_req: alloc_skb failed\n");
		return -1;
	}

	*((ushort *)skb_put(*skb, 2)) = chan->callref;


	*(skb_put(*skb, 1)) = chan->layer2link; /* Layer2 id */
	*(skb_put(*skb, 1)) = 0x00;             /* Transmit by default */

	*((ushort *) skb_put(*skb, 2)) = MRU;

	*(skb_put(*skb, 1)) = 0x01;             /* Enables reception*/

	return 7;
}

int capi_tdata_req(struct pcbit_chan *chan, struct sk_buff *skb)
{
	ushort data_len;


	/*
	 * callref      - 2
	 * layer2link   - 1
	 * wBlockLength - 2
	 * data         - 4
	 * sernum       - 1
	 */

	data_len = skb->len;

	if (skb_headroom(skb) < 10)
	{
		printk(KERN_CRIT "No headspace (%u) on headroom %p for capi header\n", skb_headroom(skb), skb);
	}
	else
	{
		skb_push(skb, 10);
	}

	*((u16 *) (skb->data)) = chan->callref;
	skb->data[2] = chan->layer2link;
	*((u16 *) (skb->data + 3)) = data_len;

	chan->s_refnum = (chan->s_refnum + 1) % 8;
	*((u32 *) (skb->data + 5)) = chan->s_refnum;

	skb->data[9] = 0;                           /* HDLC frame number */

	return 10;
}

int capi_tdata_resp(struct pcbit_chan *chan, struct sk_buff **skb)

{
	if ((*skb = dev_alloc_skb(4)) == NULL) {

		printk(KERN_WARNING "capi_tdata_resp: alloc_skb failed\n");
		return -1;
	}

	*((ushort *)skb_put(*skb, 2)) = chan->callref;

	*(skb_put(*skb, 1)) = chan->layer2link;
	*(skb_put(*skb, 1)) = chan->r_refnum;

	return (*skb)->len;
}

int capi_disc_req(ushort callref, struct sk_buff **skb, u_char cause)
{

	if ((*skb = dev_alloc_skb(6)) == NULL) {

		printk(KERN_WARNING "capi_disc_req: alloc_skb failed\n");
		return -1;
	}

	*((ushort *)skb_put(*skb, 2)) = callref;

	*(skb_put(*skb, 1)) = 2;                  /* Cause.Length = 2; */
	*(skb_put(*skb, 1)) = 0x80;
	*(skb_put(*skb, 1)) = 0x80 | cause;

	/*
	 * Change it: we should send 'Sic transit gloria Mundi' here ;-)
	 */

	*(skb_put(*skb, 1)) = 0;                   /* UTUS.Length = 0;  */

	return 6;
}

int capi_disc_resp(struct pcbit_chan *chan, struct sk_buff **skb)
{
	if ((*skb = dev_alloc_skb(2)) == NULL) {

		printk(KERN_WARNING "capi_disc_resp: alloc_skb failed\n");
		return -1;
	}

	*((ushort *)skb_put(*skb, 2)) = chan->callref;

	return 2;
}


/*
 *  Decoding of CAPI messages
 *
 */

int capi_decode_conn_ind(struct pcbit_chan *chan,
			 struct sk_buff *skb,
			 struct callb_data *info)
{
	int CIlen, len;

	/* Call Reference [CAPI] */
	chan->callref = *((ushort *)skb->data);
	skb_pull(skb, 2);

#ifdef DEBUG
	printk(KERN_DEBUG "Call Reference: %04x\n", chan->callref);
#endif

	/* Channel Identification */

	/* Expect
	   Len = 1
	   Octect 3 = 0100 10CC - [ 7 Basic, 4 , 2-1 chan ]
	*/

	CIlen = skb->data[0];
#ifdef DEBUG
	if (CIlen == 1) {

		if (((skb->data[1]) & 0xFC) == 0x48)
			printk(KERN_DEBUG "decode_conn_ind: chan ok\n");
		printk(KERN_DEBUG "phyChan = %d\n", skb->data[1] & 0x03);
	}
	else
		printk(KERN_DEBUG "conn_ind: CIlen = %d\n", CIlen);
#endif
	skb_pull(skb, CIlen + 1);

	/* Calling Party Number */
	/* An "additional service" as far as Portugal Telecom is concerned */

	len = skb->data[0];

	if (len > 0) {
		int count = 1;

#ifdef DEBUG
		printk(KERN_DEBUG "CPN: Octect 3 %02x\n", skb->data[1]);
#endif
		if ((skb->data[1] & 0x80) == 0)
			count = 2;

		if (!(info->data.setup.CallingPN = kmalloc(len - count + 1, GFP_ATOMIC)))
			return -1;

		skb_copy_from_linear_data_offset(skb, count + 1,
						 info->data.setup.CallingPN,
						 len - count);
		info->data.setup.CallingPN[len - count] = 0;

	}
	else {
		info->data.setup.CallingPN = NULL;
		printk(KERN_DEBUG "NULL CallingPN\n");
	}

	skb_pull(skb, len + 1);

	/* Calling Party Subaddress */
	skb_pull(skb, skb->data[0] + 1);

	/* Called Party Number */

	len = skb->data[0];

	if (len > 0) {
		int count = 1;

		if ((skb->data[1] & 0x80) == 0)
			count = 2;

		if (!(info->data.setup.CalledPN = kmalloc(len - count + 1, GFP_ATOMIC)))
			return -1;

		skb_copy_from_linear_data_offset(skb, count + 1,
						 info->data.setup.CalledPN,
						 len - count);
		info->data.setup.CalledPN[len - count] = 0;

	}
	else {
		info->data.setup.CalledPN = NULL;
		printk(KERN_DEBUG "NULL CalledPN\n");
	}

	skb_pull(skb, len + 1);

	/* Called Party Subaddress */
	skb_pull(skb, skb->data[0] + 1);

	/* LLC */
	skb_pull(skb, skb->data[0] + 1);

	/* HLC */
	skb_pull(skb, skb->data[0] + 1);

	/* U2U */
	skb_pull(skb, skb->data[0] + 1);

	return 0;
}

/*
 *  returns errcode
 */

int capi_decode_conn_conf(struct pcbit_chan *chan, struct sk_buff *skb,
			  int *complete)
{
	int errcode;

	chan->callref = *((ushort *)skb->data);     /* Update CallReference */
	skb_pull(skb, 2);

	errcode = *((ushort *) skb->data);   /* read errcode */
	skb_pull(skb, 2);

	*complete = *(skb->data);
	skb_pull(skb, 1);

	/* FIX ME */
	/* This is actually a firmware bug */
	if (!*complete)
	{
		printk(KERN_DEBUG "complete=%02x\n", *complete);
		*complete = 1;
	}


	/* Optional Bearer Capability */
	skb_pull(skb, *(skb->data) + 1);

	/* Channel Identification */
	skb_pull(skb, *(skb->data) + 1);

	/* High Layer Compatibility follows */
	skb_pull(skb, *(skb->data) + 1);

	return errcode;
}

int capi_decode_conn_actv_ind(struct pcbit_chan *chan, struct sk_buff *skb)
{
	ushort len;
#ifdef DEBUG
	char str[32];
#endif

	/* Yet Another Bearer Capability */
	skb_pull(skb, *(skb->data) + 1);


	/* Connected Party Number */
	len = *(skb->data);

#ifdef DEBUG
	if (len > 1 && len < 31) {
		skb_copy_from_linear_data_offset(skb, 2, str, len - 1);
		str[len] = 0;
		printk(KERN_DEBUG "Connected Party Number: %s\n", str);
	}
	else
		printk(KERN_DEBUG "actv_ind CPN len = %d\n", len);
#endif

	skb_pull(skb, len + 1);

	/* Connected Subaddress */
	skb_pull(skb, *(skb->data) + 1);

	/* Low Layer Capability */
	skb_pull(skb, *(skb->data) + 1);

	/* High Layer Capability */
	skb_pull(skb, *(skb->data) + 1);

	return 0;
}

int capi_decode_conn_actv_conf(struct pcbit_chan *chan, struct sk_buff *skb)
{
	ushort errcode;

	errcode = *((ushort *)skb->data);
	skb_pull(skb, 2);

	/* Channel Identification
	   skb_pull(skb, skb->data[0] + 1);
	*/
	return errcode;
}


int capi_decode_sel_proto_conf(struct pcbit_chan *chan, struct sk_buff *skb)
{
	ushort errcode;

	chan->layer2link = *(skb->data);
	skb_pull(skb, 1);

	errcode = *((ushort *)skb->data);
	skb_pull(skb, 2);

	return errcode;
}

int capi_decode_actv_trans_conf(struct pcbit_chan *chan, struct sk_buff *skb)
{
	ushort errcode;

	if (chan->layer2link != *(skb->data))
		printk("capi_decode_actv_trans_conf: layer2link doesn't match\n");

	skb_pull(skb, 1);

	errcode = *((ushort *)skb->data);
	skb_pull(skb, 2);

	return errcode;
}

int capi_decode_disc_ind(struct pcbit_chan *chan, struct sk_buff *skb)
{
	ushort len;
#ifdef DEBUG
	int i;
#endif
	/* Cause */

	len = *(skb->data);
	skb_pull(skb, 1);

#ifdef DEBUG

	for (i = 0; i < len; i++)
		printk(KERN_DEBUG "Cause Octect %d: %02x\n", i + 3,
		       *(skb->data + i));
#endif

	skb_pull(skb, len);

	return 0;
}

#ifdef DEBUG
int capi_decode_debug_188(u_char *hdr, ushort hdrlen)
{
	char str[64];
	int len;

	len = hdr[0];

	if (len < 64 && len == hdrlen - 1) {
		memcpy(str, hdr + 1, hdrlen - 1);
		str[hdrlen - 1] = 0;
		printk("%s\n", str);
	}
	else
		printk("debug message incorrect\n");

	return 0;
}
#endif