1569 lines
43 KiB
C
1569 lines
43 KiB
C
/*
|
|
* SpanDSP - a series of DSP components for telephony
|
|
*
|
|
* v42.c
|
|
*
|
|
* Written by Steve Underwood <steveu@coppice.org>
|
|
*
|
|
* Copyright (C) 2004, 2011 Steve Underwood
|
|
*
|
|
* All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License version 2.1,
|
|
* as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*/
|
|
|
|
/* THIS IS A WORK IN PROGRESS. IT IS NOT FINISHED. */
|
|
|
|
/*! \file */
|
|
|
|
#if defined(HAVE_CONFIG_H)
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <inttypes.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <ctype.h>
|
|
#include <assert.h>
|
|
|
|
#include "spandsp/telephony.h"
|
|
#include "spandsp/logging.h"
|
|
#include "spandsp/bit_operations.h"
|
|
#include "spandsp/async.h"
|
|
#include "spandsp/hdlc.h"
|
|
#include "spandsp/v42.h"
|
|
|
|
#include "spandsp/private/logging.h"
|
|
#include "spandsp/private/hdlc.h"
|
|
#include "spandsp/private/v42.h"
|
|
|
|
#define FALSE 0
|
|
#define TRUE (!FALSE)
|
|
|
|
/* Detection phase timer */
|
|
#define T_400 750
|
|
/* Acknowledgement timer - 1 second between SABME's */
|
|
#define T_401 1000
|
|
/* Replay delay timer (optional) */
|
|
#define T_402 1000
|
|
/* Inactivity timer (optional). No default - use 10 seconds with no packets */
|
|
#define T_403 10000
|
|
|
|
#define LAPM_DLCI_DTE_TO_DTE 0
|
|
#define LAPM_DLCI_LAYER2_MANAGEMENT 63
|
|
|
|
#define elements(a) (sizeof(a)/sizeof((a)[0]))
|
|
|
|
/* LAPM definitions */
|
|
|
|
#define LAPM_FRAMETYPE_MASK 0x03
|
|
|
|
enum
|
|
{
|
|
LAPM_FRAMETYPE_I = 0x00,
|
|
LAPM_FRAMETYPE_I_ALT = 0x02,
|
|
LAPM_FRAMETYPE_S = 0x01,
|
|
LAPM_FRAMETYPE_U = 0x03
|
|
};
|
|
|
|
/* Supervisory headers */
|
|
enum
|
|
{
|
|
LAPM_S_RR = 0x00, /* cr */
|
|
LAPM_S_RNR = 0x04, /* cr */
|
|
LAPM_S_REJ = 0x08, /* cr */
|
|
LAPM_S_SREJ = 0x0C /* cr */
|
|
};
|
|
|
|
#define LAPM_S_PF 0x01
|
|
|
|
/* Unnumbered headers */
|
|
enum
|
|
{
|
|
LAPM_U_UI = 0x00, /* cr */
|
|
LAPM_U_DM = 0x0C, /* r */
|
|
LAPM_U_DISC = 0x40, /* c */
|
|
LAPM_U_UA = 0x60, /* r */
|
|
LAPM_U_SABME = 0x6C, /* c */
|
|
LAPM_U_FRMR = 0x84, /* r */
|
|
LAPM_U_XID = 0xAC, /* cr */
|
|
LAPM_U_TEST = 0xE0 /* c */
|
|
};
|
|
|
|
#define LAPM_U_PF 0x10
|
|
|
|
/* XID sub-field definitions */
|
|
#define FI_GENERAL 0x82
|
|
#define GI_PARAM_NEGOTIATION 0x80
|
|
#define GI_PRIVATE_NEGOTIATION 0xF0
|
|
#define GI_USER_DATA 0xFF
|
|
|
|
/* Param negotiation (Table 11a/V.42) */
|
|
enum
|
|
{
|
|
PI_HDLC_OPTIONAL_FUNCTIONS = 0x03,
|
|
PI_TX_INFO_MAXSIZE = 0x05,
|
|
PI_RX_INFO_MAXSIZE = 0x06,
|
|
PI_TX_WINDOW_SIZE = 0x07,
|
|
PI_RX_WINDOW_SIZE = 0x08
|
|
};
|
|
|
|
/* Private param negotiation (Table 11b/V.42) */
|
|
enum
|
|
{
|
|
PI_PARAMETER_SET_ID = 0x00,
|
|
PI_V42BIS_COMPRESSION_REQUEST = 0x01,
|
|
PI_V42BIS_NUM_CODEWORDS = 0x02,
|
|
PI_V42BIS_MAX_STRING_LENGTH = 0x03
|
|
};
|
|
|
|
#define LAPM_DLCI_DTE_TO_DTE 0
|
|
#define LAPM_DLCI_LAYER2_MANAGEMENT 63
|
|
|
|
/* Type definitions */
|
|
enum
|
|
{
|
|
LAPM_DETECT = 0,
|
|
LAPM_IDLE = 1,
|
|
LAPM_ESTABLISH = 2,
|
|
LAPM_DATA = 3,
|
|
LAPM_RELEASE = 4,
|
|
LAPM_SIGNAL = 5,
|
|
LAPM_SETPARM = 6,
|
|
LAPM_TEST = 7,
|
|
LAPM_V42_UNSUPPORTED = 8
|
|
};
|
|
|
|
/* Prototypes */
|
|
static int lapm_connect(v42_state_t *ss);
|
|
static int lapm_disconnect(v42_state_t *s);
|
|
static void reset_lapm(v42_state_t *s);
|
|
static void lapm_hdlc_underflow(void *user_data);
|
|
|
|
static int lapm_config(v42_state_t *ss);
|
|
|
|
SPAN_DECLARE(const char *) lapm_status_to_str(int status)
|
|
{
|
|
switch (status)
|
|
{
|
|
case LAPM_DETECT:
|
|
return "LAPM_DETECT";
|
|
case LAPM_IDLE:
|
|
return "LAPM_IDLE";
|
|
case LAPM_ESTABLISH:
|
|
return "LAPM_ESTABLISH";
|
|
case LAPM_DATA:
|
|
return "LAPM_DATA";
|
|
case LAPM_RELEASE:
|
|
return "LAPM_RELEASE";
|
|
case LAPM_SIGNAL:
|
|
return "LAPM_SIGNAL";
|
|
case LAPM_SETPARM:
|
|
return "LAPM_SETPARM";
|
|
case LAPM_TEST:
|
|
return "LAPM_TEST";
|
|
case LAPM_V42_UNSUPPORTED:
|
|
return "LAPM_V42_UNSUPPORTED";
|
|
}
|
|
/*endswitch*/
|
|
return "???";
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void report_rx_status_change(v42_state_t *s, int status)
|
|
{
|
|
if (s->lapm.status_handler)
|
|
s->lapm.status_handler(s->lapm.status_user_data, status);
|
|
else if (s->lapm.iframe_put)
|
|
s->lapm.iframe_put(s->lapm.iframe_put_user_data, NULL, status);
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static inline uint32_t pack_value(const uint8_t *buf, int len)
|
|
{
|
|
uint32_t val;
|
|
|
|
val = 0;
|
|
while (len--)
|
|
{
|
|
val <<= 8;
|
|
val |= *buf++;
|
|
}
|
|
return val;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static inline v42_frame_t *get_next_free_ctrl_frame(lapm_state_t *s)
|
|
{
|
|
v42_frame_t *f;
|
|
int ctrl_put_next;
|
|
|
|
if ((ctrl_put_next = s->ctrl_put + 1) >= V42_CTRL_FRAMES)
|
|
ctrl_put_next = 0;
|
|
if (ctrl_put_next == s->ctrl_get)
|
|
return NULL;
|
|
f = &s->ctrl_buf[s->ctrl_put];
|
|
s->ctrl_put = ctrl_put_next;
|
|
return f;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int tx_unnumbered_frame(lapm_state_t *s, uint8_t addr, uint8_t ctrl, uint8_t *info, int len)
|
|
{
|
|
v42_frame_t *f;
|
|
uint8_t *buf;
|
|
|
|
if ((f = get_next_free_ctrl_frame(s)) == NULL)
|
|
return -1;
|
|
buf = f->buf;
|
|
buf[0] = addr;
|
|
buf[1] = LAPM_FRAMETYPE_U | ctrl;
|
|
f->len = 2;
|
|
if (info && len)
|
|
{
|
|
memcpy(buf + f->len, info, len);
|
|
f->len += len;
|
|
}
|
|
return 0;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int tx_supervisory_frame(lapm_state_t *s, uint8_t addr, uint8_t ctrl, uint8_t pf_mask)
|
|
{
|
|
v42_frame_t *f;
|
|
uint8_t *buf;
|
|
|
|
if ((f = get_next_free_ctrl_frame(s)) == NULL)
|
|
return -1;
|
|
buf = f->buf;
|
|
buf[0] = addr;
|
|
buf[1] = LAPM_FRAMETYPE_S | ctrl;
|
|
buf[2] = (s->vr << 1) | pf_mask;
|
|
f->len = 3;
|
|
return 0;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static __inline__ int set_param(int param, int value, int def)
|
|
{
|
|
if ((value < def && param >= def) || (value >= def && param < def))
|
|
return def;
|
|
if ((value < def && param < value) || (value >= def && param > value))
|
|
return value;
|
|
return param;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int receive_xid(v42_state_t *ss, const uint8_t *frame, int len)
|
|
{
|
|
lapm_state_t *s;
|
|
v42_config_parameters_t config;
|
|
const uint8_t *buf;
|
|
uint8_t group_id;
|
|
uint16_t group_len;
|
|
uint32_t param_val;
|
|
uint8_t param_id;
|
|
uint8_t param_len;
|
|
|
|
s = &ss->lapm;
|
|
if (frame[2] != FI_GENERAL)
|
|
return -1;
|
|
memset(&config, 0, sizeof(config));
|
|
/* Skip the header octets */
|
|
frame += 3;
|
|
len -= 3;
|
|
while (len > 0)
|
|
{
|
|
group_id = frame[0];
|
|
group_len = frame[1];
|
|
group_len = (group_len << 8) | frame[2];
|
|
frame += 3;
|
|
len -= (3 + group_len);
|
|
if (len < 0)
|
|
break;
|
|
buf = frame;
|
|
frame += group_len;
|
|
switch (group_id)
|
|
{
|
|
case GI_PARAM_NEGOTIATION:
|
|
while (group_len > 0)
|
|
{
|
|
param_id = buf[0];
|
|
param_len = buf[1];
|
|
buf += 2;
|
|
group_len -= (2 + param_len);
|
|
if (group_len < 0)
|
|
break;
|
|
switch (param_id)
|
|
{
|
|
case PI_HDLC_OPTIONAL_FUNCTIONS:
|
|
param_val = pack_value(buf, param_len);
|
|
break;
|
|
case PI_TX_INFO_MAXSIZE:
|
|
param_val = pack_value(buf, param_len);
|
|
param_val >>= 3;
|
|
config.v42_tx_n401 =
|
|
s->tx_n401 = set_param(s->tx_n401, param_val, ss->config.v42_tx_n401);
|
|
break;
|
|
case PI_RX_INFO_MAXSIZE:
|
|
param_val = pack_value(buf, param_len);
|
|
param_val >>= 3;
|
|
config.v42_rx_n401 =
|
|
s->rx_n401 = set_param(s->rx_n401, param_val, ss->config.v42_rx_n401);
|
|
break;
|
|
case PI_TX_WINDOW_SIZE:
|
|
param_val = pack_value(buf, param_len);
|
|
config.v42_tx_window_size_k =
|
|
s->tx_window_size_k = set_param(s->tx_window_size_k, param_val, ss->config.v42_tx_window_size_k);
|
|
break;
|
|
case PI_RX_WINDOW_SIZE:
|
|
param_val = pack_value(buf, param_len);
|
|
config.v42_rx_window_size_k =
|
|
s->rx_window_size_k = set_param(s->rx_window_size_k, param_val, ss->config.v42_rx_window_size_k);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
buf += param_len;
|
|
}
|
|
break;
|
|
case GI_PRIVATE_NEGOTIATION:
|
|
while (group_len > 0)
|
|
{
|
|
param_id = buf[0];
|
|
param_len = buf[1];
|
|
buf += 2;
|
|
group_len -= (2 + param_len);
|
|
if (group_len < 0)
|
|
break;
|
|
switch (param_id)
|
|
{
|
|
case PI_PARAMETER_SET_ID:
|
|
/* This might be worth monitoring, but it doesn't serve mnuch other purpose */
|
|
break;
|
|
case PI_V42BIS_COMPRESSION_REQUEST:
|
|
config.comp = pack_value(buf, param_len);
|
|
break;
|
|
case PI_V42BIS_NUM_CODEWORDS:
|
|
config.comp_dict_size = pack_value(buf, param_len);
|
|
break;
|
|
case PI_V42BIS_MAX_STRING_LENGTH:
|
|
config.comp_max_string = pack_value(buf, param_len);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
buf += param_len;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
//v42_update_config(ss, &config);
|
|
return 0;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void transmit_xid(v42_state_t *ss, uint8_t addr)
|
|
{
|
|
lapm_state_t *s;
|
|
uint8_t *buf;
|
|
int len;
|
|
int group_len;
|
|
uint32_t param_val;
|
|
v42_frame_t *f;
|
|
|
|
s = &ss->lapm;
|
|
if ((f = get_next_free_ctrl_frame(s)) == NULL)
|
|
return;
|
|
|
|
buf = f->buf;
|
|
len = 0;
|
|
|
|
/* Figure 11/V.42 */
|
|
*buf++ = addr;
|
|
*buf++ = LAPM_U_XID | LAPM_FRAMETYPE_U;
|
|
/* Format identifier subfield */
|
|
*buf++ = FI_GENERAL;
|
|
len += 3;
|
|
|
|
/* Parameter negotiation group */
|
|
group_len = 20;
|
|
*buf++ = GI_PARAM_NEGOTIATION;
|
|
*buf++ = (group_len >> 8) & 0xFF;
|
|
*buf++ = group_len & 0xFF;
|
|
len += 3;
|
|
|
|
/* For conformance with the encoding rules in ISO/IEC 8885, the transmitter of an XID command frame shall
|
|
set bit positions 2, 4, 8, 9, 12 and 16 to 1. (Table 11a/V.42)
|
|
Optional bits are:
|
|
3 Selective retransmission procedure (SREJ frame) single I frame request
|
|
14 Loop-back test procedure (TEST frame)
|
|
17
|
|
Extended FCS procedure (32-bit FCS)
|
|
24 Selective retransmission procedure (SREJ frame) multiple I frame request with span list
|
|
capability. */
|
|
*buf++ = PI_HDLC_OPTIONAL_FUNCTIONS;
|
|
*buf++ = 4;
|
|
*buf++ = 0x8A; /* Bits 2, 4, and 8 set */
|
|
*buf++ = 0x89; /* Bits 9, 12, and 16 set */
|
|
*buf++ = 0x00;
|
|
*buf++ = 0x00;
|
|
|
|
/* Send the maximum as a number of bits, rather than octets */
|
|
param_val = ss->config.v42_tx_n401 << 3;
|
|
*buf++ = PI_TX_INFO_MAXSIZE;
|
|
*buf++ = 2;
|
|
*buf++ = (param_val >> 8) & 0xFF;
|
|
*buf++ = (param_val & 0xFF);
|
|
|
|
/* Send the maximum as a number of bits, rather than octets */
|
|
param_val = ss->config.v42_rx_n401 << 3;
|
|
*buf++ = PI_RX_INFO_MAXSIZE;
|
|
*buf++ = 2;
|
|
*buf++ = (param_val >> 8) & 0xFF;
|
|
*buf++ = (param_val & 0xFF);
|
|
|
|
*buf++ = PI_TX_WINDOW_SIZE;
|
|
*buf++ = 1;
|
|
*buf++ = ss->config.v42_tx_window_size_k;
|
|
|
|
*buf++ = PI_RX_WINDOW_SIZE;
|
|
*buf++ = 1;
|
|
*buf++ = ss->config.v42_rx_window_size_k;
|
|
|
|
len += group_len;
|
|
|
|
if (ss->config.comp)
|
|
{
|
|
/* Private parameter negotiation group */
|
|
group_len = 15;
|
|
*buf++ = GI_PRIVATE_NEGOTIATION;
|
|
*buf++ = (group_len >> 8) & 0xFF;
|
|
*buf++ = group_len & 0xFF;
|
|
len += 3;
|
|
|
|
/* Private parameter for V.42 (ASCII for V42). V.42 says ".42", but V.42bis says "V42",
|
|
and that seems to be what should be used. */
|
|
*buf++ = PI_PARAMETER_SET_ID;
|
|
*buf++ = 3;
|
|
*buf++ = 'V';
|
|
*buf++ = '4';
|
|
*buf++ = '2';
|
|
|
|
/* V.42bis P0
|
|
00 Compression in neither direction (default);
|
|
01 Negotiation initiator-responder direction only;
|
|
10 Negotiation responder-initiator direction only;
|
|
11 Both directions. */
|
|
*buf++ = PI_V42BIS_COMPRESSION_REQUEST;
|
|
*buf++ = 1;
|
|
*buf++ = ss->config.comp;
|
|
|
|
/* V.42bis P1 */
|
|
param_val = ss->config.comp_dict_size;
|
|
*buf++ = PI_V42BIS_NUM_CODEWORDS;
|
|
*buf++ = 2;
|
|
*buf++ = (param_val >> 8) & 0xFF;
|
|
*buf++ = param_val & 0xFF;
|
|
|
|
/* V.42bis P2 */
|
|
*buf++ = PI_V42BIS_MAX_STRING_LENGTH;
|
|
*buf++ = 1;
|
|
*buf++ = ss->config.comp_max_string;
|
|
|
|
len += group_len;
|
|
}
|
|
|
|
f->len = len;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int ms_to_bits(v42_state_t *s, int time)
|
|
{
|
|
return ((time*s->tx_bit_rate)/1000);
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void t400_expired(v42_state_t *ss)
|
|
{
|
|
/* Give up trying to detect a V.42 capable peer. */
|
|
ss->bit_timer = 0;
|
|
ss->lapm.state = LAPM_V42_UNSUPPORTED;
|
|
report_rx_status_change(ss, ss->lapm.state);
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static __inline__ void t400_start(v42_state_t *s)
|
|
{
|
|
s->bit_timer = ms_to_bits(s, T_400);
|
|
s->bit_timer_func = t400_expired;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static __inline__ void t400_stop(v42_state_t *s)
|
|
{
|
|
s->bit_timer = 0;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void t401_expired(v42_state_t *ss)
|
|
{
|
|
lapm_state_t *s;
|
|
|
|
span_log(&ss->logging, SPAN_LOG_FLOW, "T.401 expired\n");
|
|
s = &ss->lapm;
|
|
if (s->retry_count > V42_DEFAULT_N_400)
|
|
{
|
|
s->retry_count = 0;
|
|
switch (s->state)
|
|
{
|
|
case LAPM_ESTABLISH:
|
|
case LAPM_RELEASE:
|
|
s->state = LAPM_IDLE;
|
|
report_rx_status_change(ss, SIG_STATUS_LINK_DISCONNECTED);
|
|
break;
|
|
case LAPM_DATA:
|
|
lapm_disconnect(ss);
|
|
break;
|
|
}
|
|
return ;
|
|
}
|
|
s->retry_count++;
|
|
if (s->configuring)
|
|
{
|
|
transmit_xid(ss, s->cmd_addr);
|
|
}
|
|
else
|
|
{
|
|
switch (s->state)
|
|
{
|
|
case LAPM_ESTABLISH:
|
|
tx_unnumbered_frame(s, s->cmd_addr, LAPM_U_SABME | LAPM_U_PF, NULL, 0);
|
|
break;
|
|
case LAPM_RELEASE:
|
|
tx_unnumbered_frame(s, s->cmd_addr, LAPM_U_DISC | LAPM_U_PF, NULL, 0);
|
|
break;
|
|
case LAPM_DATA:
|
|
tx_supervisory_frame(s, s->cmd_addr, (s->local_busy) ? LAPM_S_RNR : LAPM_S_RR, 1);
|
|
break;
|
|
}
|
|
}
|
|
ss->bit_timer = ms_to_bits(ss, T_401);
|
|
ss->bit_timer_func = t401_expired;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static __inline__ void t401_start(v42_state_t *s)
|
|
{
|
|
s->bit_timer = ms_to_bits(s, T_401);
|
|
s->bit_timer_func = t401_expired;
|
|
s->lapm.retry_count = 0;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static __inline__ void t401_stop(v42_state_t *s)
|
|
{
|
|
s->bit_timer = 0;
|
|
s->lapm.retry_count = 0;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void t403_expired(v42_state_t *ss)
|
|
{
|
|
lapm_state_t *s;
|
|
|
|
span_log(&ss->logging, SPAN_LOG_FLOW, "T.403 expired\n");
|
|
if (ss->lapm.state != LAPM_DATA)
|
|
return;
|
|
s = &ss->lapm;
|
|
tx_supervisory_frame(s, s->cmd_addr, (ss->lapm.local_busy) ? LAPM_S_RNR : LAPM_S_RR, 1);
|
|
t401_start(ss);
|
|
ss->lapm.retry_count = 1;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static __inline__ void t401_stop_t403_start(v42_state_t *s)
|
|
{
|
|
s->bit_timer = ms_to_bits(s, T_403);
|
|
s->bit_timer_func = t403_expired;
|
|
s->lapm.retry_count = 0;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void initiate_negotiation_expired(v42_state_t *s)
|
|
{
|
|
/* Timer service routine */
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Start negotiation\n");
|
|
lapm_config(s);
|
|
lapm_hdlc_underflow(s);
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int tx_information_frame(v42_state_t *ss)
|
|
{
|
|
lapm_state_t *s;
|
|
v42_frame_t *f;
|
|
uint8_t *buf;
|
|
int n;
|
|
int info_put_next;
|
|
|
|
s = &ss->lapm;
|
|
if (s->far_busy || ((s->vs - s->va) & 0x7F) >= s->tx_window_size_k)
|
|
return FALSE;
|
|
if (s->info_get != s->info_put)
|
|
return TRUE;
|
|
if ((info_put_next = s->info_put + 1) >= V42_INFO_FRAMES)
|
|
info_put_next = 0;
|
|
if (info_put_next == s->info_get || info_put_next == s->info_acked)
|
|
return FALSE;
|
|
f = &s->info_buf[s->info_put];
|
|
buf = f->buf;
|
|
if (s->iframe_get == NULL)
|
|
return FALSE;
|
|
n = s->iframe_get(s->iframe_get_user_data, buf + 3, s->tx_n401);
|
|
if (n < 0)
|
|
{
|
|
/* Error */
|
|
report_rx_status_change(ss, SIG_STATUS_LINK_ERROR);
|
|
return FALSE;
|
|
}
|
|
if (n == 0)
|
|
return FALSE;
|
|
|
|
f->len = n + 3;
|
|
s->info_put = info_put_next;
|
|
return TRUE;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void tx_information_rr_rnr_response(v42_state_t *ss, const uint8_t *frame, int len)
|
|
{
|
|
lapm_state_t *s;
|
|
|
|
s = &ss->lapm;
|
|
/* Respond with information frame, RR, or RNR, as appropriate */
|
|
/* p = 1 may be used for status checking */
|
|
if ((frame[2] & 0x1) || !tx_information_frame(ss))
|
|
tx_supervisory_frame(s, frame[0], (s->local_busy) ? LAPM_S_RNR : LAPM_S_RR, 1);
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int reject_info(lapm_state_t *s)
|
|
{
|
|
uint8_t n;
|
|
|
|
/* Reject all non-acked frames */
|
|
if (s->state != LAPM_DATA)
|
|
return 0;
|
|
n = (s->vs - s->va) & 0x7F;
|
|
s->vs = s->va;
|
|
s->info_get = s->info_acked;
|
|
return n;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int ack_info(v42_state_t *ss, uint8_t nr)
|
|
{
|
|
lapm_state_t *s;
|
|
int n;
|
|
|
|
s = &ss->lapm;
|
|
/* Check that NR is valid - i.e. VA <= NR <= VS && VS-VA <= k */
|
|
if (!((((nr - s->va) & 0x7F) + ((s->vs - nr) & 0x7F)) <= s->tx_window_size_k
|
|
&&
|
|
((s->vs - s->va) & 0x7F) <= s->tx_window_size_k))
|
|
{
|
|
lapm_disconnect(ss);
|
|
return -1;
|
|
}
|
|
n = 0;
|
|
while (s->va != nr && s->info_acked != s->info_get)
|
|
{
|
|
if (++s->info_acked >= V42_INFO_FRAMES)
|
|
s->info_acked = 0;
|
|
s->va = (s->va + 1) & 0x7F;
|
|
n++;
|
|
}
|
|
if (n > 0 && s->retry_count == 0)
|
|
{
|
|
t401_stop_t403_start(ss);
|
|
/* 8.4.8 */
|
|
if (((s->vs - s->va) & 0x7F))
|
|
t401_start(ss);
|
|
}
|
|
return n;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int valid_data_state(v42_state_t *ss)
|
|
{
|
|
lapm_state_t *s;
|
|
|
|
s = &ss->lapm;
|
|
switch (s->state)
|
|
{
|
|
case LAPM_DETECT:
|
|
case LAPM_IDLE:
|
|
break;
|
|
case LAPM_ESTABLISH:
|
|
reset_lapm(ss);
|
|
s->state = LAPM_DATA;
|
|
report_rx_status_change(ss, SIG_STATUS_LINK_CONNECTED);
|
|
return 1;
|
|
case LAPM_DATA:
|
|
return 1;
|
|
case LAPM_RELEASE:
|
|
reset_lapm(ss);
|
|
s->state = LAPM_IDLE;
|
|
report_rx_status_change(ss, SIG_STATUS_LINK_DISCONNECTED);
|
|
break;
|
|
case LAPM_SIGNAL:
|
|
case LAPM_SETPARM:
|
|
case LAPM_TEST:
|
|
case LAPM_V42_UNSUPPORTED:
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void receive_information_frame(v42_state_t *ss, const uint8_t *frame, int len)
|
|
{
|
|
lapm_state_t *s;
|
|
int ret;
|
|
int n;
|
|
|
|
s = &ss->lapm;
|
|
if (!valid_data_state(ss))
|
|
return;
|
|
if (len > s->rx_n401 + 3)
|
|
return;
|
|
ret = 0;
|
|
/* Ack I frames: NR - 1 */
|
|
n = ack_info(ss, frame[2] >> 1);
|
|
if (s->local_busy)
|
|
{
|
|
/* 8.4.7 */
|
|
if ((frame[2] & 0x1))
|
|
tx_supervisory_frame(s, s->rsp_addr, LAPM_S_RNR, 1);
|
|
return;
|
|
}
|
|
/* NS sequence error */
|
|
if ((frame[1] >> 1) != s->vr)
|
|
{
|
|
if (!s->rejected)
|
|
{
|
|
tx_supervisory_frame(s, s->rsp_addr, LAPM_S_REJ, (frame[2] & 0x1));
|
|
s->rejected = TRUE;
|
|
}
|
|
return;
|
|
}
|
|
s->rejected = FALSE;
|
|
|
|
s->iframe_put(s->iframe_put_user_data, frame + 3, len - 3);
|
|
/* Increment vr */
|
|
s->vr = (s->vr + 1) & 0x7F;
|
|
tx_information_rr_rnr_response(ss, frame, len);
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void rx_supervisory_cmd_frame(v42_state_t *ss, const uint8_t *frame, int len)
|
|
{
|
|
lapm_state_t *s;
|
|
int n;
|
|
|
|
s = &ss->lapm;
|
|
/* If l->local_busy each RR,RNR,REJ with p=1 should be replied by RNR with f=1 (8.4.7) */
|
|
switch (frame[1] & 0x0C)
|
|
{
|
|
case LAPM_S_RR:
|
|
s->far_busy = FALSE;
|
|
n = ack_info(ss, frame[2] >> 1);
|
|
/* If p = 1 may be used for status checking? */
|
|
tx_information_rr_rnr_response(ss, frame, len);
|
|
break;
|
|
case LAPM_S_RNR:
|
|
s->far_busy = TRUE;
|
|
n = ack_info(ss, frame[2] >> 1);
|
|
/* If p = 1 may be used for status checking? */
|
|
if ((frame[2] & 0x1))
|
|
tx_supervisory_frame(s, s->rsp_addr, (s->local_busy) ? LAPM_S_RNR : LAPM_S_RR, 1);
|
|
break;
|
|
case LAPM_S_REJ:
|
|
s->far_busy = FALSE;
|
|
n = ack_info(ss, frame[2] >> 1);
|
|
if (s->retry_count == 0)
|
|
{
|
|
t401_stop_t403_start(ss);
|
|
reject_info(s);
|
|
}
|
|
tx_information_rr_rnr_response(ss, frame, len);
|
|
break;
|
|
case LAPM_S_SREJ:
|
|
/* TODO: */
|
|
return;
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void rx_supervisory_rsp_frame(v42_state_t *ss, const uint8_t *frame, int len)
|
|
{
|
|
lapm_state_t *s;
|
|
int n;
|
|
|
|
s = &ss->lapm;
|
|
if (s->retry_count == 0 && (frame[2] & 0x1))
|
|
return;
|
|
/* Ack I frames <= NR - 1 */
|
|
switch (frame[1] & 0x0C)
|
|
{
|
|
case LAPM_S_RR:
|
|
s->far_busy = FALSE;
|
|
n = ack_info(ss, frame[2] >> 1);
|
|
if (s->retry_count && (frame[2] & 0x1))
|
|
{
|
|
reject_info(s);
|
|
t401_stop_t403_start(ss);
|
|
}
|
|
break;
|
|
case LAPM_S_RNR:
|
|
s->far_busy = TRUE;
|
|
n = ack_info(ss, frame[2] >> 1);
|
|
if (s->retry_count && (frame[2] & 0x1))
|
|
{
|
|
reject_info(s);
|
|
t401_stop_t403_start(ss);
|
|
}
|
|
if (s->retry_count == 0)
|
|
t401_start(ss);
|
|
break;
|
|
case LAPM_S_REJ:
|
|
s->far_busy = FALSE;
|
|
n = ack_info(ss, frame[2] >> 1);
|
|
if (s->retry_count == 0 || (frame[2] & 0x1))
|
|
{
|
|
reject_info(s);
|
|
t401_stop_t403_start(ss);
|
|
}
|
|
break;
|
|
case LAPM_S_SREJ:
|
|
/* TODO: */
|
|
return;
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int rx_unnumbered_cmd_frame(v42_state_t *ss, const uint8_t *frame, int len)
|
|
{
|
|
lapm_state_t *s;
|
|
|
|
s = &ss->lapm;
|
|
switch (frame[1] & 0xEC)
|
|
{
|
|
case LAPM_U_SABME:
|
|
/* Discard un-acked I frames. Reset vs, vr, and va. Clear exceptions */
|
|
reset_lapm(ss);
|
|
/* Going to connected state */
|
|
s->state = LAPM_DATA;
|
|
/* Respond UA (or DM on error) */
|
|
// fixme: why may be error and LAPM_U_DM ??
|
|
tx_unnumbered_frame(s, s->rsp_addr, LAPM_U_UA | (frame[1] & 0x10), NULL, 0);
|
|
t401_stop_t403_start(ss);
|
|
report_rx_status_change(ss, SIG_STATUS_LINK_CONNECTED);
|
|
break;
|
|
case LAPM_U_UI:
|
|
/* Break signal */
|
|
/* TODO: */
|
|
break;
|
|
case LAPM_U_DISC:
|
|
/* Respond UA (or DM) */
|
|
if (s->state == LAPM_IDLE)
|
|
{
|
|
tx_unnumbered_frame(s, s->rsp_addr, LAPM_U_DM | LAPM_U_PF, NULL, 0);
|
|
}
|
|
else
|
|
{
|
|
/* Going to disconnected state, discard unacked I frames, reset all. */
|
|
s->state = LAPM_IDLE;
|
|
reset_lapm(ss);
|
|
tx_unnumbered_frame(s, s->rsp_addr, LAPM_U_UA | (frame[1] & 0x10), NULL, 0);
|
|
t401_stop(ss);
|
|
/* TODO: notify CF */
|
|
report_rx_status_change(ss, SIG_STATUS_LINK_DISCONNECTED);
|
|
}
|
|
break;
|
|
case LAPM_U_XID:
|
|
/* Exchange general ID info */
|
|
receive_xid(ss, frame, len);
|
|
transmit_xid(ss, s->rsp_addr);
|
|
break;
|
|
case LAPM_U_TEST:
|
|
/* TODO: */
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int rx_unnumbered_rsp_frame(v42_state_t *ss, const uint8_t *frame, int len)
|
|
{
|
|
lapm_state_t *s;
|
|
|
|
s = &ss->lapm;
|
|
switch (frame[1] & 0xEC)
|
|
{
|
|
case LAPM_U_DM:
|
|
switch (s->state)
|
|
{
|
|
case LAPM_IDLE:
|
|
if (!(frame[1] & 0x10))
|
|
{
|
|
/* TODO: notify CF */
|
|
report_rx_status_change(ss, SIG_STATUS_LINK_CONNECTED);
|
|
}
|
|
break;
|
|
case LAPM_ESTABLISH:
|
|
case LAPM_RELEASE:
|
|
if ((frame[1] & 0x10))
|
|
{
|
|
s->state = LAPM_IDLE;
|
|
reset_lapm(ss);
|
|
t401_stop(ss);
|
|
/* TODO: notify CF */
|
|
report_rx_status_change(ss, SIG_STATUS_LINK_DISCONNECTED);
|
|
}
|
|
break;
|
|
case LAPM_DATA:
|
|
if (s->retry_count || !(frame[1] & 0x10))
|
|
{
|
|
s->state = LAPM_IDLE;
|
|
reset_lapm(ss);
|
|
/* TODO: notify CF */
|
|
report_rx_status_change(ss, SIG_STATUS_LINK_DISCONNECTED);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case LAPM_U_UI:
|
|
/* TODO: */
|
|
break;
|
|
case LAPM_U_UA:
|
|
switch (s->state)
|
|
{
|
|
case LAPM_ESTABLISH:
|
|
s->state = LAPM_DATA;
|
|
reset_lapm(ss);
|
|
t401_stop_t403_start(ss);
|
|
report_rx_status_change(ss, SIG_STATUS_LINK_CONNECTED);
|
|
break;
|
|
case LAPM_RELEASE:
|
|
s->state = LAPM_IDLE;
|
|
reset_lapm(ss);
|
|
t401_stop(ss);
|
|
report_rx_status_change(ss, SIG_STATUS_LINK_DISCONNECTED);
|
|
break;
|
|
default:
|
|
/* Unsolicited UA */
|
|
/* TODO: */
|
|
break;
|
|
}
|
|
/* Clear all exceptions, busy states (self and peer) */
|
|
/* Reset vars */
|
|
break;
|
|
case LAPM_U_FRMR:
|
|
/* Non-recoverable error */
|
|
/* TODO: */
|
|
break;
|
|
case LAPM_U_XID:
|
|
if (s->configuring)
|
|
{
|
|
receive_xid(ss, frame, len);
|
|
s->configuring = FALSE;
|
|
t401_stop(ss);
|
|
switch (s->state)
|
|
{
|
|
case LAPM_IDLE:
|
|
lapm_connect(ss);
|
|
break;
|
|
case LAPM_DATA:
|
|
s->local_busy = FALSE;
|
|
tx_supervisory_frame(s, s->cmd_addr, LAPM_S_RR, 0);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void lapm_hdlc_underflow(void *user_data)
|
|
{
|
|
lapm_state_t *s;
|
|
v42_state_t *ss;
|
|
v42_frame_t *f;
|
|
|
|
ss = (v42_state_t *) user_data;
|
|
s = &ss->lapm;
|
|
if (s->ctrl_get != s->ctrl_put)
|
|
{
|
|
/* Send control frame */
|
|
f = &s->ctrl_buf[s->ctrl_get];
|
|
if (++s->ctrl_get >= V42_CTRL_FRAMES)
|
|
s->ctrl_get = 0;
|
|
}
|
|
else
|
|
{
|
|
if (s->far_busy || s->configuring || s->state != LAPM_DATA)
|
|
{
|
|
hdlc_tx_flags(&s->hdlc_tx, 10);
|
|
return;
|
|
}
|
|
if (s->info_get == s->info_put && !tx_information_frame(ss))
|
|
{
|
|
hdlc_tx_flags(&s->hdlc_tx, 10);
|
|
return;
|
|
}
|
|
/* Send info frame */
|
|
f = &s->info_buf[s->info_get];
|
|
if (++s->info_get >= V42_INFO_FRAMES)
|
|
s->info_get = 0;
|
|
|
|
f->buf[0] = s->cmd_addr;
|
|
f->buf[1] = s->vs << 1;
|
|
f->buf[2] = s->vr << 1;
|
|
s->vs = (s->vs + 1) & 0x7F;
|
|
if (ss->bit_timer == 0)
|
|
t401_start(ss);
|
|
}
|
|
hdlc_tx_frame(&s->hdlc_tx, f->buf, f->len);
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
SPAN_DECLARE_NONSTD(void) lapm_receive(void *user_data, const uint8_t *frame, int len, int ok)
|
|
{
|
|
lapm_state_t *s;
|
|
v42_state_t *ss;
|
|
|
|
ss = (v42_state_t *) user_data;
|
|
s = &ss->lapm;
|
|
if (len < 0)
|
|
{
|
|
span_log(&ss->logging, SPAN_LOG_DEBUG, "V.42 rx status is %s (%d)\n", signal_status_to_str(len), len);
|
|
return;
|
|
}
|
|
if (!ok)
|
|
return;
|
|
|
|
switch ((frame[1] & LAPM_FRAMETYPE_MASK))
|
|
{
|
|
case LAPM_FRAMETYPE_I:
|
|
case LAPM_FRAMETYPE_I_ALT:
|
|
receive_information_frame(ss, frame, len);
|
|
break;
|
|
case LAPM_FRAMETYPE_S:
|
|
if (!valid_data_state(ss))
|
|
return;
|
|
if (frame[0] == s->rsp_addr)
|
|
rx_supervisory_cmd_frame(ss, frame, len);
|
|
else
|
|
rx_supervisory_rsp_frame(ss, frame, len);
|
|
break;
|
|
case LAPM_FRAMETYPE_U:
|
|
if (frame[0] == s->rsp_addr)
|
|
rx_unnumbered_cmd_frame(ss, frame, len);
|
|
else
|
|
rx_unnumbered_rsp_frame(ss, frame, len);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int lapm_connect(v42_state_t *ss)
|
|
{
|
|
lapm_state_t *s;
|
|
|
|
s = &ss->lapm;
|
|
if (s->state != LAPM_IDLE)
|
|
return -1;
|
|
|
|
/* Negotiate params */
|
|
//transmit_xid(s, s->cmd_addr);
|
|
|
|
reset_lapm(ss);
|
|
/* Connect */
|
|
s->state = LAPM_ESTABLISH;
|
|
tx_unnumbered_frame(s, s->cmd_addr, LAPM_U_SABME | LAPM_U_PF, NULL, 0);
|
|
/* Start T401 (and not T403) */
|
|
t401_start(ss);
|
|
return 0;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int lapm_disconnect(v42_state_t *ss)
|
|
{
|
|
lapm_state_t *s;
|
|
|
|
s = &ss->lapm;
|
|
s->state = LAPM_RELEASE;
|
|
tx_unnumbered_frame(s, s->cmd_addr, LAPM_U_DISC | LAPM_U_PF, NULL, 0);
|
|
t401_start(ss);
|
|
return 0;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int lapm_config(v42_state_t *ss)
|
|
{
|
|
lapm_state_t *s;
|
|
|
|
s = &ss->lapm;
|
|
s->configuring = TRUE;
|
|
if (s->state == LAPM_DATA)
|
|
{
|
|
s->local_busy = TRUE;
|
|
tx_supervisory_frame(s, s->cmd_addr, LAPM_S_RNR, 1);
|
|
}
|
|
transmit_xid(ss, s->cmd_addr);
|
|
t401_start(ss);
|
|
return 0;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void reset_lapm(v42_state_t *ss)
|
|
{
|
|
lapm_state_t *s;
|
|
|
|
s = &ss->lapm;
|
|
/* Reset the LAP.M state */
|
|
s->local_busy = FALSE;
|
|
s->far_busy = FALSE;
|
|
s->vs = 0;
|
|
s->va = 0;
|
|
s->vr = 0;
|
|
/* Discard any info frames still queued for transmission */
|
|
s->info_put = 0;
|
|
s->info_acked = 0;
|
|
s->info_get = 0;
|
|
/* Discard any control frames */
|
|
s->ctrl_put = 0;
|
|
s->ctrl_get = 0;
|
|
|
|
s->tx_window_size_k = ss->config.v42_tx_window_size_k;
|
|
s->rx_window_size_k = ss->config.v42_rx_window_size_k;
|
|
s->tx_n401 = ss->config.v42_tx_n401;
|
|
s->rx_n401 = ss->config.v42_rx_n401;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
SPAN_DECLARE(void) v42_stop(v42_state_t *ss)
|
|
{
|
|
lapm_state_t *s;
|
|
|
|
s = &ss->lapm;
|
|
ss->bit_timer = 0;
|
|
s->packer_process = NULL;
|
|
lapm_disconnect(ss);
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void restart_lapm(v42_state_t *s)
|
|
{
|
|
if (s->calling_party)
|
|
{
|
|
s->bit_timer = 48*8;
|
|
s->bit_timer_func = initiate_negotiation_expired;
|
|
}
|
|
else
|
|
{
|
|
lapm_hdlc_underflow(s);
|
|
}
|
|
s->lapm.packer_process = NULL;
|
|
s->lapm.state = LAPM_IDLE;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void negotiation_rx_bit(v42_state_t *s, int new_bit)
|
|
{
|
|
/* DC1 with even parity, 8-16 ones, DC1 with odd parity, 8-16 ones */
|
|
/* uint8_t odp = "0100010001 11111111 0100010011 11111111"; */
|
|
/* V.42 OK E , 8-16 ones, C, 8-16 ones */
|
|
/* uint8_t adp_v42 = "0101000101 11111111 0110000101 11111111"; */
|
|
/* V.42 disabled E, 8-16 ones, NULL, 8-16 ones */
|
|
/* uint8_t adp_nov42 = "0101000101 11111111 0000000001 11111111"; */
|
|
|
|
/* There may be no negotiation, so we need to process this data through the
|
|
HDLC receiver as well */
|
|
if (new_bit < 0)
|
|
{
|
|
/* Special conditions */
|
|
span_log(&s->logging, SPAN_LOG_DEBUG, "V.42 rx status is %s (%d)\n", signal_status_to_str(new_bit), new_bit);
|
|
return;
|
|
}
|
|
/*endif*/
|
|
new_bit &= 1;
|
|
s->neg.rxstream = (s->neg.rxstream << 1) | new_bit;
|
|
switch (s->neg.rx_negotiation_step)
|
|
{
|
|
case 0:
|
|
/* Look for some ones */
|
|
if (new_bit)
|
|
break;
|
|
/*endif*/
|
|
s->neg.rx_negotiation_step = 1;
|
|
s->neg.rxbits = 0;
|
|
s->neg.rxstream = ~1;
|
|
s->neg.rxoks = 0;
|
|
break;
|
|
case 1:
|
|
/* Look for the first character */
|
|
if (++s->neg.rxbits < 9)
|
|
break;
|
|
/*endif*/
|
|
s->neg.rxstream &= 0x3FF;
|
|
if (s->calling_party && s->neg.rxstream == 0x145)
|
|
{
|
|
s->neg.rx_negotiation_step++;
|
|
}
|
|
else if (!s->calling_party && s->neg.rxstream == 0x111)
|
|
{
|
|
s->neg.rx_negotiation_step++;
|
|
}
|
|
else
|
|
{
|
|
s->neg.rx_negotiation_step = 0;
|
|
}
|
|
/*endif*/
|
|
s->neg.rxbits = 0;
|
|
s->neg.rxstream = ~0;
|
|
break;
|
|
case 2:
|
|
/* Look for 8 to 16 ones */
|
|
s->neg.rxbits++;
|
|
if (new_bit)
|
|
break;
|
|
/*endif*/
|
|
if (s->neg.rxbits >= 8 && s->neg.rxbits <= 16)
|
|
s->neg.rx_negotiation_step++;
|
|
else
|
|
s->neg.rx_negotiation_step = 0;
|
|
/*endif*/
|
|
s->neg.rxbits = 0;
|
|
s->neg.rxstream = ~1;
|
|
break;
|
|
case 3:
|
|
/* Look for the second character */
|
|
if (++s->neg.rxbits < 9)
|
|
break;
|
|
/*endif*/
|
|
s->neg.rxstream &= 0x3FF;
|
|
if (s->calling_party && s->neg.rxstream == 0x185)
|
|
{
|
|
s->neg.rx_negotiation_step++;
|
|
}
|
|
else if (s->calling_party && s->neg.rxstream == 0x001)
|
|
{
|
|
s->neg.rx_negotiation_step++;
|
|
}
|
|
else if (!s->calling_party && s->neg.rxstream == 0x113)
|
|
{
|
|
s->neg.rx_negotiation_step++;
|
|
}
|
|
else
|
|
{
|
|
s->neg.rx_negotiation_step = 0;
|
|
}
|
|
/*endif*/
|
|
s->neg.rxbits = 0;
|
|
s->neg.rxstream = ~0;
|
|
break;
|
|
case 4:
|
|
/* Look for 8 to 16 ones */
|
|
s->neg.rxbits++;
|
|
if (new_bit)
|
|
break;
|
|
/*endif*/
|
|
if (s->neg.rxbits >= 8 && s->neg.rxbits <= 16)
|
|
{
|
|
if (++s->neg.rxoks >= 2)
|
|
{
|
|
/* HIT - we have found the "V.42 supported" pattern. */
|
|
s->neg.rx_negotiation_step++;
|
|
if (s->calling_party)
|
|
{
|
|
t400_stop(s);
|
|
s->lapm.state = LAPM_IDLE;
|
|
report_rx_status_change(s, s->lapm.state);
|
|
restart_lapm(s);
|
|
}
|
|
else
|
|
{
|
|
s->neg.odp_seen = TRUE;
|
|
}
|
|
/*endif*/
|
|
break;
|
|
}
|
|
/*endif*/
|
|
s->neg.rx_negotiation_step = 1;
|
|
s->neg.rxbits = 0;
|
|
s->neg.rxstream = ~1;
|
|
}
|
|
else
|
|
{
|
|
s->neg.rx_negotiation_step = 0;
|
|
s->neg.rxbits = 0;
|
|
s->neg.rxstream = ~0;
|
|
}
|
|
/*endif*/
|
|
break;
|
|
case 5:
|
|
/* Parked */
|
|
break;
|
|
}
|
|
/*endswitch*/
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int v42_support_negotiation_tx_bit(v42_state_t *s)
|
|
{
|
|
int bit;
|
|
|
|
if (s->calling_party)
|
|
{
|
|
if (s->neg.txbits <= 0)
|
|
{
|
|
s->neg.txstream = 0x3FE22;
|
|
s->neg.txbits = 36;
|
|
}
|
|
else if (s->neg.txbits == 18)
|
|
{
|
|
s->neg.txstream = 0x3FF22;
|
|
}
|
|
/*endif*/
|
|
bit = s->neg.txstream & 1;
|
|
s->neg.txstream >>= 1;
|
|
s->neg.txbits--;
|
|
}
|
|
else
|
|
{
|
|
if (s->neg.odp_seen && s->neg.txadps < 10)
|
|
{
|
|
if (s->neg.txbits <= 0)
|
|
{
|
|
if (++s->neg.txadps >= 10)
|
|
{
|
|
t400_stop(s);
|
|
s->lapm.state = LAPM_IDLE;
|
|
report_rx_status_change(s, s->lapm.state);
|
|
s->neg.txstream = 1;
|
|
restart_lapm(s);
|
|
}
|
|
else
|
|
{
|
|
s->neg.txstream = 0x3FE8A;
|
|
s->neg.txbits = 36;
|
|
}
|
|
/*endif*/
|
|
}
|
|
else if (s->neg.txbits == 18)
|
|
{
|
|
s->neg.txstream = 0x3FE86;
|
|
}
|
|
/*endif*/
|
|
bit = s->neg.txstream & 1;
|
|
s->neg.txstream >>= 1;
|
|
s->neg.txbits--;
|
|
}
|
|
else
|
|
{
|
|
bit = 1;
|
|
}
|
|
/*endif*/
|
|
}
|
|
/*endif*/
|
|
return bit;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
SPAN_DECLARE(void) v42_rx_bit(void *user_data, int bit)
|
|
{
|
|
v42_state_t *s;
|
|
|
|
s = (v42_state_t *) user_data;
|
|
if (s->lapm.state == LAPM_DETECT)
|
|
negotiation_rx_bit(s, bit);
|
|
else
|
|
hdlc_rx_put_bit(&s->lapm.hdlc_rx, bit);
|
|
/*endif*/
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
SPAN_DECLARE(int) v42_tx_bit(void *user_data)
|
|
{
|
|
v42_state_t *s;
|
|
int bit;
|
|
|
|
s = (v42_state_t *) user_data;
|
|
if (s->bit_timer && (--s->bit_timer) <= 0)
|
|
{
|
|
s->bit_timer = 0;
|
|
s->bit_timer_func(s);
|
|
}
|
|
if (s->lapm.state == LAPM_DETECT)
|
|
bit = v42_support_negotiation_tx_bit(s);
|
|
else
|
|
bit = hdlc_tx_get_bit(&s->lapm.hdlc_tx);
|
|
/*endif*/
|
|
return bit;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
SPAN_DECLARE(int) v42_set_local_busy_status(v42_state_t *s, int busy)
|
|
{
|
|
int previous_busy;
|
|
|
|
previous_busy = s->lapm.local_busy;
|
|
s->lapm.local_busy = busy;
|
|
return previous_busy;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
SPAN_DECLARE(int) v42_get_far_busy_status(v42_state_t *s)
|
|
{
|
|
return s->lapm.far_busy;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
SPAN_DECLARE(void) v42_set_status_callback(v42_state_t *s, modem_status_func_t status_handler, void *user_data)
|
|
{
|
|
s->lapm.status_handler = status_handler;
|
|
s->lapm.status_user_data = user_data;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
SPAN_DECLARE(void) v42_restart(v42_state_t *s)
|
|
{
|
|
hdlc_tx_init(&s->lapm.hdlc_tx, FALSE, 1, TRUE, lapm_hdlc_underflow, s);
|
|
hdlc_rx_init(&s->lapm.hdlc_rx, FALSE, FALSE, 1, lapm_receive, s);
|
|
|
|
if (s->detect)
|
|
{
|
|
/* We need to do the V.42 support detection sequence */
|
|
s->neg.txstream = ~0;
|
|
s->neg.txbits = 0;
|
|
s->neg.rxstream = ~0;
|
|
s->neg.rxbits = 0;
|
|
s->neg.rxoks = 0;
|
|
s->neg.txadps = 0;
|
|
s->neg.rx_negotiation_step = 0;
|
|
s->neg.odp_seen = FALSE;
|
|
t400_start(s);
|
|
s->lapm.state = LAPM_DETECT;
|
|
}
|
|
else
|
|
{
|
|
/* Go directly to LAP.M mode */
|
|
s->lapm.state = LAPM_IDLE;
|
|
restart_lapm(s);
|
|
}
|
|
/*endif*/
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
SPAN_DECLARE(v42_state_t *) v42_init(v42_state_t *ss,
|
|
int calling_party,
|
|
int detect,
|
|
get_msg_func_t iframe_get,
|
|
put_msg_func_t iframe_put,
|
|
void *user_data)
|
|
{
|
|
lapm_state_t *s;
|
|
|
|
if (ss == NULL)
|
|
{
|
|
if ((ss = (v42_state_t *) malloc(sizeof(*ss))) == NULL)
|
|
return NULL;
|
|
}
|
|
memset(ss, 0, sizeof(*ss));
|
|
|
|
s = &ss->lapm;
|
|
ss->calling_party = calling_party;
|
|
ss->detect = detect;
|
|
s->iframe_get = iframe_get;
|
|
s->iframe_get_user_data = user_data;
|
|
s->iframe_put = iframe_put;
|
|
s->iframe_put_user_data = user_data;
|
|
|
|
s->state = (ss->detect) ? LAPM_DETECT : LAPM_IDLE;
|
|
s->local_busy = FALSE;
|
|
s->far_busy = FALSE;
|
|
|
|
/* The address octet is:
|
|
Data link connection identifier (0)
|
|
Command/response (0 if answerer, 1 if originator)
|
|
Extended address (1) */
|
|
s->cmd_addr = (LAPM_DLCI_DTE_TO_DTE << 2) | ((ss->calling_party) ? 0x02 : 0x00) | 0x01;
|
|
s->rsp_addr = (LAPM_DLCI_DTE_TO_DTE << 2) | ((ss->calling_party) ? 0x00 : 0x02) | 0x01;
|
|
|
|
/* Set default values for the LAP.M parameters. These can be modified later. */
|
|
ss->config.v42_tx_window_size_k = V42_DEFAULT_WINDOW_SIZE_K;
|
|
ss->config.v42_rx_window_size_k = V42_DEFAULT_WINDOW_SIZE_K;
|
|
ss->config.v42_tx_n401 = V42_DEFAULT_N_401;
|
|
ss->config.v42_rx_n401 = V42_DEFAULT_N_401;
|
|
|
|
/* TODO: This should be part of the V.42bis startup */
|
|
ss->config.comp = 1;
|
|
ss->config.comp_dict_size = 512;
|
|
ss->config.comp_max_string = 6;
|
|
|
|
ss->tx_bit_rate = 28800;
|
|
|
|
reset_lapm(ss);
|
|
|
|
span_log_init(&ss->logging, SPAN_LOG_NONE, NULL);
|
|
span_log_set_protocol(&ss->logging, "V.42");
|
|
return ss;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
SPAN_DECLARE(void) v42_release(v42_state_t *s)
|
|
{
|
|
reset_lapm(s);
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
SPAN_DECLARE(void) v42_free(v42_state_t *s)
|
|
{
|
|
v42_release(s);
|
|
free(s);
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
/*- End of file ------------------------------------------------------------*/
|