/* 
 * lplci.c
 *
 * Author       Karsten Keil <kkeil@linux-pingi.de>
 *
 * Copyright 2011  by Karsten Keil <kkeil@linux-pingi.de>
 *
 * This code is free software; you can redistribute it and/or modify
 * it under the terms of the GNU LESSER GENERAL PUBLIC LICENSE
 * version 2.1 as published by the Free Software Foundation.
 *
 * This code 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 LESSER GENERAL PUBLIC LICENSE for more details.
 *
 */

#include "m_capi.h"
#include "mc_buffer.h"
#include "SupplementaryService.h"
#include "../lib/include/helper.h"
#include <mISDN/q931.h>

static int lPLCILinkUp(struct lPLCI *);
static int lPLCILinkDown(struct lPLCI *);

// --------------------------------------------------------------------
// PLCI state machine
//
// Some rules:
//   *  EV_AP_*  events come from CAPI Application
//   *  EV_L3_*  events come from the ISDN stack
//   *  EV_PI_*  events generated in PLCI handling
//   *  messages are send in the routine that handle the event
//
// --------------------------------------------------------------------

enum {
	ST_PLCI_P_0,
	ST_PLCI_P_0_1,
	ST_PLCI_P_1,
	ST_PLCI_P_2,
	ST_PLCI_P_3,
	ST_PLCI_P_4,
	ST_PLCI_P_ACT,
	ST_PLCI_P_HELD,
	ST_PLCI_P_5,
	ST_PLCI_P_6,
	ST_PLCI_P_RES,
} const ST_PLCI_COUNT = ST_PLCI_P_RES + 1;

static char *str_st_plci[] = {
	"ST_PLCI_P_0",
	"ST_PLCI_P_0_1",
	"ST_PLCI_P_1",
	"ST_PLCI_P_2",
	"ST_PLCI_P_3",
	"ST_PLCI_P_4",
	"ST_PLCI_P_ACT",
	"ST_PLCI_P_HELD",
	"ST_PLCI_P_5",
	"ST_PLCI_P_6",
	"ST_PLCI_P_RES",
};

enum {
	EV_AP_CONNECT_REQ,
	EV_PI_CONNECT_CONF,
	EV_PI_CONNECT_IND,
	EV_AP_CONNECT_RESP,
	EV_PI_CONNECT_ACTIVE_IND,
	EV_AP_CONNECT_ACTIVE_RESP,
	EV_AP_ALERT_REQ,
	EV_AP_INFO_REQ,
	EV_PI_INFO_IND,
	EV_PI_FACILITY_IND,
	EV_AP_SELECT_B_PROTOCOL_REQ,
	EV_AP_DISCONNECT_REQ,
	EV_PI_DISCONNECT_IND,
	EV_AP_DISCONNECT_RESP,
	EV_AP_HOLD_REQ,
	EV_AP_RETRIEVE_REQ,
	EV_PI_HOLD_CONF,
	EV_PI_RETRIEVE_CONF,
	EV_AP_SUSPEND_REQ,
	EV_PI_SUSPEND_CONF,
	EV_AP_RESUME_REQ,
	EV_PI_RESUME_CONF,
	EV_PI_CHANNEL_ERR,
	EV_L3_SETUP_IND,
	EV_L3_SETUP_CONF_ERR,
	EV_L3_SETUP_CONF,
	EV_L3_SETUP_COMPL_IND,
	EV_L3_DISCONNECT_IND,
	EV_L3_RELEASE_IND,
	EV_L3_RELEASE_PROC_IND,
	EV_L3_NOTIFY_IND,
	EV_L3_HOLD_IND,
	EV_L3_HOLD_ACKNOWLEDGE,
	EV_L3_HOLD_REJECT,
	EV_L3_RETRIEVE_IND,
	EV_L3_RETRIEVE_ACKNOWLEDGE,
	EV_L3_RETRIEVE_REJECT,
	EV_L3_SUSPEND_ERR,
	EV_L3_SUSPEND_CONF,
	EV_L3_RESUME_ERR,
	EV_L3_RESUME_CONF,
	EV_L3_REJECT_IND,
	EV_PH_CONTROL_IND,
	EV_AP_RELEASE,
} const EV_PLCI_COUNT = EV_AP_RELEASE + 1;

static char *str_ev_plci[] = {
	"EV_AP_CONNECT_REQ",
	"EV_PI_CONNECT_CONF",
	"EV_PI_CONNECT_IND",
	"EV_AP_CONNECT_RESP",
	"EV_PI_CONNECT_ACTIVE_IND",
	"EV_AP_CONNECT_ACTIVE_RESP",
	"EV_AP_ALERT_REQ",
	"EV_AP_INFO_REQ",
	"EV_PI_INFO_IND",
	"EV_PI_FACILITY_IND",
	"EV_AP_SELECT_B_PROTOCOL_REQ",
	"EV_AP_DISCONNECT_REQ",
	"EV_PI_DISCONNECT_IND",
	"EV_AP_DISCONNECT_RESP",
	"EV_AP_HOLD_REQ",
	"EV_AP_RETRIEVE_REQ",
	"EV_PI_HOLD_CONF",
	"EV_PI_RETRIEVE_CONF",
	"EV_AP_SUSPEND_REQ",
	"EV_PI_SUSPEND_CONF",
	"EV_AP_RESUME_REQ",
	"EV_PI_RESUME_CONF",
	"EV_PI_CHANNEL_ERR",
	"EV_L3_SETUP_IND",
	"EV_L3_SETUP_CONF_ERR",
	"EV_L3_SETUP_CONF",
	"EV_L3_SETUP_COMPL_IND",
	"EV_L3_DISCONNECT_IND",
	"EV_L3_RELEASE_IND",
	"EV_L3_RELEASE_PROC_IND",
	"EV_L3_NOTIFY_IND",
	"EV_L3_HOLD_IND",
	"EV_L3_HOLD_ACKNOWLEDGE",
	"EV_L3_HOLD_REJECT",
	"EV_L3_RETRIEVE_IND",
	"EV_L3_RETRIEVE_ACKNOWLEDGE",
	"EV_L3_RETRIEVE_REJECT",
	"EV_L3_SUSPEND_ERR",
	"EV_L3_SUSPEND_CONF",
	"EV_L3_RESUME_ERR",
	"EV_L3_RESUME_CONF",
	"EV_L3_REJECT_IND",
	"EV_PH_CONTROL_IND",
	"EV_AP_RELEASE",
};

static struct Fsm plci_fsm = { 0, 0, 0, 0, 0 };

static void lPLCI_debug(struct FsmInst *fi, char *fmt, ...)
{
	char tmp[160];
	char *p = tmp;
	va_list args;
	struct lPLCI *lp = fi->userdata;

	if (!(MIDEBUG_STATES & mI_debug_mask))
		return;
	va_start(args, fmt);
	p += sprintf(p, "lPLCI %04x: ", lp->plci);
	p += vsprintf(p, fmt, args);
	*p = 0;
	dprint(MIDEBUG_STATES, "%s\n", tmp);
	va_end(args);
}

static inline void Send2Application(struct lPLCI *lp, struct mc_buf *mc)
{
	SendCmsg2Application(lp->lc->Appl, mc);
}

static inline void lPLCICmsgHeader(struct lPLCI *lp, _cmsg * cmsg, __u8 cmd, __u8 subcmd)
{
	capi_cmsg_header(cmsg, lp->lc->Appl->AppId, cmd, subcmd, lp->lc->Appl->MsgId++, lp->plci);
}

static void lPLCIClearOtherApps(struct lPLCI *lp)
{
	struct lPLCI *o_lp;
	struct mc_buf *mc = NULL;

	if (!lp->PLCI)
		return;
	o_lp = lp->PLCI->lPLCIs;
	while (o_lp) {
		if (o_lp != lp) {
			MC_BUF_ALLOC(mc);
			lPLCICmsgHeader(o_lp, &mc->cmsg, CAPI_DISCONNECT, CAPI_IND);
			mc->cmsg.Reason = 0x3304;	// other application got the call
			FsmEvent(&o_lp->plci_m, EV_PI_DISCONNECT_IND, mc);
		}
		o_lp = o_lp->next;
	}
	if (mc)
		free_mc_buf(mc);
}

static void lPLCIInfoIndMsg(struct lPLCI *lp, uint32_t mask, unsigned char mt, struct mc_buf *arg)
{
	struct mc_buf *mc = arg;

	if ((!(lp->lc->InfoMask & mask)))
		return;

	if (!mc) {
		MC_BUF_ALLOC(mc);
	}
	lPLCICmsgHeader(lp, &mc->cmsg, CAPI_INFO, CAPI_IND);
	mc->cmsg.InfoNumber = 0x8000 | mt;
	mc->cmsg.InfoElement = 0;
	Send2Application(lp, mc);
	if (!arg)
		free_mc_buf(mc);
	else
		mc_clear_cmsg(mc);
}

static void lPLCIInfoIndIE(struct lPLCI *lp, unsigned char ie, uint32_t mask, struct mc_buf *mc)
{
	unsigned char **v_ie, *iep;
	int pos;

	if (!mc || mc->l3m)
		return;

	if (!(lp->lc->InfoMask & mask))	/* not requested by application */
		return;

	if (ie & 0x80) {	/* Single octet */
		if (ie != IE_COMPLETE)
			return;
		iep = &mc->l3m->sending_complete;
	} else {
		v_ie = &mc->l3m->bearer_capability;
		pos = l3_ie2pos(ie);
		if (pos < 0)	/* not supported IE */
			return;
		iep = v_ie[pos];
	}
	if ((iep == NULL) || (*iep == 0))	/* not available in message */
		return;

	mc_clear_cmsg(mc);
	lPLCICmsgHeader(lp, &mc->cmsg, CAPI_INFO, CAPI_IND);
	mc->cmsg.InfoNumber = ie;
	mc->cmsg.InfoElement = iep;
#ifdef HANDLE_EARLYB3
	if (ie == IE_PROGRESS && lp->lc->InfoMask & CAPI_INFOMASK_EARLYB3) {
		if (iep[0] == 0x02 && iep[2] == 0x88) {	// in-band information available
			lPLCILinkUp(lp);
			if (!test_bit(PLCI_STATE_STACKREADY, &lp->plci->state)) {
				Send2ApplicationDelayed(lp, cmsg);
				return;
			}
		}
	}
#endif
	Send2Application(lp, mc);
}

uint16_t q931CIPValue(struct mc_buf * mc)
{
	uint16_t CIPValue = 0;
	int capability, mode, rate, oct5;
	int hlc = -1, ehlc = -1;
	int ret, l;
	char bdebug[48];

	if (!mc->l3m)
		return 0;
	if (!mc->l3m->bearer_capability)
		return 0;

	/* mi_decode_bearer_capability(struct l3_msg *l3m,
	   int *coding, int *capability, int *mode, int *rate,
	   int *oct_4a, int *oct_4b, int *oct_5, int *oct_5a, int *oct_5b1,
	   int *oct_5b2, int *oct_5c, int *oct_5d, int *oct_6, int *oct_7)
	 */

	ret = mi_decode_bearer_capability(mc->l3m, NULL, &capability, &mode, &rate,
					  NULL, NULL, &oct5, NULL, NULL, NULL, NULL, NULL, NULL, NULL);

	l = *mc->l3m->bearer_capability;
	if (l > 13)
		l = 14;
	else
		l++;
	mi_shexprint(bdebug, mc->l3m->bearer_capability, l);
	if (ret) {
		wprint("Error decoding bearer %s return %d - %s\n", bdebug, ret, strerror(-ret));
		return 0;
	}

	if (mode == 0) {
		switch (capability) {
		case Q931_CAP_SPEECH:
			CIPValue = 1;
			break;
		case Q931_CAP_UNRES_DIGITAL:
			CIPValue = 2;
			break;
		case Q931_CAP_RES_DIGITAL:
			CIPValue = 3;
			break;
		case Q931_CAP_3KHZ_AUDIO:
			CIPValue = 4;
			break;
		case Q931_CAP_7KHZ_AUDIO:
			if (oct5 == 0xA5)
				CIPValue = 9;
			else
				CIPValue = 5;
			break;
		case Q931_CAP_VIDEO:
			CIPValue = 6;
			break;
		default:
			wprint("No valid capability %x in bearer %s\n", capability, bdebug);
			return 0;
		}
	} else if (mode == 2) {
		CIPValue = 7;
	} else {
		wprint("Invalid mode %d in bearer %s\n", mode, bdebug);
		return 0;
	}

	if (mc->l3m->hlc) {
		ret = mi_decode_hlc(mc->l3m, &hlc, &ehlc);
		l = *mc->l3m->hlc;
		if (l > 5)
			l = 6;
		else
			l++;
		mi_shexprint(bdebug, mc->l3m->hlc, l);
		if (!ret) {
			switch (hlc) {
			case 0x01:
				if (CIPValue == 1)
					CIPValue = 16;
				else if (CIPValue == 9)
					CIPValue = 26;
				break;
			case 0x04:
				if (CIPValue == 4)
					CIPValue = 17;
				break;
			case 0x21:
				if (CIPValue == 2)
					CIPValue = 18;
				break;
			case 0x24:
				if (CIPValue == 2)
					CIPValue = 19;
				break;
			case 0x28:
				if (CIPValue == 2)
					CIPValue = 20;
				break;
			case 0x31:
				if (CIPValue == 2)
					CIPValue = 21;
				break;
			case 0x32:
				if (CIPValue == 2)
					CIPValue = 22;
				break;
			case 0x35:
				if (CIPValue == 2)
					CIPValue = 23;
				break;
			case 0x38:
				if (CIPValue == 2)
					CIPValue = 24;
				break;
			case 0x41:
				if (CIPValue == 2)
					CIPValue = 25;
				break;
			case 0x60:
				if (CIPValue == 2 && ehlc == 2)
					CIPValue = 28;
				else if (CIPValue == 9 && ehlc == 1)
					CIPValue = 27;
				break;
			default:
				break;
			}
		} else
			wprint("Cannot decode HLC %s return %d - %s\n", bdebug, ret, strerror(-ret));
	}
	return CIPValue;
}

uint16_t CIPValue2setup(uint16_t CIPValue, struct l3_msg * l3m)
{
	switch (CIPValue) {
	case 16:
		mi_encode_bearer(l3m, Q931_CAP_SPEECH, Q931_L1INFO_ALAW, 0, 0x10);
		mi_encode_hlc(l3m, 1, -1);
		break;
	case 17:
		mi_encode_bearer(l3m, Q931_CAP_3KHZ_AUDIO, Q931_L1INFO_ALAW, 0, 0x10);
		// mi_encode_hlc(l3m, 4, -1);
		break;
	case 1:
		mi_encode_bearer(l3m, Q931_CAP_SPEECH, Q931_L1INFO_ALAW, 0, 0x10);
		break;
	case 2:
		mi_encode_bearer(l3m, Q931_CAP_UNRES_DIGITAL, -1, 0, 0x10);
		break;
	case 3:
		mi_encode_bearer(l3m, Q931_CAP_RES_DIGITAL, -1, 0, 0x10);
		break;
	case 4:
		mi_encode_bearer(l3m, Q931_CAP_3KHZ_AUDIO, Q931_L1INFO_ALAW, 0, 0x10);
		break;
	default:
		return CapiIllMessageParmCoding;
	}
	return 0;
}

uint16_t cmsg2setup_req(_cmsg * cmsg, struct l3_msg * l3m)
{
	if (CIPValue2setup(cmsg->CIPValue, l3m))
		goto err;
	if (cmsg->CallingPartyNumber && cmsg->CallingPartyNumber[0])
		add_layer3_ie(l3m, IE_CALLING_PN, cmsg->CallingPartyNumber[0], &cmsg->CallingPartyNumber[1]);
	if (cmsg->CallingPartySubaddress && cmsg->CallingPartySubaddress[0])
		add_layer3_ie(l3m, IE_CALLING_SUB, cmsg->CallingPartySubaddress[0], &cmsg->CallingPartySubaddress[1]);
	if (cmsg->CalledPartyNumber && cmsg->CalledPartyNumber[0])
		add_layer3_ie(l3m, IE_CALLED_PN, cmsg->CalledPartyNumber[0], &cmsg->CalledPartyNumber[1]);
	if (cmsg->CalledPartySubaddress && cmsg->CalledPartySubaddress[0])
		add_layer3_ie(l3m, IE_CALLED_SUB, cmsg->CalledPartySubaddress[0], &cmsg->CalledPartySubaddress[1]);
	if (cmsg->LLC && cmsg->LLC[0])
		add_layer3_ie(l3m, IE_LLC, cmsg->LLC[0], &cmsg->LLC[1]);
	if (cmsg->HLC && cmsg->HLC[0]) {
		l3m->hlc = NULL;
		add_layer3_ie(l3m, IE_HLC, cmsg->HLC[0], &cmsg->HLC[1]);
	}
	return 0;
err:
	return CapiIllMessageParmCoding;
}

uint16_t cmsg2info_req(_cmsg * cmsg, struct l3_msg * l3m)
{
	if (cmsg->Keypadfacility && cmsg->Keypadfacility[0])
		add_layer3_ie(l3m, IE_KEYPAD, cmsg->Keypadfacility[0], &cmsg->Keypadfacility[1]);
	if (cmsg->CalledPartyNumber && cmsg->CalledPartyNumber[0])
		add_layer3_ie(l3m, IE_CALLED_PN, cmsg->CalledPartyNumber[0], &cmsg->CalledPartyNumber[1]);
	return 0;
}

uint16_t cmsg2alerting_req(_cmsg * cmsg, struct l3_msg * l3m)
{
	if (cmsg->Useruserdata && cmsg->Useruserdata[0])
		add_layer3_ie(l3m, IE_USER_USER, cmsg->Useruserdata[0], &cmsg->Useruserdata[1]);
	return 0;
}

uint16_t lPLCICheckBprotocol(struct lPLCI * lp, _cmsg * cmsg)
{
	struct pController *pc = lp->PLCI->pc;
	unsigned long sprot;
	int val;

	/* no endian translation */
	sprot = pc->profile.support1;
	if (!test_bit(cmsg->B1protocol, &sprot))
		return CapiB1ProtocolNotSupported;
	sprot = pc->profile.support2;
	if (!test_bit(cmsg->B2protocol, &sprot))
		return CapiB2ProtocolNotSupported;
	sprot = pc->profile.support3;
	if (!test_bit(cmsg->B3protocol, &sprot))
		return CapiB3ProtocolNotSupported;
	lp->Bprotocol.B1 = cmsg->B1protocol;
	lp->Bprotocol.B2 = cmsg->B2protocol;
	lp->Bprotocol.B3 = cmsg->B3protocol;
	if (cmsg->B1configuration && cmsg->B1configuration[0]) {
		if (cmsg->B1configuration[0] > 15) {
			wprint("B1cfg too large(%d)\n", cmsg->B1configuration[0]);
			return CapiB1ProtocolParameterNotSupported;
		}
		memcpy(&lp->Bprotocol.B1cfg[0], cmsg->B1configuration, cmsg->B1configuration[0] + 1);
	} else
		lp->Bprotocol.B1cfg[0] = 0;
	if (cmsg->B2configuration && cmsg->B2configuration[0]) {
		if (cmsg->B2configuration[0] > 15) {
			wprint("B2cfg too large(%d)\n", cmsg->B2configuration[0]);
			return CapiB2ProtocolParameterNotSupported;
		}
		memcpy(&lp->Bprotocol.B2cfg[0], cmsg->B2configuration, cmsg->B2configuration[0] + 1);
	} else
		lp->Bprotocol.B2cfg[0] = 0;
	if (cmsg->B3configuration && cmsg->B3configuration[0]) {
		if (cmsg->B3configuration[0] > 79) {
			wprint("B3cfg too large(%d)\n", cmsg->B3configuration[0]);
			return CapiB3ProtocolParameterNotSupported;
		}
		memcpy(&lp->Bprotocol.B3cfg[0], cmsg->B3configuration, cmsg->B3configuration[0] + 1);
	} else
		lp->Bprotocol.B3cfg[0] = 0;
	if (lp->Bprotocol.B1 == 4) {
		if (lp->Bprotocol.B2 != 4 || ((lp->Bprotocol.B3 != 4 && lp->Bprotocol.B3 != 5))) {
			wprint("B1 Fax but B2(%d) or B3(%d) not\n", lp->Bprotocol.B2, lp->Bprotocol.B3);
			return CapiProtocolCombinationNotSupported;
		}
		/* valid Fax combination */
		if (lp->Bprotocol.B3cfg[0]) {
			val = CAPIMSG_U16(lp->Bprotocol.B3cfg, 3);
			switch (val) {
			case FAX_B3_FORMAT_SFF:
			case FAX_B3_FORMAT_TIFF:
				break;
			default: /* Others not suported yet */
				wprint("B3cfg Fax format %d not supported\n", val);
				return CapiB3ProtocolParameterNotSupported;
			}
		}
		if (lp->Bprotocol.B1cfg[0]) {
			val = CAPIMSG_U16(lp->Bprotocol.B3cfg, 1);
			switch (val) {
			case 0: /* Adaptive */
			case 4800:
			case 7200:
			case 9600:
			case 12000:
			case 14400:
				break;
			default: /* Others not suported */
				wprint("B1cfg Fax bitrate %d not supported\n", val);
				return CapiB1ProtocolParameterNotSupported;
			}
		}
	}
	return 0;
}

/*
 * return  1 channel was set modified now
 * return  0 channel was set but not modified by this message
 * return -1 channel is not set or not a physical channel (e.g ANY)
 * return -2 decoding error
 */
static int plci_parse_channel_id(struct lPLCI *lp, struct mc_buf *mc)
{
	int ret;

	if (mc) {
		ret = mi_decode_channel_id(mc->l3m, &lp->chid);
		if (ret < 0) {
			wprint("Channel ID IE decoding error - %s\n", strerror(-ret));
		}
		return -2;
	}
	if (lp->chid.ctrl & MI_CHAN_CTRL_UPDATED) {
		ret = 1;
		lp->chid.ctrl &= ~MI_CHAN_CTRL_UPDATED;
	} else
		ret = 0;

	if (lp->chid.nr == MI_CHAN_NONE || lp->chid.nr == MI_CHAN_ANY)
		ret = -1;

	return ret;
}

static void plci_connect_req(struct FsmInst *fi, int event, void *arg)
{
	struct lPLCI *lp = fi->userdata;
	struct mPLCI *plci = lp->PLCI;
	struct mc_buf *mc = arg;
	struct l3_msg *l3m;
	uint16_t Info = 0;

	FsmChangeState(fi, ST_PLCI_P_0_1);
	plci->outgoing = 1;

	l3m = alloc_l3_msg();
	if (!l3m) {
		Info = CapiNoPlciAvailable;
		goto answer;
	}
	if ((Info = cmsg2setup_req(&mc->cmsg, l3m))) {
		goto answer;
	}
	if ((Info = lPLCICheckBprotocol(lp, &mc->cmsg))) {
		goto answer;
	}

	plci->pid = request_new_pid(plci->pc->l3);
	plciL4L3(plci, MT_SETUP, l3m);
answer:
	capi_cmsg_answer(&mc->cmsg);
	mc->cmsg.Info = Info;
	if (mc->cmsg.Info == 0)
		mc->cmsg.adr.adrPLCI = lp->plci;
	FsmEvent(fi, EV_PI_CONNECT_CONF, mc);
}

static void plci_connect_conf(struct FsmInst *fi, int event, void *arg)
{
	struct lPLCI *lp = fi->userdata;
	struct mc_buf *mc = arg;

	if (mc->cmsg.Info == 0) {
		Send2Application(lp, mc);
		FsmChangeState(fi, ST_PLCI_P_1);
	} else {
		Send2Application(lp, mc);
		FsmChangeState(fi, ST_PLCI_P_0);
		lPLCI_free(lp);
	}
}

static void plci_connect_ind(struct FsmInst *fi, int event, void *arg)
{
	FsmChangeState(fi, ST_PLCI_P_2);
	Send2Application(fi->userdata, arg);
}

static void plci_hold_req(struct FsmInst *fi, int event, void *arg)
{
	struct lPLCI *lp = fi->userdata;
	struct mPLCI *plci = lp->PLCI;

	plciL4L3(plci, MT_HOLD, arg);
}

static void plci_retrieve_req(struct FsmInst *fi, int event, void *arg)
{
	struct lPLCI *lp = fi->userdata;
	struct mPLCI *plci = lp->PLCI;

	plciL4L3(plci, MT_RETRIEVE, arg);
}

static void plci_suspend_req(struct FsmInst *fi, int event, void *arg)
{
	struct lPLCI *lp = fi->userdata;
	struct mPLCI *plci = lp->PLCI;

	plciL4L3(plci, MT_SUSPEND, arg);
}

static void plci_resume_req(struct FsmInst *fi, int event, void *arg)
{
	struct lPLCI *lp = fi->userdata;
	struct mPLCI *plci = lp->PLCI;

	// we already sent CONF with Info = SuppInfo = 0
	FsmChangeState(fi, ST_PLCI_P_RES);
	plci->pid = request_new_pid(plci->pc->l3);
	plciL4L3(plci, MT_RESUME, arg);
}

static void plci_alert_req(struct FsmInst *fi, int event, void *arg)
{
	struct lPLCI *lp = fi->userdata;
	struct mPLCI *plci = lp->PLCI;
	struct mc_buf *mc = arg;
	uint16_t Info = 0;

	if (plci->alerting) {
		Info = 0x0003;	// other app is already alerting
	} else {
		if (!mc->l3m)
			mc->l3m = alloc_l3_msg();
		if (!mc->l3m) {
			wprint("alerting not send no l3m\n");
			goto answer;
		}
		Info = cmsg2alerting_req(&mc->cmsg, mc->l3m);
		if (Info == 0) {
			plciL4L3(plci, MT_ALERTING, mc->l3m);
			plci->alerting = 1;
			mc->l3m = NULL; /* freed in plciL4L3*/
		}
	}
answer:
	capi_cmsg_answer(&mc->cmsg);
	mc->cmsg.Info = Info;
	Send2Application(lp, mc);
}

static void plci_connect_resp(struct FsmInst *fi, int event, void *arg)
{
	struct lPLCI *lp = fi->userdata;
	struct mPLCI *plci = lp->PLCI;
	struct mc_buf *mc = arg;
	struct l3_msg *l3m;
	int cause;

	if (mc->cmsg.Reject == 0) {	// accept
		if (lPLCICheckBprotocol(lp, &mc->cmsg)) {
			wprint("Bprotocol mismatch\n");
		}
		lPLCIClearOtherApps(lp);
		l3m = alloc_l3_msg();
		/* TODO connect number, sub,llc, addie... */
		plciL4L3(plci, MT_CONNECT, l3m);
		FsmChangeState(fi, ST_PLCI_P_4);
		return;
	}
	// ignore, reject 
	switch (mc->cmsg.Reject) {
	case 1:
		break;		// Ignore - no message should be sent
	case 2:
		cause = 0x90;
		break;		// normal call clearing
	case 3:
		cause = 0x91;
		break;		// user busy
	case 4:
		cause = 0xac;
		break;		// req circuit/channel not avail
	case 5:
		cause = 0x9d;
		break;		// fac rejected
	case 6:
		cause = 0x86;
		break;		// channel unacceptable
	case 7:
		cause = 0xd8;
		break;		// incompatible dest
	case 8:
		cause = 0x9b;
		break;		// dest out of order
	default:
		if ((mc->cmsg.Reject & 0xff00) == 0x3400) {
			cause = mc->cmsg.Reject & 0xff;
		} else {
			cause = 0x90;	// normal call clearing
		}
	}
	// FIXME
	// WHY ???
	// if (cmsg->Reject != 1) {
	// ignore
	//      lPLCIClearOtherApps(lp);
	// }
	// plciDetachlPLCI(plci, lp);
	if (mc->cmsg.Reject != 1) {
		if (plci->nAppl == 1) {
			int mt;

			l3m = alloc_l3_msg();
			if (!l3m) {
				wprint("disconnect not send no l3m\n");
			} else {
				if (plci->alerting)
					mt = MT_DISCONNECT;
				else
					mt = MT_RELEASE_COMPLETE;
				mi_encode_cause(l3m, cause, CAUSE_LOC_USER, 0, NULL);
				plciL4L3(plci, mt, l3m);
			}
		}
	}
	mc->cmsg.Command = CAPI_DISCONNECT;
	mc->cmsg.Subcommand = CAPI_IND;
	mc->cmsg.Messagenumber = lp->lc->Appl->MsgId++;
	mc->cmsg.Reason = 0;
	FsmEvent(&lp->plci_m, EV_PI_DISCONNECT_IND, mc);
}

static void plci_connect_active_ind(struct FsmInst *fi, int event, void *arg)
{
	struct lPLCI *lp = fi->userdata;

	FsmChangeState(fi, ST_PLCI_P_ACT);
	lPLCILinkUp(lp);
	Send2Application(lp, arg);
}

static void plci_connect_active_resp(struct FsmInst *fi, int event, void *arg)
{
}

static void plci_disconnect_req(struct FsmInst *fi, int event, void *arg)
{
	struct lPLCI *lp = fi->userdata;
	struct mPLCI *plci = lp->PLCI;
	struct mc_buf *mc = arg;
	struct l3_msg *l3m;
	int cause;

	FsmChangeState(fi, ST_PLCI_P_5);

	// FIXME handle additional Info
	capi_cmsg_answer(&mc->cmsg);
	mc->cmsg.Reason = 0;	// disconnect initiated
	Send2Application(lp, mc);

	lPLCILinkDown(lp);

	lp->disc_req = 1;
	if (lp->cause == 0) {
		dprint(MIDEBUG_PLCI, "Disconnect send to card\n");
		l3m = alloc_l3_msg();
		if (!l3m) {
			wprint("disconnect not send no l3m\n");
		} else {
			cause = CAUSE_NORMAL_CLEARING;
			mi_encode_cause(l3m, cause, CAUSE_LOC_USER, 0, NULL);
			plciL4L3(plci, MT_DISCONNECT, l3m);
		}
	} else {
		/* release physical link */
		// FIXME
		dprint(MIDEBUG_PLCI, "Connection was disconnected with cause %02x - send RELEASE\n", lp->cause);
		plciL4L3(plci, MT_RELEASE, NULL);
	}
}

static void plci_suspend_conf(struct FsmInst *fi, int event, void *arg)
{
	FsmChangeState(fi, ST_PLCI_P_5);
}

static void plci_resume_conf(struct FsmInst *fi, int event, void *arg)
{
	// facility_ind Resume: Reason = 0
	struct lPLCI *lp = fi->userdata;

	FsmChangeState(fi, ST_PLCI_P_ACT);
	lPLCILinkUp(lp);
	Send2Application(lp, arg);
}

static void plci_disconnect_ind(struct FsmInst *fi, int event, void *arg)
{
	FsmChangeState(fi, ST_PLCI_P_6);
	Send2Application(fi->userdata, arg);
}

static void plci_disconnect_resp(struct FsmInst *fi, int event, void *arg)
{
	FsmChangeState(fi, ST_PLCI_P_0);
	lPLCI_free(fi->userdata);
}

static void plci_appl_release(struct FsmInst *fi, int event, void *arg)
{
	lPLCI_free(fi->userdata);
}

static void plci_appl_release_disc(struct FsmInst *fi, int event, void *arg)
{
	struct lPLCI *lp = fi->userdata;
	struct mPLCI *plci = lp->PLCI;
	int cause;
	struct l3_msg *l3m;

	FsmChangeState(fi, ST_PLCI_P_5);

	lPLCILinkDown(lp);

	if (lp->cause == 0) {
		l3m = alloc_l3_msg();
		if (!l3m) {
			wprint("disconnect not send no l3m\n");
		} else {
			cause = CAUSE_NORMALUNSPECIFIED;
			mi_encode_cause(l3m, cause, CAUSE_LOC_USER, 0, NULL);
			plciL4L3(plci, MT_DISCONNECT, l3m);
		}
	} else {
		/* release physical link */
		// FIXME
		plciL4L3(plci, MT_RELEASE, NULL);
	}
}

static void plci_cc_setup_conf(struct FsmInst *fi, int event, void *arg)
{
	struct lPLCI *lp = fi->userdata;
	struct mc_buf *mc = arg;

	if (lp->chid.nr == MI_CHAN_NONE || lp->chid.nr == MI_CHAN_ANY) {
		/* no valid channel set */
		FsmEvent(fi, EV_PI_CHANNEL_ERR, mc);
		return;
	}
	lPLCICmsgHeader(lp, &mc->cmsg, CAPI_CONNECT_ACTIVE, CAPI_IND);
	if (mc->l3m) {
		if (mc->l3m->connected_nr && *mc->l3m->connected_nr)
			mc->cmsg.ConnectedNumber = mc->l3m->connected_nr;
		if (mc->l3m->connected_sub && *mc->l3m->connected_sub)
			mc->cmsg.ConnectedSubaddress = mc->l3m->connected_sub;
		if (mc->l3m->llc && *mc->l3m->llc)
			mc->cmsg.LLC = mc->l3m->llc;
	}
	FsmEvent(fi, EV_PI_CONNECT_ACTIVE_IND, mc);
}

static void plci_cc_setup_conf_err(struct FsmInst *fi, int event, void *arg)
{
	struct lPLCI *lp = fi->userdata;
	struct mc_buf *mc = arg;

	if (!mc) {
		mc = alloc_mc_buf();
		if (!mc) {
			eprint("No mc buffers\n");
			return;
		}
	}
	lPLCICmsgHeader(lp, &mc->cmsg, CAPI_DISCONNECT, CAPI_IND);
	mc->cmsg.Reason = CapiProtocolErrorLayer3;
	FsmEvent(&lp->plci_m, EV_PI_DISCONNECT_IND, mc);
	if (!arg)		/* if allocated local */
		free_mc_buf(mc);
}

static void plci_channel_err(struct FsmInst *fi, int event, void *arg)
{
	struct lPLCI *lp = fi->userdata;
	int cause;
	struct l3_msg *l3m;
	struct mc_buf *mc = arg;

	l3m = alloc_l3_msg();
	if (l3m) {
		cause = CAUSE_CHANNEL_UNACCEPT;
		mi_encode_cause(l3m, cause, CAUSE_LOC_USER, 0, NULL);
		plciL4L3(lp->PLCI, MT_RELEASE_COMPLETE, l3m);
	} else
		eprint("no l3_msg\n");
	if (!mc) {
		mc = alloc_mc_buf();
		if (!mc) {
			eprint("No mc buffers\n");
			return;
		}
	}
	lPLCICmsgHeader(lp, &mc->cmsg, CAPI_DISCONNECT, CAPI_IND);
	mc->cmsg.Reason = CapiProtocolErrorLayer3;
	FsmEvent(&lp->plci_m, EV_PI_DISCONNECT_IND, mc);
	if (!arg)		/* if allocated local */
		free_mc_buf(mc);
}

static void plci_cc_setup_ind(struct FsmInst *fi, int event, void *arg)
{
	struct lPLCI *lp = fi->userdata;
	struct mc_buf *mc = arg;

	lPLCICmsgHeader(lp, &mc->cmsg, CAPI_CONNECT, CAPI_IND);

	// FIXME: CW
	if (mc->l3m) {
		mc->cmsg.CIPValue = q931CIPValue(mc);
		if (mc->l3m->called_nr && *mc->l3m->called_nr)
			mc->cmsg.CalledPartyNumber = mc->l3m->called_nr;
		if (mc->l3m->called_sub && *mc->l3m->called_sub)
			mc->cmsg.CalledPartySubaddress = mc->l3m->called_sub;
		if (mc->l3m->calling_nr && *mc->l3m->calling_nr)
			mc->cmsg.CallingPartyNumber = mc->l3m->calling_nr;
		if (mc->l3m->calling_sub && *mc->l3m->calling_sub)
			mc->cmsg.CallingPartySubaddress = mc->l3m->calling_sub;
		if (mc->l3m->bearer_capability && *mc->l3m->bearer_capability)
			mc->cmsg.BC = mc->l3m->bearer_capability;
		if (mc->l3m->llc && *mc->l3m->llc)
			mc->cmsg.LLC = mc->l3m->llc;
		if (mc->l3m->hlc && *mc->l3m->hlc)
			mc->cmsg.HLC = mc->l3m->hlc;
#if CAPIUTIL_VERSION > 1
		{
			struct m_extie *eie;
			int i;
			/* ETS 300 092 Annex B */
			eie = mc->l3m->extra;
			for (i = 0; i < 8; i++) {
				if (!eie->ie)	/* stop if no additional ie */
					break;
				if (eie->ie == IE_CALLING_PN && eie->codeset == 0) {
					if (eie->val && *eie->val)
						mc->cmsg.CallingPartyNumber2 = eie->val;
					break;
				}
				eie++;
			}
		}
#endif
		// all else set to default
	}
	FsmEvent(&lp->plci_m, EV_PI_CONNECT_IND, mc);
}

static void plci_cc_setup_compl_ind(struct FsmInst *fi, int event, void *arg)
{
	struct lPLCI *lp = fi->userdata;
	struct mc_buf *mc = arg;

	if (!mc) {
		mc = alloc_mc_buf();
		if (!mc) {
			eprint("No mc buffers\n");
			return;
		}
	}
	lPLCICmsgHeader(lp, &mc->cmsg, CAPI_CONNECT_ACTIVE, CAPI_IND);
	FsmEvent(&lp->plci_m, EV_PI_CONNECT_ACTIVE_IND, mc);
	if (!arg)		/* if allocated local */
		free_mc_buf(mc);
}

static void plci_cc_disconnect_ind(struct FsmInst *fi, int event, void *arg)
{
	struct lPLCI *lp = fi->userdata;
	struct mc_buf *mc = arg;
	int ret;

	lp->cause = -1;
	if (mc->l3m) {
		ret = mi_decode_cause(mc->l3m, NULL, &lp->cause_loc, NULL, &lp->cause, NULL, NULL);
		if (ret) {
			wprint("Error decoding cause %d - %s\n", ret, strerror(-ret));
		} else
			iprint("PLCI %04x: Got Disconnect with cause %02d (0x%02x) loc %02x\n",
			       lp->plci, lp->cause, lp->cause, lp->cause_loc);
	} else
		iprint("PLCI %04x: Got Disconnect without cause info\n", lp->plci);

	if (lp->autohangup) {
		if (lp->lc->InfoMask & CAPI_INFOMASK_EARLYB3)
			return;

		lPLCILinkDown(lp);
		plciL4L3(lp->PLCI, MT_RELEASE, NULL);
	}
}

static void plci_cc_release_ind(struct FsmInst *fi, int event, void *arg)
{
	struct lPLCI *lp = fi->userdata;
	struct mc_buf *mc = arg;
	int cause = -1, loc = -1, ret;

	lPLCILinkDown(lp);
	if (!mc) {
		mc = alloc_mc_buf();
		if (!mc) {
			eprint("No mc buffers\n");
			return;
		}
	}
	lPLCICmsgHeader(lp, &mc->cmsg, CAPI_DISCONNECT, CAPI_IND);
	if (mc->l3m) {
		if (mc->l3m->cause && *mc->l3m->cause) {
			ret = mi_decode_cause(mc->l3m, NULL, &loc, NULL, &cause, NULL, NULL);
			if (ret) {
				wprint("Error decoding cause %d - %s\n", ret, strerror(-ret));
			} else {
				iprint("Release with cause %d loc %d main cause %d\n", cause, loc, lp->cause);
			}
		}
	}
	if (!lp->disc_req) {
		/* We only should send causes from RELEASE when we did not request the RELEASE 
		 * Some applications will see Reasons not 00 as error - maybe thats a bug
		 */
		if (lp->cause > 0)
			cause = lp->cause;
		if (cause > 0)
			mc->cmsg.Reason = 0x3400 | (cause & 0x7f);
		else
			mc->cmsg.Reason = 0;
	} else
		mc->cmsg.Reason = 0;
	FsmEvent(&lp->plci_m, EV_PI_DISCONNECT_IND, mc);
	if (!arg)		/* if allocated local */
		free_mc_buf(mc);
}

static void plci_cc_notify_ind(struct FsmInst *fi, int event, void *arg)
{
	struct lPLCI *lp = fi->userdata;
	struct mc_buf *mc = arg;

	if (!mc || !mc->l3m || !mc->l3m->notify)
		return;
	if (mc->l3m->notify[0] != 1)	// len != 1
		return;
	switch (mc->l3m->notify[1]) {
	case 0xF9:		// user hold
		SendSSNotificationEvent(lp, 0x8000);
		break;
	case 0xFA:		// user retrieve
		SendSSNotificationEvent(lp, 0x8001);
		break;
	case 0x80:		// user suspended
		SendSSNotificationEvent(lp, 0x8002);
		break;
	case 0x81:		// user resumed
		SendSSNotificationEvent(lp, 0x8003);
		break;
	case 0xFB:		// call is diverting
		SendSSNotificationEvent(lp, 0x8004);
		break;
	case 0xE8:		// diversion activated
		SendSSNotificationEvent(lp, 0x8005);
		break;
	default:
		eprint("unhandled notification %x\n", mc->l3m->notify[1]);
	}
}

static void plci_hold_conf(struct FsmInst *fi, int event, void *arg)
{
	FsmChangeState(fi, ST_PLCI_P_HELD);
}

static void lPLCI_hold_reply(struct lPLCI *lp, uint16_t SuppServiceReason, void *arg)
{
	unsigned char tmp[10], *p;
	struct mc_buf *mc = arg;

	if (!mc) {
		mc = alloc_mc_buf();
		if (!mc) {
			eprint("No mc buffers\n");
			return;
		}
	}
	lPLCICmsgHeader(lp, &mc->cmsg, CAPI_FACILITY, CAPI_IND);
	p = &tmp[1];
	p += capiEncodeWord(p, 0x0002);	// Hold
	p += capiEncodeFacIndSuspend(p, SuppServiceReason);
	tmp[0] = p - &tmp[1];
	mc->cmsg.FacilitySelector = 0x0003;
	mc->cmsg.FacilityIndicationParameter = tmp;
	Send2Application(lp, mc);
	if (SuppServiceReason == CapiNoError)
		FsmEvent(&lp->plci_m, EV_PI_HOLD_CONF, NULL);
	if (!arg)		/* if allocated local */
		free_mc_buf(mc);
}

static void plci_cc_hold_rej(struct FsmInst *fi, int event, void *arg)
{
	struct lPLCI *lp = fi->userdata;
	uint16_t SuppServiceReason;
	struct mc_buf *mc = arg;
	int cause = 0, ret;

	if (mc && mc->l3m) {
		if (mc->l3m->cause && *mc->l3m->cause) {
			ret = mi_decode_cause(mc->l3m, NULL, NULL, NULL, &cause, NULL, NULL);
			if (ret)
				wprint("Error decoding cause %d - %s\n", ret, strerror(-ret));
			SuppServiceReason = 0x3400 | (cause & 0x7f);
		} else
			SuppServiceReason = CapiProtocolErrorLayer3;
	} else {		// timeout
		SuppServiceReason = CapiProtocolErrorLayer3;
	}
	lPLCI_hold_reply(lp, SuppServiceReason, arg);
}

static void plci_cc_hold_ack(struct FsmInst *fi, int event, void *arg)
{
	struct lPLCI *lp = fi->userdata;

	lPLCI_hold_reply(lp, CapiNoError, arg);
	lPLCILinkDown(lp);
}

static void plci_cc_hold_ind(struct FsmInst *fi, int event, void *arg)
{
	struct lPLCI *lp = fi->userdata;

	lPLCI_hold_reply(lp, CapiNoError, arg);
	lPLCILinkDown(lp);
	plciL4L3(lp->PLCI, MT_HOLD_ACKNOWLEDGE, NULL);
}

static void plci_retrieve_conf(struct FsmInst *fi, int event, void *arg)
{
	struct lPLCI *lp = fi->userdata;

	FsmChangeState(fi, ST_PLCI_P_ACT);
	lPLCILinkUp(lp);
	Send2Application(lp, arg);
}

static void lPLCI_retrieve_reply(struct lPLCI *lp, uint16_t SuppServiceReason, void *arg)
{
	unsigned char tmp[10], *p;
	struct mc_buf *mc = arg;

	if (!mc) {
		mc = alloc_mc_buf();
		if (!mc) {
			eprint("No mc buffers\n");
			return;
		}
	}
	lPLCICmsgHeader(lp, &mc->cmsg, CAPI_FACILITY, CAPI_IND);
	p = &tmp[1];
	p += capiEncodeWord(p, 0x0003);	// Retrieve
	p += capiEncodeFacIndSuspend(p, SuppServiceReason);
	tmp[0] = p - &tmp[1];
	mc->cmsg.FacilitySelector = 0x0003;
	mc->cmsg.FacilityIndicationParameter = tmp;

	if (SuppServiceReason != CapiNoError)
		Send2Application(lp, mc);
	else
		FsmEvent(&lp->plci_m, EV_PI_RETRIEVE_CONF, mc);

	if (!arg)		/* if allocated local */
		free_mc_buf(mc);
}

static void plci_cc_retrieve_rej(struct FsmInst *fi, int event, void *arg)
{
	struct lPLCI *lp = fi->userdata;
	uint16_t SuppServiceReason;
	struct mc_buf *mc = arg;
	int cause = 0, ret;

	if (mc && mc->l3m) {
		if (mc->l3m->cause && *mc->l3m->cause) {
			ret = mi_decode_cause(mc->l3m, NULL, NULL, NULL, &cause, NULL, NULL);
			if (ret)
				wprint("Error decoding cause %d - %s\n", ret, strerror(-ret));
			SuppServiceReason = 0x3400 | (cause & 0x7f);
		} else
			SuppServiceReason = CapiProtocolErrorLayer3;
	} else {		// timeout
		SuppServiceReason = CapiProtocolErrorLayer3;
	}
	lPLCI_retrieve_reply(lp, SuppServiceReason, arg);
}

static void plci_cc_retrieve_ack(struct FsmInst *fi, int event, void *arg)
{
	struct lPLCI *lp = fi->userdata;
	struct mc_buf *mc = arg;
	int ret;

	ret = plci_parse_channel_id(lp, mc);
	if (ret >= 0) {
		lPLCI_retrieve_reply(lp, CapiNoError, arg);
	} else {
		wprint("No valid channel for retrieve (%d)\n", ret);
		lPLCI_retrieve_reply(lp, 0x3711, arg);	/* resource Error */
	}
}

static void plci_cc_retrieve_ind(struct FsmInst *fi, int event, void *arg)
{
	struct lPLCI *lp = fi->userdata;
	struct mc_buf *mc = arg;
	int mt;
	int ret;

	ret = plci_parse_channel_id(lp, mc);
	if (ret >= 0) {
		lPLCI_retrieve_reply(lp, CapiNoError, arg);
		mt = MT_RETRIEVE_ACKNOWLEDGE;
	} else {
		wprint("No valid channel for retrieve (%d)\n", ret);
		lPLCI_retrieve_reply(lp, 0x3711, arg);	/* resource Error */
		mt = MT_RETRIEVE_REJECT;
	}
	plciL4L3(lp->PLCI, mt, NULL);
}

static void lPLCI_suspend_reply(struct lPLCI *lp, uint16_t SuppServiceReason, void *arg)
{
	unsigned char tmp[10], *p;
	struct mc_buf *mc = arg;

	if (!mc) {
		mc = alloc_mc_buf();
		if (!mc) {
			eprint("No mc buffers\n");
			return;
		}
	}
	lPLCICmsgHeader(lp, &mc->cmsg, CAPI_FACILITY, CAPI_IND);
	p = &tmp[1];
	p += capiEncodeWord(p, 0x0004);	// Suspend
	p += capiEncodeFacIndSuspend(p, SuppServiceReason);
	tmp[0] = p - &tmp[1];
	mc->cmsg.FacilitySelector = 0x0003;
	mc->cmsg.FacilityIndicationParameter = tmp;
	Send2Application(lp, mc);

	if (SuppServiceReason == CapiNoError)
		FsmEvent(&lp->plci_m, EV_PI_SUSPEND_CONF, NULL);

	if (!arg)		/* if allocated local */
		free_mc_buf(mc);
}

static void plci_cc_suspend_err(struct FsmInst *fi, int event, void *arg)
{
	struct lPLCI *lp = fi->userdata;
	uint16_t SuppServiceReason;
	struct mc_buf *mc = arg;
	int cause = 0, ret;

	if (mc && mc->l3m) {
		if (mc->l3m->cause && *mc->l3m->cause) {
			ret = mi_decode_cause(mc->l3m, NULL, NULL, NULL, &cause, NULL, NULL);
			if (ret)
				wprint("Error decoding cause %d - %s\n", ret, strerror(-ret));
			SuppServiceReason = 0x3400 | (cause & 0x7f);
		} else
			SuppServiceReason = CapiProtocolErrorLayer3;
	} else {		// timeout
		SuppServiceReason = CapiProtocolErrorLayer3;
	}
	lPLCI_suspend_reply(lp, SuppServiceReason, arg);
}

static void plci_cc_suspend_conf(struct FsmInst *fi, int event, void *arg)
{
	struct lPLCI *lp = fi->userdata;
	struct mc_buf *mc = arg;

	if (!mc) {
		mc = alloc_mc_buf();
		if (!mc) {
			eprint("No mc buffers\n");
			return;
		}
	}
	lPLCILinkDown(lp);

	lPLCI_suspend_reply(lp, CapiNoError, arg);

	lPLCICmsgHeader(lp, &mc->cmsg, CAPI_DISCONNECT, CAPI_IND);
	FsmEvent(&lp->plci_m, EV_PI_DISCONNECT_IND, mc);

	if (!arg)		/* if allocated local */
		free_mc_buf(mc);
}

static void plci_cc_resume_err(struct FsmInst *fi, int event, void *arg)
{
	struct lPLCI *lp = fi->userdata;
	struct mc_buf *mc = arg;
	int cause = 0, ret;

	if (!mc) {
		mc = alloc_mc_buf();
		if (!mc) {
			eprint("No mc buffers\n");
			return;
		}
	}
	lPLCICmsgHeader(lp, &mc->cmsg, CAPI_DISCONNECT, CAPI_IND);
	if (mc->l3m) {
		if (mc->l3m->cause && *mc->l3m->cause) {
			ret = mi_decode_cause(mc->l3m, NULL, NULL, NULL, &cause, NULL, NULL);
			if (ret)
				wprint("Error decoding cause %d - %s\n", ret, strerror(-ret));
			mc->cmsg.Reason = 0x3400 | (cause & 0x7f);
		} else
			mc->cmsg.Reason = 0;
	} else			// timeout
		mc->cmsg.Reason = CapiProtocolErrorLayer1;

	FsmEvent(&lp->plci_m, EV_PI_DISCONNECT_IND, mc);
	if (!arg)		/* if allocated local */
		free_mc_buf(mc);
}

static void plci_cc_resume_conf(struct FsmInst *fi, int event, void *arg)
{
	struct lPLCI *lp = fi->userdata;
	struct mc_buf *mc = arg;
	unsigned char tmp[10], *p;
	int ret;

	ret = plci_parse_channel_id(lp, mc);
	if (ret < 0) {
		wprint("No valid channel for resume (%d)\n", ret);
		return;
	}
	lPLCICmsgHeader(lp, &mc->cmsg, CAPI_FACILITY, CAPI_IND);
	p = &tmp[1];
	p += capiEncodeWord(p, 0x0005);	// Suspend
	p += capiEncodeFacIndSuspend(p, CapiNoError);
	tmp[0] = p - &tmp[1];
	mc->cmsg.FacilitySelector = 0x0003;
	mc->cmsg.FacilityIndicationParameter = tmp;
	FsmEvent(&lp->plci_m, EV_PI_RESUME_CONF, mc);
}

static void plci_select_b_protocol_req(struct FsmInst *fi, int event, void *arg)
{
	struct lPLCI *lp = fi->userdata;
	struct mc_buf *mc = arg;
	uint16_t Info;
	int ret;

	Info = lPLCICheckBprotocol(lp, &mc->cmsg);
	if (Info)
		goto answer;

	ret = lPLCILinkDown(lp);
	if (ret) {
		Info = CapiMessageNotSupportedInCurrentState;
		goto answer;
	}
	ret = lPLCILinkUp(lp);
	if (ret < 0)
		Info = CapiMessageNotSupportedInCurrentState;
	else
		Info = ret;
answer:
	capi_cmsg_answer(&mc->cmsg);
	mc->cmsg.Info = Info;
	Send2Application(lp, arg);
}

static void plci_info_req_overlap(struct FsmInst *fi, int event, void *arg)
{
	struct lPLCI *lp = fi->userdata;
	struct mc_buf *mc = arg;
	uint16_t Info = 0;
	struct l3_msg *l3m;

	l3m = alloc_l3_msg();
	if (l3m) {
		Info = cmsg2info_req(&mc->cmsg, l3m);
		if (Info == CapiNoError)
			plciL4L3(lp->PLCI, MT_INFORMATION, l3m);
		else
			free_l3_msg(l3m);
	}
	capi_cmsg_answer(&mc->cmsg);
	mc->cmsg.Info = Info;
	Send2Application(lp, mc);
}

static void plci_cc_ph_control_ind(struct FsmInst *fi, int event, void *arg)
{
	struct lPLCI *lp = fi->userdata;
	struct mc_buf *mc = arg;
	unsigned int *tt;
	__u8 tmp[2];

	tt = (unsigned int *)(mc->rb + sizeof(struct mISDNhead));
	dprint(MIDEBUG_PLCI, "PLCI:%04x tt(%x)\n", lp->plci, *tt);
	if ((*tt & ~DTMF_TONE_MASK) != DTMF_TONE_VAL) {
		wprint("PLCI:%04x PH_CONTROL but not a touchtone (%x) ?\n", lp->plci, *tt);
	} else {
		lPLCICmsgHeader(lp, &mc->cmsg, CAPI_FACILITY, CAPI_IND);
		tmp[0] = 1;
		tmp[1] = *tt & DTMF_TONE_MASK;
		mc->cmsg.FacilitySelector = 0x0001;
		mc->cmsg.FacilityIndicationParameter = tmp;
		Send2Application(lp, mc);
	}
}

static void plci_info_req(struct FsmInst *fi, int event, void *arg)
{
	// FIXME handle INFO CONF
}

static struct FsmNode fn_plci_list[] = {
	{ST_PLCI_P_0, EV_AP_CONNECT_REQ, plci_connect_req},
	{ST_PLCI_P_0, EV_PI_CONNECT_IND, plci_connect_ind},
	{ST_PLCI_P_0, EV_AP_RESUME_REQ, plci_resume_req},
	{ST_PLCI_P_0, EV_L3_SETUP_IND, plci_cc_setup_ind},
	{ST_PLCI_P_0, EV_AP_RELEASE, plci_appl_release},

	{ST_PLCI_P_0_1, EV_PI_CONNECT_CONF, plci_connect_conf},
	{ST_PLCI_P_0_1, EV_AP_RELEASE, plci_appl_release},

	{ST_PLCI_P_1, EV_PI_CONNECT_ACTIVE_IND, plci_connect_active_ind},
	{ST_PLCI_P_1, EV_AP_DISCONNECT_REQ, plci_disconnect_req},
	{ST_PLCI_P_1, EV_PI_DISCONNECT_IND, plci_disconnect_ind},
	{ST_PLCI_P_1, EV_AP_INFO_REQ, plci_info_req_overlap},
	{ST_PLCI_P_1, EV_L3_SETUP_CONF, plci_cc_setup_conf},
	{ST_PLCI_P_1, EV_L3_SETUP_CONF_ERR, plci_cc_setup_conf_err},
	{ST_PLCI_P_1, EV_L3_DISCONNECT_IND, plci_cc_disconnect_ind},
	{ST_PLCI_P_1, EV_L3_RELEASE_PROC_IND, plci_cc_setup_conf_err},
	{ST_PLCI_P_1, EV_L3_RELEASE_IND, plci_cc_release_ind},
	{ST_PLCI_P_1, EV_L3_REJECT_IND, plci_cc_release_ind},
	{ST_PLCI_P_1, EV_PI_CHANNEL_ERR, plci_channel_err},
	{ST_PLCI_P_1, EV_AP_RELEASE, plci_appl_release_disc},

	{ST_PLCI_P_2, EV_AP_ALERT_REQ, plci_alert_req},
	{ST_PLCI_P_2, EV_AP_CONNECT_RESP, plci_connect_resp},
	{ST_PLCI_P_2, EV_AP_DISCONNECT_REQ, plci_disconnect_req},
	{ST_PLCI_P_2, EV_PI_DISCONNECT_IND, plci_disconnect_ind},
	{ST_PLCI_P_2, EV_L3_RELEASE_PROC_IND, plci_cc_release_ind},
	{ST_PLCI_P_2, EV_AP_INFO_REQ, plci_info_req},
	{ST_PLCI_P_2, EV_L3_RELEASE_IND, plci_cc_release_ind},
	{ST_PLCI_P_2, EV_AP_RELEASE, plci_appl_release_disc},

	{ST_PLCI_P_4, EV_PI_CONNECT_ACTIVE_IND, plci_connect_active_ind},
	{ST_PLCI_P_4, EV_AP_DISCONNECT_REQ, plci_disconnect_req},
	{ST_PLCI_P_4, EV_PI_DISCONNECT_IND, plci_disconnect_ind},
	{ST_PLCI_P_4, EV_AP_INFO_REQ, plci_info_req},
	{ST_PLCI_P_4, EV_L3_SETUP_COMPL_IND, plci_cc_setup_compl_ind},
	{ST_PLCI_P_4, EV_L3_RELEASE_IND, plci_cc_release_ind},
	{ST_PLCI_P_4, EV_L3_RELEASE_PROC_IND, plci_cc_release_ind},
	{ST_PLCI_P_4, EV_PI_CHANNEL_ERR, plci_channel_err},
	{ST_PLCI_P_4, EV_AP_RELEASE, plci_appl_release_disc},

	{ST_PLCI_P_ACT, EV_AP_CONNECT_ACTIVE_RESP, plci_connect_active_resp},
	{ST_PLCI_P_ACT, EV_AP_DISCONNECT_REQ, plci_disconnect_req},
	{ST_PLCI_P_ACT, EV_PI_DISCONNECT_IND, plci_disconnect_ind},
	{ST_PLCI_P_ACT, EV_AP_INFO_REQ, plci_info_req},
	{ST_PLCI_P_ACT, EV_AP_SELECT_B_PROTOCOL_REQ, plci_select_b_protocol_req},
	{ST_PLCI_P_ACT, EV_AP_HOLD_REQ, plci_hold_req},
	{ST_PLCI_P_ACT, EV_AP_SUSPEND_REQ, plci_suspend_req},
	{ST_PLCI_P_ACT, EV_PI_SUSPEND_CONF, plci_suspend_conf},
	{ST_PLCI_P_ACT, EV_L3_DISCONNECT_IND, plci_cc_disconnect_ind},
	{ST_PLCI_P_ACT, EV_L3_RELEASE_IND, plci_cc_release_ind},
	{ST_PLCI_P_ACT, EV_L3_RELEASE_PROC_IND, plci_cc_release_ind},
	{ST_PLCI_P_ACT, EV_L3_NOTIFY_IND, plci_cc_notify_ind},
	{ST_PLCI_P_ACT, EV_L3_HOLD_IND, plci_cc_hold_ind},
	{ST_PLCI_P_ACT, EV_L3_HOLD_ACKNOWLEDGE, plci_cc_hold_ack},
	{ST_PLCI_P_ACT, EV_L3_HOLD_REJECT, plci_cc_hold_rej},
	{ST_PLCI_P_ACT, EV_PI_HOLD_CONF, plci_hold_conf},
	{ST_PLCI_P_ACT, EV_L3_SUSPEND_ERR, plci_cc_suspend_err},
	{ST_PLCI_P_ACT, EV_L3_SUSPEND_CONF, plci_cc_suspend_conf},
	{ST_PLCI_P_ACT, EV_PH_CONTROL_IND, plci_cc_ph_control_ind},
	{ST_PLCI_P_ACT, EV_AP_RELEASE, plci_appl_release_disc},

	{ST_PLCI_P_HELD, EV_AP_RETRIEVE_REQ, plci_retrieve_req},
	{ST_PLCI_P_HELD, EV_L3_RETRIEVE_ACKNOWLEDGE, plci_cc_retrieve_ack},
	{ST_PLCI_P_HELD, EV_L3_RETRIEVE_REJECT, plci_cc_retrieve_rej},
	{ST_PLCI_P_HELD, EV_PI_RETRIEVE_CONF, plci_retrieve_conf},
	{ST_PLCI_P_HELD, EV_AP_DISCONNECT_REQ, plci_disconnect_req},
	{ST_PLCI_P_HELD, EV_AP_INFO_REQ, plci_info_req},
	{ST_PLCI_P_HELD, EV_L3_RETRIEVE_IND, plci_cc_retrieve_ind},
	{ST_PLCI_P_HELD, EV_L3_DISCONNECT_IND, plci_cc_disconnect_ind},
	{ST_PLCI_P_HELD, EV_L3_RELEASE_IND, plci_cc_release_ind},
	{ST_PLCI_P_HELD, EV_L3_RELEASE_PROC_IND, plci_cc_release_ind},
	{ST_PLCI_P_HELD, EV_L3_NOTIFY_IND, plci_cc_notify_ind},
	{ST_PLCI_P_HELD, EV_PI_DISCONNECT_IND, plci_disconnect_ind},
	{ST_PLCI_P_HELD, EV_AP_RELEASE, plci_appl_release_disc},

	{ST_PLCI_P_5, EV_PI_DISCONNECT_IND, plci_disconnect_ind},
	{ST_PLCI_P_5, EV_L3_RELEASE_IND, plci_cc_release_ind},
	{ST_PLCI_P_5, EV_L3_RELEASE_PROC_IND, plci_cc_release_ind},
	{ST_PLCI_P_5, EV_AP_RELEASE, plci_appl_release},

	{ST_PLCI_P_6, EV_AP_DISCONNECT_RESP, plci_disconnect_resp},
	{ST_PLCI_P_6, EV_AP_RELEASE, plci_disconnect_resp},

	{ST_PLCI_P_RES, EV_PI_RESUME_CONF, plci_resume_conf},
	{ST_PLCI_P_RES, EV_PI_DISCONNECT_IND, plci_disconnect_ind},
	{ST_PLCI_P_RES, EV_L3_RESUME_ERR, plci_cc_resume_err},
	{ST_PLCI_P_RES, EV_L3_RESUME_CONF, plci_cc_resume_conf},
	{ST_PLCI_P_RES, EV_AP_RELEASE, plci_appl_release_disc},
};

const int FN_PLCI_COUNT = sizeof(fn_plci_list) / sizeof(struct FsmNode);

int lPLCICreate(struct lPLCI **lpp, struct lController *lc, struct mPLCI *plci)
{
	struct lPLCI *lp;

	lp = calloc(1, sizeof(*lp));
	if (!lp)
		return -ENOMEM;
	lp->plci = plci->plci;
	lp->lc = lc;
	lp->PLCI = plci;
	if (lc->Contr->profile.goptions & 0x0008) {
		/* DTMF */
		lp->l1dtmf = 1;
	}
	lp->plci_m.fsm = &plci_fsm;
	lp->plci_m.state = ST_PLCI_P_0;
//      lp->plci_m.debug      = plci->contr->debug & CAPI_DBG_PLCI_STATE;
	lp->plci_m.debug = MIDEBUG_PLCI & mI_debug_mask;
	lp->plci_m.userdata = lp;
	lp->plci_m.printdebug = lPLCI_debug;
	lp->chid.nr = MI_CHAN_NONE;
	lp->autohangup = 1;
	pthread_mutex_init(&lp->lock, NULL);
	*lpp = lp;
	return 0;
}

void lPLCI_free(struct lPLCI *lp)
{
	struct mNCCI *nc, *nn;

	dprint(MIDEBUG_PLCI, "PLCI:%04x free now\n", lp->plci);
	if (lp->BIlink)
		CloseBInstance(lp->BIlink);
	if (lp->PLCI) {
		dprint(MIDEBUG_PLCI, "PLCI:%04x plci state %s\n", lp->plci, str_st_plci[lp->plci_m.state]);
		if (lp->plci_m.state != ST_PLCI_P_0) {
			struct l3_msg *l3m = alloc_l3_msg();

			if (l3m) {
				mi_encode_cause(l3m, CAUSE_RESOURCES_UNAVAIL, CAUSE_LOC_USER, 0, NULL);
				plciL4L3(lp->PLCI, MT_RELEASE_COMPLETE, l3m);
			} else
				eprint("No l3m for RELEASE_COMPLETE for PLCI:%04x\n", lp->plci);
		}
		plciDetachlPLCI(lp);
	}
	nc = lp->Nccis;
	lp->Nccis = NULL;
	while (nc) {
		nn = nc->next;
		nc->next = NULL;
		ncciDel_lPlci(nc);
		nc = nn;
	}
	free(lp);
}

void lPLCIRelease(struct lPLCI *lp)
{
	/* TODO clean NCCIs */
	dprint(MIDEBUG_PLCI, "PLCI:%04x plci state %s - %s NCCIs\n", lp->plci, str_st_plci[lp->plci_m.state], lp->Nccis ? "have" : "no");
	FsmEvent(&lp->plci_m, EV_AP_RELEASE, NULL);
}

static int lPLCILinkUp(struct lPLCI *lp)
{
	int proto = -1, ret = 0, act_l1;
	struct mISDNhead mh;
	enum BType btype = BType_None;
	struct mApplication *ap = lp->lc->Appl;

	mh.id = 1;
	mh.prim = 0;
	act_l1 = !lp->PLCI->outgoing;

	if (lp->chid.nr == MI_CHAN_NONE || lp->chid.nr == MI_CHAN_ANY) {
		/* no valid channel set */
		wprint("PLCI:%04x no channel selected (0x%x)\n", lp->plci, lp->chid.nr);
		return -EINVAL;
	}

	switch (lp->Bprotocol.B1) {
	case 0:		/* HDLC */
		proto = ISDN_P_B_HDLC;
		mh.prim = PH_ACTIVATE_REQ;
		break;
	case 1:		/* trans */
		if (lp->l1dtmf) {
			proto = ISDN_P_B_L2DSP;
			mh.prim = DL_ESTABLISH_REQ;
		} else {
			proto = ISDN_P_B_RAW;
			mh.prim = PH_ACTIVATE_REQ;
		}
		break;
#ifdef USE_SOFTFAX
	case 4:
		proto = ISDN_P_B_RAW;
		mh.prim = PH_ACTIVATE_REQ;
		act_l1 = 1;
		lp->autohangup = 0;
		break;
#endif
	default:
		wprint("Unsupported B1 prot %x\n", lp->Bprotocol.B1);
		ret = 0x3001;
		break;
	}

	switch (lp->Bprotocol.B2) {
	case 0:		/* HDLC */
		proto = ISDN_P_B_X75SLP;
		mh.prim = DL_ESTABLISH_REQ;
		break;
	case 1:		/* trans */
#ifdef USE_SOFTFAX
	case 4:
#endif
		break;
	default:
		wprint("Unsupported B2 prot %x\n", lp->Bprotocol.B2);
		ret = 0x3002;
		break;
	}

	switch (lp->Bprotocol.B3) {
	case 0:	/* trans */
		btype = BType_Direct;
		break;
#ifdef USE_SOFTFAX
	case 4:
	case 5:
		btype = BType_Fax;
		break;
#endif
	default:
		wprint("Unsupported B3 prot %x\n", lp->Bprotocol.B3);
		ret = 0x3003;
		break;
	}

	if (ret)
		return ret;

	if (ap->UserFlags & CAPIFLAG_HIGHJACKING) {
		btype = BType_tty;
	}
	dprint(MIDEBUG_PLCI, "lPLCILinkUp B1(%x) B2(%x) B3(%x) ch(%d) proto(%x)\n",
	       lp->Bprotocol.B1, lp->Bprotocol.B2, lp->Bprotocol.B3, lp->chid.nr, proto);

	lp->BIlink = ControllerSelChannel(lp->lc->Contr, lp->chid.nr, proto);
	if (!lp->BIlink) {
		wprint("PLCI:%04x channel %d busy\n", lp->plci, lp->chid.nr);
		return CapiMsgOSResourceErr;
	}
	dprint(MIDEBUG_PLCI, "lPLCILinkUp lp->link(%p)\n", lp->BIlink);
	if (!OpenBInstance(lp->BIlink, lp, btype)) {
		if (act_l1) {
			ret = send(lp->BIlink->fd, &mh, 8, 0);
			if (ret < 0) {
				wprint("Cannot send to Bchannel socket\n");
				ret = CapiMsgOSResourceErr;
			} else {
				dprint(MIDEBUG_NCCI, "Sent %d bytes to controller\n", ret);
			}
		}
	} else {
		wprint("OpenBInstance PLCI:%04x failed\n", lp->plci);
		ret = CapiMsgOSResourceErr;
	}
	return ret;
}

struct mNCCI *getNCCI4addr(struct lPLCI *lp, uint32_t addr, int mode)
{
	struct mNCCI *ncci = NULL;
	int cnt = 0;

	if (!lp)
		return NULL;
	ncci = lp->Nccis;
	while (ncci) {
		cnt++;
		if (ncci->ncci == addr)
			return ncci;
		if (mode == GET_NCCI_ONLY_PLCI) {
			if (ncci->ncci == (addr & 0xffff))
				return ncci;
		}
		ncci = ncci->next;
	}
	if (!cnt)
		return NULL;
	if (mode != GET_NCCI_PLCI)
		return NULL;
	if (1 == cnt) {
		if (!(addr & 0xffff0000))
			return lp->Nccis;
	}
	return NULL;
}

void lPLCIDelNCCI(struct mNCCI *ncci)
{
	struct lPLCI *lp = ncci->lp;
	struct mNCCI *nc, *onc;

	pthread_mutex_lock(&lp->lock);
	nc = lp->Nccis;
	onc = NULL;
	while (nc) {
		if (nc == ncci) {
			lp->NcciCnt--;
			if (onc)
				onc->next = nc->next;
			break;
		}
		onc = nc;
		nc = nc->next;
	}
	if (lp->Nccis ==  ncci)
		lp->Nccis = ncci->next;

	if (lp->Nccis || lp->NcciCnt) {
		iprint("PLCI:%04x still %d NCCIs do not shutdown link\n", lp->plci, lp->NcciCnt);
	} else {
		dprint(MIDEBUG_PLCI, "PLCI:%04x all NCCIs gone %s BIlink\n", lp->plci, ncci->BIlink ? "shutdown" : "no");
		if (ncci->BIlink) {
			ncci->BIlink->b3data = NULL;
			if (ncci->BIlink->fd > 0)
				CloseBInstance(ncci->BIlink);
			ncci->BIlink = NULL;
			lp->BIlink = NULL;
		}
	}
	pthread_mutex_unlock(&lp->lock);
}

void B3ReleaseLink(struct lPLCI *lp, struct BInstance *bi)
{
	switch(bi->type) {
	case BType_Direct:
		ncciReleaseLink(bi->b3data);
		break;
#ifdef USE_SOFTFAX
	case BType_Fax:
		FaxReleaseLink(bi);
		break;
#endif
	default:
		eprint("Unknown BType %d\n", bi->type);
	}
}

void lPLCI_l3l4(struct lPLCI *lp, int pr, struct mc_buf *mc)
{
	int ret;

	dprint(MIDEBUG_PLCI, "lp(%x) %s %s arg\n", lp->plci, _mi_msg_type2str(pr), mc ? "with" : "no");
	switch (pr) {
	case MT_SETUP:
		if (!mc || !mc->l3m)
			return;
		plci_parse_channel_id(lp, mc);
		FsmEvent(&lp->plci_m, EV_L3_SETUP_IND, mc);
		if (mc->l3m) {
			lPLCIInfoIndIE(lp, IE_DISPLAY, CAPI_INFOMASK_DISPLAY, mc);
			lPLCIInfoIndIE(lp, IE_USER_USER, CAPI_INFOMASK_USERUSER, mc);
			lPLCIInfoIndIE(lp, IE_PROGRESS, CAPI_INFOMASK_PROGRESS, mc);
			lPLCIInfoIndIE(lp, IE_FACILITY, CAPI_INFOMASK_FACILITY, mc);
			lPLCIInfoIndIE(lp, IE_CHANNEL_ID, CAPI_INFOMASK_CHANNELID, mc);
			lPLCIInfoIndIE(lp, IE_CALLED_PN, CAPI_INFOMASK_CALLEDPN, mc);
			lPLCIInfoIndIE(lp, IE_COMPLETE, CAPI_INFOMASK_COMPLETE, mc);
		}
		break;
	case MT_TIMEOUT:
		FsmEvent(&lp->plci_m, EV_L3_SETUP_CONF_ERR, mc);
		break;
	case MT_CONNECT:
		if (mc->l3m) {
			lPLCIInfoIndIE(lp, IE_DATE, CAPI_INFOMASK_DISPLAY, mc);
			lPLCIInfoIndIE(lp, IE_DISPLAY, CAPI_INFOMASK_DISPLAY, mc);
			lPLCIInfoIndIE(lp, IE_USER_USER, CAPI_INFOMASK_USERUSER, mc);
			lPLCIInfoIndIE(lp, IE_PROGRESS, CAPI_INFOMASK_PROGRESS, mc);
			lPLCIInfoIndIE(lp, IE_FACILITY, CAPI_INFOMASK_FACILITY, mc);
			lPLCIInfoIndIE(lp, IE_CHANNEL_ID, CAPI_INFOMASK_CHANNELID, mc);
			ret = plci_parse_channel_id(lp, mc);
			if (ret < 0) {
				dprint(MIDEBUG_PLCI, "Got no valid channel on %s (%d)\n", _mi_msg_type2str(pr), ret);
			}
		}
		FsmEvent(&lp->plci_m, EV_L3_SETUP_CONF, mc);
		break;
	case MT_CONNECT_ACKNOWLEDGE:
		if (mc->l3m) {
			lPLCIInfoIndIE(lp, IE_DISPLAY, CAPI_INFOMASK_DISPLAY, mc);
			lPLCIInfoIndIE(lp, IE_CHANNEL_ID, CAPI_INFOMASK_CHANNELID, mc);
			ret = plci_parse_channel_id(lp, mc);
			if (ret < 0) {
				dprint(MIDEBUG_PLCI, "Got no valid channel on %s (%d)\n", _mi_msg_type2str(pr), ret);
			}
		}
		FsmEvent(&lp->plci_m, EV_L3_SETUP_COMPL_IND, mc);
		break;
	case MT_DISCONNECT:
		if (mc->l3m) {
			lPLCIInfoIndMsg(lp, CAPI_INFOMASK_EARLYB3, MT_DISCONNECT, mc);
			lPLCIInfoIndIE(lp, IE_CAUSE, CAPI_INFOMASK_CAUSE, mc);
			lPLCIInfoIndIE(lp, IE_DISPLAY, CAPI_INFOMASK_DISPLAY, mc);
			lPLCIInfoIndIE(lp, IE_USER_USER, CAPI_INFOMASK_USERUSER, mc);
			lPLCIInfoIndIE(lp, IE_PROGRESS, CAPI_INFOMASK_PROGRESS, mc);
			lPLCIInfoIndIE(lp, IE_FACILITY, CAPI_INFOMASK_FACILITY, mc);
		}
		FsmEvent(&lp->plci_m, EV_L3_DISCONNECT_IND, mc);
		break;
	case MT_RELEASE:
		if (mc->l3m) {
			lPLCIInfoIndIE(lp, IE_CAUSE, CAPI_INFOMASK_CAUSE, mc);
			lPLCIInfoIndIE(lp, IE_DISPLAY, CAPI_INFOMASK_DISPLAY, mc);
			lPLCIInfoIndIE(lp, IE_USER_USER, CAPI_INFOMASK_USERUSER, mc);
			lPLCIInfoIndIE(lp, IE_FACILITY, CAPI_INFOMASK_FACILITY, mc);
		}
		FsmEvent(&lp->plci_m, EV_L3_RELEASE_IND, mc);
		break;
	case MT_RELEASE_COMPLETE:
		if (mc->l3m) {
			lPLCIInfoIndIE(lp, IE_CAUSE, CAPI_INFOMASK_CAUSE, mc);
			lPLCIInfoIndIE(lp, IE_DISPLAY, CAPI_INFOMASK_DISPLAY, mc);
			lPLCIInfoIndIE(lp, IE_USER_USER, CAPI_INFOMASK_USERUSER, mc);
			lPLCIInfoIndIE(lp, IE_FACILITY, CAPI_INFOMASK_FACILITY, mc);
		}
		FsmEvent(&lp->plci_m, EV_L3_RELEASE_IND, mc);
		break;
	case MT_FREE:
		FsmEvent(&lp->plci_m, EV_L3_RELEASE_PROC_IND, mc);
		break;
	case MT_SETUP_ACKNOWLEDGE:
		if (mc->l3m) {
			lPLCIInfoIndMsg(lp, CAPI_INFOMASK_PROGRESS, MT_SETUP_ACKNOWLEDGE, mc);
			lPLCIInfoIndIE(lp, IE_DISPLAY, CAPI_INFOMASK_DISPLAY, mc);
			lPLCIInfoIndIE(lp, IE_PROGRESS, CAPI_INFOMASK_PROGRESS | CAPI_INFOMASK_EARLYB3, mc);
			lPLCIInfoIndIE(lp, IE_CHANNEL_ID, CAPI_INFOMASK_CHANNELID, mc);
			ret = plci_parse_channel_id(lp, mc);
			if (ret < -1) {
				wprint("Got channel coding error in %s (%d)\n", _mi_msg_type2str(pr), ret);
			}
		}
		break;
	case MT_CALL_PROCEEDING:
		if (mc->l3m) {
			lPLCIInfoIndMsg(lp, CAPI_INFOMASK_PROGRESS, MT_CALL_PROCEEDING, mc);
			lPLCIInfoIndIE(lp, IE_DISPLAY, CAPI_INFOMASK_DISPLAY, mc);
			lPLCIInfoIndIE(lp, IE_PROGRESS, CAPI_INFOMASK_PROGRESS | CAPI_INFOMASK_EARLYB3, mc);
			lPLCIInfoIndIE(lp, IE_CHANNEL_ID, CAPI_INFOMASK_CHANNELID, mc);
		}
		break;
	case MT_ALERTING:
		if (mc->l3m) {
			lPLCIInfoIndMsg(lp, CAPI_INFOMASK_PROGRESS, MT_ALERTING, mc);
			lPLCIInfoIndIE(lp, IE_DISPLAY, CAPI_INFOMASK_DISPLAY, mc);
			lPLCIInfoIndIE(lp, IE_USER_USER, CAPI_INFOMASK_USERUSER, mc);
			lPLCIInfoIndIE(lp, IE_PROGRESS, CAPI_INFOMASK_PROGRESS | CAPI_INFOMASK_EARLYB3, mc);
			lPLCIInfoIndIE(lp, IE_FACILITY, CAPI_INFOMASK_FACILITY, mc);
			lPLCIInfoIndIE(lp, IE_CHANNEL_ID, CAPI_INFOMASK_CHANNELID, mc);
			ret = plci_parse_channel_id(lp, mc);
			if (ret < -1) {
				wprint("Got channel coding error in %s (%d)\n", _mi_msg_type2str(pr), ret);
			}
		}
		break;
	case MT_PROGRESS:
		if (mc->l3m) {
			lPLCIInfoIndMsg(lp, CAPI_INFOMASK_PROGRESS, MT_PROGRESS, mc);
			lPLCIInfoIndIE(lp, IE_CAUSE, CAPI_INFOMASK_CAUSE, mc);
			lPLCIInfoIndIE(lp, IE_DISPLAY, CAPI_INFOMASK_DISPLAY, mc);
			lPLCIInfoIndIE(lp, IE_USER_USER, CAPI_INFOMASK_USERUSER, mc);
			lPLCIInfoIndIE(lp, IE_PROGRESS, CAPI_INFOMASK_PROGRESS | CAPI_INFOMASK_EARLYB3, mc);
		}
		break;
	case MT_HOLD:
		if (mc->l3m)
			lPLCIInfoIndIE(lp, IE_DISPLAY, CAPI_INFOMASK_DISPLAY, mc);
		if (FsmEvent(&lp->plci_m, EV_L3_HOLD_IND, mc)) {
			/* no routine reject L3 */
			plciL4L3(lp->PLCI, MT_HOLD_REJECT, NULL);
		}
		break;
	case MT_HOLD_ACKNOWLEDGE:
		if (mc->l3m)
			lPLCIInfoIndIE(lp, IE_DISPLAY, CAPI_INFOMASK_DISPLAY, mc);
		FsmEvent(&lp->plci_m, EV_L3_HOLD_ACKNOWLEDGE, mc);
		break;
	case MT_HOLD_REJECT:
		if (mc->l3m) {
			lPLCIInfoIndIE(lp, IE_CAUSE, CAPI_INFOMASK_CAUSE, mc);
			lPLCIInfoIndIE(lp, IE_DISPLAY, CAPI_INFOMASK_DISPLAY, mc);
		}
		FsmEvent(&lp->plci_m, EV_L3_HOLD_REJECT, mc);
		break;
	case MT_RETRIEVE:
		if (mc->l3m) {
			lPLCIInfoIndIE(lp, IE_DISPLAY, CAPI_INFOMASK_DISPLAY, mc);
			lPLCIInfoIndIE(lp, IE_CHANNEL_ID, CAPI_INFOMASK_CHANNELID, mc);
		}
		if (FsmEvent(&lp->plci_m, EV_L3_RETRIEVE_IND, mc)) {
			/* no routine reject L3 */
			plciL4L3(lp->PLCI, MT_RETRIEVE_REJECT, NULL);
		}
		break;
	case MT_RETRIEVE_ACKNOWLEDGE:
		if (mc->l3m) {
			lPLCIInfoIndIE(lp, IE_DISPLAY, CAPI_INFOMASK_DISPLAY, mc);
			lPLCIInfoIndIE(lp, IE_CHANNEL_ID, CAPI_INFOMASK_CHANNELID, mc);
		}
		FsmEvent(&lp->plci_m, EV_L3_RETRIEVE_ACKNOWLEDGE, mc);
		break;
	case MT_RETRIEVE_REJECT:
		if (mc->l3m) {
			lPLCIInfoIndIE(lp, IE_CAUSE, CAPI_INFOMASK_CAUSE, mc);
			lPLCIInfoIndIE(lp, IE_DISPLAY, CAPI_INFOMASK_DISPLAY, mc);
		}
		FsmEvent(&lp->plci_m, EV_L3_RETRIEVE_REJECT, mc);
		break;
	case MT_SUSPEND_ACKNOWLEDGE:
		FsmEvent(&lp->plci_m, EV_L3_SUSPEND_CONF, mc);
		break;
	case MT_SUSPEND_REJECT:
		FsmEvent(&lp->plci_m, EV_L3_SUSPEND_ERR, mc);
		break;
	case MT_RESUME_ACKNOWLEDGE:
		FsmEvent(&lp->plci_m, EV_L3_RESUME_CONF, mc);
		break;
	case MT_RESUME_REJECT:
		FsmEvent(&lp->plci_m, EV_L3_RESUME_ERR, mc);
		break;
	case MT_NOTIFY:
		FsmEvent(&lp->plci_m, EV_L3_NOTIFY_IND, mc);
		break;
	case PH_CONTROL_IND:
		/* TOUCH TONE */
		FsmEvent(&lp->plci_m, EV_PH_CONTROL_IND, mc);
		break;
	default:
		wprint("PLCI %x pr 0x%x not handled\n", lp->plci, pr);
		break;
	}
}

void lPLCIGetCmsg(struct lPLCI *lp, struct mc_buf *mc)
{
	int retval = 0;

	switch (CMSGCMD(&mc->cmsg)) {
	case CAPI_INFO_REQ:
		retval = FsmEvent(&lp->plci_m, EV_AP_INFO_REQ, mc);
		break;
	case CAPI_ALERT_REQ:
		retval = FsmEvent(&lp->plci_m, EV_AP_ALERT_REQ, mc);
		break;
	case CAPI_CONNECT_REQ:
		retval = FsmEvent(&lp->plci_m, EV_AP_CONNECT_REQ, mc);
		break;
	case CAPI_CONNECT_RESP:
		retval = FsmEvent(&lp->plci_m, EV_AP_CONNECT_RESP, mc);
		break;
	case CAPI_DISCONNECT_REQ:
		retval = FsmEvent(&lp->plci_m, EV_AP_DISCONNECT_REQ, mc);
		break;
	case CAPI_DISCONNECT_RESP:
		retval = FsmEvent(&lp->plci_m, EV_AP_DISCONNECT_RESP, mc);
		break;
	case CAPI_CONNECT_ACTIVE_RESP:
		retval = FsmEvent(&lp->plci_m, EV_AP_CONNECT_ACTIVE_RESP, mc);
		break;
	case CAPI_SELECT_B_PROTOCOL_REQ:
		retval = FsmEvent(&lp->plci_m, EV_AP_SELECT_B_PROTOCOL_REQ, mc);
		break;
	default:
		wprint("PLCI:%x command %02x/%02x not handled\n", lp->plci, mc->cmsg.Command, mc->cmsg.Subcommand);
		retval = -1;
	}
	if (retval) {
		if (mc->cmsg.Subcommand == CAPI_REQ) {
			capi_cmsg_answer(&mc->cmsg);
			mc->cmsg.Info = CapiMessageNotSupportedInCurrentState;
			Send2Application(lp, mc);
		}
	}
}

uint16_t lPLCISendMessage(struct lPLCI *lp, struct mc_buf *mc)
{
	uint16_t ret;

	lPLCIGetCmsg(lp, mc);
	ret = CapiNoError;
	free_mc_buf(mc);
	return ret;
}

struct mNCCI *ConnectB3Request(struct lPLCI *lp, struct mc_buf *mc)
{
	struct mNCCI *ncci = ncciCreate(lp);

	if (!ncci) {
		wprint("Could not create NCCI for PCLI %04x\n", lp->plci);
	} else if (!ncci->BIlink) {
		wprint("No channel instance assigned for PCLI %04x\n", lp->plci);
	}
	return ncci;
}

static int lPLCILinkDown(struct lPLCI *lp)
{
	struct mNCCI *nc, *nn;
	struct BInstance *bi;

	nc = lp->Nccis;
	dprint(MIDEBUG_PLCI, "PLCI:%04x LinkDown %s Nccis %d\n", lp->plci, lp->Nccis ? "" : "no", lp->NcciCnt);
	if (nc) {
		while (nc) {
			nn = nc->next;
			ncciReleaseLink(nc);
			nc = nn;
		}
	} else if (lp->BIlink) {
		bi = lp->BIlink;
		lp->BIlink = NULL;
		CloseBInstance(bi);
	}
	return 0;
}

int lPLCIFacHoldReq(struct lPLCI *lp, struct FacReqParm *facReqParm, struct FacConfParm *facConfParm)
{
	/* no parameter needed so we do not need a l3m */
	if (FsmEvent(&lp->plci_m, EV_AP_HOLD_REQ, NULL)) {
		// no routine
		facConfParm->u.Info.SupplementaryServiceInfo = CapiRequestNotAllowedInThisState;
		return CapiMessageNotSupportedInCurrentState;
	} else {
		facConfParm->u.Info.SupplementaryServiceInfo = CapiNoError;
	}
	return CapiNoError;
}

int lPLCIFacRetrieveReq(struct lPLCI *lp, struct FacReqParm *facReqParm, struct FacConfParm *facConfParm)
{
	/* no parameter needed so we do not need a l3m */
	if (FsmEvent(&lp->plci_m, EV_AP_RETRIEVE_REQ, NULL)) {
		// no routine
		facConfParm->u.Info.SupplementaryServiceInfo = CapiRequestNotAllowedInThisState;
		return CapiMessageNotSupportedInCurrentState;
	} else {
		facConfParm->u.Info.SupplementaryServiceInfo = CapiNoError;
	}
	return CapiNoError;
}

int lPLCIFacSuspendReq(struct lPLCI *lp, struct FacReqParm *facReqParm, struct FacConfParm *facConfParm)
{
	unsigned char *CallId;
	struct l3_msg *l3m;

	CallId = facReqParm->u.Suspend.CallIdentity;
	if (CallId && CallId[0] > 8)
		return CapiIllMessageParmCoding;
	l3m = alloc_l3_msg();
	if (!l3m) {
		wprint("Could not allocate l3 message for PLCI %04x\n", lp->plci);
		return CapiMsgOSResourceErr;
	}
	if (CallId && CallId[0])
		add_layer3_ie(l3m, IE_CALL_ID, CallId[0], &CallId[1]);

	if (FsmEvent(&lp->plci_m, EV_AP_SUSPEND_REQ, l3m)) {
		// no routine
		facConfParm->u.Info.SupplementaryServiceInfo = CapiRequestNotAllowedInThisState;
		free_l3_msg(l3m);
		return CapiMessageNotSupportedInCurrentState;
	} else {
		facConfParm->u.Info.SupplementaryServiceInfo = CapiNoError;
	}
	return CapiNoError;
}

int lPLCIFacResumeReq(struct lPLCI *lp, struct FacReqParm *facReqParm, struct FacConfParm *facConfParm)
{
	__u8 *CallId;
	struct l3_msg *l3m;

	CallId = facReqParm->u.Resume.CallIdentity;
	if (CallId && CallId[0] > 8) {
		lPLCI_free(lp);
		return CapiIllMessageParmCoding;
	}
	l3m = alloc_l3_msg();
	if (!l3m) {
		wprint("Could not allocate l3 message for PLCI %04x\n", lp->plci);
		lPLCI_free(lp);
		return CapiIllMessageParmCoding;
	}
	if (CallId && CallId[0])
		add_layer3_ie(l3m, IE_CALL_ID, CallId[0], &CallId[1]);

	if (FsmEvent(&lp->plci_m, EV_AP_RESUME_REQ, l3m)) {
		// no routine
		facConfParm->u.Info.SupplementaryServiceInfo = CapiRequestNotAllowedInThisState;
		free_l3_msg(l3m);
		lPLCI_free(lp);
		return CapiMessageNotSupportedInCurrentState;
	}
	facConfParm->u.Info.SupplementaryServiceInfo = CapiNoError;
	return CapiNoError;
}

void init_lPLCI_fsm(void)
{
	plci_fsm.state_count = ST_PLCI_COUNT;
	plci_fsm.event_count = EV_PLCI_COUNT;
	plci_fsm.strEvent = str_ev_plci;
	plci_fsm.strState = str_st_plci;

	FsmNew(&plci_fsm, fn_plci_list, FN_PLCI_COUNT);
}

void free_lPLCI_fsm(void)
{
	FsmFree(&plci_fsm);
}
