Steve Underwood 7b9e4ff674 Various updates to spandsp tests
spandsp logging now passes an opaque pointer to the logging routine, to
increase flexibility. Right now the pointer is set to NULL in all calls.
2012-03-28 23:36:30 +08:00

1564 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;
s = &ss->lapm;
if (!valid_data_state(ss))
return;
if (len > s->rx_n401 + 3)
return;
/* Ack I frames: NR - 1 */
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;
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;
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;
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;
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;
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;
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;
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;
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 ------------------------------------------------------------*/