/* * SpanDSP - a series of DSP components for telephony * * v42.c * * Written by Steve Underwood * * 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 #include #include #include #include #include #include #include #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 ------------------------------------------------------------*/