mirror of
https://github.com/signalwire/freeswitch.git
synced 2025-02-11 20:49:02 +00:00
git-svn-id: http://svn.openzap.org/svn/openzap/branches/sangoma_boost@967 a93c3328-9c30-0410-af19-c9cd2b2d52af
3519 lines
84 KiB
C
3519 lines
84 KiB
C
/*****************************************************************************
|
|
|
|
FileName: q921.c
|
|
|
|
Description: Contains the implementation of a Q.921 protocol
|
|
|
|
Created: 27.dec.2000/JVB
|
|
|
|
License/Copyright:
|
|
|
|
Copyright (c) 2007, Jan Vidar Berger, Case Labs, Ltd. All rights reserved.
|
|
email:janvb@caselaboratories.com
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions are
|
|
met:
|
|
|
|
* Redistributions of source code must retain the above copyright notice,
|
|
this list of conditions and the following disclaimer.
|
|
* 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.
|
|
* Neither the name of the Case Labs, Ltd 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 COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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.
|
|
|
|
*****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Changes:
|
|
*
|
|
* - June-August 2008: Stefan Knoblich <s.knoblich@axsentis.de>:
|
|
* Add PTMP TEI management (NT + TE mode)
|
|
* Add timers
|
|
* Add retransmit counters
|
|
* Add logging
|
|
* Various cleanups
|
|
* Queues, retransmission of I frames
|
|
* PTMP NT mode
|
|
*
|
|
*
|
|
* TODO:
|
|
*
|
|
* - Cleanup queueing, test retransmission
|
|
*
|
|
* - Q921Start() /-Stop() TEI acquire + release
|
|
* (move everything related into these functions)
|
|
*
|
|
* - Q.921 '97 Appendix I (and maybe III, IV)
|
|
*
|
|
* - More complete Appendix II
|
|
*
|
|
* - Test PTP mode
|
|
*
|
|
* - PTMP NT mode (in progress)
|
|
*
|
|
* - NT mode TEI management: (ab)use T202 for TEI Check Request retransmission
|
|
*
|
|
* - General cleanup (move all non-public declarations into private header file)
|
|
*
|
|
* - Statistics, per-Frame type debug message filter
|
|
*
|
|
****************************************************************************/
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdarg.h>
|
|
|
|
#include "freetdm.h"
|
|
#include "Q921.h"
|
|
#include "Q921priv.h"
|
|
#include "mfifo.h"
|
|
|
|
#ifdef WIN32
|
|
#pragma warning(disable:4100 4244)
|
|
#endif
|
|
|
|
/******************************************************************************************************
|
|
* Actual code below this line
|
|
******************************************************************************************************/
|
|
|
|
|
|
/**
|
|
* Q921StateNames
|
|
* \brief Static array of state name / value mappings
|
|
*/
|
|
static struct Q921StateName {
|
|
Q921State_t value;
|
|
const char *name;
|
|
} Q921StateNames[10] = {
|
|
{ Q921_STATE_STOPPED, "Stopped" },
|
|
{ Q921_STATE_TEI_UNASSIGNED, "TEI Unassigned" },
|
|
{ Q921_STATE_TEI_AWAITING, "TEI Awaiting Assignment" },
|
|
{ Q921_STATE_TEI_ESTABLISH, "TEI Awaiting Establishment" },
|
|
{ Q921_STATE_TEI_ASSIGNED, "TEI Assigned" },
|
|
{ Q921_STATE_AWAITING_ESTABLISHMENT, "Awaiting Establishment" },
|
|
{ Q921_STATE_AWAITING_RELEASE, "Awaiting Release" },
|
|
{ Q921_STATE_MULTIPLE_FRAME_ESTABLISHED, "Multiple Frame Mode Established" },
|
|
{ Q921_STATE_TIMER_RECOVERY, "Timer Recovery" },
|
|
{ 0, 0 }
|
|
};
|
|
|
|
/**
|
|
* Q921State2Name
|
|
* \brief Convert state value to name
|
|
* \param[in] state the state value
|
|
* \return the state name or "Unknown"
|
|
*
|
|
* \author Stefan Knoblich
|
|
*/
|
|
static const char *Q921State2Name(Q921State_t state)
|
|
{
|
|
struct Q921StateName *p = Q921StateNames;
|
|
|
|
while(p->name) {
|
|
if(p->value == state)
|
|
return p->name;
|
|
p++;
|
|
}
|
|
|
|
return "Unknown";
|
|
}
|
|
|
|
|
|
/**
|
|
* Q921SendEnquiry
|
|
*/
|
|
static int Q921SendEnquiry(L2TRUNK trunk, L2UCHAR tei)
|
|
{
|
|
struct Q921_Link *link = Q921_LINK_CONTEXT(trunk, tei);
|
|
|
|
/* send enquiry: begin */
|
|
if(Q921_CHECK_FLAG(link, Q921_FLAG_RECV_BUSY)) {
|
|
|
|
Q921SendRNR(trunk, trunk->sapi, Q921_COMMAND(trunk), tei, 1);
|
|
}
|
|
else {
|
|
Q921SendRR(trunk, trunk->sapi, Q921_COMMAND(trunk), tei, 1);
|
|
}
|
|
|
|
/* clear acknowledge pending */
|
|
Q921_CLEAR_FLAG(link, Q921_FLAG_ACK_PENDING);
|
|
|
|
/* "Start" T200 */
|
|
Q921T200TimerReset(trunk, tei);
|
|
|
|
/* send enquiry: end */
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Q921SendEnquiryResponse
|
|
*/
|
|
static int Q921SendEnquiryResponse(L2TRUNK trunk, L2UCHAR tei)
|
|
{
|
|
struct Q921_Link *link = Q921_LINK_CONTEXT(trunk, tei);
|
|
|
|
/* send enquiry: begin */
|
|
if(Q921_CHECK_FLAG(link, Q921_FLAG_RECV_BUSY)) {
|
|
|
|
Q921SendRNR(trunk, trunk->sapi, Q921_RESPONSE(trunk), tei, 1);
|
|
}
|
|
else {
|
|
Q921SendRR(trunk, trunk->sapi, Q921_RESPONSE(trunk), tei, 1);
|
|
|
|
/* clear acknowledge pending */
|
|
Q921_CLEAR_FLAG(link, Q921_FLAG_ACK_PENDING);
|
|
}
|
|
/* send enquiry: end */
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Q921ResetExceptionConditions
|
|
* \brief Reset Q.921 Exception conditions procedure
|
|
* \param trunk Q.921 data structure
|
|
* \param tei TEI
|
|
* \todo Do something
|
|
*/
|
|
static void Q921ResetExceptionConditions(L2TRUNK trunk, L2UCHAR tei)
|
|
{
|
|
struct Q921_Link *link = Q921_LINK_CONTEXT(trunk, tei);
|
|
|
|
/* Clear peer receiver busy */
|
|
Q921_CLEAR_FLAG(link, Q921_FLAG_PEER_RECV_BUSY);
|
|
|
|
/* Clear reject exception */
|
|
Q921_CLEAR_FLAG(link, Q921_FLAG_REJECT);
|
|
|
|
/* Clear own receiver busy */
|
|
Q921_CLEAR_FLAG(link, Q921_FLAG_RECV_BUSY);
|
|
|
|
/* Clear acknowledge pending */
|
|
Q921_CLEAR_FLAG(link, Q921_FLAG_ACK_PENDING);
|
|
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Q921EstablishDataLink
|
|
* \brief Q.921 Establish data link procedure
|
|
* \param trunk Q.921 data structure
|
|
* \param tei TEI
|
|
* \return always 1 (success)
|
|
*/
|
|
static int Q921EstablishDataLink(L2TRUNK trunk, L2UCHAR tei)
|
|
{
|
|
struct Q921_Link *link = Q921_LINK_CONTEXT(trunk, tei);
|
|
|
|
/* reset exception conditions */
|
|
Q921ResetExceptionConditions(trunk, tei);
|
|
|
|
/* RC = 0 */
|
|
link->N200 = 0;
|
|
|
|
/* Send SABME */
|
|
Q921SendSABME(trunk, trunk->sapi, Q921_COMMAND(trunk), tei, 1);
|
|
|
|
/* Restart T200, stop T203 */
|
|
Q921T200TimerReset(trunk, tei);
|
|
Q921T203TimerStop(trunk, tei);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Q921NrErrorRecovery
|
|
* \brief NR(R) Error recovery procedure
|
|
* \param trunk Q.921 data structure
|
|
* \param tei TEI
|
|
* \return always 1 (success)
|
|
*/
|
|
static int Q921NrErrorRecovery(L2TRUNK trunk, L2UCHAR tei)
|
|
{
|
|
struct Q921_Link *link = Q921_LINK_CONTEXT(trunk, tei);
|
|
|
|
/* MDL Error indication (J) */
|
|
|
|
/* Establish datalink */
|
|
Q921EstablishDataLink(trunk, tei);
|
|
|
|
/* Clear L3 initiated */
|
|
Q921_CLEAR_FLAG(link, Q921_FLAG_L3_INITIATED);
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/**
|
|
* Q921InvokeRetransmission
|
|
* \brief I Frame retransmission procedure
|
|
* \param trunk Q.921 data structure
|
|
* \param tei TEI
|
|
* \param nr N(R) for retransmission
|
|
* \return always 1 (success)
|
|
*/
|
|
static int Q921InvokeRetransmission(L2TRUNK trunk, L2UCHAR tei, L2UCHAR nr)
|
|
{
|
|
struct Q921_Link *link = Q921_LINK_CONTEXT(trunk, tei);
|
|
L2UCHAR *mes;
|
|
L2INT qpos, qnum, size = 0;
|
|
|
|
qnum = MFIFOGetMesCount(link->IFrameResendQueue);
|
|
qpos = qnum - 1;
|
|
|
|
/*
|
|
* slightly different than what is shown in the spec
|
|
* (Q.921 '97 Annex B, Figure B.9, page 104)
|
|
*
|
|
* what the above mentioned figure probably means is:
|
|
* "as long as V(S) != N(R), move the pointer marking
|
|
* the first frame to start resending at to the previous
|
|
* frame"
|
|
*
|
|
* if we actually implemented it as shown in the figure, we'd be
|
|
* resending frames in the wrong order (moving backwards in time)
|
|
* meaning we'd have to add an incoming queue to reorder the frames
|
|
*
|
|
*/
|
|
/*
|
|
* TODO: There's a "traditional" off-by-one error hidden in the original
|
|
* mfifo implementation + it's late, i'm tired and being lazy,
|
|
* so i'll probably have added another one :P
|
|
*
|
|
* wow, the first while loop sucks and can be removed
|
|
*/
|
|
while(link->vs != nr && qpos > 0) { /* ???? */
|
|
/* V(S) = V(S) - 1 */
|
|
Q921_DEC_COUNTER(link->vs); /* huh? backwards? */
|
|
|
|
/* next frame in queue (backtrack along I queue) ??? */
|
|
qpos--;
|
|
}
|
|
|
|
/*
|
|
* being lazy and trying to avoid mod 128 math this way...
|
|
*/
|
|
if(link->vs != nr && !qpos) {
|
|
/* fatal, we don't have enough history to resend all missing frames */
|
|
/* TODO: how to handle this? */
|
|
}
|
|
|
|
/*
|
|
* resend frames in correct order (oldest missing frame first,
|
|
* contrary to what the spec figure shows)
|
|
*/
|
|
while(qpos < qnum) {
|
|
/* Grab frame's buffer ptr and size from queue */
|
|
mes = MFIFOGetMesPtrOffset(link->IFrameResendQueue, &size, qpos);
|
|
if(mes) {
|
|
/* requeue frame (TODO: check queue full condition) */
|
|
MFIFOWriteMes(link->IFrameQueue, mes, size);
|
|
|
|
/* set I frame queued */
|
|
}
|
|
|
|
qpos++;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
static int Q921AcknowledgePending(L2TRUNK trunk, L2UCHAR tei)
|
|
{
|
|
struct Q921_Link *link = Q921_LINK_CONTEXT(trunk, tei);
|
|
|
|
switch(link->state) {
|
|
case Q921_STATE_MULTIPLE_FRAME_ESTABLISHED:
|
|
case Q921_STATE_TIMER_RECOVERY:
|
|
if(Q921_CHECK_FLAG(link, Q921_FLAG_ACK_PENDING)) {
|
|
/* clear acknowledge pending */
|
|
Q921_CLEAR_FLAG(link, Q921_FLAG_ACK_PENDING);
|
|
|
|
/* send RR */
|
|
Q921SendRR(trunk, trunk->sapi, Q921_COMMAND(trunk), tei, 0);
|
|
|
|
return 1;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
|
|
Function: Q921_InitTrunk
|
|
|
|
Decription: Initialize a Q.921 trunk so it is ready for use. This
|
|
function MUST be called before you call any other functions.
|
|
|
|
*****************************************************************************/
|
|
int Q921_InitTrunk(L2TRUNK trunk,
|
|
L2UCHAR sapi,
|
|
L2UCHAR tei,
|
|
Q921NetUser_t NetUser,
|
|
Q921NetType_t NetType,
|
|
L2INT hsize,
|
|
Q921Tx21CB_t cb21,
|
|
Q921Tx23CB_t cb23,
|
|
void *priv21,
|
|
void *priv23)
|
|
{
|
|
int numlinks = 0;
|
|
|
|
trunk->sapi = sapi;
|
|
trunk->tei = tei;
|
|
trunk->NetUser = NetUser;
|
|
trunk->NetType = NetType;
|
|
trunk->Q921Tx21Proc = cb21;
|
|
trunk->Q921Tx23Proc = cb23;
|
|
trunk->PrivateData21 = priv21;
|
|
trunk->PrivateData23 = priv23;
|
|
trunk->Q921HeaderSpace = hsize;
|
|
|
|
numlinks = Q921_IS_PTMP_NT(trunk) ? Q921_TEI_MAX : 1;
|
|
|
|
if (trunk->initialized != INITIALIZED_MAGIC) {
|
|
MFIFOCreate(trunk->HDLCInQueue, Q921MAXHDLCSPACE, 10);
|
|
|
|
/*
|
|
* Allocate space for per-link context(s)
|
|
*/
|
|
trunk->context = ftdm_malloc(numlinks * sizeof(struct Q921_Link));
|
|
if(!trunk->context)
|
|
return -1;
|
|
|
|
trunk->initialized = INITIALIZED_MAGIC;
|
|
}
|
|
|
|
/* timeout default values */
|
|
trunk->T200Timeout = 1000; /* 1 second */
|
|
trunk->T203Timeout = 10000; /* 10 seconds */
|
|
trunk->T202Timeout = 2000; /* 2 seconds */
|
|
trunk->T201Timeout = 200000; /* 200 seconds */
|
|
trunk->TM01Timeout = 10000; /* 10 seconds */
|
|
|
|
/* octet / retransmit counter default limits */
|
|
trunk->N200Limit = 3; /* 3 retransmits */
|
|
trunk->N201Limit = 260; /* 260 octets */
|
|
trunk->N202Limit = 3; /* 3 retransmits */
|
|
trunk->k = 7; /* 7 outstanding ACKs */
|
|
|
|
/* reset counters, timers, etc. */
|
|
trunk->T202 = 0;
|
|
trunk->N202 = 0;
|
|
|
|
/* Reset per-link contexts */
|
|
memset(trunk->context, 0, numlinks * sizeof(struct Q921_Link));
|
|
|
|
/* clear tei map */
|
|
memset(trunk->tei_map, 0, Q921_TEI_MAX + 1);
|
|
|
|
if(Q921_IS_PTMP(trunk)) {
|
|
/*
|
|
* We're either the Network side (NT, TEI = 0)
|
|
* or user-side equipment (TE) which will get it's TEI via
|
|
* dynamic assignment
|
|
*/
|
|
trunk->tei = 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Q921Tx21Proc
|
|
* \brief Submit frame to layer 1 (for sending)
|
|
* \param[in] trunk Pointer to trunk struct
|
|
* \param[in] mes pointer to message buffer
|
|
* \param[in] size size of message
|
|
* \return > 0 on success; <= 0 on error
|
|
*/
|
|
static int Q921Tx21Proc(L2TRUNK trunk, L2UCHAR *Msg, L2INT size)
|
|
{
|
|
Q921LogMesg(trunk, Q921_LOG_DEBUG, 0, Msg, size, "Sending frame");
|
|
|
|
return trunk->Q921Tx21Proc(trunk->PrivateData21, Msg, size);
|
|
}
|
|
|
|
|
|
/**
|
|
* Q921Tx23Proc
|
|
* \brief Submit frame to layer 3
|
|
* \param[in] trunk Pointer to trunk struct
|
|
* \param[in] mes pointer to message buffer
|
|
* \param[in] size size of message
|
|
* \return > 0 on success; <= 0 on error
|
|
*/
|
|
static int Q921Tx23Proc(L2TRUNK trunk, Q921DLMsg_t ind, L2UCHAR tei, L2UCHAR *Msg, L2INT size)
|
|
{
|
|
return trunk->Q921Tx23Proc(trunk->PrivateData23, ind, tei, Msg, size);
|
|
}
|
|
|
|
|
|
/**
|
|
* Q921LogProc
|
|
* \brief Used for logging, converts to string and submits to higher level log function via callback
|
|
* \param[in] trunk Pointer to trunk struct
|
|
* \param[in] level Q921 Loglevel
|
|
* \param[in] fmt format of logmessage
|
|
* \return >= 0 on success, < 0 on error
|
|
*
|
|
* \author Stefan Knoblich
|
|
*/
|
|
static int Q921Log(L2TRUNK trunk, Q921LogLevel_t level, const char *fmt, ...)
|
|
{
|
|
char buf[Q921_LOGBUFSIZE];
|
|
L2INT len;
|
|
va_list ap;
|
|
|
|
if(!trunk->Q921LogProc)
|
|
return 0;
|
|
|
|
if(trunk->loglevel < level)
|
|
return 0;
|
|
|
|
va_start(ap, fmt);
|
|
|
|
len = vsnprintf(buf, sizeof(buf)-1, fmt, ap);
|
|
if(len <= 0) {
|
|
/* TODO: error handling */
|
|
return -1;
|
|
}
|
|
if(len >= sizeof(buf))
|
|
len = sizeof(buf) - 1;
|
|
|
|
buf[len] = '\0';
|
|
|
|
va_end(ap);
|
|
|
|
return trunk->Q921LogProc(trunk->PrivateDataLog, level, buf, len);
|
|
}
|
|
|
|
|
|
static int print_hex(char *buf, int bsize, const unsigned char *in, const int len)
|
|
{
|
|
static const char hex[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
|
|
int offset = 0;
|
|
int pos = 0;
|
|
int nr = 0;
|
|
|
|
buf[pos++] = '[';
|
|
bsize -= 3;
|
|
|
|
while((bsize - pos) > 0 && offset < len) {
|
|
buf[pos++] = hex[(in[offset] & 0xF0) >> 4];
|
|
buf[pos++] = hex[(in[offset++] & 0x0F)];
|
|
|
|
if(++nr == 32 && offset < len && (bsize - pos) > 3) {
|
|
nr = 0;
|
|
buf[pos++] = ']';
|
|
buf[pos++] = '\n';
|
|
buf[pos++] = '[';
|
|
}
|
|
else if(offset < len) {
|
|
buf[pos++] = ' ';
|
|
}
|
|
}
|
|
|
|
buf[pos++] = ']';
|
|
buf[pos++] = '\n';
|
|
buf[pos] = '\0';
|
|
|
|
return pos;
|
|
}
|
|
|
|
#define APPEND_MSG(buf, off, lef, fmt, ...) \
|
|
len = snprintf(buf + off, lef, fmt, ##__VA_ARGS__); \
|
|
if(len > 0) { \
|
|
off += len; \
|
|
lef -= len; \
|
|
} else { \
|
|
goto out; \
|
|
}
|
|
|
|
/**
|
|
* Q921LogProcMesg
|
|
* \brief Used for logging, converts to string and submits to higher level log function via callback
|
|
* \param[in] trunk Pointer to trunk struct
|
|
* \param[in] level Q921 Loglevel
|
|
* \param[in] received direction of the message (received = 1, sending = 0)
|
|
* \param[in] mes pointer to message buffer
|
|
* \param[in] size size of message
|
|
* \param[in] fmt format of logmessage
|
|
* \return >= 0 on success, < 0 on error
|
|
*
|
|
* \author Stefan Knoblich
|
|
*/
|
|
static int Q921LogMesg(L2TRUNK trunk, Q921LogLevel_t level, L2UCHAR received, L2UCHAR *mes, L2INT size, const char *fmt, ...)
|
|
{
|
|
char buf[Q921_LOGBUFSIZE];
|
|
size_t len, left;
|
|
va_list ap;
|
|
|
|
if(!trunk->Q921LogProc)
|
|
return 0;
|
|
|
|
if(trunk->loglevel < level)
|
|
return 0;
|
|
|
|
if(!mes)
|
|
return 0;
|
|
|
|
memset(buf, 0, sizeof(buf));
|
|
|
|
left = sizeof(buf) - 1;
|
|
|
|
va_start(ap, fmt);
|
|
|
|
len = vsnprintf(buf, left, fmt, ap);
|
|
if(len > 0)
|
|
left -= len;
|
|
else {
|
|
/* TODO: error handling */
|
|
return -1;
|
|
}
|
|
|
|
va_end(ap);
|
|
|
|
if(trunk->loglevel == Q921_LOG_DEBUG) {
|
|
char pbuf[1024];
|
|
size_t pleft, poffset;
|
|
L2UCHAR sapi, tei, cr;
|
|
L2UCHAR *pmes = mes + trunk->Q921HeaderSpace;
|
|
struct Q921_Link *link;
|
|
|
|
memset(pbuf, 0, sizeof(pbuf));
|
|
|
|
pleft = sizeof(pbuf);
|
|
poffset = 0;
|
|
|
|
/*
|
|
* Decode packet
|
|
*/
|
|
sapi = (pmes[0] & 0xfc) >> 2;
|
|
cr = (pmes[0] & 0x02) >> 1;
|
|
tei = (pmes[1] & 0xfe) >> 1;
|
|
link = Q921_LINK_CONTEXT(trunk, tei);
|
|
|
|
/* make cr actually useful */
|
|
cr = (received) ? Q921_IS_COMMAND(trunk, cr) : Q921_IS_RESPONSE(trunk, cr);
|
|
|
|
/* filter */
|
|
if((pmes[2] & 0x01) == 0x00) {
|
|
;
|
|
}
|
|
else if((pmes[2] & 0x03) == 0x01) {
|
|
; //return 0;
|
|
}
|
|
else if((pmes[2] & 0x03) == 0x03) {
|
|
;
|
|
}
|
|
|
|
APPEND_MSG(pbuf, poffset, pleft, "\n----------------- Q.921 Packet [%s%s] ---------------\n", received ? "Incoming" : "Outgoing",
|
|
(tei == link->tei || tei == Q921_TEI_BCAST) ? "" : ", Ignored" );
|
|
|
|
/* common header */
|
|
APPEND_MSG(pbuf, poffset, pleft, " SAPI: %u, TEI: %u, C/R: %s (%d)\n\n", sapi, tei, (cr) ? "Command" : "Response", (mes[0] & 0x02) >> 1 );
|
|
|
|
/*
|
|
* message specific
|
|
*/
|
|
if((pmes[2] & 0x01) == 0x00) {
|
|
/*
|
|
* I frame
|
|
*/
|
|
L2UCHAR pf = pmes[3] & 0x01; /* poll / final flag */
|
|
L2UCHAR nr = pmes[3] >> 1; /* receive sequence number */
|
|
L2UCHAR ns = pmes[2] >> 1; /* send sequence number */
|
|
|
|
APPEND_MSG(pbuf, poffset, pleft, " Type: I Frame\n P/F: %d, N(S): %d, N(R): %d [V(A): %d, V(R): %d, V(S): %d]\n", pf, ns, nr,
|
|
link->va, link->vr, link->vs);
|
|
|
|
/* Dump content of I Frames for foreign TEIs */
|
|
if(tei != link->tei) {
|
|
APPEND_MSG(pbuf, poffset, pleft, " CONTENT:\n");
|
|
|
|
len = print_hex(pbuf + poffset, (int)pleft, &pmes[4], size - (trunk->Q921HeaderSpace + 4));
|
|
poffset += len;
|
|
pleft -= len;
|
|
}
|
|
}
|
|
else if((pmes[2] & 0x03) == 0x01) {
|
|
/*
|
|
* S frame
|
|
*/
|
|
L2UCHAR sv = (pmes[2] & 0x0c) >> 2; /* supervisory format id */
|
|
L2UCHAR pf = pmes[3] & 0x01; /* poll / final flag */
|
|
L2UCHAR nr = pmes[3] >> 1; /* receive sequence number */
|
|
const char *type;
|
|
|
|
switch(sv) {
|
|
case 0x00: /* RR : Receive Ready */
|
|
type = "RR (Receive Ready)";
|
|
break;
|
|
|
|
case 0x02: /* RNR : Receive Not Ready */
|
|
type = "RNR (Receiver Not Ready)";
|
|
break;
|
|
|
|
case 0x04: /* REJ : Reject */
|
|
type = "REJ (Reject)";
|
|
break;
|
|
|
|
default: /* Invalid / Unknown */
|
|
type = "Unknown";
|
|
break;
|
|
}
|
|
|
|
APPEND_MSG(pbuf, poffset, pleft, " Type: S Frame, SV: %s\n P/F: %d, N(R): %d [V(A): %d, V(R): %d, V(S): %d]\n", type, pf, nr,
|
|
link->va, link->vr, link->vs);
|
|
}
|
|
else if((pmes[2] & 0x03) == 0x03) {
|
|
/*
|
|
* U frame
|
|
*/
|
|
L2UCHAR m = (pmes[2] & 0xe0) >> 3 | (pmes[2] & 0x0c) >> 2; /* modifier function id */
|
|
L2UCHAR pf = (pmes[2] & 0x10) >> 4; /* poll / final flag */
|
|
const char *type;
|
|
|
|
switch(m) {
|
|
case 0x00:
|
|
type = "UI (Unnumbered Information)";
|
|
break;
|
|
|
|
case 0x03:
|
|
type = "DM (Disconnected Mode)";
|
|
break;
|
|
|
|
case 0x08:
|
|
type = "DISC (Disconnect)";
|
|
break;
|
|
|
|
case 0x0c:
|
|
type = "UA (Unnumbered Acknowledgement)";
|
|
break;
|
|
|
|
case 0x0f:
|
|
type = "SABME";
|
|
break;
|
|
|
|
case 0x11:
|
|
type = "FRMR (Frame Reject)";
|
|
break;
|
|
|
|
case 0x17:
|
|
type = "XID (Exchange Identification)";
|
|
break;
|
|
|
|
default:
|
|
type = "Unknown";
|
|
}
|
|
|
|
|
|
APPEND_MSG(pbuf, poffset, pleft, " Type: U Frame (%s)\n P/F: %d\n", type, pf);
|
|
|
|
if(m == 0x00) {
|
|
switch(pmes[3]) {
|
|
case Q921_LAYER_ENT_ID_TEI:
|
|
type = "TEI Mgmt";
|
|
break;
|
|
|
|
case Q921_LAYER_ENT_ID_Q931:
|
|
type = "Q.931";
|
|
break;
|
|
|
|
default:
|
|
type = "Unknown";
|
|
}
|
|
|
|
if(pmes[3] == Q921_LAYER_ENT_ID_TEI) {
|
|
const char *command = "";
|
|
|
|
switch(pmes[6]) {
|
|
case Q921_TEI_ID_REQUEST:
|
|
command = "Request";
|
|
break;
|
|
case Q921_TEI_ID_VERIFY:
|
|
command = "Verify";
|
|
break;
|
|
case Q921_TEI_ID_CHECKREQ:
|
|
command = "Check req";
|
|
break;
|
|
case Q921_TEI_ID_CHECKRESP:
|
|
command = "Check resp";
|
|
break;
|
|
case Q921_TEI_ID_REMOVE:
|
|
command = "Remove";
|
|
break;
|
|
case Q921_TEI_ID_ASSIGNED:
|
|
command = "Assign";
|
|
break;
|
|
case Q921_TEI_ID_DENIED:
|
|
command = "Denied";
|
|
break;
|
|
}
|
|
APPEND_MSG(pbuf, poffset, pleft, " ENT ID: %d (%s), COMMAND: %d (%s), RI: %#x, AI: %d\n",
|
|
pmes[3], type, pmes[6], command, (int)((pmes[4] << 8) | pmes[5]), pmes[7] >> 1);
|
|
}
|
|
else {
|
|
APPEND_MSG(pbuf, poffset, pleft, " ENT ID: %d (%s), MESSAGE CONTENT:\n", pmes[3], type);
|
|
|
|
len = print_hex(pbuf + poffset, (int)pleft, &pmes[3], size - (trunk->Q921HeaderSpace + 3));
|
|
poffset += len;
|
|
pleft -= len;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
/*
|
|
* Unknown
|
|
*/
|
|
strncat(pbuf + poffset, " -- unknown frame type --\n", pleft);
|
|
|
|
len = (sizeof(pbuf) - poffset) - strlen(pbuf + poffset);
|
|
if(len > 0) {
|
|
poffset += len;
|
|
pleft -= len;
|
|
} else
|
|
goto out;
|
|
}
|
|
|
|
APPEND_MSG(pbuf, poffset, pleft, "\n Q.921 state: \"%s\" (%d) [flags: %c%c%c%c]\n", Q921State2Name(link->state), link->state,
|
|
Q921_CHECK_FLAG(link, Q921_FLAG_ACK_PENDING) ? 'A' : '-',
|
|
Q921_CHECK_FLAG(link, Q921_FLAG_REJECT) ? 'R' : '-',
|
|
Q921_CHECK_FLAG(link, Q921_FLAG_PEER_RECV_BUSY) ? 'P' : '-',
|
|
Q921_CHECK_FLAG(link, Q921_FLAG_RECV_BUSY) ? 'B' : '-');
|
|
|
|
strncat(pbuf + poffset, "----------------------------------------------\n\n", pleft);
|
|
|
|
len = (sizeof(pbuf) - poffset) - strlen(pbuf + poffset);
|
|
if(len > 0) {
|
|
poffset += len;
|
|
pleft -= len;
|
|
} else
|
|
goto out;
|
|
|
|
|
|
/* concat buffers together */
|
|
len = strlen(pbuf);
|
|
if(len <= left)
|
|
strncat(buf, pbuf, left);
|
|
else
|
|
strncat(buf, "-- packet truncated --\n", left);
|
|
}
|
|
|
|
out:
|
|
buf[sizeof(buf) - 1] = '\0';
|
|
|
|
return trunk->Q921LogProc(trunk->PrivateDataLog, level, buf, (int)strlen(buf));
|
|
}
|
|
|
|
/*****************************************************************************
|
|
|
|
Function: Q921TimeTick
|
|
|
|
Description: Called periodically from an external source to allow the
|
|
stack to process and maintain it's own timers.
|
|
|
|
Return Value: none
|
|
|
|
*****************************************************************************/
|
|
static L2ULONG (*Q921GetTimeProc) (void) = NULL; /* callback for func reading time in ms */
|
|
static L2ULONG tLast = {0};
|
|
|
|
static L2ULONG Q921GetTime(void)
|
|
{
|
|
L2ULONG tNow = 0;
|
|
|
|
if(Q921GetTimeProc)
|
|
{
|
|
tNow = Q921GetTimeProc();
|
|
if(tNow < tLast) /* wrapped */
|
|
{
|
|
/* TODO */
|
|
}
|
|
tLast = tNow;
|
|
}
|
|
return tNow;
|
|
}
|
|
|
|
/*
|
|
* T200 handling (per-TEI in PTMP NT mode, tei=0 otherwise)
|
|
*/
|
|
static void Q921T200TimerStart(L2TRUNK trunk, L2UCHAR tei)
|
|
{
|
|
struct Q921_Link *link = Q921_LINK_CONTEXT(trunk, tei);
|
|
|
|
if (!link->T200) {
|
|
link->T200 = Q921GetTime() + trunk->T200Timeout;
|
|
|
|
Q921Log(trunk, Q921_LOG_DEBUG, "T200 (timeout: %d msecs) started for TEI %d\n", trunk->T200Timeout, tei);
|
|
}
|
|
}
|
|
|
|
static void Q921T200TimerStop(L2TRUNK trunk, L2UCHAR tei)
|
|
{
|
|
struct Q921_Link *link = Q921_LINK_CONTEXT(trunk, tei);
|
|
|
|
link->T200 = 0;
|
|
|
|
Q921Log(trunk, Q921_LOG_DEBUG, "T200 stopped for TEI %d\n", tei);
|
|
}
|
|
|
|
static void Q921T200TimerReset(L2TRUNK trunk, L2UCHAR tei)
|
|
{
|
|
struct Q921_Link *link = Q921_LINK_CONTEXT(trunk, tei);
|
|
|
|
link->T200 = Q921GetTime() + trunk->T200Timeout;
|
|
|
|
Q921Log(trunk, Q921_LOG_DEBUG, "T200 (timeout: %d msecs) restarted for TEI %d\n", trunk->T200Timeout, tei);
|
|
}
|
|
|
|
/*
|
|
* T203 handling (per-TEI in PTMP NT mode, tei=0 otherwise)
|
|
*/
|
|
static void Q921T203TimerStart(L2TRUNK trunk, L2UCHAR tei)
|
|
{
|
|
struct Q921_Link *link = Q921_LINK_CONTEXT(trunk, tei);
|
|
|
|
if (!link->T203) {
|
|
link->T203 = Q921GetTime() + trunk->T203Timeout;
|
|
|
|
Q921Log(trunk, Q921_LOG_DEBUG, "T203 (timeout: %d msecs) started for TEI %d\n", trunk->T203Timeout, tei);
|
|
}
|
|
}
|
|
|
|
static void Q921T203TimerStop(L2TRUNK trunk, L2UCHAR tei)
|
|
{
|
|
struct Q921_Link *link = Q921_LINK_CONTEXT(trunk, tei);
|
|
|
|
link->T203 = 0;
|
|
|
|
Q921Log(trunk, Q921_LOG_DEBUG, "T203 stopped for TEI %d\n", tei);
|
|
}
|
|
|
|
static void Q921T203TimerReset(L2TRUNK trunk, L2UCHAR tei)
|
|
{
|
|
struct Q921_Link *link = Q921_LINK_CONTEXT(trunk, tei);
|
|
|
|
link->T203 = Q921GetTime() + trunk->T203Timeout;
|
|
|
|
Q921Log(trunk, Q921_LOG_DEBUG, "T203 (timeout: %d msecs) restarted for TEI %d\n", trunk->T203Timeout, tei);
|
|
}
|
|
|
|
/*
|
|
* T202 handling (TEI message timeout, TE mode only)
|
|
*/
|
|
static void Q921T202TimerStart(L2TRUNK trunk)
|
|
{
|
|
if (!trunk->T202) {
|
|
trunk->T202 = Q921GetTime() + trunk->T202Timeout;
|
|
|
|
Q921Log(trunk, Q921_LOG_DEBUG, "T202 (timeout: %d msecs) started\n", trunk->T202Timeout);
|
|
}
|
|
}
|
|
|
|
static void Q921T202TimerStop(L2TRUNK trunk)
|
|
{
|
|
trunk->T202 = 0;
|
|
|
|
Q921Log(trunk, Q921_LOG_DEBUG, "T202 stopped\n");
|
|
}
|
|
|
|
static void Q921T202TimerReset(L2TRUNK trunk)
|
|
{
|
|
trunk->T202 = Q921GetTime() + trunk->T202Timeout;
|
|
|
|
Q921Log(trunk, Q921_LOG_DEBUG, "T202 (timeout: %d msecs) restarted\n", trunk->T202Timeout);
|
|
}
|
|
|
|
/*
|
|
* T201 handling (TEI management (NT side), per-TEI)
|
|
*/
|
|
static void Q921T201TimerStart(L2TRUNK trunk, L2UCHAR tei)
|
|
{
|
|
struct Q921_Link *link = Q921_LINK_CONTEXT(trunk, tei);
|
|
|
|
if (!link->T201) {
|
|
link->T201 = Q921GetTime() + trunk->T201Timeout;
|
|
|
|
Q921Log(trunk, Q921_LOG_DEBUG, "T201 (timeout: %d msecs) started for TEI %d\n", trunk->T201Timeout, tei);
|
|
}
|
|
}
|
|
|
|
static void Q921T201TimerStop(L2TRUNK trunk, L2UCHAR tei)
|
|
{
|
|
struct Q921_Link *link = Q921_LINK_CONTEXT(trunk, tei);
|
|
|
|
link->T201 = 0;
|
|
|
|
Q921Log(trunk, Q921_LOG_DEBUG, "T201 stopped for TEI %d\n", tei);
|
|
}
|
|
|
|
#ifdef __UNUSED_FOR_NOW__
|
|
static void Q921T201TimerReset(L2TRUNK trunk, L2UCHAR tei)
|
|
{
|
|
struct Q921_Link *link = Q921_LINK_CONTEXT(trunk, tei);
|
|
|
|
link->T201 = Q921GetTime() + trunk->T201Timeout;
|
|
|
|
Q921Log(trunk, Q921_LOG_DEBUG, "T201 (timeout: %d msecs) restarted for TEI %d\n", trunk->T201Timeout, tei);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* TM01 handling (Datalink inactivity shutdown timer)
|
|
*/
|
|
static void Q921TM01TimerStart(L2TRUNK trunk, L2UCHAR tei)
|
|
{
|
|
struct Q921_Link *link = Q921_LINK_CONTEXT(trunk, tei);
|
|
|
|
if (!link->TM01) {
|
|
link->TM01 = Q921GetTime() + trunk->TM01Timeout;
|
|
|
|
Q921Log(trunk, Q921_LOG_DEBUG, "TM01 (timeout: %d msecs) started for TEI %d\n", trunk->TM01Timeout, tei);
|
|
}
|
|
}
|
|
|
|
#ifdef __UNUSED_FOR_NOW__
|
|
static void Q921TM01TimerStop(L2TRUNK trunk, L2UCHAR tei)
|
|
{
|
|
struct Q921_Link *link = Q921_LINK_CONTEXT(trunk, tei);
|
|
|
|
link->TM01 = 0;
|
|
|
|
Q921Log(trunk, Q921_LOG_DEBUG, "TM01 stopped for TEI %d\n", tei);
|
|
}
|
|
#endif
|
|
|
|
static void Q921TM01TimerReset(L2TRUNK trunk, L2UCHAR tei)
|
|
{
|
|
struct Q921_Link *link = Q921_LINK_CONTEXT(trunk, tei);
|
|
|
|
link->TM01 = Q921GetTime() + trunk->TM01Timeout;
|
|
|
|
Q921Log(trunk, Q921_LOG_DEBUG, "TM01 (timeout: %d msecs) restarted for TEI %d\n", trunk->TM01Timeout, tei);
|
|
}
|
|
|
|
/*
|
|
* Expiry callbacks
|
|
*/
|
|
static void Q921T200TimerExpire(L2TRUNK trunk, L2UCHAR tei)
|
|
{
|
|
struct Q921_Link *link = Q921_LINK_CONTEXT(trunk, tei);
|
|
struct Q921_Link *trlink = Q921_TRUNK_CONTEXT(trunk);
|
|
|
|
Q921Log(trunk, Q921_LOG_DEBUG, "T200 expired for TEI %d (trunk TEI %d)\n", tei, trlink->tei);
|
|
|
|
/* Stop timer first */
|
|
Q921T200TimerStop(trunk, tei);
|
|
|
|
switch(link->state) {
|
|
case Q921_STATE_AWAITING_ESTABLISHMENT:
|
|
if(link->N200 >= trunk->N200Limit) {
|
|
/* Discard I queue */
|
|
MFIFOClear(link->IFrameQueue);
|
|
|
|
/* MDL-Error indication (G) */
|
|
Q921Log(trunk, Q921_LOG_ERROR, "Failed to establish Q.921 link in %d retries\n", link->N200);
|
|
|
|
/* DL-Release indication */
|
|
Q921Tx23Proc(trunk, Q921_DL_RELEASE, tei, NULL, 0);
|
|
|
|
/* change state (no action) */
|
|
Q921ChangeState(trunk, Q921_STATE_TEI_ASSIGNED, tei);
|
|
} else {
|
|
/* Increment retry counter */
|
|
link->N200++;
|
|
|
|
/* Send SABME */
|
|
Q921SendSABME(trunk,
|
|
trunk->sapi,
|
|
Q921_COMMAND(trunk),
|
|
tei,
|
|
1);
|
|
|
|
/* Start T200 */
|
|
Q921T200TimerStart(trunk, tei);
|
|
}
|
|
break;
|
|
|
|
case Q921_STATE_MULTIPLE_FRAME_ESTABLISHED:
|
|
link->N200 = 0;
|
|
|
|
if(!Q921_CHECK_FLAG(link, Q921_FLAG_PEER_RECV_BUSY)) {
|
|
/* get last transmitted I frame */
|
|
|
|
/* V(S) = V(S) - 1 */
|
|
Q921_DEC_COUNTER(link->vs);
|
|
|
|
/* retransmit I frame */
|
|
|
|
/* V(S) = V(S) + 1 (done by Q921SendI() ) */
|
|
//Q921_INC_COUNTER(link->vs);
|
|
|
|
/* clear acknowledge pending */
|
|
Q921_CLEAR_FLAG(link, Q921_FLAG_ACK_PENDING);
|
|
|
|
/* Start T200 */
|
|
Q921T200TimerStart(trunk, tei);
|
|
} else {
|
|
/* transmit enquiry */
|
|
Q921SendEnquiry(trunk, tei);
|
|
}
|
|
|
|
/* increment counter */
|
|
link->N200++;
|
|
|
|
/* change state (no action) */
|
|
Q921ChangeState(trunk, Q921_STATE_TIMER_RECOVERY, tei);
|
|
break;
|
|
|
|
case Q921_STATE_TIMER_RECOVERY:
|
|
if(link->N200 == trunk->N200Limit) {
|
|
/* MDL Error indication (I) */
|
|
|
|
/* Establish data link */
|
|
Q921EstablishDataLink(trunk, tei);
|
|
|
|
/* Clear L3 initiated */
|
|
Q921_CLEAR_FLAG(link, Q921_FLAG_L3_INITIATED);
|
|
|
|
/* change state (no action) */
|
|
Q921ChangeState(trunk, Q921_STATE_AWAITING_ESTABLISHMENT, tei);
|
|
} else {
|
|
if(link->vs == link->va) {
|
|
/* transmit enquiry */
|
|
Q921SendEnquiry(trunk, tei);
|
|
|
|
} else if(!Q921_CHECK_FLAG(link, Q921_FLAG_PEER_RECV_BUSY)) {
|
|
/* get last transmitted frame */
|
|
|
|
/* V(S) = V(S) - 1 */
|
|
Q921_DEC_COUNTER(link->vs);
|
|
|
|
/* retrans frame */
|
|
|
|
/* V(S) = V(S) + 1 (done by Q921SendI() ) */
|
|
//Q921_INC_COUNTER(link->vs);
|
|
|
|
/* clear acknowledge pending */
|
|
Q921_CLEAR_FLAG(link, Q921_FLAG_ACK_PENDING);
|
|
|
|
/* Start T200 */
|
|
Q921T200TimerStart(trunk, tei);
|
|
}
|
|
|
|
/* increment counter */
|
|
link->N200++;
|
|
|
|
/* no state change */
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void Q921T203TimerExpire(L2TRUNK trunk, L2UCHAR tei)
|
|
{
|
|
struct Q921_Link *link = Q921_LINK_CONTEXT(trunk, tei);
|
|
struct Q921_Link *trlink = Q921_TRUNK_CONTEXT(trunk);
|
|
|
|
Q921Log(trunk, Q921_LOG_DEBUG, "T203 expired for TEI %d (trunk TEI %d)\n", tei, trlink->tei);
|
|
|
|
/* Stop Timer first */
|
|
Q921T203TimerStop(trunk, tei);
|
|
|
|
switch(link->state) {
|
|
case Q921_STATE_MULTIPLE_FRAME_ESTABLISHED:
|
|
/* Send Enquiry */
|
|
Q921SendEnquiry(trunk, tei);
|
|
|
|
/* RC = 0 */
|
|
link->N200 = 0;
|
|
|
|
/* no state change */
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void Q921T202TimerExpire(L2TRUNK trunk)
|
|
{
|
|
struct Q921_Link *link = Q921_TRUNK_CONTEXT(trunk);
|
|
|
|
Q921T202TimerReset(trunk);
|
|
|
|
Q921Log(trunk, Q921_LOG_DEBUG, "T202 expired for Q.921 trunk with TEI %d\n", link->tei);
|
|
|
|
/* todo: implement resend counter */
|
|
|
|
switch(link->state) {
|
|
case Q921_STATE_TEI_ASSIGNED: /* Tei identity verify timeout */
|
|
Q921TeiSendVerifyRequest(trunk);
|
|
break;
|
|
|
|
default: /* Tei assignment request timeout (TODO: refine) */
|
|
|
|
if(trunk->N202 >= trunk->N202Limit) {
|
|
/* Too many retransmits, reset counter, stop timer and handle case (TODO) */
|
|
trunk->N202 = 0;
|
|
|
|
Q921T202TimerStop(trunk);
|
|
|
|
return;
|
|
}
|
|
Q921TeiSendAssignRequest(trunk);
|
|
|
|
trunk->N202++;
|
|
}
|
|
}
|
|
|
|
static void Q921T201TimerExpire(L2TRUNK trunk, L2UCHAR tei)
|
|
{
|
|
struct Q921_Link *link = Q921_LINK_CONTEXT(trunk, tei);
|
|
struct Q921_Link *trlink = Q921_TRUNK_CONTEXT(trunk);
|
|
|
|
Q921Log(trunk, Q921_LOG_DEBUG, "T201 expired for TEI %d (trunk TEI: %d)\n", tei, trlink->tei);
|
|
|
|
Q921T201TimerStop(trunk, tei);
|
|
|
|
/* NOTE: abusing N202 for this */
|
|
if(link->N202 < trunk->N202Limit) {
|
|
/* send check request */
|
|
Q921TeiSendCheckRequest(trunk, tei);
|
|
|
|
/* increment counter */
|
|
link->N202++;
|
|
} else {
|
|
/* put context in STOPPED state */
|
|
Q921ChangeState(trunk, Q921_STATE_STOPPED, tei);
|
|
|
|
/* NOTE: should we clear the link too? */
|
|
memset(link, 0, sizeof(struct Q921_Link));
|
|
|
|
/* mark TEI free */
|
|
trunk->tei_map[tei] = 0;
|
|
}
|
|
}
|
|
|
|
#ifdef __UNUSED_FOR_NOW__
|
|
static void Q921TM01TimerExpire(L2TRUNK trunk, L2UCHAR tei)
|
|
{
|
|
struct Q921_Link *link = Q921_LINK_CONTEXT(trunk, tei);
|
|
struct Q921_Link *trlink = Q921_TRUNK_CONTEXT(trunk);
|
|
|
|
Q921Log(trunk, Q921_LOG_DEBUG, "TM01 expired for TEI %d (trunk TEI: %d)\n", tei, trlink->tei);
|
|
|
|
/* Restart TM01 */
|
|
Q921TM01TimerReset(trunk, tei);
|
|
|
|
switch(link->state) {
|
|
case Q921_STATE_MULTIPLE_FRAME_ESTABLISHED:
|
|
case Q921_STATE_TIMER_RECOVERY:
|
|
/*
|
|
* NT-only, needs more support from L3
|
|
*/
|
|
#if 0
|
|
/* No activity, shutdown link */
|
|
Q921SendDISC(trunk, trunk->sapi, Q921_COMMAND(trunk), tei, 1);
|
|
|
|
/* clear I queue */
|
|
MFIFOClear(link->IFrameQueue);
|
|
|
|
/* change state */
|
|
Q921ChangeState(trunk, Q921_STATE_AWAITING_RELEASE, tei);
|
|
#endif
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Timer Tick function
|
|
*/
|
|
void Q921TimerTick(L2TRUNK trunk)
|
|
{
|
|
struct Q921_Link *link;
|
|
L2ULONG tNow = Q921GetTime();
|
|
int numlinks = Q921_IS_PTMP_NT(trunk) ? Q921_TEI_MAX : 1;
|
|
int x;
|
|
|
|
for(x = 0; x <= numlinks; x++) {
|
|
link = Q921_LINK_CONTEXT(trunk, x);
|
|
|
|
/* TODO: check if TEI is assigned and skip check if not (speedup!) */
|
|
if(link->state == Q921_STATE_STOPPED)
|
|
continue;
|
|
|
|
if (link->T200 && tNow > link->T200) {
|
|
Q921T200TimerExpire(trunk, link->tei);
|
|
}
|
|
if (link->T203 && tNow > link->T203) {
|
|
Q921T203TimerExpire(trunk, link->tei);
|
|
}
|
|
|
|
if(Q921_IS_PTMP_NT(trunk) && link->tei) {
|
|
if (link->T201 && tNow > link->T201) {
|
|
Q921T201TimerExpire(trunk, link->tei);
|
|
}
|
|
}
|
|
|
|
if(!Q921_IS_PTMP_NT(trunk)) {
|
|
if (trunk->T202 && tNow > trunk->T202) {
|
|
Q921T202TimerExpire(trunk);
|
|
}
|
|
}
|
|
|
|
/* Send enqueued I frame, if available */
|
|
Q921SendQueuedIFrame(trunk, link->tei);
|
|
|
|
/* Send ack if pending */
|
|
Q921AcknowledgePending(trunk, link->tei);
|
|
}
|
|
|
|
}
|
|
|
|
void Q921SetGetTimeCB(L2ULONG (*callback)(void))
|
|
{
|
|
Q921GetTimeProc = callback;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
|
|
Function: Q921QueueHDLCFrame
|
|
|
|
Description: Called to receive and queue an incoming HDLC frame. Will
|
|
queue this in Q921HDLCInQueue. The called must either call
|
|
Q921Rx12 directly afterwards or signal Q921Rx12 to be called
|
|
later. Q921Rx12 will read from the same queue and process
|
|
the frame.
|
|
|
|
This function assumes that the message contains header
|
|
space. This is removed for internal Q921 processing, but
|
|
must be keept for I frames.
|
|
|
|
Parameters: trunk trunk #
|
|
b ptr to frame;
|
|
size size of frame in bytes
|
|
|
|
*****************************************************************************/
|
|
int Q921QueueHDLCFrame(L2TRUNK trunk, L2UCHAR *b, L2INT size)
|
|
{
|
|
return MFIFOWriteMes(trunk->HDLCInQueue, b, size);
|
|
}
|
|
|
|
/**
|
|
* Q921EnqueueI
|
|
* \brief Put I frame into transmit queue
|
|
*
|
|
*/
|
|
static int Q921EnqueueI(L2TRUNK trunk, L2UCHAR Sapi, char cr, L2UCHAR Tei, char pf, L2UCHAR *mes, L2INT size)
|
|
{
|
|
struct Q921_Link *link = Q921_LINK_CONTEXT(trunk, Tei);
|
|
|
|
/* I frame header */
|
|
mes[trunk->Q921HeaderSpace+0] = ((Sapi << 2) & 0xfc) | ((cr << 1) & 0x02);
|
|
mes[trunk->Q921HeaderSpace+1] = (Tei << 1) | 0x01;
|
|
mes[trunk->Q921HeaderSpace+2] = 0x00;
|
|
mes[trunk->Q921HeaderSpace+3] = (pf & 0x01);
|
|
|
|
Q921Log(trunk, Q921_LOG_DEBUG, "Enqueueing I frame for TEI %d [%d]\n", link->tei, Tei);
|
|
|
|
/* transmit queue, (TODO: check for full condition!) */
|
|
MFIFOWriteMes(link->IFrameQueue, mes, size);
|
|
|
|
/* try to send queued frame */
|
|
Q921SendQueuedIFrame(trunk, link->tei);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Q921SendQueuedIFrame
|
|
* \brief Try to transmit queued I frame (if available)
|
|
*/
|
|
static int Q921SendQueuedIFrame(L2TRUNK trunk, L2UCHAR tei)
|
|
{
|
|
struct Q921_Link *link = Q921_LINK_CONTEXT(trunk, tei);
|
|
|
|
L2INT size = 0;
|
|
L2UCHAR *mes;
|
|
|
|
if(MFIFOGetMesCount(link->IFrameQueue) == 0) {
|
|
return 0;
|
|
}
|
|
|
|
/* Link ready? */
|
|
if(link->state != Q921_STATE_MULTIPLE_FRAME_ESTABLISHED) {
|
|
return 0;
|
|
}
|
|
|
|
/* peer receiver busy? */
|
|
if(Q921_CHECK_FLAG(link, Q921_FLAG_PEER_RECV_BUSY)) {
|
|
return 0;
|
|
}
|
|
|
|
/* V(S) = V(A) + k? */
|
|
if(link->vs == ((link->va + trunk->k) % 128)) {
|
|
Q921Log(trunk, Q921_LOG_WARNING, "Maximum number (%d) of outstanding I frames reached for TEI %d\n", trunk->k, tei);
|
|
return 0;
|
|
}
|
|
|
|
mes = MFIFOGetMesPtr(link->IFrameQueue, &size);
|
|
if(mes) {
|
|
/* Fill in + update counter values */
|
|
mes[trunk->Q921HeaderSpace+2] = link->vs << 1;
|
|
mes[trunk->Q921HeaderSpace+3] |= link->vr << 1;
|
|
|
|
if(MFIFOGetMesCount(link->IFrameQueue) == 0) {
|
|
/* clear I frame queued */
|
|
}
|
|
|
|
/* Send I frame */
|
|
Q921Tx21Proc(trunk, mes, size);
|
|
|
|
/* V(S) = V(S) + 1 */
|
|
Q921_INC_COUNTER(link->vs);
|
|
|
|
/* clear acknowledge pending */
|
|
Q921_CLEAR_FLAG(link, Q921_FLAG_ACK_PENDING);
|
|
|
|
/* T200 running? */
|
|
if(!link->T200) {
|
|
/* Stop T203, Start T200 */
|
|
Q921T200TimerStart(trunk, tei);
|
|
Q921T203TimerStop(trunk, tei);
|
|
}
|
|
|
|
/* put frame into resend queue */
|
|
MFIFOWriteMesOverwrite(link->IFrameResendQueue, mes, size);
|
|
|
|
/* dequeue frame */
|
|
MFIFOKillNext(link->IFrameQueue);
|
|
|
|
/* Restart TM01 */
|
|
if(Q921_IS_NT(trunk)) {
|
|
Q921TM01TimerReset(trunk, tei);
|
|
}
|
|
|
|
/* no state change */
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Q921SendS
|
|
* \brief Prepare and send S frame
|
|
*/
|
|
static int Q921SendS(L2TRUNK trunk, L2UCHAR Sapi, char cr, L2UCHAR Tei, char pf, L2UCHAR sv, L2UCHAR *mes, L2INT size)
|
|
{
|
|
struct Q921_Link *link = Q921_LINK_CONTEXT(trunk, Tei);
|
|
|
|
if(!Q921_IS_READY(link)) {
|
|
/* don't even bother trying */
|
|
Q921Log(trunk, Q921_LOG_DEBUG, "Link not ready, discarding S frame for TEI %d\n", Tei);
|
|
return 0;
|
|
}
|
|
|
|
/* S frame header */
|
|
mes[trunk->Q921HeaderSpace+0] = ((Sapi << 2) & 0xfc) | ((cr << 1) & 0x02);
|
|
mes[trunk->Q921HeaderSpace+1] = (Tei << 1) | 0x01;
|
|
mes[trunk->Q921HeaderSpace+2] = ((sv << 2) & 0x0c) | 0x01;
|
|
mes[trunk->Q921HeaderSpace+3] = (link->vr << 1) | (pf & 0x01);
|
|
|
|
return Q921Tx21Proc(trunk, mes, size);
|
|
}
|
|
|
|
|
|
/**
|
|
* Q921SendU
|
|
* \brief Prepare and send U frame
|
|
*/
|
|
static int Q921SendU(L2TRUNK trunk, L2UCHAR Sapi, char cr, L2UCHAR Tei, char pf, L2UCHAR m, L2UCHAR *mes, L2INT size)
|
|
{
|
|
struct Q921_Link *link = Q921_LINK_CONTEXT(trunk, Tei);
|
|
|
|
/* U frame header */
|
|
mes[trunk->Q921HeaderSpace+0] = ((Sapi << 2) & 0xfc) | ((cr << 1) & 0x02);
|
|
mes[trunk->Q921HeaderSpace+1] = (Tei << 1) | 0x01;
|
|
mes[trunk->Q921HeaderSpace+2] = ((m << 3) & 0xe0) | ((pf << 4) & 0x10) | ((m << 2) & 0x0c) | 0x03;
|
|
|
|
/* link not ready? enqueue non-TEI-mgmt UI (DL-UNIT DATA) frames */
|
|
if(m == 0x00 && Sapi != Q921_SAPI_TEI && link->state < Q921_STATE_TEI_ASSIGNED) {
|
|
|
|
/* write frame to queue */
|
|
MFIFOWriteMes(link->UIFrameQueue, mes, size);
|
|
|
|
Q921Log(trunk, Q921_LOG_DEBUG, "Link not ready, UI Frame of size %d bytes queued for TEI %d\n", size, Tei);
|
|
return 1;
|
|
}
|
|
|
|
return Q921Tx21Proc(trunk, mes, size);
|
|
}
|
|
|
|
/**
|
|
* TODO: NT mode handling? Need a way to get Link context from Q.931
|
|
*/
|
|
int Q921Rx32(L2TRUNK trunk, Q921DLMsg_t ind, L2UCHAR tei, L2UCHAR * Mes, L2INT Size)
|
|
{
|
|
struct Q921_Link *link = Q921_LINK_CONTEXT(trunk, tei); /* TODO: need real link tei for NT mode */
|
|
L2INT res = 0;
|
|
|
|
Q921Log(trunk, Q921_LOG_DEBUG, "Got frame from Q.931, type: %d, tei: %d, size: %d\n", ind, tei, Size);
|
|
|
|
switch(ind) {
|
|
case Q921_DL_ESTABLISH:
|
|
/*
|
|
* Hmm...
|
|
*/
|
|
switch(link->state) {
|
|
case Q921_STATE_TEI_ASSIGNED:
|
|
if(!Q921_IS_NT(trunk)) {
|
|
/* establish data link */
|
|
Q921EstablishDataLink(trunk, link->tei);
|
|
|
|
/* Set layer 3 initiated */
|
|
Q921_SET_FLAG(link, Q921_FLAG_L3_INITIATED);
|
|
|
|
/* change state (no action) */
|
|
Q921ChangeState(trunk, Q921_STATE_AWAITING_ESTABLISHMENT, link->tei);
|
|
}
|
|
break;
|
|
|
|
case Q921_STATE_AWAITING_ESTABLISHMENT:
|
|
if(!Q921_IS_NT(trunk)) {
|
|
/* Discard I queue */
|
|
MFIFOClear(link->IFrameQueue);
|
|
|
|
/* Set layer 3 initiated */
|
|
Q921_SET_FLAG(link, Q921_FLAG_L3_INITIATED);
|
|
}
|
|
break;
|
|
|
|
case Q921_STATE_MULTIPLE_FRAME_ESTABLISHED:
|
|
case Q921_STATE_TIMER_RECOVERY:
|
|
if(!Q921_IS_NT(trunk)) {
|
|
/* Discard I queue */
|
|
MFIFOClear(link->IFrameQueue);
|
|
|
|
/* establish data link */
|
|
Q921EstablishDataLink(trunk, link->tei);
|
|
|
|
/* Set layer 3 initiated */
|
|
Q921_SET_FLAG(link, Q921_FLAG_L3_INITIATED);
|
|
|
|
/* change state (no action) */
|
|
Q921ChangeState(trunk, Q921_STATE_AWAITING_ESTABLISHMENT, link->tei);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case Q921_DL_RELEASE:
|
|
switch(link->state) {
|
|
case Q921_STATE_TEI_ASSIGNED:
|
|
/* send DL-RELEASE confirm */
|
|
Q921Tx23Proc(trunk, Q921_DL_RELEASE, tei, NULL, 0);
|
|
break;
|
|
|
|
case Q921_STATE_MULTIPLE_FRAME_ESTABLISHED:
|
|
case Q921_STATE_TIMER_RECOVERY:
|
|
if(!Q921_IS_NT(trunk)) {
|
|
/* Discard I queue */
|
|
MFIFOClear(link->IFrameQueue);
|
|
|
|
/* RC = 0 */
|
|
link->N200 = 0;
|
|
|
|
/* send DISC command */
|
|
Q921SendDISC(trunk, trunk->sapi, Q921_COMMAND(trunk), link->tei, 1);
|
|
|
|
/* Stop T203, restart T200 */
|
|
if(link->state == Q921_STATE_MULTIPLE_FRAME_ESTABLISHED) {
|
|
Q921T203TimerStop(trunk, link->tei);
|
|
}
|
|
Q921T200TimerReset(trunk, link->tei);
|
|
|
|
/* change state */
|
|
Q921ChangeState(trunk, Q921_STATE_AWAITING_RELEASE, link->tei);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case Q921_DL_DATA: /* DL-DATA request */
|
|
res = Q921EnqueueI(trunk,
|
|
trunk->sapi,
|
|
Q921_COMMAND(trunk),
|
|
link->tei,
|
|
0,
|
|
Mes,
|
|
Size);
|
|
|
|
if(link->state < Q921_STATE_MULTIPLE_FRAME_ESTABLISHED) {
|
|
/* Treat as implicit DL-ESTABLISH request */
|
|
|
|
/* establish data link */
|
|
Q921EstablishDataLink(trunk, link->tei);
|
|
|
|
/* Set layer 3 initiated */
|
|
Q921_SET_FLAG(link, Q921_FLAG_L3_INITIATED);
|
|
|
|
/* change state (no action) */
|
|
Q921ChangeState(trunk, Q921_STATE_AWAITING_ESTABLISHMENT, link->tei);
|
|
}
|
|
break;
|
|
|
|
case Q921_DL_UNIT_DATA: /* DL-UNIT DATA request */
|
|
res = Q921SendUN(trunk,
|
|
trunk->sapi,
|
|
Q921_COMMAND(trunk),
|
|
Q921_TEI_BCAST,
|
|
0,
|
|
Mes,
|
|
Size);
|
|
/* NOTE: Let the other side initiate link establishment */
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
/*****************************************************************************
|
|
|
|
Function: Q921SendRR
|
|
|
|
Description: Compose and send Receive Ready.
|
|
|
|
Parameters: trunk trunk #
|
|
Sapi Sapi
|
|
cr C/R field.
|
|
Tei Tei.
|
|
pf P/F fiels octet 5
|
|
|
|
Return Value: 0 if failed, 1 if Send.
|
|
|
|
*****************************************************************************/
|
|
|
|
static int Q921SendRR(L2TRUNK trunk, int Sapi, int cr, int Tei, int pf)
|
|
{
|
|
L2UCHAR mes[25];
|
|
|
|
return Q921SendS(trunk, Sapi, cr, Tei, pf, 0x00, mes, trunk->Q921HeaderSpace+4);
|
|
}
|
|
|
|
/*****************************************************************************
|
|
|
|
Function: Q921SendRNR
|
|
|
|
Description: Compose and send Receive Nor Ready
|
|
|
|
Parameters: trunk trunk #
|
|
Sapi Sapi
|
|
cr C/R field.
|
|
Tei Tei.
|
|
pf P/F fiels octet 5
|
|
|
|
Return Value: 0 if failed, 1 if Send.
|
|
|
|
*****************************************************************************/
|
|
static int Q921SendRNR(L2TRUNK trunk, int Sapi, int cr, int Tei, int pf)
|
|
{
|
|
L2UCHAR mes[25];
|
|
|
|
return Q921SendS(trunk, Sapi, cr, Tei, pf, 0x01, mes, trunk->Q921HeaderSpace+4);
|
|
}
|
|
|
|
/*****************************************************************************
|
|
|
|
Function: Q921SendREJ
|
|
|
|
Description: Compose and Send Reject.
|
|
|
|
Parameters: trunk trunk #
|
|
Sapi Sapi
|
|
cr C/R field.
|
|
Tei Tei.
|
|
pf P/F fiels octet 5
|
|
|
|
Return Value: 0 if failed, 1 if Send.
|
|
|
|
*****************************************************************************/
|
|
static int Q921SendREJ(L2TRUNK trunk, int Sapi, int cr, int Tei, int pf)
|
|
{
|
|
L2UCHAR mes[25];
|
|
|
|
return Q921SendS(trunk, Sapi, cr, Tei, pf, 0x03, mes, trunk->Q921HeaderSpace+4);
|
|
}
|
|
|
|
/*****************************************************************************
|
|
|
|
Function: Q921SendSABME
|
|
|
|
Description: Compose and send SABME
|
|
|
|
Parameters: trunk trunk #
|
|
Sapi Sapi
|
|
cr C/R field.
|
|
Tei Tei.
|
|
pf P fiels octet 4
|
|
|
|
Return Value: 0 if failed, 1 if Send.
|
|
|
|
*****************************************************************************/
|
|
static int Q921SendSABME(L2TRUNK trunk, int Sapi, int cr, int Tei, int pf)
|
|
{
|
|
L2UCHAR mes[25];
|
|
|
|
return Q921SendU(trunk, Sapi, cr, Tei, pf, 0x0f, mes, trunk->Q921HeaderSpace+3);
|
|
}
|
|
|
|
|
|
/**
|
|
* Q921Start
|
|
* \brief Start trunk
|
|
* \param[in] trunk pointer to Q921 data struct
|
|
* \return > 0 on success; <= 0 on error
|
|
*/
|
|
int Q921Start(L2TRUNK trunk)
|
|
{
|
|
int x, numlinks = Q921_IS_PTMP_NT(trunk) ? Q921_TEI_MAX : 1;
|
|
struct Q921_Link *link = Q921_TRUNK_CONTEXT(trunk);
|
|
|
|
if(trunk->initialized != INITIALIZED_MAGIC)
|
|
return 0;
|
|
|
|
memset(trunk->context, 0, numlinks * sizeof(struct Q921_Link));
|
|
|
|
/* Common init part */
|
|
for(x = 0; x <= numlinks; x++) {
|
|
link = Q921_LINK_CONTEXT(trunk, x);
|
|
|
|
link->state = Q921_STATE_TEI_UNASSIGNED;
|
|
link->tei = 0;
|
|
|
|
/* Initialize per-TEI I + UI queues */
|
|
MFIFOCreate(link->UIFrameQueue, Q921MAXHDLCSPACE, 10);
|
|
MFIFOCreate(link->IFrameQueue, Q921MAXHDLCSPACE, 10);
|
|
MFIFOCreate(link->IFrameResendQueue, Q921MAXHDLCSPACE, 10);
|
|
}
|
|
|
|
if(Q921_IS_PTMP_TE(trunk)) {
|
|
link->state = Q921_STATE_TEI_UNASSIGNED;
|
|
link->tei = 0;
|
|
}
|
|
else if(Q921_IS_PTMP_NT(trunk)) {
|
|
link = Q921_TRUNK_CONTEXT(trunk);
|
|
|
|
link->state = Q921_STATE_TEI_ASSIGNED;
|
|
link->tei = trunk->tei;
|
|
|
|
/* clear tei map */
|
|
memset(trunk->tei_map, 0, Q921_TEI_MAX + 1);
|
|
}
|
|
else {
|
|
link->state = Q921_STATE_TEI_ASSIGNED;
|
|
link->tei = trunk->tei;
|
|
}
|
|
|
|
Q921Log(trunk, Q921_LOG_DEBUG, "Starting trunk %p (sapi: %d, tei: %d, mode: %s %s)\n",
|
|
trunk,
|
|
trunk->sapi,
|
|
link->tei,
|
|
Q921_IS_PTMP(trunk) ? "PTMP" : "PTP",
|
|
Q921_IS_TE(trunk) ? "TE" : "NT");
|
|
|
|
if(Q921_IS_PTP(trunk)) {
|
|
Q921Log(trunk, Q921_LOG_DEBUG, "Sending SABME\n");
|
|
|
|
return Q921SendSABME(trunk,
|
|
trunk->sapi,
|
|
Q921_COMMAND(trunk),
|
|
link->tei,
|
|
1);
|
|
|
|
} else if(Q921_IS_PTMP_NT(trunk)) {
|
|
|
|
Q921Log(trunk, Q921_LOG_DEBUG, "Revoking all TEIs\n");
|
|
|
|
return Q921TeiSendRemoveRequest(trunk, Q921_TEI_BCAST); /* Revoke all TEIs in use */
|
|
} else {
|
|
|
|
Q921Log(trunk, Q921_LOG_DEBUG, "Requesting TEI\n");
|
|
|
|
return Q921TeiSendAssignRequest(trunk);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Q921Stop
|
|
* \brief Stop trunk
|
|
* \param[in] trunk pointer to Q921 data struct
|
|
* \return > 0 on success; <= 0 on error
|
|
*
|
|
* \author Stefan Knoblich
|
|
*/
|
|
int Q921Stop(L2TRUNK trunk)
|
|
{
|
|
struct Q921_Link *link;
|
|
int x, numlinks;
|
|
|
|
if(!trunk)
|
|
return -1;
|
|
|
|
link = Q921_TRUNK_CONTEXT(trunk);
|
|
numlinks = Q921_IS_PTMP_NT(trunk) ? Q921_TEI_MAX : 1;
|
|
|
|
if(Q921_IS_STOPPED(link))
|
|
return 0;
|
|
|
|
/* Release TEI */
|
|
if(Q921_IS_PTMP_TE(trunk)) {
|
|
/* send verify request */
|
|
Q921TeiSendVerifyRequest(trunk);
|
|
|
|
/* drop TEI */
|
|
link->tei = 0;
|
|
}
|
|
|
|
/* Stop timers, stop link, flush queues */
|
|
for(x = 0; x <= numlinks; x++) {
|
|
Q921T200TimerStop(trunk, x);
|
|
Q921T203TimerStop(trunk, x);
|
|
Q921T201TimerStop(trunk, x);
|
|
|
|
/* Change state (no action) */
|
|
Q921ChangeState(trunk, Q921_STATE_STOPPED, x);
|
|
|
|
/* Flush per-tei I/UI queues */
|
|
MFIFOClear(link->UIFrameQueue);
|
|
MFIFOClear(link->IFrameQueue);
|
|
MFIFOClear(link->IFrameResendQueue);
|
|
}
|
|
Q921T202TimerStop(trunk);
|
|
|
|
/* Flush HDLC queue */
|
|
MFIFOClear(trunk->HDLCInQueue);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
|
|
Function: Q921SendDM
|
|
|
|
Description: Compose and Send DM (Disconnected Mode)
|
|
|
|
Parameters: trunk trunk #
|
|
Sapi Sapi
|
|
cr C/R field.
|
|
Tei Tei.
|
|
pf F fiels octet 4
|
|
|
|
Return Value: 0 if failed, 1 if Send.
|
|
|
|
*****************************************************************************/
|
|
static int Q921SendDM(L2TRUNK trunk, int Sapi, int cr, int Tei, int pf)
|
|
{
|
|
L2UCHAR mes[25];
|
|
|
|
return Q921SendU(trunk, Sapi, cr, Tei, pf, 0x03, mes, trunk->Q921HeaderSpace+3);
|
|
}
|
|
|
|
/*****************************************************************************
|
|
|
|
Function: Q921SendDISC
|
|
|
|
Description: Compose and Send Disconnect
|
|
|
|
Parameters: trunk trunk #
|
|
Sapi Sapi
|
|
cr C/R field.
|
|
Tei Tei.
|
|
pf P fiels octet 4
|
|
|
|
Return Value: 0 if failed, 1 if Send.
|
|
|
|
*****************************************************************************/
|
|
static int Q921SendDISC(L2TRUNK trunk, int Sapi, int cr, int Tei, int pf)
|
|
{
|
|
L2UCHAR mes[25];
|
|
|
|
return Q921SendU(trunk, Sapi, cr, Tei, pf, 0x08, mes, trunk->Q921HeaderSpace+3);
|
|
}
|
|
|
|
/*****************************************************************************
|
|
|
|
Function: Q921SendUA
|
|
|
|
Description: Compose and Send UA
|
|
|
|
Parameters: trunk trunk #
|
|
Sapi Sapi
|
|
cr C/R field.
|
|
Tei Tei.
|
|
pf F fiels octet 4
|
|
|
|
Return Value: 0 if failed, 1 if Send.
|
|
|
|
*****************************************************************************/
|
|
static int Q921SendUA(L2TRUNK trunk, int Sapi, int cr, int Tei, int pf)
|
|
{
|
|
L2UCHAR mes[25];
|
|
|
|
return Q921SendU(trunk, Sapi, cr, Tei, pf, 0x0c, mes, trunk->Q921HeaderSpace+3);
|
|
}
|
|
|
|
static int Q921SendUN(L2TRUNK trunk, int Sapi, int cr, int Tei, int pf, L2UCHAR *mes, L2INT size)
|
|
{
|
|
return Q921SendU(trunk, Sapi, cr, Tei, pf, 0x00, mes, size+trunk->Q921HeaderSpace+3);
|
|
}
|
|
|
|
|
|
/**
|
|
* Q921ProcSABME
|
|
* \brief Handle incoming SABME
|
|
* \param[in] trunk pointer to Q921 data struct
|
|
* \param[in] mes pointer to message buffer
|
|
* \param[in] size size of message
|
|
* \return > 0 on success, <= 0 on error
|
|
*/
|
|
static int Q921ProcSABME(L2TRUNK trunk, L2UCHAR *mes, L2INT size)
|
|
{
|
|
L2UCHAR pf = (mes[2] & 0x10) >> 4; /* poll / final flag */
|
|
L2UCHAR tei = (mes[1] & 0xfe) >> 1;
|
|
struct Q921_Link *link = Q921_LINK_CONTEXT(trunk, tei);
|
|
|
|
switch(link->state) {
|
|
case Q921_STATE_TEI_ASSIGNED:
|
|
/* send UA */
|
|
Q921SendUA(trunk,
|
|
trunk->sapi,
|
|
Q921_RESPONSE(trunk), /* or command? */
|
|
tei, pf);
|
|
|
|
/* clear counters */
|
|
link->vr=0;
|
|
link->vs=0;
|
|
link->va=0;
|
|
|
|
/* TODO: send DL-Establish indication to Q.931 */
|
|
Q921Tx23Proc(trunk, Q921_DL_ESTABLISH, tei, NULL, 0);
|
|
|
|
/* start T203 */
|
|
Q921T203TimerStart(trunk, tei);
|
|
|
|
/* change state (no action) */
|
|
Q921ChangeState(trunk, Q921_STATE_MULTIPLE_FRAME_ESTABLISHED, tei);
|
|
break;
|
|
|
|
case Q921_STATE_AWAITING_ESTABLISHMENT:
|
|
/* send UA */
|
|
Q921SendUA(trunk,
|
|
trunk->sapi,
|
|
Q921_RESPONSE(trunk),
|
|
tei, pf);
|
|
|
|
/* no state change */
|
|
break;
|
|
|
|
case Q921_STATE_AWAITING_RELEASE:
|
|
/* send DM */
|
|
Q921SendDM(trunk,
|
|
trunk->sapi,
|
|
Q921_RESPONSE(trunk),
|
|
tei, pf);
|
|
|
|
/* no state change */
|
|
break;
|
|
|
|
case Q921_STATE_MULTIPLE_FRAME_ESTABLISHED:
|
|
case Q921_STATE_TIMER_RECOVERY:
|
|
/* send UA */
|
|
Q921SendUA(trunk,
|
|
trunk->sapi,
|
|
Q921_RESPONSE(trunk),
|
|
tei, pf);
|
|
|
|
/* clear exception conditions */
|
|
Q921ResetExceptionConditions(trunk, tei);
|
|
|
|
/* send MDL-Error indication */
|
|
|
|
/* V(S) == V(A) ? */
|
|
if(link->vs != link->va) {
|
|
/* clear I queue */
|
|
MFIFOClear(link->IFrameQueue);
|
|
|
|
/* DL-Establish indication */
|
|
Q921Tx23Proc(trunk, Q921_DL_ESTABLISH, tei, NULL, 0);
|
|
}
|
|
|
|
/* clear counters */
|
|
link->vr=0;
|
|
link->vs=0;
|
|
link->va=0;
|
|
|
|
/* Stop T200, start T203 */
|
|
Q921T200TimerStop(trunk, tei);
|
|
Q921T203TimerStart(trunk, tei);
|
|
|
|
/* state change only if in TIMER_RECOVERY state */
|
|
if(link->state == Q921_STATE_TIMER_RECOVERY)
|
|
Q921ChangeState(trunk, Q921_STATE_MULTIPLE_FRAME_ESTABLISHED, tei);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/**
|
|
* Q921ProcDM
|
|
* \brief Handle incoming DM
|
|
* \param[in] trunk pointer to Q921 data struct
|
|
* \param[in] mes pointer to message buffer
|
|
* \param[in] size size of message
|
|
* \return > 0 on success, <= 0 on error
|
|
*/
|
|
static int Q921ProcDM(L2TRUNK trunk, L2UCHAR *mes, L2INT size)
|
|
{
|
|
L2UCHAR pf = (mes[2] & 0x10) >> 4; /* poll / final flag */
|
|
L2UCHAR tei = (mes[1] & 0xfe) >> 1;
|
|
struct Q921_Link *link = Q921_LINK_CONTEXT(trunk, tei);
|
|
|
|
switch(link->state) {
|
|
case Q921_STATE_TEI_ASSIGNED:
|
|
if(!pf) {
|
|
/* to next state (no action) */
|
|
Q921ChangeState(trunk, Q921_STATE_AWAITING_ESTABLISHMENT, tei);
|
|
}
|
|
break;
|
|
|
|
case Q921_STATE_AWAITING_ESTABLISHMENT:
|
|
case Q921_STATE_AWAITING_RELEASE:
|
|
if(pf) {
|
|
if(link->state == Q921_STATE_AWAITING_ESTABLISHMENT) {
|
|
/* Discard I queue */
|
|
MFIFOClear(link->IFrameQueue);
|
|
}
|
|
|
|
/* Send DL-Release indication to Q.931 */
|
|
Q921Tx23Proc(trunk, Q921_DL_RELEASE, tei, NULL, 0);
|
|
|
|
/* Stop T200 */
|
|
Q921T200TimerStop(trunk, tei);
|
|
|
|
/* Change state (no action) */
|
|
Q921ChangeState(trunk, Q921_STATE_TEI_ASSIGNED, tei);
|
|
}
|
|
break;
|
|
|
|
case Q921_STATE_MULTIPLE_FRAME_ESTABLISHED:
|
|
if(pf) {
|
|
/* MDL-Error indication (B) */
|
|
|
|
/* no state change */
|
|
} else {
|
|
/* MDL-Error indication (E) */
|
|
|
|
/* establish data link */
|
|
Q921EstablishDataLink(trunk, tei);
|
|
|
|
/* clear L3 initiated */
|
|
Q921_CLEAR_FLAG(link, Q921_FLAG_L3_INITIATED);
|
|
|
|
/* change state (no action?) */
|
|
Q921ChangeState(trunk, Q921_STATE_AWAITING_ESTABLISHMENT, tei);
|
|
}
|
|
break;
|
|
|
|
case Q921_STATE_TIMER_RECOVERY:
|
|
if(pf) {
|
|
/* MDL Error indication (B) */
|
|
} else {
|
|
/* MDL Error indication (E) */
|
|
}
|
|
|
|
/* establish data link */
|
|
Q921EstablishDataLink(trunk, tei);
|
|
|
|
/* clear layer 3 initiated */
|
|
Q921_CLEAR_FLAG(link, Q921_FLAG_L3_INITIATED);
|
|
|
|
/* change state */
|
|
Q921ChangeState(trunk, Q921_STATE_AWAITING_ESTABLISHMENT, tei);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Q921ProcUA
|
|
* \brief Handle incoming UA
|
|
* \param[in] trunk pointer to Q921 data struct
|
|
* \param[in] mes pointer to message buffer
|
|
* \param[in] size size of message
|
|
* \return > 0 on success, <= 0 on error
|
|
*/
|
|
static int Q921ProcUA(L2TRUNK trunk, L2UCHAR *mes, L2INT size)
|
|
{
|
|
L2UCHAR pf = (mes[2] & 0x10) >> 4; /* poll / final flag */
|
|
L2UCHAR tei = (mes[1] & 0xfe) >> 1;
|
|
struct Q921_Link *link = Q921_LINK_CONTEXT(trunk, tei);
|
|
|
|
switch(link->state) {
|
|
case Q921_STATE_TEI_ASSIGNED:
|
|
case Q921_STATE_TIMER_RECOVERY:
|
|
/* MDL Error indication (C, D) */
|
|
Q921Log(trunk, Q921_LOG_ERROR, "Received UA frame in invalid state\n");
|
|
break;
|
|
|
|
case Q921_STATE_AWAITING_ESTABLISHMENT:
|
|
if(pf) {
|
|
/* TODO: other fancy stuff (see docs) */
|
|
if(Q921_CHECK_FLAG(link, Q921_FLAG_L3_INITIATED)) { /* layer3 initiated */
|
|
link->vr = 0;
|
|
|
|
/* DL-Establish confirm */
|
|
Q921Tx23Proc(trunk, Q921_DL_ESTABLISH_CONFIRM, tei, NULL, 0);
|
|
|
|
} else if(link->vs != link->va) {
|
|
|
|
/* discard I queue */
|
|
MFIFOClear(link->IFrameQueue);
|
|
|
|
/* DL-Establish indication */
|
|
Q921Tx23Proc(trunk, Q921_DL_ESTABLISH, tei, NULL, 0);
|
|
}
|
|
|
|
/* Stop T200, start T203 */
|
|
Q921T200TimerStop(trunk, tei);
|
|
Q921T203TimerStart(trunk, tei);
|
|
|
|
link->vs = 0;
|
|
link->va = 0;
|
|
|
|
/* change state (no action) */
|
|
Q921ChangeState(trunk, Q921_STATE_MULTIPLE_FRAME_ESTABLISHED, tei);
|
|
} else {
|
|
/* MDL Error indication (C, D) */
|
|
Q921Log(trunk, Q921_LOG_ERROR, "Received UA frame is not a response to a request\n");
|
|
|
|
/* no state change */
|
|
}
|
|
break;
|
|
|
|
case Q921_STATE_AWAITING_RELEASE:
|
|
if(pf) {
|
|
/* DL Release confirm */
|
|
Q921Tx23Proc(trunk, Q921_DL_RELEASE_CONFIRM, tei, NULL, 0);
|
|
|
|
/* Stop T200 */
|
|
Q921T200TimerStop(trunk, tei);
|
|
|
|
/* change state (no action) */
|
|
Q921ChangeState(trunk, Q921_STATE_TEI_ASSIGNED, tei);
|
|
} else {
|
|
/* MDL Error indication (D) */
|
|
Q921Log(trunk, Q921_LOG_ERROR, "Received UA frame is not a response to a request\n");
|
|
|
|
/* no state change */
|
|
}
|
|
break;
|
|
|
|
case Q921_STATE_MULTIPLE_FRAME_ESTABLISHED:
|
|
/* MDL Error indication (C, D) */
|
|
Q921Log(trunk, Q921_LOG_ERROR, "Received UA frame in invalid state\n");
|
|
|
|
/* no state change */
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/**
|
|
* Q921ProcDISC
|
|
* \brief Handle incoming DISC
|
|
* \param[in] trunk pointer to Q921 data struct
|
|
* \param[in] mes pointer to message buffer
|
|
* \param[in] size size of message
|
|
* \return > 0 on success, <= 0 on error
|
|
*/
|
|
static int Q921ProcDISC(L2TRUNK trunk, L2UCHAR *mes, L2INT size)
|
|
{
|
|
L2UCHAR pf = (mes[2] & 0x10) >> 4; /* poll / final flag */
|
|
L2UCHAR tei = (mes[1] & 0xfe) >> 1;
|
|
struct Q921_Link *link = Q921_LINK_CONTEXT(trunk, tei);
|
|
|
|
switch(link->state) {
|
|
case Q921_STATE_TEI_ASSIGNED:
|
|
case Q921_STATE_AWAITING_ESTABLISHMENT:
|
|
/* Send DM */
|
|
Q921SendDM(trunk,
|
|
trunk->sapi,
|
|
Q921_RESPONSE(trunk),
|
|
tei, pf);
|
|
|
|
/* no state change */
|
|
break;
|
|
|
|
case Q921_STATE_AWAITING_RELEASE:
|
|
Q921SendUA(trunk,
|
|
trunk->sapi,
|
|
Q921_RESPONSE(trunk),
|
|
tei, pf);
|
|
|
|
/* no state change */
|
|
break;
|
|
|
|
case Q921_STATE_MULTIPLE_FRAME_ESTABLISHED:
|
|
case Q921_STATE_TIMER_RECOVERY:
|
|
/* Discard I queue */
|
|
MFIFOClear(link->IFrameQueue);
|
|
|
|
/* send UA */
|
|
Q921SendUA(trunk,
|
|
trunk->sapi,
|
|
Q921_RESPONSE(trunk),
|
|
tei, pf);
|
|
|
|
/* DL Release indication */
|
|
Q921Tx23Proc(trunk, Q921_DL_RELEASE, tei, NULL, 0);
|
|
|
|
/* Stop T200 */
|
|
Q921T200TimerStop(trunk, tei);
|
|
|
|
if(link->state == Q921_STATE_MULTIPLE_FRAME_ESTABLISHED) {
|
|
/* Stop T203 */
|
|
Q921T203TimerStop(trunk, tei);
|
|
}
|
|
|
|
/* change state (no action) */
|
|
Q921ChangeState(trunk, Q921_STATE_TEI_ASSIGNED, tei);
|
|
break;
|
|
|
|
default:
|
|
Q921Log(trunk, Q921_LOG_ERROR, "Invalid DISC received in state \"%s\" (%d)", Q921State2Name(link->state), link->state);
|
|
break;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/**
|
|
* Q921ProcRR
|
|
* \brief Handle incoming RR
|
|
* \param[in] trunk pointer to Q921 data struct
|
|
* \param[in] mes pointer to message buffer
|
|
* \param[in] size size of message
|
|
* \return > 0 on success, <= 0 on error
|
|
*/
|
|
static int Q921ProcRR(L2TRUNK trunk, L2UCHAR *mes, L2INT size)
|
|
{
|
|
L2UCHAR cr = (mes[0] & 0x02) >> 1;
|
|
L2UCHAR pf = mes[3] & 0x01; /* poll / final flag */
|
|
L2UCHAR nr = (mes[3] >> 1);
|
|
// L2UCHAR sapi = (mes[0] & 0xfc) >> 2;
|
|
L2UCHAR tei = (mes[1] & 0xfe) >> 1;
|
|
struct Q921_Link *link = Q921_LINK_CONTEXT(trunk, tei);
|
|
|
|
switch(link->state) {
|
|
case Q921_STATE_MULTIPLE_FRAME_ESTABLISHED:
|
|
/* clear receiver peer busy */
|
|
Q921_CLEAR_FLAG(link, Q921_FLAG_PEER_RECV_BUSY);
|
|
|
|
if (Q921_IS_COMMAND(trunk, cr)) { /* if this is a command */
|
|
if(pf) {
|
|
/* Enquiry response */
|
|
Q921SendEnquiryResponse(trunk, tei);
|
|
}
|
|
} else {
|
|
if(pf) {
|
|
/* MDL Error indication */
|
|
}
|
|
}
|
|
|
|
/* */
|
|
if(link->va <= nr && nr <= link->vs) {
|
|
|
|
if(nr == link->vs) {
|
|
/* V(A) = N(R) */
|
|
link->va = nr;
|
|
|
|
/* Stop T200, restart T203 */
|
|
Q921T200TimerStop(trunk, tei);
|
|
Q921T203TimerReset(trunk, tei);
|
|
|
|
} else if(nr == link->va) {
|
|
|
|
/* do nothing */
|
|
|
|
} else {
|
|
/* V(A) = N(R) */
|
|
link->va = nr;
|
|
|
|
/* Restart T200 */
|
|
Q921T200TimerReset(trunk, tei);
|
|
}
|
|
/* no state change */
|
|
|
|
} else {
|
|
/* N(R) Error recovery */
|
|
Q921NrErrorRecovery(trunk, tei);
|
|
|
|
/* change state (no action) */
|
|
Q921ChangeState(trunk, Q921_STATE_AWAITING_ESTABLISHMENT, tei);
|
|
}
|
|
break;
|
|
|
|
case Q921_STATE_TIMER_RECOVERY:
|
|
/* clear receiver peer busy */
|
|
Q921_CLEAR_FLAG(link, Q921_FLAG_PEER_RECV_BUSY);
|
|
|
|
/* command + P? */
|
|
if(Q921_IS_COMMAND(trunk, cr) && pf) {
|
|
/* Enquiry response */
|
|
Q921SendEnquiryResponse(trunk, tei);
|
|
}
|
|
|
|
/* */
|
|
if(link->va <= nr && nr <= link->vs) {
|
|
/* V(A) = N(R) */
|
|
link->va = nr;
|
|
|
|
if(!Q921_IS_COMMAND(trunk, cr) && pf) {
|
|
/* Stop T200, start T203 */
|
|
Q921T200TimerStop(trunk, tei);
|
|
Q921T203TimerStart(trunk, tei);
|
|
|
|
/* Invoke retransmission */
|
|
Q921InvokeRetransmission(trunk, tei, nr);
|
|
|
|
/* change state (no action) */
|
|
Q921ChangeState(trunk, Q921_STATE_MULTIPLE_FRAME_ESTABLISHED, tei);
|
|
}
|
|
/* no state change otherwise */
|
|
} else {
|
|
/* N(R) Error recovery */
|
|
Q921NrErrorRecovery(trunk, tei);
|
|
|
|
/* change state (no action) */
|
|
Q921ChangeState(trunk, Q921_STATE_AWAITING_ESTABLISHMENT, tei);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
|
|
/**
|
|
* Q921ProcREJ
|
|
* \brief Handle incoming REJ
|
|
* \param[in] trunk pointer to Q921 data struct
|
|
* \param[in] mes pointer to message buffer
|
|
* \param[in] size size of message
|
|
* \return > 0 on success, <= 0 on error
|
|
*/
|
|
static int Q921ProcREJ(L2TRUNK trunk, L2UCHAR *mes, L2INT size)
|
|
{
|
|
L2UCHAR cr = (mes[0] & 0x02) >> 1;
|
|
L2UCHAR pf = mes[3] & 0x01; /* poll / final flag */
|
|
L2UCHAR nr = (mes[3] >> 1);
|
|
// L2UCHAR sapi = (mes[0] & 0xfc) >> 2;
|
|
L2UCHAR tei = (mes[1] & 0xfe) >> 1;
|
|
struct Q921_Link *link = Q921_LINK_CONTEXT(trunk, tei);
|
|
|
|
switch(link->state) {
|
|
case Q921_STATE_MULTIPLE_FRAME_ESTABLISHED:
|
|
/* clear receiver peer busy */
|
|
Q921_CLEAR_FLAG(link, Q921_FLAG_PEER_RECV_BUSY);
|
|
|
|
/* command? */
|
|
if(Q921_IS_COMMAND(trunk, cr)) {
|
|
if(pf) {
|
|
/* Enquiry response */
|
|
Q921SendEnquiryResponse(trunk, tei);
|
|
}
|
|
} else {
|
|
if(pf) {
|
|
/* MDL Error indication (A) */
|
|
}
|
|
}
|
|
|
|
/* */
|
|
if(link->va <= nr && nr <= link->vs) {
|
|
|
|
/* V(A) = N(R) */
|
|
link->va = nr;
|
|
|
|
/* Stop T200, start T203 */
|
|
Q921T200TimerStop(trunk, tei);
|
|
Q921T203TimerStart(trunk, tei);
|
|
|
|
/* Invoke retransmission of frame >N(R) (?) */
|
|
Q921InvokeRetransmission(trunk, tei, nr);
|
|
|
|
/* no state change */
|
|
} else {
|
|
/* N(R) Error recovery */
|
|
Q921NrErrorRecovery(trunk, tei);
|
|
|
|
/* change state (no action) */
|
|
Q921ChangeState(trunk, Q921_STATE_AWAITING_ESTABLISHMENT, tei);
|
|
}
|
|
break;
|
|
|
|
case Q921_STATE_TIMER_RECOVERY:
|
|
/* clear receiver peer busy */
|
|
Q921_CLEAR_FLAG(link, Q921_FLAG_PEER_RECV_BUSY);
|
|
|
|
/* command + P ? */
|
|
if(Q921_IS_COMMAND(trunk, cr) && pf) {
|
|
/* Enquiry response */
|
|
Q921SendEnquiryResponse(trunk, tei);
|
|
}
|
|
|
|
/* */
|
|
if(link->va <= nr && nr <= link->vs) {
|
|
|
|
/* V(A) = N(R) */
|
|
link->va = nr;
|
|
|
|
if(!Q921_IS_COMMAND(trunk, cr) && pf) {
|
|
/* Stop T200, start T203 */
|
|
Q921T200TimerStop(trunk, tei);
|
|
Q921T203TimerStart(trunk, tei);
|
|
|
|
/* Invoke retransmission */
|
|
Q921InvokeRetransmission(trunk, tei, nr);
|
|
|
|
/* change state (no action) */
|
|
Q921ChangeState(trunk, Q921_STATE_MULTIPLE_FRAME_ESTABLISHED, tei);
|
|
}
|
|
/* no state change otherwise */
|
|
} else {
|
|
/* N(R) Error recovery */
|
|
Q921NrErrorRecovery(trunk, tei);
|
|
|
|
/* change state (no action) */
|
|
Q921ChangeState(trunk, Q921_STATE_AWAITING_ESTABLISHMENT, tei);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/**
|
|
* Q921ProcRNR
|
|
* \brief Handle incoming RNR
|
|
* \param[in] trunk pointer to Q921 data struct
|
|
* \param[in] mes pointer to message buffer
|
|
* \param[in] size size of message
|
|
* \return > 0 on success, <= 0 on error
|
|
*/
|
|
static int Q921ProcRNR(L2TRUNK trunk, L2UCHAR *mes, L2INT size)
|
|
{
|
|
L2UCHAR cr = (mes[0] & 0x02) >> 1;
|
|
L2UCHAR pf = mes[3] & 0x01; /* poll / final flag */
|
|
L2UCHAR nr = (mes[3] >> 1);
|
|
// L2UCHAR sapi = (mes[0] & 0xfc) >> 2;
|
|
L2UCHAR tei = (mes[1] & 0xfe) >> 1;
|
|
struct Q921_Link *link = Q921_LINK_CONTEXT(trunk, tei);
|
|
|
|
switch(link->state) {
|
|
case Q921_STATE_MULTIPLE_FRAME_ESTABLISHED:
|
|
/* set peer receiver busy */
|
|
Q921_SET_FLAG(link, Q921_FLAG_PEER_RECV_BUSY);
|
|
|
|
/* command? */
|
|
if(Q921_IS_COMMAND(trunk, cr)) {
|
|
if(pf) {
|
|
/* Enquiry response */
|
|
Q921SendEnquiryResponse(trunk, tei);
|
|
}
|
|
} else {
|
|
if(pf) {
|
|
/* MDL Error indication (A) */
|
|
}
|
|
}
|
|
|
|
/* */
|
|
if(link->va <= nr && nr <= link->vs) {
|
|
|
|
/* V(A) = N(R) */
|
|
link->va = nr;
|
|
|
|
/* Stop T203, restart T200 */
|
|
Q921T200TimerReset(trunk, tei);
|
|
Q921T203TimerStop(trunk, tei);
|
|
|
|
/* no state change */
|
|
} else {
|
|
/* N(R) Error recovery */
|
|
Q921NrErrorRecovery(trunk, tei);
|
|
|
|
/* change state (no action) */
|
|
Q921ChangeState(trunk, Q921_STATE_AWAITING_ESTABLISHMENT, tei);
|
|
}
|
|
break;
|
|
|
|
case Q921_STATE_TIMER_RECOVERY:
|
|
/* set peer receiver busy */
|
|
Q921_SET_FLAG(link, Q921_FLAG_PEER_RECV_BUSY);
|
|
|
|
/* command + P? */
|
|
if(Q921_IS_COMMAND(trunk, cr) && pf) {
|
|
/* Enquiry response */
|
|
Q921SendEnquiryResponse(trunk, tei);
|
|
}
|
|
|
|
/* */
|
|
if(link->va <= nr && nr <= link->vs) {
|
|
|
|
/* V(A) = N(R) */
|
|
link->va = nr;
|
|
|
|
if(!Q921_IS_COMMAND(trunk, cr) && pf) {
|
|
/* Restart T200 */
|
|
Q921T200TimerReset(trunk, tei);
|
|
|
|
/* Invoke retransmission */
|
|
Q921InvokeRetransmission(trunk, tei, nr);
|
|
|
|
/* change state (no action) */
|
|
Q921ChangeState(trunk, Q921_STATE_MULTIPLE_FRAME_ESTABLISHED, tei);
|
|
}
|
|
/* no state change otherwise */
|
|
} else {
|
|
/* N(R) Error recovery */
|
|
Q921NrErrorRecovery(trunk, tei);
|
|
|
|
/* change state (no action) */
|
|
Q921ChangeState(trunk, Q921_STATE_AWAITING_ESTABLISHMENT, tei);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
#if 0
|
|
static int Q921SetReceiverBusy(L2TRUNK trunk)
|
|
{
|
|
struct Q921_Link *link = Q921_LINK_CONTEXT(trunk, tei);
|
|
|
|
switch(link->state) {
|
|
case Q921_STATE_MULTIPLE_FRAME_ESTABLISHED:
|
|
if(!Q921_CHECK_FLAG(link, Q921_FLAG_RECV_BUSY)) {
|
|
/* set own receiver busy */
|
|
Q921_SET_FLAG(link, Q921_FLAG_RECV_BUSY);
|
|
|
|
/* send RR response */
|
|
Q921SendRR(trunk, trunk->sapi, Q921_RESPONSE(trunk), link->tei, 0);
|
|
|
|
/* clear ack pending */
|
|
Q921_CLEAR_FLAG(link, Q921_FLAG_ACK_PENDING);
|
|
}
|
|
break;
|
|
|
|
case Q921_STATE_TIMER_RECOVERY:
|
|
if(!Q921_CHECK_FLAG(link, Q921_FLAG_RECV_BUSY)) {
|
|
/* set own receiver busy */
|
|
Q921_SET_FLAG(link, Q921_FLAG_RECV_BUSY);
|
|
|
|
/* send RNR response */
|
|
Q921SendRNR(trunk, trunk->sapi, Q921_RESPONSE(trunk), link->tei, 0);
|
|
|
|
/* clear ack pending */
|
|
Q921_CLEAR_FLAG(link, Q921_FLAG_ACK_PENDING);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int Q921ClearReceiverBusy(L2TRUNK trunk)
|
|
{
|
|
struct Q921_Link *link = Q921_LINK_CONTEXT(trunk, tei);
|
|
|
|
switch(link->state) {
|
|
case Q921_STATE_MULTIPLE_FRAME_ESTABLISHED:
|
|
case Q921_STATE_TIMER_RECOVERY:
|
|
if(Q921_CHECK_FLAG(link, Q921_FLAG_RECV_BUSY)) {
|
|
/* clear own receiver busy */
|
|
Q921_CLEAR_FLAG(link, Q921_FLAG_RECV_BUSY);
|
|
|
|
/* send RNR response */
|
|
Q921SendRNR(trunk, trunk->sapi, Q921_RESPONSE(trunk), link->tei, 0);
|
|
|
|
/* clear ack pending */
|
|
Q921_CLEAR_FLAG(link, Q921_FLAG_ACK_PENDING);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int Q921ProcIFrame(L2TRUNK trunk, L2UCHAR *mes, L2INT size)
|
|
{
|
|
/* common fields: get sapi, tei and cr */
|
|
// L2UCHAR sapi = (mes[0] & 0xfc) >> 2;
|
|
// L2UCHAR cr = (mes[0] & 0x02) >> 1;
|
|
L2UCHAR tei = (mes[1] & 0xfe) >> 1;
|
|
L2UCHAR pf = mes[3] & 0x01; /* poll / final flag */
|
|
L2UCHAR nr = mes[3] >> 1; /* receive sequence number */
|
|
L2UCHAR ns = mes[2] >> 1; /* send sequence number */
|
|
L2UCHAR discard = 0;
|
|
struct Q921_Link *link = Q921_LINK_CONTEXT(trunk, tei);
|
|
|
|
/* Ignore I frames in earlier states */
|
|
if(link->state < Q921_STATE_MULTIPLE_FRAME_ESTABLISHED) {
|
|
Q921Log(trunk, Q921_LOG_NOTICE, "I frame in invalid state ignored\n");
|
|
return 0;
|
|
}
|
|
|
|
/* Receiver busy? */
|
|
if(Q921_CHECK_FLAG(link, Q921_FLAG_RECV_BUSY)) {
|
|
/* discard information */
|
|
discard = 1;
|
|
|
|
if(pf) {
|
|
/* send RNR Response */
|
|
Q921SendRNR(trunk, trunk->sapi, Q921_RESPONSE(trunk), tei, 1);
|
|
|
|
/* Clear ack pending */
|
|
Q921_CLEAR_FLAG(link, Q921_FLAG_ACK_PENDING);
|
|
}
|
|
}
|
|
else {
|
|
if(ns != link->vr) {
|
|
/* discard information */
|
|
discard = 1;
|
|
|
|
if(Q921_CHECK_FLAG(link, Q921_FLAG_REJECT) && pf) {
|
|
|
|
/* Send RR response */
|
|
Q921SendRR(trunk, trunk->sapi, Q921_RESPONSE(trunk), tei, 1);
|
|
|
|
/* clear ack pending */
|
|
Q921_CLEAR_FLAG(link, Q921_FLAG_ACK_PENDING);
|
|
}
|
|
else if(!Q921_CHECK_FLAG(link, Q921_FLAG_REJECT)){
|
|
|
|
/* set reject exception */
|
|
Q921_SET_FLAG(link, Q921_FLAG_REJECT);
|
|
|
|
/* Send REJ response */
|
|
Q921SendREJ(trunk, trunk->sapi, Q921_RESPONSE(trunk), tei, pf);
|
|
|
|
/* clear ack pending */
|
|
Q921_CLEAR_FLAG(link, Q921_FLAG_ACK_PENDING);
|
|
}
|
|
}
|
|
else {
|
|
/* V(R) = V(R) + 1 */
|
|
Q921_INC_COUNTER(link->vr);
|
|
|
|
/* clear reject exception */
|
|
Q921_CLEAR_FLAG(link, Q921_FLAG_REJECT);
|
|
|
|
/* DL-Data indication */
|
|
Q921Tx23Proc(trunk, Q921_DL_DATA, tei, mes, size);
|
|
|
|
if(pf) {
|
|
/* Send RR response */
|
|
Q921SendRR(trunk, trunk->sapi, Q921_RESPONSE(trunk), tei, 1);
|
|
|
|
/* clear ack pending */
|
|
Q921_CLEAR_FLAG(link, Q921_FLAG_ACK_PENDING);
|
|
}
|
|
else if(!Q921_CHECK_FLAG(link, Q921_FLAG_ACK_PENDING)) {
|
|
/* ack pending */
|
|
|
|
/* Send RR response */
|
|
Q921SendRR(trunk, trunk->sapi, Q921_RESPONSE(trunk), tei, 0);
|
|
|
|
/* set ack pending*/
|
|
Q921_SET_FLAG(link, Q921_FLAG_ACK_PENDING);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
switch(link->state) {
|
|
case Q921_STATE_MULTIPLE_FRAME_ESTABLISHED:
|
|
if(link->va <= nr && nr <= link->vs) {
|
|
if(Q921_CHECK_FLAG(link, Q921_FLAG_PEER_RECV_BUSY)) {
|
|
link->va = nr;
|
|
}
|
|
else if(nr == link->vs) {
|
|
/* V(A) = N(R) */
|
|
link->va = nr;
|
|
|
|
/* stop t200, restart t203 */
|
|
Q921T200TimerStop(trunk, tei);
|
|
Q921T203TimerReset(trunk, tei);
|
|
}
|
|
else if(nr != link->va) {
|
|
/* V(A) = N(R) */
|
|
link->va = nr;
|
|
|
|
/* restart T200 */
|
|
Q921T200TimerReset(trunk, tei);
|
|
}
|
|
|
|
/* Restart TM01 */
|
|
if(Q921_IS_NT(trunk)) {
|
|
Q921TM01TimerReset(trunk, tei);
|
|
}
|
|
}
|
|
else {
|
|
/* N(R) error recovery */
|
|
Q921NrErrorRecovery(trunk, tei);
|
|
|
|
/* change state (no action) */
|
|
Q921ChangeState(trunk, Q921_STATE_AWAITING_ESTABLISHMENT, tei);
|
|
}
|
|
break;
|
|
|
|
case Q921_STATE_TIMER_RECOVERY:
|
|
if(link->va <= nr && nr <= link->vs) {
|
|
/* V(A) = N(R) */
|
|
link->va = nr;
|
|
|
|
/* Restart TM01 */
|
|
if(Q921_IS_NT(trunk)) {
|
|
Q921TM01TimerReset(trunk, tei);
|
|
}
|
|
}
|
|
else {
|
|
/* N(R) error recovery */
|
|
Q921NrErrorRecovery(trunk, tei);
|
|
|
|
/* change state (no action) */
|
|
Q921ChangeState(trunk, Q921_STATE_AWAITING_ESTABLISHMENT, tei);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int Q921ProcSFrame(L2TRUNK trunk, L2UCHAR *mes, L2INT size)
|
|
{
|
|
L2UCHAR sv = (mes[2] & 0x0c) >> 2; /* supervisory format id */
|
|
//L2UCHAR pf = mes[3] & 0x01; /* poll / final flag */
|
|
//L2UCHAR nr = mes[3] >> 1; /* receive sequence number */
|
|
L2INT res = -1;
|
|
|
|
switch(sv) {
|
|
case 0x00: /* RR : Receive Ready */
|
|
res = Q921ProcRR(trunk, mes, size);
|
|
break;
|
|
|
|
case 0x02: /* RNR : Receive Not Ready */
|
|
res = Q921ProcRNR(trunk, mes, size);
|
|
break;
|
|
|
|
case 0x04: /* REJ : Reject */
|
|
res = Q921ProcREJ(trunk, mes, size);
|
|
break;
|
|
|
|
default: /* Invalid / Unknown */
|
|
Q921Log(trunk, Q921_LOG_ERROR, "Invalid S frame type %d\n", sv);
|
|
break;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
|
|
static int Q921ProcUFrame(L2TRUNK trunk, L2UCHAR *mes, L2INT size)
|
|
{
|
|
L2UCHAR m = (mes[2] & 0xe0) >> 3 | (mes[2] & 0x0c) >> 2; /* modifier function id */
|
|
// L2UCHAR pf = (mes[2] & 0x10) >> 4; /* poll / final flag */
|
|
L2INT res = -1;
|
|
|
|
switch(m) {
|
|
case 0x00: /* UN : Unnumbered Information */
|
|
if(mes[3] == Q921_LAYER_ENT_ID_TEI)
|
|
{
|
|
if(!Q921_IS_PTMP(trunk)) {
|
|
/* wtf? nice try */
|
|
return res;
|
|
}
|
|
|
|
switch(mes[6]) {
|
|
case Q921_TEI_ID_REQUEST: /* (TE ->) NT */
|
|
res = Q921TeiProcAssignRequest(trunk, mes, size);
|
|
break;
|
|
|
|
case Q921_TEI_ID_ASSIGNED: /* (NT ->) TE */
|
|
case Q921_TEI_ID_DENIED:
|
|
res = Q921TeiProcAssignResponse(trunk, mes, size);
|
|
break;
|
|
|
|
case Q921_TEI_ID_CHECKREQ: /* (NT ->) TE */
|
|
res = Q921TeiProcCheckRequest(trunk, mes, size);
|
|
break;
|
|
|
|
case Q921_TEI_ID_CHECKRESP: /* (TE ->) NT */
|
|
res = Q921TeiProcCheckResponse(trunk, mes, size);
|
|
break;
|
|
|
|
case Q921_TEI_ID_REMOVE: /* (NT ->) TE */
|
|
res = Q921TeiProcRemoveRequest(trunk, mes, size);
|
|
break;
|
|
|
|
case Q921_TEI_ID_VERIFY: /* (TE ->) NT */
|
|
res = Q921TeiProcVerifyRequest(trunk, mes, size);
|
|
break;
|
|
|
|
default: /* Invalid / Unknown */
|
|
Q921Log(trunk, Q921_LOG_ERROR, "Invalid UN message from TEI management/endpoint\n");
|
|
break;
|
|
}
|
|
}
|
|
else if(mes[3] == Q921_LAYER_ENT_ID_Q931) {
|
|
|
|
Q921Log(trunk, Q921_LOG_DEBUG, "UI Frame for Layer 3 received\n");
|
|
|
|
res = Q921Tx23Proc(trunk, Q921_DL_UNIT_DATA, 0, mes, size);
|
|
}
|
|
break;
|
|
|
|
case 0x03: /* DM : Disconnect Mode */
|
|
res = Q921ProcDM(trunk, mes, size);
|
|
break;
|
|
|
|
case 0x08: /* DISC : Disconnect */
|
|
res = Q921ProcDISC(trunk, mes, size);
|
|
break;
|
|
|
|
case 0x0c: /* UA : Unnumbered Acknowledgement */
|
|
res = Q921ProcUA(trunk, mes, size);
|
|
break;
|
|
|
|
case 0x0f: /* SABME : Set Asynchronous Balanced Mode Extend */
|
|
res = Q921ProcSABME(trunk, mes, size);
|
|
break;
|
|
|
|
case 0x11: /* FRMR : Frame Reject */
|
|
case 0x17: /* XID : Exchange Identification */
|
|
res = 0;
|
|
break;
|
|
|
|
default: /* Unknown / Invalid */
|
|
Q921Log(trunk, Q921_LOG_ERROR, "Invalid U frame type: %d\n", m);
|
|
break;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
|
|
Function: Q921Rx12
|
|
|
|
Description: Called to process a message frame from layer 1. Will
|
|
identify the message and call the proper 'processor' for
|
|
layer 2 messages and forward I frames to the layer 3 entity.
|
|
|
|
Q921Rx12 will check the input fifo for a message, and if a
|
|
message exist process one message before it exits. The caller
|
|
must either call Q921Rx12 polling or keep track on #
|
|
messages in the queue.
|
|
|
|
Parameters: trunk trunk #.
|
|
|
|
Return Value: # messages processed (always 1 or 0).
|
|
|
|
*****************************************************************************/
|
|
int Q921Rx12(L2TRUNK trunk)
|
|
{
|
|
L2INT size; /* receive size & Q921 frame size*/
|
|
L2UCHAR *smes = MFIFOGetMesPtr(trunk->HDLCInQueue, &size);
|
|
|
|
if(smes)
|
|
{
|
|
struct Q921_Link *link;
|
|
L2UCHAR sapi, tei;
|
|
L2UCHAR *mes;
|
|
L2INT rs;
|
|
|
|
rs = size - trunk->Q921HeaderSpace;
|
|
mes = &smes[trunk->Q921HeaderSpace];
|
|
|
|
Q921LogMesg(trunk, Q921_LOG_DEBUG, 1, mes, rs, "New packet received (%d bytes)", rs);
|
|
|
|
/* common fields: get sapi, tei and cr */
|
|
sapi = (mes[0] & 0xfc) >> 2;
|
|
tei = (mes[1] & 0xfe) >> 1;
|
|
link = Q921_LINK_CONTEXT(trunk, tei);
|
|
|
|
if(Q921_IS_PTMP_TE(trunk) && (
|
|
(link->state >= Q921_STATE_TEI_ASSIGNED && tei != link->tei && tei != Q921_TEI_BCAST) || /* Assigned TEI: Only BCAST and directed */
|
|
(link->state == Q921_STATE_TEI_UNASSIGNED && tei != Q921_TEI_BCAST))) /* No assigned TEI: Only BCAST */
|
|
{
|
|
/* Ignore Messages with foreign TEIs */
|
|
goto out;
|
|
}
|
|
|
|
if((mes[2] & 0x01) == 0x00) { /* I frame */
|
|
Q921ProcIFrame(trunk, mes, rs);
|
|
}
|
|
else if((mes[2] & 0x03) == 0x01) { /* S frame */
|
|
Q921ProcSFrame(trunk, mes, rs);
|
|
}
|
|
else if((mes[2] & 0x03) == 0x03) { /* U frame */
|
|
Q921ProcUFrame(trunk, mes, rs);
|
|
}
|
|
else {
|
|
Q921Log(trunk, Q921_LOG_ERROR, "Invalid frame type: %d\n", (int)(mes[2] & 0x03));
|
|
/* TODO: send FRMR or REJ */
|
|
}
|
|
|
|
out:
|
|
MFIFOKillNext(trunk->HDLCInQueue);
|
|
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Misc
|
|
*/
|
|
/**
|
|
* Q921SetLogCB
|
|
* \brief Set logging callback
|
|
* \param[in] trunk pointer to Q921 data struct
|
|
* \param[in] func pointer to logging callback function
|
|
* \param[in] priv pointer to private data
|
|
*
|
|
* \author Stefan Knoblich
|
|
*/
|
|
void Q921SetLogCB(L2TRUNK trunk, Q921LogCB_t func, void *priv)
|
|
{
|
|
if(!trunk)
|
|
return;
|
|
|
|
trunk->Q921LogProc = func;
|
|
trunk->PrivateDataLog = priv;
|
|
}
|
|
|
|
/**
|
|
* Q921SetLogLevel
|
|
* \brief Set loglevel of Q.921 logging functions
|
|
* \param[in] trunk pointer to Q921 data struct
|
|
* \param[in] level new loglevel
|
|
*
|
|
* \author Stefan Knoblich
|
|
*/
|
|
void Q921SetLogLevel(L2TRUNK trunk, Q921LogLevel_t level)
|
|
{
|
|
if(!trunk)
|
|
return;
|
|
|
|
if (level < Q921_LOG_NONE) {
|
|
level = Q921_LOG_NONE;
|
|
} else if (level > Q921_LOG_DEBUG) {
|
|
level = Q921_LOG_DEBUG;
|
|
}
|
|
|
|
trunk->loglevel = level;
|
|
}
|
|
|
|
|
|
/**
|
|
* Q921ChangeState
|
|
* \brief Change state, invoke neccessary actions
|
|
* \param[in] trunk pointer to Q921 data struct
|
|
* \param[in] state state to change to
|
|
* \return > 0 on success; <= 0 on error
|
|
*
|
|
* \author Stefan Knoblich
|
|
*/
|
|
static int Q921ChangeState(L2TRUNK trunk, Q921State_t state, L2UCHAR tei)
|
|
{
|
|
struct Q921_Link *link = Q921_LINK_CONTEXT(trunk, tei);
|
|
Q921State_t oldstate = link->state;
|
|
int res = 0;
|
|
|
|
Q921Log(trunk, Q921_LOG_DEBUG, "Changing state from \"%s\" (%d) to \"%s\" (%d) for TEI %d\n",
|
|
Q921State2Name(oldstate), oldstate,
|
|
Q921State2Name(state), state,
|
|
tei);
|
|
|
|
/*
|
|
* generic actions (depending on the target state only)
|
|
*/
|
|
switch(state) {
|
|
case Q921_STATE_MULTIPLE_FRAME_ESTABLISHED:
|
|
/* Start TM01 */
|
|
if(Q921_IS_NT(trunk)) {
|
|
Q921TM01TimerStart(trunk, tei);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* actions that depend on type of the old -> new state transition
|
|
*/
|
|
switch(oldstate) {
|
|
case Q921_STATE_STOPPED:
|
|
|
|
switch(state) {
|
|
case Q921_STATE_TEI_UNASSIGNED:
|
|
if(Q921_IS_PTMP_TE(trunk)) {
|
|
res = Q921TeiSendAssignRequest(trunk);
|
|
}
|
|
break;
|
|
|
|
case Q921_STATE_TEI_ASSIGNED:
|
|
if(Q921_IS_PTMP_NT(trunk)) {
|
|
res = Q921TeiSendRemoveRequest(trunk, Q921_TEI_BCAST);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
link->state = state;
|
|
|
|
Q921Log(trunk, Q921_LOG_DEBUG, "Q921ChangeState() returns %d, new state is \"%s\" (%d) for TEI %d\n", res, Q921State2Name(state), state, tei);
|
|
|
|
return res;
|
|
}
|
|
|
|
/*
|
|
* TEI Management functions
|
|
* \note All TEI-mgmt UN frames are sent with cr = command!
|
|
*/
|
|
static int Q921TeiSend(L2TRUNK trunk, L2UCHAR type, L2USHORT ri, L2UCHAR ai)
|
|
{
|
|
L2UCHAR mes[10];
|
|
L2UCHAR offset = Q921_UFRAME_DATA_OFFSET(trunk);
|
|
|
|
mes[offset++] = Q921_LAYER_ENT_ID_TEI; /* layer management entity identifier */
|
|
mes[offset++] = (ri & 0xff00) >> 8; /* reference number upper part */
|
|
mes[offset++] = ri & 0xff; /* reference number lower part */
|
|
mes[offset++] = type; /* message type: Identity Request */
|
|
mes[offset++] = ai << 1 | 0x01; /* action indicator: TEI */
|
|
|
|
return Q921SendU(trunk, Q921_SAPI_TEI, Q921_COMMAND(trunk), Q921_TEI_BCAST, 0, 0x00, mes, offset);
|
|
}
|
|
|
|
|
|
/**
|
|
* Q921TeiSendAssignRequest
|
|
* \brief Ask for new TEI (TE mode only)
|
|
* \param[in] trunk pointer to Q921 data struct
|
|
* \return > 0 on success, <= 0 on error
|
|
*
|
|
* \author Stefan Knoblich
|
|
*/
|
|
static int Q921TeiSendAssignRequest(L2TRUNK trunk)
|
|
{
|
|
struct Q921_Link *link = Q921_TRUNK_CONTEXT(trunk);
|
|
L2INT res;
|
|
|
|
if (!Q921_IS_PTMP_TE(trunk)) /* only ptmp te mode*/
|
|
return 0;
|
|
|
|
#ifndef WIN32
|
|
link->ri = (L2USHORT)(random() % 0xffff);
|
|
#else
|
|
link->ri = (L2USHORT)(rand() % 0xffff); //todo
|
|
#endif
|
|
|
|
/* send TEI assign request */
|
|
res = Q921TeiSend(trunk, Q921_TEI_ID_REQUEST, link->ri, Q921_TEI_BCAST);
|
|
|
|
/* start T202 */
|
|
Q921T202TimerStart(trunk);
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
/**
|
|
* Q921TeiProcessAssignResponse
|
|
* \brief Process assign response (TE mode only)
|
|
* \param[in] trunk pointer to Q921 data struct
|
|
* \param[in] mes pointer to message buffer
|
|
* \param[in] size size of message
|
|
* \return > 0 on success; <= 0 on error
|
|
*
|
|
* \author Stefan Knoblich
|
|
*/
|
|
static int Q921TeiProcAssignResponse(L2TRUNK trunk, L2UCHAR *mes, L2INT size)
|
|
{
|
|
struct Q921_Link *link = Q921_TRUNK_CONTEXT(trunk);
|
|
L2UCHAR offset = Q921_UFRAME_DATA_OFFSET(trunk);
|
|
L2USHORT ri = 0;
|
|
|
|
if (!Q921_IS_PTMP_TE(trunk)) /* PTMP TE only */
|
|
return 0;
|
|
|
|
ri = (mes[offset + 1] << 8) | mes[offset + 2];
|
|
|
|
if(ri != link->ri) {
|
|
/* hmmm ..., not our response i guess */
|
|
return 0;
|
|
}
|
|
|
|
switch(mes[offset + 3]) {
|
|
case Q921_TEI_ID_ASSIGNED:
|
|
/* Yay, use the new TEI and change state to assigned */
|
|
link->tei = mes[offset + 4] >> 1;
|
|
|
|
Q921Log(trunk, Q921_LOG_DEBUG, "Assigned TEI %d, setting state to TEI_ASSIGNED\n", link->tei);
|
|
|
|
Q921ChangeState(trunk, Q921_STATE_TEI_ASSIGNED, link->tei);
|
|
break;
|
|
|
|
case Q921_TEI_ID_DENIED:
|
|
/* oops, what to do now? */
|
|
if ((mes[offset + 4] >> 1) == Q921_TEI_BCAST) {
|
|
/* No more free TEIs? this is bad */
|
|
|
|
//Q921TeiSendVerifyRequest(trunk, Q921_TEI_BCAST); /* TODO: does this work ?? */
|
|
} else {
|
|
/* other reason, this is fatal, shutdown link */
|
|
}
|
|
|
|
Q921Log(trunk, Q921_LOG_DEBUG, "TEI assignment has been denied, reason: %s\n",
|
|
((mes[offset +4] >> 1) == Q921_TEI_BCAST) ? "No free TEIs available" : "Unknown");
|
|
|
|
Q921ChangeState(trunk, Q921_STATE_TEI_UNASSIGNED, link->tei);
|
|
break;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
/* stop T202 */
|
|
Q921T202TimerStop(trunk);
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/**
|
|
* Q921TeiSendVerifyRequest
|
|
* \brief Verify TEI (TE mode only)
|
|
* \param[in] trunk pointer to Q921 data struct
|
|
* \return > 0 on success; <= 0 on error
|
|
*
|
|
* \author Stefan Knoblich
|
|
*/
|
|
static int Q921TeiSendVerifyRequest(L2TRUNK trunk)
|
|
{
|
|
struct Q921_Link *link = Q921_TRUNK_CONTEXT(trunk);
|
|
L2INT res;
|
|
|
|
if (!Q921_IS_PTMP_TE(trunk)) /* only ptmp te mode*/
|
|
return 0;
|
|
|
|
/* Request running? */
|
|
if (trunk->T202)
|
|
return 0;
|
|
|
|
/* Send TEI verify request */
|
|
res = Q921TeiSend(trunk, Q921_TEI_ID_VERIFY, link->ri, link->tei);
|
|
|
|
/* start T202 */
|
|
Q921T202TimerStart(trunk);
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
/**
|
|
* Q921TeiProcCheckRequest
|
|
* \brief Process Check Request (TE mode only)
|
|
* \param[in] trunk pointer to Q921 data struct
|
|
* \param[in] mes pointer to message buffer
|
|
* \param[in] size size of message
|
|
* \return > 0 on success; <= 0 on error
|
|
*
|
|
* \author Stefan Knoblich
|
|
*/
|
|
static int Q921TeiProcCheckRequest(L2TRUNK trunk, L2UCHAR *mes, L2INT size)
|
|
{
|
|
struct Q921_Link *link = Q921_TRUNK_CONTEXT(trunk);
|
|
L2UCHAR offset = Q921_UFRAME_DATA_OFFSET(trunk);
|
|
L2UCHAR tei = (mes[offset + 4] >> 1); /* action indicator => tei */
|
|
L2INT res = 0;
|
|
|
|
if (!Q921_IS_PTMP_TE(trunk)) /* ptmp te mode only */
|
|
return 0;
|
|
|
|
Q921Log(trunk, Q921_LOG_DEBUG, "Received TEI Check request for TEI %d\n", tei);
|
|
|
|
if (tei == Q921_TEI_BCAST || tei == link->tei) {
|
|
/*
|
|
* Broadcast TEI check or for our assigned TEI
|
|
*/
|
|
|
|
/* send TEI check reponse */
|
|
res = Q921TeiSend(trunk, Q921_TEI_ID_CHECKRESP, link->ri, link->tei);
|
|
|
|
Q921T202TimerStop(trunk);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
/**
|
|
* Q921TeiProcRemoveRequest
|
|
* \brief Process remove Request (TE mode only)
|
|
* \param[in] trunk pointer to Q921 data struct
|
|
* \param[in] mes pointer to message buffer
|
|
* \param[in] size size of message
|
|
* \return > 0 on success; <= 0 on error
|
|
*
|
|
* \author Stefan Knoblich
|
|
*/
|
|
static int Q921TeiProcRemoveRequest(L2TRUNK trunk, L2UCHAR *mes, L2INT size)
|
|
{
|
|
struct Q921_Link *link = Q921_TRUNK_CONTEXT(trunk);
|
|
L2UCHAR offset = Q921_UFRAME_DATA_OFFSET(trunk);
|
|
L2UCHAR tei = (mes[offset + 4] >> 1); /* action indicator => tei */
|
|
L2INT res = 0;
|
|
|
|
if (!Q921_IS_PTMP_TE(trunk)) /* ptmp te mode only */
|
|
return 0;
|
|
|
|
Q921Log(trunk, Q921_LOG_DEBUG, "Received TEI Remove request for TEI %d\n", tei);
|
|
|
|
if (tei == Q921_TEI_BCAST || tei == link->tei) {
|
|
/*
|
|
* Broadcast TEI remove or for our assigned TEI
|
|
*/
|
|
|
|
/* reset tei */
|
|
link->tei = 0;
|
|
|
|
/* change state (no action) */
|
|
Q921ChangeState(trunk, Q921_STATE_TEI_UNASSIGNED, link->tei);
|
|
|
|
/* TODO: hmm, request new one ? */
|
|
res = Q921TeiSendAssignRequest(trunk);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
/**
|
|
* Q921TeiProcAssignRequest
|
|
* \brief Process assign request from peer (NT mode only)
|
|
* \param[in] trunk pointer to Q921 data struct
|
|
* \param[in] mes pointer to message buffer
|
|
* \param[in] size size of message
|
|
* \return > 0 on success; <= 0 on error
|
|
*
|
|
* \author Stefan Knoblich
|
|
*/
|
|
static int Q921TeiProcAssignRequest(L2TRUNK trunk, L2UCHAR *mes, L2INT size)
|
|
{
|
|
L2UCHAR offset = Q921_UFRAME_DATA_OFFSET(trunk);
|
|
L2USHORT ri = 0;
|
|
L2UCHAR tei = 0;
|
|
|
|
if (!Q921_IS_PTMP_NT(trunk)) /* PTMP NT only */
|
|
return 0;
|
|
|
|
ri = (mes[offset + 1] << 8) | mes[offset + 2];
|
|
tei = mes[offset + 4] >> 1;
|
|
|
|
if(tei == Q921_TEI_BCAST) {
|
|
int x;
|
|
|
|
/* dynamically allocate TEI */
|
|
for(x = Q921_TEI_DYN_MIN, tei = 0; x <= Q921_TEI_MAX; x++) {
|
|
if(!trunk->tei_map[x]) {
|
|
tei = x;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if(!(tei > 0 && tei < Q921_TEI_DYN_MIN)) {
|
|
/* reject TEIs that are not in the static area */
|
|
Q921TeiSendDenyResponse(trunk, 0, ri);
|
|
|
|
return 0;
|
|
}
|
|
|
|
if(!tei) {
|
|
/* no free TEI found */
|
|
Q921TeiSendDenyResponse(trunk, Q921_TEI_BCAST, ri);
|
|
}
|
|
else {
|
|
struct Q921_Link *link = Q921_LINK_CONTEXT(trunk, tei);
|
|
|
|
/* mark used */
|
|
trunk->tei_map[tei] = 1;
|
|
|
|
/* assign tei */
|
|
link->tei = tei;
|
|
|
|
/* put context in TEI ASSIGNED state */
|
|
Q921ChangeState(trunk, Q921_STATE_TEI_ASSIGNED, tei);
|
|
|
|
/* send assign response */
|
|
Q921TeiSendAssignedResponse(trunk, tei, ri);
|
|
|
|
/* Start T201 */
|
|
Q921T201TimerStart(trunk, tei);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Q921TeiSendCheckRequest
|
|
* \brief Send check request (NT mode only)
|
|
* \param[in] trunk pointer to Q921 data struct
|
|
* \param[in] tei TEI to check
|
|
* \return > 0 on success; <= 0 on error
|
|
*
|
|
* \author Stefan Knoblich
|
|
*/
|
|
static int Q921TeiSendCheckRequest(L2TRUNK trunk, L2UCHAR tei)
|
|
{
|
|
L2INT res = 0;
|
|
|
|
if (!Q921_IS_PTMP_NT(trunk)) /* PTMP NT only */
|
|
return 0;
|
|
|
|
/* send TEI check request */
|
|
res = Q921TeiSend(trunk, Q921_TEI_ID_CHECKREQ, 0, tei);
|
|
|
|
/* (Re-)Start T201 timer */
|
|
Q921T201TimerStart(trunk, tei);
|
|
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* Q921TeiProcCheckResponse
|
|
* \brief Process Check Response (NT mode only)
|
|
* \param[in] trunk pointer to Q921 data struct
|
|
* \param[in] mes pointer to message buffer
|
|
* \param[in] size size of message
|
|
* \return > 0 on success; <= 0 on error
|
|
*
|
|
* \author Stefan Knoblich
|
|
*/
|
|
static int Q921TeiProcCheckResponse(L2TRUNK trunk, L2UCHAR *mes, L2INT size)
|
|
{
|
|
struct Q921_Link *link;
|
|
L2UCHAR offset = Q921_UFRAME_DATA_OFFSET(trunk);
|
|
L2USHORT ri = 0;
|
|
L2UCHAR tei = 0;
|
|
|
|
if (!Q921_IS_PTMP_NT(trunk)) /* PTMP NT mode only */
|
|
return 0;
|
|
|
|
ri = (mes[offset + 1] << 8) | mes[offset + 2];
|
|
tei = mes[offset + 4] >> 1;
|
|
|
|
/* restart T201 */
|
|
Q921T201TimerStop(trunk, tei);
|
|
|
|
/* reset counter */
|
|
link = Q921_LINK_CONTEXT(trunk, tei);
|
|
link->N202 = 0;
|
|
|
|
if(!(tei > 0 && tei < Q921_TEI_MAX) || !trunk->tei_map[tei]) {
|
|
/* TODO: Should we send a DISC first? */
|
|
|
|
/* TEI not assigned? Invalid TEI? */
|
|
Q921TeiSendRemoveRequest(trunk, tei);
|
|
|
|
/* change state */
|
|
Q921ChangeState(trunk, Q921_STATE_STOPPED, tei);
|
|
|
|
/* clear */
|
|
memset(link, 0, sizeof(struct Q921_Link));
|
|
} else {
|
|
/* Start T201 */
|
|
Q921T201TimerStart(trunk, tei);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Q921TeiProcVerifyRequest
|
|
* \brief Process Verify Request (NT mode only)
|
|
* \param[in] trunk pointer to Q921 data struct
|
|
* \param[in] mes pointer to message buffer
|
|
* \param[in] size size of message
|
|
* \return > 0 on success; <= 0 on error
|
|
*
|
|
* \author Stefan Knoblich
|
|
*/
|
|
static int Q921TeiProcVerifyRequest(L2TRUNK trunk, L2UCHAR *mes, L2INT size)
|
|
{
|
|
L2UCHAR resp[25];
|
|
L2UCHAR offset = Q921_UFRAME_DATA_OFFSET(trunk);
|
|
L2UCHAR tei = 0;
|
|
|
|
if (!Q921_IS_PTMP_NT(trunk)) /* PTMP NT mode only */
|
|
return 0;
|
|
|
|
tei = mes[offset + 4] >> 1;
|
|
|
|
/* todo: handle response... verify assigned TEI */
|
|
resp[offset + 0] = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Q921TeiSendDenyResponse
|
|
* \brief Send Deny Response (NT mode only)
|
|
* \param[in] trunk pointer to Q921 data struct
|
|
* \return > 0 on success; <= 0 on error
|
|
*
|
|
* \author Stefan Knoblich
|
|
*/
|
|
static int Q921TeiSendDenyResponse(L2TRUNK trunk, L2UCHAR tei, L2USHORT ri)
|
|
{
|
|
if (!Q921_IS_PTMP_NT(trunk)) /* PTMP NT only */
|
|
return 0;
|
|
|
|
return Q921TeiSend(trunk, Q921_TEI_ID_DENIED, ri, tei);
|
|
}
|
|
|
|
|
|
/**
|
|
* Q921TeiSendAssignedResponse
|
|
* \brief Send Assigned Response (NT mode only)
|
|
* \param[in] trunk pointer to Q921 data struct
|
|
* \param[in] tei TEI to assign
|
|
* \param[in] ri RI of request
|
|
* \return > 0 on success; <= 0 on error
|
|
*
|
|
* \author Stefan Knoblich
|
|
*/
|
|
static int Q921TeiSendAssignedResponse(L2TRUNK trunk, L2UCHAR tei, L2USHORT ri)
|
|
{
|
|
if (!Q921_IS_PTMP_NT(trunk)) /* PTMP NT only */
|
|
return 0;
|
|
|
|
return Q921TeiSend(trunk, Q921_TEI_ID_ASSIGNED, ri, tei);
|
|
}
|
|
|
|
/**
|
|
* Q921TeiSendRemoveRequest
|
|
* \brief Send Remove Request (NT mode only)
|
|
* \param[in] trunk pointer to Q921 data struct
|
|
* \param[in] tei TEI to remove
|
|
* \return > 0 on success; <= 0 on error
|
|
*
|
|
* \author Stefan Knoblich
|
|
*/
|
|
static int Q921TeiSendRemoveRequest(L2TRUNK trunk, L2UCHAR tei)
|
|
{
|
|
if (!Q921_IS_PTMP_NT(trunk)) /* PTMP NT only */
|
|
return 0;
|
|
|
|
return Q921TeiSend(trunk, Q921_TEI_ID_REMOVE, 0, tei);
|
|
}
|