6070 lines
206 KiB
C
6070 lines
206 KiB
C
/*
|
|
* SpanDSP - a series of DSP components for telephony
|
|
*
|
|
* t30.c - ITU T.30 FAX transfer processing
|
|
*
|
|
* Written by Steve Underwood <steveu@coppice.org>
|
|
*
|
|
* Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009 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.
|
|
*
|
|
* $Id: t30.c,v 1.298 2009/04/30 18:46:14 steveu Exp $
|
|
*/
|
|
|
|
/*! \file */
|
|
|
|
#if defined(HAVE_CONFIG_H)
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <inttypes.h>
|
|
#include <string.h>
|
|
#include <fcntl.h>
|
|
#include <time.h>
|
|
#if defined(HAVE_TGMATH_H)
|
|
#include <tgmath.h>
|
|
#endif
|
|
#if defined(HAVE_MATH_H)
|
|
#include <math.h>
|
|
#endif
|
|
#include "floating_fudge.h"
|
|
#include <tiffio.h>
|
|
|
|
#include "spandsp/telephony.h"
|
|
#include "spandsp/logging.h"
|
|
#include "spandsp/bit_operations.h"
|
|
#include "spandsp/queue.h"
|
|
#include "spandsp/power_meter.h"
|
|
#include "spandsp/complex.h"
|
|
#include "spandsp/tone_generate.h"
|
|
#include "spandsp/async.h"
|
|
#include "spandsp/hdlc.h"
|
|
#include "spandsp/fsk.h"
|
|
#include "spandsp/v29rx.h"
|
|
#include "spandsp/v29tx.h"
|
|
#include "spandsp/v27ter_rx.h"
|
|
#include "spandsp/v27ter_tx.h"
|
|
#include "spandsp/t4.h"
|
|
#include "spandsp/t30_fcf.h"
|
|
#include "spandsp/t35.h"
|
|
#include "spandsp/t30.h"
|
|
#include "spandsp/t30_api.h"
|
|
#include "spandsp/t30_logging.h"
|
|
|
|
#include "spandsp/private/logging.h"
|
|
#include "spandsp/private/t4.h"
|
|
#include "spandsp/private/t30.h"
|
|
|
|
#include "t30_local.h"
|
|
|
|
/*! The maximum permitted number of retries of a single command allowed. */
|
|
#define MAX_COMMAND_TRIES 3
|
|
|
|
/*! The maximum permitted number of retries of a single response request allowed. This
|
|
is not specified in T.30. However, if you don't apply some limit a messed up FAX
|
|
terminal could keep you retrying all day. Its a backstop protection. */
|
|
#define MAX_RESPONSE_TRIES 6
|
|
|
|
/*! Conversion between milliseconds and audio samples. */
|
|
#define ms_to_samples(t) (((t)*SAMPLE_RATE)/1000)
|
|
|
|
/* T.30 defines the following call phases:
|
|
Phase A: Call set-up.
|
|
Exchange of CNG, CED and the called terminal identification.
|
|
Phase B: Pre-message procedure for identifying and selecting the required facilities.
|
|
Capabilities negotiation, and training, up the the confirmation to receive.
|
|
Phase C: Message transmission (includes phasing and synchronization where appropriate).
|
|
Transfer of the message at high speed.
|
|
Phase D: Post-message procedure, including end-of-message and confirmation and multi-document procedures.
|
|
End of message and acknowledgement.
|
|
Phase E: Call release
|
|
Final call disconnect. */
|
|
enum
|
|
{
|
|
T30_PHASE_IDLE = 0, /* Freshly initialised */
|
|
T30_PHASE_A_CED, /* Doing the CED (answer) sequence */
|
|
T30_PHASE_A_CNG, /* Doing the CNG (caller) sequence */
|
|
T30_PHASE_B_RX, /* Receiving pre-message control messages */
|
|
T30_PHASE_B_TX, /* Transmitting pre-message control messages */
|
|
T30_PHASE_C_NON_ECM_RX, /* Receiving a document message in non-ECM mode */
|
|
T30_PHASE_C_NON_ECM_TX, /* Transmitting a document message in non-ECM mode */
|
|
T30_PHASE_C_ECM_RX, /* Receiving a document message in ECM (HDLC) mode */
|
|
T30_PHASE_C_ECM_TX, /* Transmitting a document message in ECM (HDLC) mode */
|
|
T30_PHASE_D_RX, /* Receiving post-message control messages */
|
|
T30_PHASE_D_TX, /* Transmitting post-message control messages */
|
|
T30_PHASE_E, /* In phase E */
|
|
T30_PHASE_CALL_FINISHED /* Call completely finished */
|
|
};
|
|
|
|
static const char *phase_names[] =
|
|
{
|
|
"T30_PHASE_IDLE",
|
|
"T30_PHASE_A_CED",
|
|
"T30_PHASE_A_CNG",
|
|
"T30_PHASE_B_RX",
|
|
"T30_PHASE_B_TX",
|
|
"T30_PHASE_C_NON_ECM_RX",
|
|
"T30_PHASE_C_NON_ECM_TX",
|
|
"T30_PHASE_C_ECM_RX",
|
|
"T30_PHASE_C_ECM_TX",
|
|
"T30_PHASE_D_RX",
|
|
"T30_PHASE_D_TX",
|
|
"T30_PHASE_E",
|
|
"T30_PHASE_CALL_FINISHED"
|
|
};
|
|
|
|
/* These state names are modelled after places in the T.30 flow charts. */
|
|
enum
|
|
{
|
|
T30_STATE_ANSWERING = 1,
|
|
T30_STATE_B,
|
|
T30_STATE_C,
|
|
T30_STATE_D,
|
|
T30_STATE_D_TCF,
|
|
T30_STATE_D_POST_TCF,
|
|
T30_STATE_F_TCF,
|
|
T30_STATE_F_CFR,
|
|
T30_STATE_F_FTT,
|
|
T30_STATE_F_DOC_NON_ECM,
|
|
T30_STATE_F_POST_DOC_NON_ECM,
|
|
T30_STATE_F_DOC_ECM,
|
|
T30_STATE_F_POST_DOC_ECM,
|
|
T30_STATE_F_POST_RCP_MCF,
|
|
T30_STATE_F_POST_RCP_PPR,
|
|
T30_STATE_F_POST_RCP_RNR,
|
|
T30_STATE_R,
|
|
T30_STATE_T,
|
|
T30_STATE_I,
|
|
T30_STATE_II,
|
|
T30_STATE_II_Q,
|
|
T30_STATE_III_Q_MCF,
|
|
T30_STATE_III_Q_RTP,
|
|
T30_STATE_III_Q_RTN,
|
|
T30_STATE_IV,
|
|
T30_STATE_IV_PPS_NULL,
|
|
T30_STATE_IV_PPS_Q,
|
|
T30_STATE_IV_PPS_RNR,
|
|
T30_STATE_IV_CTC,
|
|
T30_STATE_IV_EOR,
|
|
T30_STATE_IV_EOR_RNR,
|
|
T30_STATE_CALL_FINISHED
|
|
};
|
|
|
|
enum
|
|
{
|
|
T30_MIN_SCAN_20MS = 0,
|
|
T30_MIN_SCAN_5MS = 1,
|
|
T30_MIN_SCAN_10MS = 2,
|
|
T30_MIN_SCAN_40MS = 4,
|
|
T30_MIN_SCAN_0MS = 7,
|
|
};
|
|
|
|
enum
|
|
{
|
|
T30_MODE_SEND_DOC = 1,
|
|
T30_MODE_RECEIVE_DOC
|
|
};
|
|
|
|
/*! These are internal assessments of received image quality, used to determine whether we
|
|
continue, retrain, or abandon the call. */
|
|
enum
|
|
{
|
|
T30_COPY_QUALITY_PERFECT = 0,
|
|
T30_COPY_QUALITY_GOOD,
|
|
T30_COPY_QUALITY_POOR,
|
|
T30_COPY_QUALITY_BAD
|
|
};
|
|
|
|
enum
|
|
{
|
|
DISBIT1 = 0x01,
|
|
DISBIT2 = 0x02,
|
|
DISBIT3 = 0x04,
|
|
DISBIT4 = 0x08,
|
|
DISBIT5 = 0x10,
|
|
DISBIT6 = 0x20,
|
|
DISBIT7 = 0x40,
|
|
DISBIT8 = 0x80
|
|
};
|
|
|
|
/*! There are high level indications of what is happening at any instant, to guide the cleanup
|
|
process if the call is abandoned. */
|
|
enum
|
|
{
|
|
OPERATION_IN_PROGRESS_NONE = 0,
|
|
OPERATION_IN_PROGRESS_T4_RX,
|
|
OPERATION_IN_PROGRESS_T4_TX
|
|
};
|
|
|
|
/* All timers specified in milliseconds */
|
|
|
|
/*! Time-out T0 defines the amount of time an automatic calling terminal waits for the called terminal
|
|
to answer the call.
|
|
T0 begins after the dialling of the number is completed and is reset:
|
|
a) when T0 times out; or
|
|
b) when timer T1 is started; or
|
|
c) if the terminal is capable of detecting any condition which indicates that the call will not be
|
|
successful, when such a condition is detected.
|
|
The recommended value of T0 is 60+-5s. However, when it is anticipated that a long call set-up
|
|
time may be encountered, an alternative value of up to 120s may be used.
|
|
NOTE - National regulations may require the use of other values for T0. */
|
|
#define DEFAULT_TIMER_T0 60000
|
|
|
|
/*! Time-out T1 defines the amount of time two terminals will continue to attempt to identify each
|
|
other. T1 is 35+-5s, begins upon entering phase B, and is reset upon detecting a valid signal or
|
|
when T1 times out.
|
|
For operating methods 3 and 4 (see 3.1), the calling terminal starts time-out T1 upon reception of
|
|
the V.21 modulation scheme.
|
|
For operating method 4 bis a (see 3.1), the calling terminal starts time-out T1 upon starting
|
|
transmission using the V.21 modulation scheme.
|
|
Annex A says T1 is also the timeout to be used for the receipt of the first HDLC frame after the
|
|
start of high speed flags in ECM mode. This seems a strange reuse of the T1 name, so we distinguish
|
|
it here by calling it T1A. */
|
|
#define DEFAULT_TIMER_T1 35000
|
|
#define DEFAULT_TIMER_T1A 35000
|
|
|
|
/*! Time-out T2 makes use of the tight control between commands and responses to detect the loss of
|
|
command/response synchronization. T2 is 6+-1s, and begins when initiating a command search
|
|
(e.g., the first entrance into the "command received" subroutine, reference flow diagram in section 5.2).
|
|
T2 is reset when an HDLC flag is received or when T2 times out. */
|
|
#define DEFAULT_TIMER_T2 7000
|
|
|
|
/*! Once HDLC flags begin, T2 is reset, and a 3s timer begins. This timer is unnamed in T.30. Here we
|
|
term it T2A. No tolerance is specified for this timer. T2A specifies the maximum time to wait for the
|
|
end of a frame, after the initial flag has been seen. */
|
|
#define DEFAULT_TIMER_T2A 3000
|
|
|
|
/*! If the HDLC carrier falls during reception, we need to apply a minimum time before continuing. If we
|
|
don't, there are circumstances where we could continue and reply before the incoming signals have
|
|
really finished. E.g. if a bad DCS is received in a DCS-TCF sequence, we need wait for the TCF
|
|
carrier to pass, before continuing. This timer is specified as 200ms, but no tolerance is specified.
|
|
It is unnamed in T.30. Here we term it T2B */
|
|
#define DEFAULT_TIMER_T2B 200
|
|
|
|
/*! Time-out T3 defines the amount of time a terminal will attempt to alert the local operator in
|
|
response to a procedural interrupt. Failing to achieve operator intervention, the terminal will
|
|
discontinue this attempt and shall issue other commands or responses. T3 is 10+-5s, begins on the
|
|
first detection of a procedural interrupt command/response signal (i.e., PIN/PIP or PRI-Q) and is
|
|
reset when T3 times out or when the operator initiates a line request. */
|
|
#define DEFAULT_TIMER_T3 15000
|
|
|
|
/*! Time-out T4 defines the amount of time a terminal will wait for flags to begin, when waiting for a
|
|
response from a remote terminal. T2 is 3s +-15%, and begins when initiating a response search
|
|
(e.g., the first entrance into the "response received" subroutine, reference flow diagram in section 5.2).
|
|
T4 is reset when an HDLC flag is received or when T4 times out.
|
|
NOTE - For manual FAX units, the value of timer T4 may be either 3.0s +-15% or 4.5s +-15%.
|
|
If the value of 4.5s is used, then after detection of a valid response to the first DIS, it may
|
|
be reduced to 3.0s +-15%. T4 = 3.0s +-15% for automatic units. */
|
|
#define DEFAULT_TIMER_T4 3450
|
|
|
|
/*! Once HDLC flags begin, T4 is reset, and a 3s timer begins. This timer is unnamed in T.30. Here we
|
|
term it T4A. No tolerance is specified for this timer. T4A specifies the maximum time to wait for the
|
|
end of a frame, after the initial flag has been seen. Note that a different timer is used for the fast
|
|
HDLC in ECM mode, to provide time for physical paper handling. */
|
|
#define DEFAULT_TIMER_T4A 3000
|
|
|
|
/*! If the HDLC carrier falls during reception, we need to apply a minimum time before continuing. if we
|
|
don't, there are circumstances where we could continue and reply before the incoming signals have
|
|
really finished. E.g. if a bad DCS is received in a DCS-TCF sequence, we need wait for the TCF
|
|
carrier to pass, before continuing. This timer is specified as 200ms, but no tolerance is specified.
|
|
It is unnamed in T.30. Here we term it T4B */
|
|
#define DEFAULT_TIMER_T4B 200
|
|
|
|
/*! Time-out T5 is defined for the optional T.4 error correction mode. Time-out T5 defines the amount
|
|
of time waiting for clearance of the busy condition of the receiving terminal. T5 is 60+-5s and
|
|
begins on the first detection of the RNR response. T5 is reset when T5 times out or the MCF or PIP
|
|
response is received or when the ERR or PIN response is received in the flow control process after
|
|
transmitting the EOR command. If the timer T5 has expired, the DCN command is transmitted for
|
|
call release. */
|
|
#define DEFAULT_TIMER_T5 65000
|
|
|
|
/*! (Annex C - ISDN) Time-out T6 defines the amount of time two terminals will continue to attempt to
|
|
identify each other. T6 is 5+-0.5s. The timeout begins upon entering Phase B, and is reset upon
|
|
detecting a valid signal, or when T6 times out. */
|
|
#define DEFAULT_TIMER_T6 5000
|
|
|
|
/*! (Annex C - ISDN) Time-out T7 is used to detect loss of command/response synchronization. T7 is 6+-1s.
|
|
The timeout begins when initiating a command search (e.g., the first entrance into the "command received"
|
|
subroutine - see flow diagram in C.5) and is reset upon detecting a valid signal or when T7 times out. */
|
|
#define DEFAULT_TIMER_T7 7000
|
|
|
|
/*! (Annex C - ISDN) Time-out T8 defines the amount of time waiting for clearance of the busy condition
|
|
of the receiving terminal. T8 is 10+-1s. The timeout begins on the first detection of the combination
|
|
of no outstanding corrections and the RNR response. T8 is reset when T8 times out or MCF response is
|
|
received. If the timer T8 expires, a DCN command is transmitted for call release. */
|
|
#define DEFAULT_TIMER_T8 10000
|
|
|
|
/*! Final time we allow for things to flush through the system, before we disconnect, in milliseconds.
|
|
200ms should be fine for a PSTN call. For a T.38 call something longer is desirable. */
|
|
#define FINAL_FLUSH_TIME 1000
|
|
|
|
/*! The number of PPRs received before CTC or EOR is sent in ECM mode. T.30 defines this as 4,
|
|
but it could be varied, and the Japanese spec, for example, does make this value a
|
|
variable. */
|
|
#define PPR_LIMIT_BEFORE_CTC_OR_EOR 4
|
|
|
|
enum
|
|
{
|
|
TIMER_IS_IDLE = 0,
|
|
TIMER_IS_T2,
|
|
TIMER_IS_T1A,
|
|
TIMER_IS_T2A,
|
|
TIMER_IS_T2B,
|
|
TIMER_IS_T2C,
|
|
TIMER_IS_T4,
|
|
TIMER_IS_T4A,
|
|
TIMER_IS_T4B,
|
|
TIMER_IS_T4C
|
|
};
|
|
|
|
/* Start points in the fallback table for different capabilities */
|
|
/*! The starting point in the modem fallback sequence if V.17 is allowed */
|
|
#define T30_V17_FALLBACK_START 0
|
|
/*! The starting point in the modem fallback sequence if V.17 is not allowed */
|
|
#define T30_V29_FALLBACK_START 3
|
|
/*! The starting point in the modem fallback sequence if V.29 is not allowed */
|
|
#define T30_V27TER_FALLBACK_START 6
|
|
|
|
static const struct
|
|
{
|
|
int bit_rate;
|
|
int modem_type;
|
|
int which;
|
|
uint8_t dcs_code;
|
|
} fallback_sequence[] =
|
|
{
|
|
{14400, T30_MODEM_V17, T30_SUPPORT_V17, DISBIT6},
|
|
{12000, T30_MODEM_V17, T30_SUPPORT_V17, (DISBIT6 | DISBIT4)},
|
|
{ 9600, T30_MODEM_V17, T30_SUPPORT_V17, (DISBIT6 | DISBIT3)},
|
|
{ 9600, T30_MODEM_V29, T30_SUPPORT_V29, DISBIT3},
|
|
{ 7200, T30_MODEM_V17, T30_SUPPORT_V17, (DISBIT6 | DISBIT4 | DISBIT3)},
|
|
{ 7200, T30_MODEM_V29, T30_SUPPORT_V29, (DISBIT4 | DISBIT3)},
|
|
{ 4800, T30_MODEM_V27TER, T30_SUPPORT_V27TER, DISBIT4},
|
|
{ 2400, T30_MODEM_V27TER, T30_SUPPORT_V27TER, 0},
|
|
{ 0, 0, 0, 0}
|
|
};
|
|
|
|
static void queue_phase(t30_state_t *s, int phase);
|
|
static void set_phase(t30_state_t *s, int phase);
|
|
static void set_state(t30_state_t *s, int state);
|
|
static void send_simple_frame(t30_state_t *s, int type);
|
|
static void send_frame(t30_state_t *s, const uint8_t *fr, int frlen);
|
|
static void send_dcn(t30_state_t *s);
|
|
static void repeat_last_command(t30_state_t *s);
|
|
static void disconnect(t30_state_t *s);
|
|
static void decode_20digit_msg(t30_state_t *s, char *msg, const uint8_t *pkt, int len);
|
|
static void decode_url_msg(t30_state_t *s, char *msg, const uint8_t *pkt, int len);
|
|
static int set_min_scan_time_code(t30_state_t *s);
|
|
static int send_cfr_sequence(t30_state_t *s, int start);
|
|
static void timer_t2_start(t30_state_t *s);
|
|
static void timer_t2a_start(t30_state_t *s);
|
|
static void timer_t2b_start(t30_state_t *s);
|
|
static void timer_t4_start(t30_state_t *s);
|
|
static void timer_t4a_start(t30_state_t *s);
|
|
static void timer_t4b_start(t30_state_t *s);
|
|
static void timer_t2_t4_stop(t30_state_t *s);
|
|
|
|
/*! Test a specified bit within a DIS, DTC or DCS frame */
|
|
#define test_ctrl_bit(s,bit) ((s)[3 + ((bit - 1)/8)] & (1 << ((bit - 1)%8)))
|
|
/*! Set a specified bit within a DIS, DTC or DCS frame */
|
|
#define set_ctrl_bit(s,bit) (s)[3 + ((bit - 1)/8)] |= (1 << ((bit - 1)%8))
|
|
/*! Set a specified block of bits within a DIS, DTC or DCS frame */
|
|
#define set_ctrl_bits(s,val,bit) (s)[3 + ((bit - 1)/8)] |= ((val) << ((bit - 1)%8))
|
|
/*! Clear a specified bit within a DIS, DTC or DCS frame */
|
|
#define clr_ctrl_bit(s,bit) (s)[3 + ((bit - 1)/8)] &= ~(1 << ((bit - 1)%8))
|
|
|
|
static int terminate_operation_in_progress(t30_state_t *s)
|
|
{
|
|
/* Make sure any FAX in progress is tidied up. If the tidying up has
|
|
already happened, repeating it here is harmless. */
|
|
switch (s->operation_in_progress)
|
|
{
|
|
case OPERATION_IN_PROGRESS_T4_TX:
|
|
t4_tx_release(&(s->t4));
|
|
break;
|
|
case OPERATION_IN_PROGRESS_T4_RX:
|
|
t4_rx_release(&(s->t4));
|
|
break;
|
|
}
|
|
s->operation_in_progress = OPERATION_IN_PROGRESS_NONE;
|
|
return 0;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int tx_start_page(t30_state_t *s)
|
|
{
|
|
if (t4_tx_start_page(&(s->t4)))
|
|
{
|
|
terminate_operation_in_progress(s);
|
|
return -1;
|
|
}
|
|
s->ecm_block = 0;
|
|
s->error_correcting_mode_retries = 0;
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Starting page %d of transfer\n", s->ecm_tx_page + 1);
|
|
return 0;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int tx_end_page(t30_state_t *s)
|
|
{
|
|
s->retries = 0;
|
|
if (t4_tx_end_page(&(s->t4)) == 0)
|
|
{
|
|
s->ecm_tx_page++;
|
|
s->ecm_block = 0;
|
|
}
|
|
return 0;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int rx_start_page(t30_state_t *s)
|
|
{
|
|
int i;
|
|
|
|
t4_rx_set_image_width(&s->t4, s->image_width);
|
|
t4_rx_set_sub_address(&s->t4, s->rx_info.sub_address);
|
|
t4_rx_set_dcs(&s->t4, s->rx_dcs_string);
|
|
t4_rx_set_far_ident(&s->t4, s->rx_info.ident);
|
|
t4_rx_set_vendor(&s->t4, s->vendor);
|
|
t4_rx_set_model(&s->t4, s->model);
|
|
|
|
t4_rx_set_rx_encoding(&s->t4, s->line_encoding);
|
|
t4_rx_set_x_resolution(&s->t4, s->x_resolution);
|
|
t4_rx_set_y_resolution(&s->t4, s->y_resolution);
|
|
|
|
if (t4_rx_start_page(&(s->t4)))
|
|
return -1;
|
|
/* Clear the buffer */
|
|
for (i = 0; i < 256; i++)
|
|
s->ecm_len[i] = -1;
|
|
s->ecm_block = 0;
|
|
s->ecm_frames = -1;
|
|
s->ecm_frames_this_tx_burst = 0;
|
|
s->error_correcting_mode_retries = 0;
|
|
return 0;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int rx_end_page(t30_state_t *s)
|
|
{
|
|
if (t4_rx_end_page(&(s->t4)) == 0)
|
|
{
|
|
s->ecm_rx_page++;
|
|
s->ecm_block = 0;
|
|
}
|
|
return 0;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int copy_quality(t30_state_t *s)
|
|
{
|
|
t4_stats_t stats;
|
|
int quality;
|
|
|
|
t4_get_transfer_statistics(&s->t4, &stats);
|
|
/* There is no specification for judging copy quality. However, we need to classify
|
|
it at three levels, to control what we do next: OK; tolerable, but retrain;
|
|
intolerable. */
|
|
/* Based on the thresholds used in the TSB85 tests, we consider:
|
|
<5% bad rows is OK
|
|
<15% bad rows to be tolerable, but retrain
|
|
>15% bad rows to be intolerable
|
|
*/
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Page no = %d\n", stats.pages_transferred + 1);
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Image size = %d x %d pixels\n", stats.width, stats.length);
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Image resolution = %d/m x %d/m\n", stats.x_resolution, stats.y_resolution);
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Bad rows = %d\n", stats.bad_rows);
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Longest bad row run = %d\n", stats.longest_bad_row_run);
|
|
/* Don't treat a page as perfect because it has zero bad rows out of zero total rows. A zero row
|
|
page has got to be some kind of total page failure. */
|
|
if (stats.bad_rows == 0 && stats.length != 0)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Page quality is perfect\n");
|
|
quality = T30_COPY_QUALITY_PERFECT;
|
|
}
|
|
else if (stats.bad_rows*20 < stats.length)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Page quality is good\n");
|
|
quality = T30_COPY_QUALITY_GOOD;
|
|
}
|
|
else if (stats.bad_rows*20 < stats.length*3)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Page quality is poor\n");
|
|
quality = T30_COPY_QUALITY_POOR;
|
|
}
|
|
else
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Page quality is bad\n");
|
|
quality = T30_COPY_QUALITY_BAD;
|
|
}
|
|
return quality;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void release_resources(t30_state_t *s)
|
|
{
|
|
if (s->tx_info.nsf)
|
|
{
|
|
free(s->tx_info.nsf);
|
|
s->tx_info.nsf = NULL;
|
|
}
|
|
s->tx_info.nsf_len = 0;
|
|
if (s->tx_info.nsc)
|
|
{
|
|
free(s->tx_info.nsc);
|
|
s->tx_info.nsc = NULL;
|
|
}
|
|
s->tx_info.nsc_len = 0;
|
|
if (s->tx_info.nss)
|
|
{
|
|
free(s->tx_info.nss);
|
|
s->tx_info.nss = NULL;
|
|
}
|
|
s->tx_info.nss_len = 0;
|
|
if (s->tx_info.tsa)
|
|
{
|
|
free(s->tx_info.tsa);
|
|
s->tx_info.tsa = NULL;
|
|
}
|
|
if (s->tx_info.ira)
|
|
{
|
|
free(s->tx_info.ira);
|
|
s->tx_info.ira = NULL;
|
|
}
|
|
if (s->tx_info.cia)
|
|
{
|
|
free(s->tx_info.cia);
|
|
s->tx_info.cia = NULL;
|
|
}
|
|
if (s->tx_info.isp)
|
|
{
|
|
free(s->tx_info.isp);
|
|
s->tx_info.isp = NULL;
|
|
}
|
|
if (s->tx_info.csa)
|
|
{
|
|
free(s->tx_info.csa);
|
|
s->tx_info.csa = NULL;
|
|
}
|
|
|
|
if (s->rx_info.nsf)
|
|
{
|
|
free(s->rx_info.nsf);
|
|
s->rx_info.nsf = NULL;
|
|
}
|
|
s->rx_info.nsf_len = 0;
|
|
if (s->rx_info.nsc)
|
|
{
|
|
free(s->rx_info.nsc);
|
|
s->rx_info.nsc = NULL;
|
|
}
|
|
s->rx_info.nsc_len = 0;
|
|
if (s->rx_info.nss)
|
|
{
|
|
free(s->rx_info.nss);
|
|
s->rx_info.nss = NULL;
|
|
}
|
|
s->rx_info.nss_len = 0;
|
|
if (s->rx_info.tsa)
|
|
{
|
|
free(s->rx_info.tsa);
|
|
s->rx_info.tsa = NULL;
|
|
}
|
|
if (s->rx_info.ira)
|
|
{
|
|
free(s->rx_info.ira);
|
|
s->rx_info.ira = NULL;
|
|
}
|
|
if (s->rx_info.cia)
|
|
{
|
|
free(s->rx_info.cia);
|
|
s->rx_info.cia = NULL;
|
|
}
|
|
if (s->rx_info.isp)
|
|
{
|
|
free(s->rx_info.isp);
|
|
s->rx_info.isp = NULL;
|
|
}
|
|
if (s->rx_info.csa)
|
|
{
|
|
free(s->rx_info.csa);
|
|
s->rx_info.csa = NULL;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static uint8_t check_next_tx_step(t30_state_t *s)
|
|
{
|
|
int res;
|
|
int more;
|
|
|
|
res = t4_tx_next_page_has_different_format(&(s->t4));
|
|
if (res == 0)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "More pages to come with the same format\n");
|
|
return (s->local_interrupt_pending) ? T30_PRI_MPS : T30_MPS;
|
|
}
|
|
if (res > 0)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "More pages to come with a different format\n");
|
|
s->tx_start_page = t4_tx_get_current_page_in_file(&(s->t4)) + 1;
|
|
return (s->local_interrupt_pending) ? T30_PRI_EOM : T30_EOM;
|
|
}
|
|
/* Call a user handler, if one is set, to check if another document is to be sent.
|
|
If so, we send an EOM, rather than an EOP. Then we will renegotiate, and the new
|
|
document will begin. */
|
|
if (s->document_handler)
|
|
more = s->document_handler(s, s->document_user_data, 0);
|
|
else
|
|
more = FALSE;
|
|
if (more)
|
|
{
|
|
//if (test_ctrl_bit(s->far_dis_dtc_frame, 34))
|
|
// return T30_EOS;
|
|
return (s->local_interrupt_pending) ? T30_PRI_EOM : T30_EOM;
|
|
}
|
|
return (s->local_interrupt_pending) ? T30_PRI_EOP : T30_EOP;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int get_partial_ecm_page(t30_state_t *s)
|
|
{
|
|
int i;
|
|
int len;
|
|
|
|
s->ppr_count = 0;
|
|
s->ecm_progress = 0;
|
|
/* Fill our partial page buffer with a partial page. Use the negotiated preferred frame size
|
|
as the basis for the size of the frames produced. */
|
|
/* We fill the buffer with complete HDLC frames, ready to send out. */
|
|
/* The frames are all marked as not being final frames. When sent, the are followed by a partial
|
|
page signal, which is marked as the final frame. */
|
|
for (i = 3; i < 32 + 3; i++)
|
|
s->ecm_frame_map[i] = 0xFF;
|
|
for (i = 0; i < 256; i++)
|
|
{
|
|
s->ecm_len[i] = -1;
|
|
s->ecm_data[i][0] = 0xFF;
|
|
s->ecm_data[i][1] = 0x03;
|
|
s->ecm_data[i][2] = T4_FCD;
|
|
/* These frames contain a frame sequence number within the partial page (one octet) followed
|
|
by some image data. */
|
|
s->ecm_data[i][3] = (uint8_t) i;
|
|
if ((len = t4_tx_get_chunk(&s->t4, &s->ecm_data[i][4], s->octets_per_ecm_frame)) < s->octets_per_ecm_frame)
|
|
{
|
|
/* The image is not big enough to fill the entire buffer */
|
|
/* We need to pad to a full frame, as most receivers expect that. */
|
|
if (len > 0)
|
|
{
|
|
memset(&s->ecm_data[i][4 + len], 0, s->octets_per_ecm_frame - len);
|
|
s->ecm_len[i++] = (int16_t) (s->octets_per_ecm_frame + 4);
|
|
}
|
|
s->ecm_frames = i;
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Partial page buffer contains %d frames (%d per frame)\n", i, s->octets_per_ecm_frame);
|
|
s->ecm_at_page_end = TRUE;
|
|
return i;
|
|
}
|
|
s->ecm_len[i] = (int16_t) (4 + len);
|
|
}
|
|
/* We filled the entire buffer */
|
|
s->ecm_frames = 256;
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Partial page buffer full (%d per frame)\n", s->octets_per_ecm_frame);
|
|
s->ecm_at_page_end = ((t4_tx_check_bit(&(s->t4)) & 2) != 0);
|
|
return 256;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int t30_ecm_commit_partial_page(t30_state_t *s)
|
|
{
|
|
int i;
|
|
int image_ended;
|
|
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Commiting partial page - block %d, %d frames\n", s->ecm_block, s->ecm_frames);
|
|
image_ended = FALSE;
|
|
for (i = 0; i < s->ecm_frames; i++)
|
|
{
|
|
if (t4_rx_put_chunk(&s->t4, s->ecm_data[i], s->ecm_len[i]))
|
|
{
|
|
/* This is the end of the document */
|
|
/* Clear the buffer */
|
|
for (i = 0; i < 256; i++)
|
|
s->ecm_len[i] = -1;
|
|
s->ecm_frames = -1;
|
|
image_ended = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
/* Clear the buffer */
|
|
for (i = 0; i < 256; i++)
|
|
s->ecm_len[i] = -1;
|
|
s->ecm_block++;
|
|
s->ecm_frames = -1;
|
|
return (image_ended) ? -1 : 0;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int send_next_ecm_frame(t30_state_t *s)
|
|
{
|
|
int i;
|
|
uint8_t frame[3];
|
|
|
|
if (s->ecm_current_tx_frame < s->ecm_frames)
|
|
{
|
|
/* Search for the next frame, within the current partial page, which has
|
|
not been tagged as transferred OK. */
|
|
for (i = s->ecm_current_tx_frame; i < s->ecm_frames; i++)
|
|
{
|
|
if (s->ecm_len[i] >= 0)
|
|
{
|
|
send_frame(s, s->ecm_data[i], s->ecm_len[i]);
|
|
s->ecm_current_tx_frame = i + 1;
|
|
s->ecm_frames_this_tx_burst++;
|
|
return 0;
|
|
}
|
|
}
|
|
s->ecm_current_tx_frame = s->ecm_frames;
|
|
}
|
|
if (s->ecm_current_tx_frame <= s->ecm_frames + 3)
|
|
{
|
|
/* We have sent all the FCD frames. Send some RCP frames. Three seems to be
|
|
a popular number, to minimise the risk of a bit error stopping the receiving
|
|
end from recognising the RCP. */
|
|
s->ecm_current_tx_frame++;
|
|
/* The RCP frame is an odd man out, as its a simple 1 byte control
|
|
frame, but is specified to not have the final bit set. It doesn't
|
|
seem to have the DIS received bit set, either. */
|
|
frame[0] = 0xFF;
|
|
frame[1] = 0x03;
|
|
frame[2] = T4_RCP;
|
|
send_frame(s, frame, 3);
|
|
/* In case we are just after a CTC/CTR exchange, which kicked us back to long training */
|
|
s->short_train = TRUE;
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void send_rr(t30_state_t *s)
|
|
{
|
|
if (s->current_status != T30_ERR_TX_T5EXP)
|
|
send_simple_frame(s, T30_RR);
|
|
else
|
|
send_dcn(s);
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int send_first_ecm_frame(t30_state_t *s)
|
|
{
|
|
s->ecm_current_tx_frame = 0;
|
|
s->ecm_frames_this_tx_burst = 0;
|
|
return send_next_ecm_frame(s);
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void print_frame(t30_state_t *s, const char *io, const uint8_t *msg, int len)
|
|
{
|
|
span_log(&s->logging,
|
|
SPAN_LOG_FLOW,
|
|
"%s %s with%s final frame tag\n",
|
|
io,
|
|
t30_frametype(msg[2]),
|
|
(msg[1] & 0x10) ? "" : "out");
|
|
span_log_buf(&s->logging, SPAN_LOG_FLOW, io, msg, len);
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void send_frame(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
print_frame(s, "Tx: ", msg, len);
|
|
|
|
if (s->real_time_frame_handler)
|
|
s->real_time_frame_handler(s, s->real_time_frame_user_data, FALSE, msg, len);
|
|
if (s->send_hdlc_handler)
|
|
s->send_hdlc_handler(s->send_hdlc_user_data, msg, len);
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void send_simple_frame(t30_state_t *s, int type)
|
|
{
|
|
uint8_t frame[3];
|
|
|
|
/* The simple command/response frames are always final frames */
|
|
frame[0] = 0xFF;
|
|
frame[1] = 0x13;
|
|
frame[2] = (uint8_t) (type | s->dis_received);
|
|
send_frame(s, frame, 3);
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void send_20digit_msg_frame(t30_state_t *s, int cmd, char *msg)
|
|
{
|
|
size_t len;
|
|
int p;
|
|
uint8_t frame[23];
|
|
|
|
len = strlen(msg);
|
|
p = 0;
|
|
frame[p++] = 0xFF;
|
|
frame[p++] = 0x03;
|
|
frame[p++] = (uint8_t) (cmd | s->dis_received);
|
|
while (len > 0)
|
|
frame[p++] = msg[--len];
|
|
while (p < 23)
|
|
frame[p++] = ' ';
|
|
send_frame(s, frame, 23);
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int send_nsf_frame(t30_state_t *s)
|
|
{
|
|
/* Only send if there is an NSF message to send. */
|
|
if (s->tx_info.nsf && s->tx_info.nsf_len)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Sending user supplied NSF - %d octets\n", s->tx_info.nsf_len);
|
|
s->tx_info.nsf[0] = 0xFF;
|
|
s->tx_info.nsf[1] = 0x03;
|
|
s->tx_info.nsf[2] = (uint8_t) (T30_NSF | s->dis_received);
|
|
send_frame(s, s->tx_info.nsf, s->tx_info.nsf_len + 3);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int send_nss_frame(t30_state_t *s)
|
|
{
|
|
/* Only send if there is an NSF message to send. */
|
|
if (s->tx_info.nss && s->tx_info.nss_len)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Sending user supplied NSS - %d octets\n", s->tx_info.nss_len);
|
|
s->tx_info.nss[0] = 0xFF;
|
|
s->tx_info.nss[1] = 0x03;
|
|
s->tx_info.nss[2] = (uint8_t) (T30_NSS | s->dis_received);
|
|
send_frame(s, s->tx_info.nss, s->tx_info.nss_len + 3);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int send_nsc_frame(t30_state_t *s)
|
|
{
|
|
/* Only send if there is an NSF message to send. */
|
|
if (s->tx_info.nsc && s->tx_info.nsc_len)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Sending user supplied NSC - %d octets\n", s->tx_info.nsc_len);
|
|
s->tx_info.nsc[0] = 0xFF;
|
|
s->tx_info.nsc[1] = 0x03;
|
|
s->tx_info.nsc[2] = (uint8_t) (T30_NSC | s->dis_received);
|
|
send_frame(s, s->tx_info.nsc, s->tx_info.nsc_len + 3);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int send_ident_frame(t30_state_t *s, uint8_t cmd)
|
|
{
|
|
if (s->tx_info.ident[0])
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Sending ident '%s'\n", s->tx_info.ident);
|
|
/* 'cmd' should be T30_TSI, T30_CIG or T30_CSI */
|
|
send_20digit_msg_frame(s, cmd, s->tx_info.ident);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int send_psa_frame(t30_state_t *s)
|
|
{
|
|
if (test_ctrl_bit(s->far_dis_dtc_frame, 35) && s->tx_info.polled_sub_address[0])
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Sending polled sub-address '%s'\n", s->tx_info.polled_sub_address);
|
|
send_20digit_msg_frame(s, T30_PSA, s->tx_info.polled_sub_address);
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 35);
|
|
return TRUE;
|
|
}
|
|
clr_ctrl_bit(s->local_dis_dtc_frame, 35);
|
|
return FALSE;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int send_sep_frame(t30_state_t *s)
|
|
{
|
|
if (test_ctrl_bit(s->far_dis_dtc_frame, 47) && s->tx_info.selective_polling_address[0])
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Sending selective polling address '%s'\n", s->tx_info.selective_polling_address);
|
|
send_20digit_msg_frame(s, T30_SEP, s->tx_info.selective_polling_address);
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 47);
|
|
return TRUE;
|
|
}
|
|
clr_ctrl_bit(s->local_dis_dtc_frame, 47);
|
|
return FALSE;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int send_sid_frame(t30_state_t *s)
|
|
{
|
|
/* Only send if there is an ID to send. */
|
|
if (test_ctrl_bit(s->far_dis_dtc_frame, 50) && s->tx_info.sender_ident[0])
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Sending sender identification '%s'\n", s->tx_info.sender_ident);
|
|
send_20digit_msg_frame(s, T30_SID, s->tx_info.sender_ident);
|
|
set_ctrl_bit(s->dcs_frame, 50);
|
|
return TRUE;
|
|
}
|
|
clr_ctrl_bit(s->dcs_frame, 50);
|
|
return FALSE;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int send_pwd_frame(t30_state_t *s)
|
|
{
|
|
/* Only send if there is a password to send. */
|
|
if (test_ctrl_bit(s->far_dis_dtc_frame, 50) && s->tx_info.password[0])
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Sending password '%s'\n", s->tx_info.password);
|
|
send_20digit_msg_frame(s, T30_PWD, s->tx_info.password);
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 50);
|
|
return TRUE;
|
|
}
|
|
clr_ctrl_bit(s->local_dis_dtc_frame, 50);
|
|
return FALSE;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int send_sub_frame(t30_state_t *s)
|
|
{
|
|
/* Only send if there is a sub-address to send. */
|
|
if (test_ctrl_bit(s->far_dis_dtc_frame, 49) && s->tx_info.sub_address[0])
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Sending sub-address '%s'\n", s->tx_info.sub_address);
|
|
send_20digit_msg_frame(s, T30_SUB, s->tx_info.sub_address);
|
|
set_ctrl_bit(s->dcs_frame, 49);
|
|
return TRUE;
|
|
}
|
|
clr_ctrl_bit(s->dcs_frame, 49);
|
|
return FALSE;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int send_tsa_frame(t30_state_t *s)
|
|
{
|
|
if ((test_ctrl_bit(s->far_dis_dtc_frame, 1) || test_ctrl_bit(s->far_dis_dtc_frame, 3)) && 0)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Sending transmitting subscriber internet address '%s'\n", "");
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int send_ira_frame(t30_state_t *s)
|
|
{
|
|
if (test_ctrl_bit(s->far_dis_dtc_frame, 102) && 0)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Sending internet routing address '%s'\n", "");
|
|
set_ctrl_bit(s->dcs_frame, 102);
|
|
return TRUE;
|
|
}
|
|
clr_ctrl_bit(s->dcs_frame, 102);
|
|
return FALSE;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int send_cia_frame(t30_state_t *s)
|
|
{
|
|
if ((test_ctrl_bit(s->far_dis_dtc_frame, 1) || test_ctrl_bit(s->far_dis_dtc_frame, 3)) && 0)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Sending calling subscriber internet address '%s'\n", "");
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int send_isp_frame(t30_state_t *s)
|
|
{
|
|
if (test_ctrl_bit(s->far_dis_dtc_frame, 101) && 0)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Sending internet selective polling address '%s'\n", "");
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 101);
|
|
return TRUE;
|
|
}
|
|
clr_ctrl_bit(s->local_dis_dtc_frame, 101);
|
|
return FALSE;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
#if 0
|
|
static int send_csa_frame(t30_state_t *s)
|
|
{
|
|
#if 0
|
|
if (("in T.37 mode" || "in T.38 mode") && 0)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Sending called subscriber internet address '%s'\n", "");
|
|
return TRUE;
|
|
}
|
|
#endif
|
|
return FALSE;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
#endif
|
|
|
|
static int send_pps_frame(t30_state_t *s)
|
|
{
|
|
uint8_t frame[7];
|
|
|
|
frame[0] = 0xFF;
|
|
frame[1] = 0x13;
|
|
frame[2] = (uint8_t) (T30_PPS | s->dis_received);
|
|
frame[3] = (s->ecm_at_page_end) ? ((uint8_t) (s->next_tx_step | s->dis_received)) : T30_NULL;
|
|
frame[4] = (uint8_t) (s->ecm_tx_page & 0xFF);
|
|
frame[5] = (uint8_t) (s->ecm_block & 0xFF);
|
|
frame[6] = (uint8_t) ((s->ecm_frames_this_tx_burst == 0) ? 0 : (s->ecm_frames_this_tx_burst - 1));
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Sending PPS + %s\n", t30_frametype(frame[3]));
|
|
send_frame(s, frame, 7);
|
|
return frame[3] & 0xFE;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int set_dis_or_dtc(t30_state_t *s)
|
|
{
|
|
/* Whether we use a DIS or a DTC is determined by whether we have received a DIS.
|
|
We just need to edit the prebuilt message. */
|
|
s->local_dis_dtc_frame[2] = (uint8_t) (T30_DIS | s->dis_received);
|
|
/* If we have a file name to receive into, then we are receive capable */
|
|
if (s->rx_file[0])
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 10);
|
|
else
|
|
clr_ctrl_bit(s->local_dis_dtc_frame, 10);
|
|
/* If we have a file name to transmit, then we are ready to transmit (polling) */
|
|
if (s->tx_file[0])
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 9);
|
|
else
|
|
clr_ctrl_bit(s->local_dis_dtc_frame, 9);
|
|
return 0;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
int t30_build_dis_or_dtc(t30_state_t *s)
|
|
{
|
|
int i;
|
|
|
|
/* Build a skeleton for the DIS and DTC messages. This will be edited for
|
|
the dynamically changing capabilities (e.g. can receive) just before
|
|
it is sent. It might also be edited if the application changes our
|
|
capabilities (e.g. disabling fine mode). Right now we set up all the
|
|
unchanging stuff about what we are capable of doing. */
|
|
s->local_dis_dtc_frame[0] = 0xFF;
|
|
s->local_dis_dtc_frame[1] = 0x13;
|
|
s->local_dis_dtc_frame[2] = (uint8_t) (T30_DIS | s->dis_received);
|
|
for (i = 3; i < 19; i++)
|
|
s->local_dis_dtc_frame[i] = 0x00;
|
|
|
|
/* Always say 256 octets per ECM frame preferred, as 64 is never used in the
|
|
real world. */
|
|
if ((s->iaf & T30_IAF_MODE_T37))
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 1);
|
|
if ((s->iaf & T30_IAF_MODE_T38))
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 3);
|
|
/* No 3G mobile */
|
|
/* No V.8 */
|
|
/* 256 octets preferred - don't bother making this optional, as everything uses 256 */
|
|
/* Ready to transmit a fax (polling) will be determined separately, and this message edited. */
|
|
/* Ready to receive a fax will be determined separately, and this message edited. */
|
|
/* With no modems set we are actually selecting V.27ter fallback at 2400bps */
|
|
if ((s->supported_modems & T30_SUPPORT_V27TER))
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 12);
|
|
if ((s->supported_modems & T30_SUPPORT_V29))
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 11);
|
|
/* V.17 is only valid when combined with V.29 and V.27ter, so if we enable V.17 we force the others too. */
|
|
if ((s->supported_modems & T30_SUPPORT_V17))
|
|
s->local_dis_dtc_frame[4] |= (DISBIT6 | DISBIT4 | DISBIT3);
|
|
if ((s->supported_resolutions & T30_SUPPORT_FINE_RESOLUTION))
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 15);
|
|
if ((s->supported_compressions & T30_SUPPORT_T4_2D_COMPRESSION))
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 16);
|
|
/* 215mm wide is always supported */
|
|
if ((s->supported_image_sizes & T30_SUPPORT_303MM_WIDTH))
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 18);
|
|
else if ((s->supported_image_sizes & T30_SUPPORT_255MM_WIDTH))
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 17);
|
|
/* A4 is always supported. */
|
|
if ((s->supported_image_sizes & T30_SUPPORT_UNLIMITED_LENGTH))
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 20);
|
|
else if ((s->supported_image_sizes & T30_SUPPORT_B4_LENGTH))
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 19);
|
|
/* No scan-line padding required, but some may be specified by the application. */
|
|
set_ctrl_bits(s->local_dis_dtc_frame, s->local_min_scan_time_code, 21);
|
|
if ((s->supported_compressions & T30_SUPPORT_NO_COMPRESSION))
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 26);
|
|
if (s->ecm_allowed)
|
|
{
|
|
/* ECM allowed */
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 27);
|
|
/* Only offer the option of fancy compression schemes, if we are
|
|
also offering the ECM option needed to support them. */
|
|
if ((s->supported_compressions & T30_SUPPORT_T6_COMPRESSION))
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 31);
|
|
if ((s->supported_compressions & T30_SUPPORT_T43_COMPRESSION))
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 36);
|
|
if ((s->supported_compressions & T30_SUPPORT_T85_COMPRESSION))
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 78);
|
|
/* No T.85 optional L0. */
|
|
if ((s->supported_compressions & T30_SUPPORT_T45_COMPRESSION))
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 116);
|
|
}
|
|
if ((s->supported_t30_features & T30_SUPPORT_FIELD_NOT_VALID))
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 33);
|
|
if ((s->supported_t30_features & T30_SUPPORT_MULTIPLE_SELECTIVE_POLLING))
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 34);
|
|
if ((s->supported_t30_features & T30_SUPPORT_POLLED_SUB_ADDRESSING))
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 35);
|
|
/* No plane interleave */
|
|
/* No G.726 */
|
|
/* No extended voice coding */
|
|
if ((s->supported_resolutions & T30_SUPPORT_SUPERFINE_RESOLUTION))
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 41);
|
|
if ((s->supported_resolutions & T30_SUPPORT_300_300_RESOLUTION))
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 42);
|
|
if ((s->supported_resolutions & (T30_SUPPORT_400_400_RESOLUTION | T30_SUPPORT_R16_RESOLUTION)))
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 43);
|
|
/* Metric */
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 45);
|
|
/* Superfine minimum scan line time pattern follows fine */
|
|
if ((s->supported_t30_features & T30_SUPPORT_SELECTIVE_POLLING))
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 47);
|
|
if ((s->supported_t30_features & T30_SUPPORT_SUB_ADDRESSING))
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 49);
|
|
if ((s->supported_t30_features & T30_SUPPORT_IDENTIFICATION))
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 50);
|
|
/* Ready to transmit a data file (polling) */
|
|
if (s->tx_file[0])
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 51);
|
|
/* No Binary file transfer (BFT) */
|
|
/* No Document transfer mode (DTM) */
|
|
/* No Electronic data interchange (EDI) */
|
|
/* No Basic transfer mode (BTM) */
|
|
/* No mixed mode (polling) */
|
|
/* No character mode */
|
|
/* No mixed mode (Annex E/T.4) */
|
|
/* No mode 26 (T.505) */
|
|
/* No digital network capability */
|
|
/* No duplex operation */
|
|
/* No JPEG */
|
|
/* No full colour */
|
|
/* No 12bits/pel */
|
|
/* No sub-sampling (1:1:1) */
|
|
/* No custom illuminant */
|
|
/* No custom gamut range */
|
|
if ((s->supported_image_sizes & T30_SUPPORT_US_LETTER_LENGTH))
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 76);
|
|
if ((s->supported_image_sizes & T30_SUPPORT_US_LEGAL_LENGTH))
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 77);
|
|
/* No HKM key management */
|
|
/* No RSA key management */
|
|
/* No override */
|
|
/* No HFX40 cipher */
|
|
/* No alternative cipher number 2 */
|
|
/* No alternative cipher number 3 */
|
|
/* No HFX40-I hashing */
|
|
/* No alternative hashing system number 2 */
|
|
/* No alternative hashing system number 3 */
|
|
/* No T.44 (mixed raster content) */
|
|
/* No page length maximum strip size for T.44 (mixed raster content) */
|
|
/* No colour/grey scale 300x300 or 400x400 */
|
|
/* No colour/grey scale 100x100 */
|
|
/* No simple phase C BFT negotiations */
|
|
/* No extended BFT negotiations */
|
|
if ((s->supported_t30_features & T30_SUPPORT_INTERNET_SELECTIVE_POLLING_ADDRESS))
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 101);
|
|
if ((s->supported_t30_features & T30_SUPPORT_INTERNET_ROUTING_ADDRESS))
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 102);
|
|
if ((s->supported_resolutions & T30_SUPPORT_600_600_RESOLUTION))
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 105);
|
|
if ((s->supported_resolutions & T30_SUPPORT_1200_1200_RESOLUTION))
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 106);
|
|
if ((s->supported_resolutions & T30_SUPPORT_300_600_RESOLUTION))
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 107);
|
|
if ((s->supported_resolutions & T30_SUPPORT_400_800_RESOLUTION))
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 108);
|
|
if ((s->supported_resolutions & T30_SUPPORT_600_1200_RESOLUTION))
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 109);
|
|
/* No colour/grey scale 600x600 */
|
|
/* No colour/grey scale 1200x1200 */
|
|
/* No double sided printing (alternate mode) */
|
|
/* No double sided printing (continuous mode) */
|
|
/* No black and white mixed raster content profile */
|
|
/* No shared data memory */
|
|
/* No T.44 colour space */
|
|
if ((s->iaf & T30_IAF_MODE_FLOW_CONTROL))
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 121);
|
|
/* No k > 4 */
|
|
if ((s->iaf & T30_IAF_MODE_CONTINUOUS_FLOW))
|
|
set_ctrl_bit(s->local_dis_dtc_frame, 123);
|
|
/* No T.89 profile */
|
|
s->local_dis_dtc_len = 19;
|
|
//t30_decode_dis_dtc_dcs(s, s->local_dis_dtc_frame, s->local_dis_dtc_len);
|
|
return 0;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int prune_dis_dtc(t30_state_t *s)
|
|
{
|
|
int i;
|
|
|
|
/* Find the last octet that is really needed, set the extension bits, and trim the message length */
|
|
for (i = 18; i >= 6; i--)
|
|
{
|
|
/* Strip the top bit */
|
|
s->local_dis_dtc_frame[i] &= (DISBIT1 | DISBIT2 | DISBIT3 | DISBIT4 | DISBIT5 | DISBIT6 | DISBIT7);
|
|
/* Check if there is some real message content here */
|
|
if (s->local_dis_dtc_frame[i])
|
|
break;
|
|
}
|
|
s->local_dis_dtc_len = i + 1;
|
|
/* Fill in any required extension bits */
|
|
s->local_dis_dtc_frame[i] &= ~DISBIT8;
|
|
for (i--; i > 4; i--)
|
|
s->local_dis_dtc_frame[i] |= DISBIT8;
|
|
t30_decode_dis_dtc_dcs(s, s->local_dis_dtc_frame, s->local_dis_dtc_len);
|
|
return s->local_dis_dtc_len;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int build_dcs(t30_state_t *s)
|
|
{
|
|
int i;
|
|
int bad;
|
|
|
|
/* Make a DCS frame based on local issues and the latest received DIS/DTC frame. Negotiate
|
|
the result based on what both parties can do. */
|
|
s->dcs_frame[0] = 0xFF;
|
|
s->dcs_frame[1] = 0x13;
|
|
s->dcs_frame[2] = (uint8_t) (T30_DCS | s->dis_received);
|
|
for (i = 3; i < 19; i++)
|
|
s->dcs_frame[i] = 0x00;
|
|
|
|
#if 0
|
|
/* Check for T.37 simple mode. */
|
|
if (test_ctrl_bit(s->far_dis_dtc_frame, 1))
|
|
set_ctrl_bit(s->dcs_frame, 1);
|
|
/* Check for T.38 mode. */
|
|
if (test_ctrl_bit(s->far_dis_dtc_frame, 3))
|
|
set_ctrl_bit(s->dcs_frame, 3);
|
|
#endif
|
|
|
|
/* Set to required modem rate */
|
|
s->dcs_frame[4] |= fallback_sequence[s->current_fallback].dcs_code;
|
|
|
|
/* Select the compression to use. */
|
|
switch (s->line_encoding)
|
|
{
|
|
case T4_COMPRESSION_ITU_T6:
|
|
set_ctrl_bit(s->dcs_frame, 31);
|
|
set_ctrl_bits(s->dcs_frame, T30_MIN_SCAN_0MS, 21);
|
|
break;
|
|
case T4_COMPRESSION_ITU_T4_2D:
|
|
set_ctrl_bit(s->dcs_frame, 16);
|
|
set_ctrl_bits(s->dcs_frame, s->min_scan_time_code, 21);
|
|
break;
|
|
case T4_COMPRESSION_ITU_T4_1D:
|
|
set_ctrl_bits(s->dcs_frame, s->min_scan_time_code, 21);
|
|
break;
|
|
default:
|
|
set_ctrl_bits(s->dcs_frame, T30_MIN_SCAN_0MS, 21);
|
|
break;
|
|
}
|
|
/* We have a file to send, so tell the far end to go into receive mode. */
|
|
set_ctrl_bit(s->dcs_frame, 10);
|
|
/* Set the Y resolution bits */
|
|
bad = T30_ERR_OK;
|
|
switch (s->y_resolution)
|
|
{
|
|
case T4_Y_RESOLUTION_1200:
|
|
switch (s->x_resolution)
|
|
{
|
|
case T4_X_RESOLUTION_600:
|
|
if (!(s->supported_resolutions & T30_SUPPORT_600_1200_RESOLUTION))
|
|
bad = T30_ERR_NORESSUPPORT;
|
|
else
|
|
set_ctrl_bit(s->dcs_frame, 109);
|
|
break;
|
|
case T4_X_RESOLUTION_1200:
|
|
if (!(s->supported_resolutions & T30_SUPPORT_1200_1200_RESOLUTION))
|
|
bad = T30_ERR_NORESSUPPORT;
|
|
else
|
|
set_ctrl_bit(s->dcs_frame, 106);
|
|
break;
|
|
default:
|
|
bad = T30_ERR_NORESSUPPORT;
|
|
break;
|
|
}
|
|
break;
|
|
case T4_Y_RESOLUTION_800:
|
|
switch (s->x_resolution)
|
|
{
|
|
case T4_X_RESOLUTION_R16:
|
|
if (!(s->supported_resolutions & T30_SUPPORT_400_800_RESOLUTION))
|
|
bad = T30_ERR_NORESSUPPORT;
|
|
else
|
|
set_ctrl_bit(s->dcs_frame, 108);
|
|
break;
|
|
default:
|
|
bad = T30_ERR_NORESSUPPORT;
|
|
break;
|
|
}
|
|
break;
|
|
case T4_Y_RESOLUTION_600:
|
|
switch (s->x_resolution)
|
|
{
|
|
case T4_X_RESOLUTION_300:
|
|
if (!(s->supported_resolutions & T30_SUPPORT_300_600_RESOLUTION))
|
|
bad = T30_ERR_NORESSUPPORT;
|
|
else
|
|
set_ctrl_bit(s->dcs_frame, 107);
|
|
break;
|
|
case T4_X_RESOLUTION_600:
|
|
if (!(s->supported_resolutions & T30_SUPPORT_600_600_RESOLUTION))
|
|
bad = T30_ERR_NORESSUPPORT;
|
|
else
|
|
set_ctrl_bit(s->dcs_frame, 105);
|
|
break;
|
|
default:
|
|
bad = T30_ERR_NORESSUPPORT;
|
|
break;
|
|
}
|
|
break;
|
|
case T4_Y_RESOLUTION_SUPERFINE:
|
|
if (!(s->supported_resolutions & T30_SUPPORT_SUPERFINE_RESOLUTION))
|
|
{
|
|
bad = T30_ERR_NORESSUPPORT;
|
|
}
|
|
else
|
|
{
|
|
switch (s->x_resolution)
|
|
{
|
|
case T4_X_RESOLUTION_R8:
|
|
set_ctrl_bit(s->dcs_frame, 41);
|
|
break;
|
|
case T4_X_RESOLUTION_R16:
|
|
set_ctrl_bit(s->dcs_frame, 43);
|
|
break;
|
|
default:
|
|
bad = T30_ERR_NORESSUPPORT;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case T4_Y_RESOLUTION_300:
|
|
switch (s->x_resolution)
|
|
{
|
|
case T4_X_RESOLUTION_300:
|
|
if (!(s->supported_resolutions & T30_SUPPORT_300_300_RESOLUTION))
|
|
bad = T30_ERR_NORESSUPPORT;
|
|
else
|
|
set_ctrl_bit(s->dcs_frame, 42);
|
|
break;
|
|
default:
|
|
bad = T30_ERR_NORESSUPPORT;
|
|
break;
|
|
}
|
|
break;
|
|
case T4_Y_RESOLUTION_FINE:
|
|
if (!(s->supported_resolutions & T30_SUPPORT_FINE_RESOLUTION))
|
|
{
|
|
bad = T30_ERR_NORESSUPPORT;
|
|
}
|
|
else
|
|
{
|
|
switch (s->x_resolution)
|
|
{
|
|
case T4_X_RESOLUTION_R8:
|
|
set_ctrl_bit(s->dcs_frame, 15);
|
|
break;
|
|
default:
|
|
bad = T30_ERR_NORESSUPPORT;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
case T4_Y_RESOLUTION_STANDARD:
|
|
switch (s->x_resolution)
|
|
{
|
|
case T4_X_RESOLUTION_R8:
|
|
/* No bits to set for this */
|
|
break;
|
|
default:
|
|
bad = T30_ERR_NORESSUPPORT;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
if (bad != T30_ERR_OK)
|
|
{
|
|
s->current_status = bad;
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Image resolution (%d x %d) not acceptable\n", s->x_resolution, s->y_resolution);
|
|
return -1;
|
|
}
|
|
/* Deal with the image width. The X resolution will fall in line with any valid width. */
|
|
/* Low (R4) res widths are not supported in recent versions of T.30 */
|
|
bad = T30_ERR_OK;
|
|
switch (s->image_width)
|
|
{
|
|
case T4_WIDTH_R8_A4:
|
|
case T4_WIDTH_300_A4:
|
|
case T4_WIDTH_R16_A4:
|
|
case T4_WIDTH_600_A4:
|
|
case T4_WIDTH_1200_A4:
|
|
/* No width related bits need to be set. */
|
|
break;
|
|
case T4_WIDTH_R8_B4:
|
|
case T4_WIDTH_300_B4:
|
|
case T4_WIDTH_R16_B4:
|
|
case T4_WIDTH_600_B4:
|
|
case T4_WIDTH_1200_B4:
|
|
if ((s->far_dis_dtc_frame[5] & (DISBIT2 | DISBIT1)) < 1)
|
|
bad = T30_ERR_NOSIZESUPPORT;
|
|
else if (!(s->supported_image_sizes & T30_SUPPORT_255MM_WIDTH))
|
|
bad = T30_ERR_BADTIFF;
|
|
else
|
|
set_ctrl_bit(s->dcs_frame, 17);
|
|
break;
|
|
case T4_WIDTH_R8_A3:
|
|
case T4_WIDTH_300_A3:
|
|
case T4_WIDTH_R16_A3:
|
|
case T4_WIDTH_600_A3:
|
|
case T4_WIDTH_1200_A3:
|
|
if ((s->far_dis_dtc_frame[5] & (DISBIT2 | DISBIT1)) < 2)
|
|
bad = T30_ERR_NOSIZESUPPORT;
|
|
else if (!(s->supported_image_sizes & T30_SUPPORT_303MM_WIDTH))
|
|
bad = T30_ERR_BADTIFF;
|
|
else
|
|
set_ctrl_bit(s->dcs_frame, 18);
|
|
break;
|
|
default:
|
|
/* T.30 does not support this width */
|
|
bad = T30_ERR_BADTIFF;
|
|
break;
|
|
}
|
|
if (bad != T30_ERR_OK)
|
|
{
|
|
s->current_status = bad;
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Image width (%d pixels) not a valid FAX image width\n", s->image_width);
|
|
return -1;
|
|
}
|
|
switch (s->image_width)
|
|
{
|
|
case T4_WIDTH_R8_A4:
|
|
case T4_WIDTH_R8_B4:
|
|
case T4_WIDTH_R8_A3:
|
|
/* These are always OK */
|
|
break;
|
|
case T4_WIDTH_300_A4:
|
|
case T4_WIDTH_300_B4:
|
|
case T4_WIDTH_300_A3:
|
|
if (!test_ctrl_bit(s->far_dis_dtc_frame, 42) && !test_ctrl_bit(s->far_dis_dtc_frame, 107))
|
|
bad = T30_ERR_NOSIZESUPPORT;
|
|
break;
|
|
case T4_WIDTH_R16_A4:
|
|
case T4_WIDTH_R16_B4:
|
|
case T4_WIDTH_R16_A3:
|
|
if (!test_ctrl_bit(s->far_dis_dtc_frame, 43))
|
|
bad = T30_ERR_NOSIZESUPPORT;
|
|
break;
|
|
case T4_WIDTH_600_A4:
|
|
case T4_WIDTH_600_B4:
|
|
case T4_WIDTH_600_A3:
|
|
if (!test_ctrl_bit(s->far_dis_dtc_frame, 105) && !test_ctrl_bit(s->far_dis_dtc_frame, 109))
|
|
bad = T30_ERR_NOSIZESUPPORT;
|
|
break;
|
|
case T4_WIDTH_1200_A4:
|
|
case T4_WIDTH_1200_B4:
|
|
case T4_WIDTH_1200_A3:
|
|
if (!test_ctrl_bit(s->far_dis_dtc_frame, 106))
|
|
bad = T30_ERR_NOSIZESUPPORT;
|
|
break;
|
|
default:
|
|
/* T.30 does not support this width */
|
|
bad = T30_ERR_BADTIFF;
|
|
break;
|
|
}
|
|
if (bad != T30_ERR_OK)
|
|
{
|
|
s->current_status = bad;
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Image width (%d pixels) not a valid FAX image width\n", s->image_width);
|
|
return -1;
|
|
}
|
|
/* Deal with the image length */
|
|
/* If the other end supports unlimited length, then use that. Otherwise, if the other end supports
|
|
B4 use that, as its longer than the default A4 length. */
|
|
if (test_ctrl_bit(s->far_dis_dtc_frame, 20))
|
|
set_ctrl_bit(s->dcs_frame, 20);
|
|
else if (test_ctrl_bit(s->far_dis_dtc_frame, 19))
|
|
set_ctrl_bit(s->dcs_frame, 19);
|
|
|
|
if (s->error_correcting_mode)
|
|
set_ctrl_bit(s->dcs_frame, 27);
|
|
|
|
if ((s->iaf & T30_IAF_MODE_FLOW_CONTROL) && test_ctrl_bit(s->far_dis_dtc_frame, 121))
|
|
set_ctrl_bit(s->dcs_frame, 121);
|
|
if ((s->iaf & T30_IAF_MODE_CONTINUOUS_FLOW) && test_ctrl_bit(s->far_dis_dtc_frame, 123))
|
|
set_ctrl_bit(s->dcs_frame, 123);
|
|
s->dcs_len = 19;
|
|
//t30_decode_dis_dtc_dcs(s, s->dcs_frame, s->dcs_len);
|
|
return 0;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int prune_dcs(t30_state_t *s)
|
|
{
|
|
int i;
|
|
|
|
/* Find the last octet that is really needed, set the extension bits, and trim the message length */
|
|
for (i = 18; i >= 6; i--)
|
|
{
|
|
/* Strip the top bit */
|
|
s->dcs_frame[i] &= (DISBIT1 | DISBIT2 | DISBIT3 | DISBIT4 | DISBIT5 | DISBIT6 | DISBIT7);
|
|
/* Check if there is some real message content here */
|
|
if (s->dcs_frame[i])
|
|
break;
|
|
}
|
|
s->dcs_len = i + 1;
|
|
/* Fill in any required extension bits */
|
|
s->local_dis_dtc_frame[i] &= ~DISBIT8;
|
|
for (i-- ; i > 4; i--)
|
|
s->dcs_frame[i] |= DISBIT8;
|
|
t30_decode_dis_dtc_dcs(s, s->dcs_frame, s->dcs_len);
|
|
return s->dcs_len;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int step_fallback_entry(t30_state_t *s)
|
|
{
|
|
int min_row_bits;
|
|
|
|
while (fallback_sequence[++s->current_fallback].which)
|
|
{
|
|
if ((fallback_sequence[s->current_fallback].which & s->current_permitted_modems))
|
|
break;
|
|
}
|
|
if (fallback_sequence[s->current_fallback].which == 0)
|
|
return -1;
|
|
/* TODO: This only sets the minimum row time for future pages. It doesn't fix up the
|
|
current page, though it is benign - fallback will only result in an excessive
|
|
minimum. */
|
|
min_row_bits = set_min_scan_time_code(s);
|
|
t4_tx_set_min_row_bits(&s->t4, min_row_bits);
|
|
/* We need to rebuild the DCS message we will send. */
|
|
build_dcs(s);
|
|
return s->current_fallback;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int find_fallback_entry(int dcs_code)
|
|
{
|
|
int i;
|
|
|
|
/* The table is short, and not searched often, so a brain-dead linear scan seems OK */
|
|
for (i = 0; fallback_sequence[i].bit_rate; i++)
|
|
{
|
|
if (fallback_sequence[i].dcs_code == dcs_code)
|
|
break;
|
|
}
|
|
if (fallback_sequence[i].bit_rate == 0)
|
|
return -1;
|
|
return i;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void send_dcn(t30_state_t *s)
|
|
{
|
|
queue_phase(s, T30_PHASE_D_TX);
|
|
set_state(s, T30_STATE_C);
|
|
send_simple_frame(s, T30_DCN);
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void return_to_phase_b(t30_state_t *s, int with_fallback)
|
|
{
|
|
/* This is what we do after things like T30_EOM is exchanged. */
|
|
if (step_fallback_entry(s) < 0)
|
|
{
|
|
/* We have fallen back as far as we can go. Give up. */
|
|
s->current_fallback = 0;
|
|
s->current_status = T30_ERR_CANNOT_TRAIN;
|
|
send_dcn(s);
|
|
}
|
|
else
|
|
{
|
|
if (s->calling_party)
|
|
set_state(s, T30_STATE_T);
|
|
else
|
|
set_state(s, T30_STATE_R);
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int send_dis_or_dtc_sequence(t30_state_t *s, int start)
|
|
{
|
|
/* (NSF) (CSI) DIS */
|
|
/* (NSC) (CIG) (PWD) (SEP) (PSA) (CIA) (ISP) DTC */
|
|
if (start)
|
|
{
|
|
set_dis_or_dtc(s);
|
|
set_state(s, T30_STATE_R);
|
|
s->step = 0;
|
|
}
|
|
if (!s->dis_received)
|
|
{
|
|
/* DIS sequence */
|
|
switch (s->step)
|
|
{
|
|
case 0:
|
|
s->step++;
|
|
if (send_nsf_frame(s))
|
|
break;
|
|
/* Fall through */
|
|
case 1:
|
|
s->step++;
|
|
if (send_ident_frame(s, T30_CSI))
|
|
break;
|
|
/* Fall through */
|
|
case 2:
|
|
s->step++;
|
|
prune_dis_dtc(s);
|
|
send_frame(s, s->local_dis_dtc_frame, s->local_dis_dtc_len);
|
|
break;
|
|
case 3:
|
|
s->step++;
|
|
/* Shut down HDLC transmission. */
|
|
if (s->send_hdlc_handler)
|
|
s->send_hdlc_handler(s->send_hdlc_user_data, NULL, 0);
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* DTC sequence */
|
|
switch (s->step)
|
|
{
|
|
case 0:
|
|
s->step++;
|
|
if (send_nsc_frame(s))
|
|
break;
|
|
/* Fall through */
|
|
case 1:
|
|
s->step++;
|
|
if (send_ident_frame(s, T30_CIG))
|
|
break;
|
|
/* Fall through */
|
|
case 2:
|
|
s->step++;
|
|
if (send_pwd_frame(s))
|
|
break;
|
|
/* Fall through */
|
|
case 3:
|
|
s->step++;
|
|
if (send_sep_frame(s))
|
|
break;
|
|
/* Fall through */
|
|
case 4:
|
|
s->step++;
|
|
if (send_psa_frame(s))
|
|
break;
|
|
/* Fall through */
|
|
case 5:
|
|
s->step++;
|
|
if (send_cia_frame(s))
|
|
break;
|
|
/* Fall through */
|
|
case 6:
|
|
s->step++;
|
|
if (send_isp_frame(s))
|
|
break;
|
|
/* Fall through */
|
|
case 7:
|
|
s->step++;
|
|
prune_dis_dtc(s);
|
|
send_frame(s, s->local_dis_dtc_frame, s->local_dis_dtc_len);
|
|
break;
|
|
case 8:
|
|
s->step++;
|
|
/* Shut down HDLC transmission. */
|
|
if (s->send_hdlc_handler)
|
|
s->send_hdlc_handler(s->send_hdlc_user_data, NULL, 0);
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int send_dcs_sequence(t30_state_t *s, int start)
|
|
{
|
|
/* (NSS) (TSI) (SUB) (SID) (TSA) (IRA) DCS */
|
|
/* Schedule training after the messages */
|
|
if (start)
|
|
{
|
|
prune_dcs(s);
|
|
set_state(s, T30_STATE_D);
|
|
s->step = 0;
|
|
}
|
|
switch (s->step)
|
|
{
|
|
case 0:
|
|
s->step++;
|
|
if (send_nss_frame(s))
|
|
break;
|
|
/* Fall through */
|
|
case 1:
|
|
s->step++;
|
|
if (send_ident_frame(s, T30_TSI))
|
|
break;
|
|
/* Fall through */
|
|
case 2:
|
|
s->step++;
|
|
if (send_sub_frame(s))
|
|
break;
|
|
/* Fall through */
|
|
case 3:
|
|
s->step++;
|
|
if (send_sid_frame(s))
|
|
break;
|
|
/* Fall through */
|
|
case 4:
|
|
s->step++;
|
|
if (send_tsa_frame(s))
|
|
break;
|
|
/* Fall through */
|
|
case 5:
|
|
s->step++;
|
|
if (send_ira_frame(s))
|
|
break;
|
|
/* Fall through */
|
|
case 6:
|
|
s->step++;
|
|
prune_dcs(s);
|
|
send_frame(s, s->dcs_frame, s->dcs_len);
|
|
break;
|
|
case 7:
|
|
s->step++;
|
|
/* Shut down HDLC transmission. */
|
|
if (s->send_hdlc_handler)
|
|
s->send_hdlc_handler(s->send_hdlc_user_data, NULL, 0);
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int send_cfr_sequence(t30_state_t *s, int start)
|
|
{
|
|
/* (CSA) CFR */
|
|
/* CFR is usually a simple frame, but can become a sequence with Internet
|
|
FAXing. */
|
|
send_simple_frame(s, T30_CFR);
|
|
return 0;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void disconnect(t30_state_t *s)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Disconnecting\n");
|
|
/* Make sure any FAX in progress is tidied up. If the tidying up has
|
|
already happened, repeating it here is harmless. */
|
|
switch (s->operation_in_progress)
|
|
{
|
|
case OPERATION_IN_PROGRESS_T4_TX:
|
|
t4_tx_release(&(s->t4));
|
|
break;
|
|
case OPERATION_IN_PROGRESS_T4_RX:
|
|
t4_rx_release(&(s->t4));
|
|
break;
|
|
}
|
|
s->operation_in_progress = OPERATION_IN_PROGRESS_NONE;
|
|
s->timer_t0_t1 = 0;
|
|
s->timer_t2_t4 = 0;
|
|
s->timer_t3 = 0;
|
|
s->timer_t5 = 0;
|
|
set_phase(s, T30_PHASE_E);
|
|
set_state(s, T30_STATE_B);
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int set_min_scan_time_code(t30_state_t *s)
|
|
{
|
|
/* Translation between the codes for the minimum scan times the other end needs,
|
|
and the codes for what we say will be used. We need 0 minimum. */
|
|
static const uint8_t translate_min_scan_time[3][8] =
|
|
{
|
|
{T30_MIN_SCAN_20MS, T30_MIN_SCAN_5MS, T30_MIN_SCAN_10MS, T30_MIN_SCAN_20MS, T30_MIN_SCAN_40MS, T30_MIN_SCAN_40MS, T30_MIN_SCAN_10MS, T30_MIN_SCAN_0MS}, /* normal */
|
|
{T30_MIN_SCAN_20MS, T30_MIN_SCAN_5MS, T30_MIN_SCAN_10MS, T30_MIN_SCAN_10MS, T30_MIN_SCAN_40MS, T30_MIN_SCAN_20MS, T30_MIN_SCAN_5MS, T30_MIN_SCAN_0MS}, /* fine */
|
|
{T30_MIN_SCAN_10MS, T30_MIN_SCAN_5MS, T30_MIN_SCAN_5MS, T30_MIN_SCAN_5MS, T30_MIN_SCAN_20MS, T30_MIN_SCAN_10MS, T30_MIN_SCAN_5MS, T30_MIN_SCAN_0MS} /* superfine, when half fine time is selected */
|
|
};
|
|
/* Translation between the codes for the minimum scan time we will use, and milliseconds. */
|
|
static const int min_scan_times[8] =
|
|
{
|
|
20, 5, 10, 0, 40, 0, 0, 0
|
|
};
|
|
int min_bits_field;
|
|
|
|
/* Set the minimum scan time bits */
|
|
if (s->error_correcting_mode)
|
|
min_bits_field = T30_MIN_SCAN_0MS;
|
|
else
|
|
min_bits_field = (s->far_dis_dtc_frame[5] >> 4) & 7;
|
|
switch (s->y_resolution)
|
|
{
|
|
case T4_Y_RESOLUTION_SUPERFINE:
|
|
if (!test_ctrl_bit(s->far_dis_dtc_frame, 41))
|
|
{
|
|
s->current_status = T30_ERR_NORESSUPPORT;
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Remote FAX does not support super-fine resolution.\n");
|
|
return -1;
|
|
}
|
|
s->min_scan_time_code = translate_min_scan_time[(test_ctrl_bit(s->far_dis_dtc_frame, 46)) ? 2 : 1][min_bits_field];
|
|
break;
|
|
case T4_Y_RESOLUTION_FINE:
|
|
if (!test_ctrl_bit(s->far_dis_dtc_frame, 15))
|
|
{
|
|
s->current_status = T30_ERR_NORESSUPPORT;
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Remote FAX does not support fine resolution.\n");
|
|
return -1;
|
|
}
|
|
s->min_scan_time_code = translate_min_scan_time[1][min_bits_field];
|
|
break;
|
|
default:
|
|
case T4_Y_RESOLUTION_STANDARD:
|
|
s->min_scan_time_code = translate_min_scan_time[0][min_bits_field];
|
|
break;
|
|
}
|
|
if (!s->error_correcting_mode && (s->iaf & T30_IAF_MODE_NO_FILL_BITS))
|
|
return 0;
|
|
return fallback_sequence[s->current_fallback].bit_rate*min_scan_times[s->min_scan_time_code]/1000;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int start_sending_document(t30_state_t *s)
|
|
{
|
|
int min_row_bits;
|
|
|
|
if (s->tx_file[0] == '\0')
|
|
{
|
|
/* There is nothing to send */
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "No document to send\n");
|
|
return -1;
|
|
}
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Start sending document\n");
|
|
if (t4_tx_init(&s->t4, s->tx_file, s->tx_start_page, s->tx_stop_page) == NULL)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_WARNING, "Cannot open source TIFF file '%s'\n", s->tx_file);
|
|
s->current_status = T30_ERR_FILEERROR;
|
|
return -1;
|
|
}
|
|
s->operation_in_progress = OPERATION_IN_PROGRESS_T4_TX;
|
|
t4_tx_get_pages_in_file(&s->t4);
|
|
t4_tx_set_tx_encoding(&s->t4, s->line_encoding);
|
|
t4_tx_set_local_ident(&s->t4, s->tx_info.ident);
|
|
t4_tx_set_header_info(&s->t4, s->header_info);
|
|
|
|
s->x_resolution = t4_tx_get_x_resolution(&s->t4);
|
|
s->y_resolution = t4_tx_get_y_resolution(&s->t4);
|
|
/* The minimum scan time to be used can't be evaluated until we know the Y resolution, and
|
|
must be evaluated before the minimum scan row bits can be evaluated. */
|
|
if ((min_row_bits = set_min_scan_time_code(s)) < 0)
|
|
{
|
|
t4_tx_release(&(s->t4));
|
|
s->operation_in_progress = OPERATION_IN_PROGRESS_NONE;
|
|
return -1;
|
|
}
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Minimum bits per row will be %d\n", min_row_bits);
|
|
t4_tx_set_min_row_bits(&s->t4, min_row_bits);
|
|
|
|
if (tx_start_page(s))
|
|
return -1;
|
|
s->image_width = t4_tx_get_image_width(&(s->t4));
|
|
if (s->error_correcting_mode)
|
|
{
|
|
if (get_partial_ecm_page(s) == 0)
|
|
span_log(&s->logging, SPAN_LOG_WARNING, "No image data to send\n");
|
|
}
|
|
return 0;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int restart_sending_document(t30_state_t *s)
|
|
{
|
|
t4_tx_restart_page(&(s->t4));
|
|
s->retries = 0;
|
|
s->ecm_block = 0;
|
|
send_dcs_sequence(s, TRUE);
|
|
return 0;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int start_receiving_document(t30_state_t *s)
|
|
{
|
|
if (s->rx_file[0] == '\0')
|
|
{
|
|
/* There is nothing to receive to */
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "No document to receive\n");
|
|
return -1;
|
|
}
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Start receiving document\n");
|
|
queue_phase(s, T30_PHASE_B_TX);
|
|
s->ecm_block = 0;
|
|
send_dis_or_dtc_sequence(s, TRUE);
|
|
return 0;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void unexpected_frame(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Unexpected %s received in state %d\n", t30_frametype(msg[2]), s->state);
|
|
switch (s->state)
|
|
{
|
|
case T30_STATE_F_DOC_ECM:
|
|
case T30_STATE_F_DOC_NON_ECM:
|
|
s->current_status = T30_ERR_RX_INVALCMD;
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void unexpected_non_final_frame(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Unexpected %s frame in state %d\n", t30_frametype(msg[2]), s->state);
|
|
s->current_status = T30_ERR_UNEXPECTED;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void unexpected_final_frame(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Unexpected %s received in state %d\n", t30_frametype(msg[2]), s->state);
|
|
s->current_status = T30_ERR_UNEXPECTED;
|
|
send_dcn(s);
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void unexpected_frame_length(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Unexpected %s frame length - %d\n", t30_frametype(msg[0]), len);
|
|
s->current_status = T30_ERR_UNEXPECTED;
|
|
send_dcn(s);
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int process_rx_dis_dtc(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
int new_status;
|
|
|
|
t30_decode_dis_dtc_dcs(s, msg, len);
|
|
if (len < 6)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Short DIS/DTC frame\n");
|
|
return -1;
|
|
}
|
|
|
|
if (msg[2] == T30_DIS)
|
|
s->dis_received = TRUE;
|
|
/* Make a local copy of the message, padded to the maximum possible length with zeros. This allows
|
|
us to simply pick out the bits, without worrying about whether they were set from the remote side. */
|
|
s->far_dis_dtc_len = (len > T30_MAX_DIS_DTC_DCS_LEN) ? T30_MAX_DIS_DTC_DCS_LEN : len;
|
|
memcpy(s->far_dis_dtc_frame, msg, s->far_dis_dtc_len);
|
|
if (s->far_dis_dtc_len < T30_MAX_DIS_DTC_DCS_LEN)
|
|
memset(s->far_dis_dtc_frame + s->far_dis_dtc_len, 0, T30_MAX_DIS_DTC_DCS_LEN - s->far_dis_dtc_len);
|
|
s->error_correcting_mode = (s->ecm_allowed && (s->far_dis_dtc_frame[6] & DISBIT3) != 0);
|
|
/* 256 octets per ECM frame */
|
|
s->octets_per_ecm_frame = 256;
|
|
/* Select the compression to use. */
|
|
if (s->error_correcting_mode && (s->supported_compressions & T30_SUPPORT_T6_COMPRESSION) && test_ctrl_bit(s->far_dis_dtc_frame, 31))
|
|
{
|
|
s->line_encoding = T4_COMPRESSION_ITU_T6;
|
|
}
|
|
else if ((s->supported_compressions & T30_SUPPORT_T4_2D_COMPRESSION) && test_ctrl_bit(s->far_dis_dtc_frame, 16))
|
|
{
|
|
s->line_encoding = T4_COMPRESSION_ITU_T4_2D;
|
|
}
|
|
else
|
|
{
|
|
s->line_encoding = T4_COMPRESSION_ITU_T4_1D;
|
|
}
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Selected compression %d\n", s->line_encoding);
|
|
switch (s->far_dis_dtc_frame[4] & (DISBIT6 | DISBIT5 | DISBIT4 | DISBIT3))
|
|
{
|
|
case (DISBIT6 | DISBIT4 | DISBIT3):
|
|
if ((s->supported_modems & T30_SUPPORT_V17))
|
|
{
|
|
s->current_permitted_modems = T30_SUPPORT_V17 | T30_SUPPORT_V29 | T30_SUPPORT_V27TER;
|
|
s->current_fallback = T30_V17_FALLBACK_START;
|
|
break;
|
|
}
|
|
/* Fall through */
|
|
case (DISBIT4 | DISBIT3):
|
|
if ((s->supported_modems & T30_SUPPORT_V29))
|
|
{
|
|
s->current_permitted_modems = T30_SUPPORT_V29 | T30_SUPPORT_V27TER;
|
|
s->current_fallback = T30_V29_FALLBACK_START;
|
|
break;
|
|
}
|
|
/* Fall through */
|
|
case DISBIT4:
|
|
s->current_permitted_modems = T30_SUPPORT_V27TER;
|
|
s->current_fallback = T30_V27TER_FALLBACK_START;
|
|
break;
|
|
case 0:
|
|
s->current_permitted_modems = T30_SUPPORT_V27TER;
|
|
s->current_fallback = T30_V27TER_FALLBACK_START + 1;
|
|
break;
|
|
case DISBIT3:
|
|
if ((s->supported_modems & T30_SUPPORT_V29))
|
|
{
|
|
/* TODO: this doesn't allow for skipping the V.27ter modes */
|
|
s->current_permitted_modems = T30_SUPPORT_V29;
|
|
s->current_fallback = T30_V29_FALLBACK_START;
|
|
break;
|
|
}
|
|
/* Fall through */
|
|
default:
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Remote does not support a compatible modem\n");
|
|
/* We cannot talk to this machine! */
|
|
s->current_status = T30_ERR_INCOMPATIBLE;
|
|
return -1;
|
|
}
|
|
if (s->phase_b_handler)
|
|
{
|
|
new_status = s->phase_b_handler(s, s->phase_b_user_data, msg[2]);
|
|
if (new_status != T30_ERR_OK)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Application rejected DIS/DTC - '%s'\n", t30_completion_code_to_str(new_status));
|
|
s->current_status = new_status;
|
|
/* TODO: If FNV is allowed, process it here */
|
|
send_dcn(s);
|
|
return -1;
|
|
}
|
|
}
|
|
queue_phase(s, T30_PHASE_B_TX);
|
|
/* Try to send something */
|
|
if (s->tx_file[0])
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Trying to send file '%s'\n", s->tx_file);
|
|
if (!test_ctrl_bit(s->far_dis_dtc_frame, 10))
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "%s far end cannot receive\n", t30_frametype(msg[2]));
|
|
s->current_status = T30_ERR_RX_INCAPABLE;
|
|
send_dcn(s);
|
|
}
|
|
if (start_sending_document(s))
|
|
{
|
|
send_dcn(s);
|
|
return -1;
|
|
}
|
|
if (build_dcs(s))
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "The far end is incompatible\n", s->tx_file);
|
|
send_dcn(s);
|
|
return -1;
|
|
}
|
|
s->retries = 0;
|
|
send_dcs_sequence(s, TRUE);
|
|
return 0;
|
|
}
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "%s nothing to send\n", t30_frametype(msg[2]));
|
|
/* ... then try to receive something */
|
|
if (s->rx_file[0])
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Trying to receive file '%s'\n", s->rx_file);
|
|
if (!test_ctrl_bit(s->far_dis_dtc_frame, 9))
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "%s far end cannot transmit\n", t30_frametype(msg[2]));
|
|
s->current_status = T30_ERR_TX_INCAPABLE;
|
|
send_dcn(s);
|
|
return -1;
|
|
}
|
|
if (start_receiving_document(s))
|
|
{
|
|
send_dcn(s);
|
|
return -1;
|
|
}
|
|
if (set_dis_or_dtc(s))
|
|
{
|
|
s->current_status = T30_ERR_INCOMPATIBLE;
|
|
send_dcn(s);
|
|
return -1;
|
|
}
|
|
s->retries = 0;
|
|
send_dis_or_dtc_sequence(s, TRUE);
|
|
return 0;
|
|
}
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "%s nothing to receive\n", t30_frametype(msg[2]));
|
|
/* There is nothing to do, or nothing we are able to do. */
|
|
send_dcn(s);
|
|
return -1;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int process_rx_dcs(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
static const int widths[6][4] =
|
|
{
|
|
{ T4_WIDTH_R4_A4, T4_WIDTH_R4_B4, T4_WIDTH_R4_A3, -1}, /* R4 resolution - no longer used in recent versions of T.30 */
|
|
{ T4_WIDTH_R8_A4, T4_WIDTH_R8_B4, T4_WIDTH_R8_A3, -1}, /* R8 resolution */
|
|
{ T4_WIDTH_300_A4, T4_WIDTH_300_B4, T4_WIDTH_300_A3, -1}, /* 300/inch resolution */
|
|
{ T4_WIDTH_R16_A4, T4_WIDTH_R16_B4, T4_WIDTH_R16_A3, -1}, /* R16 resolution */
|
|
{ T4_WIDTH_600_A4, T4_WIDTH_600_B4, T4_WIDTH_600_A3, -1}, /* 600/inch resolution */
|
|
{T4_WIDTH_1200_A4, T4_WIDTH_1200_B4, T4_WIDTH_1200_A3, -1} /* 1200/inch resolution */
|
|
};
|
|
uint8_t dcs_frame[T30_MAX_DIS_DTC_DCS_LEN];
|
|
int i;
|
|
int new_status;
|
|
|
|
t30_decode_dis_dtc_dcs(s, msg, len);
|
|
|
|
/* Check DCS frame from remote */
|
|
if (len < 6)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Short DCS frame\n");
|
|
return -1;
|
|
}
|
|
|
|
/* Make an ASCII string format copy of the message, for logging in the
|
|
received file. This string does not include the frame header octets. */
|
|
sprintf(s->rx_dcs_string, "%02X", bit_reverse8(msg[3]));
|
|
for (i = 4; i < len; i++)
|
|
sprintf(s->rx_dcs_string + 3*i - 10, " %02X", bit_reverse8(msg[i]));
|
|
/* Make a local copy of the message, padded to the maximum possible length with zeros. This allows
|
|
us to simply pick out the bits, without worrying about whether they were set from the remote side. */
|
|
if (len > T30_MAX_DIS_DTC_DCS_LEN)
|
|
{
|
|
memcpy(dcs_frame, msg, T30_MAX_DIS_DTC_DCS_LEN);
|
|
}
|
|
else
|
|
{
|
|
memcpy(dcs_frame, msg, len);
|
|
if (len < T30_MAX_DIS_DTC_DCS_LEN)
|
|
memset(dcs_frame + len, 0, T30_MAX_DIS_DTC_DCS_LEN - len);
|
|
}
|
|
|
|
s->octets_per_ecm_frame = test_ctrl_bit(dcs_frame, 28) ? 256 : 64;
|
|
|
|
if (test_ctrl_bit(dcs_frame, 106))
|
|
s->x_resolution = T4_X_RESOLUTION_1200;
|
|
else if (test_ctrl_bit(dcs_frame, 105) || test_ctrl_bit(dcs_frame, 109))
|
|
s->x_resolution = T4_X_RESOLUTION_600;
|
|
else if (test_ctrl_bit(dcs_frame, 43) || test_ctrl_bit(dcs_frame, 108))
|
|
s->x_resolution = T4_X_RESOLUTION_R16;
|
|
else if (test_ctrl_bit(dcs_frame, 42) || test_ctrl_bit(dcs_frame, 107))
|
|
s->x_resolution = T4_X_RESOLUTION_300;
|
|
else
|
|
s->x_resolution = T4_X_RESOLUTION_R8;
|
|
|
|
if (test_ctrl_bit(dcs_frame, 106) || test_ctrl_bit(dcs_frame, 109))
|
|
s->y_resolution = T4_Y_RESOLUTION_1200;
|
|
else if (test_ctrl_bit(dcs_frame, 108))
|
|
s->y_resolution = T4_Y_RESOLUTION_800;
|
|
else if (test_ctrl_bit(dcs_frame, 105) || test_ctrl_bit(dcs_frame, 107))
|
|
s->y_resolution = T4_Y_RESOLUTION_600;
|
|
else if (test_ctrl_bit(dcs_frame, 41) || test_ctrl_bit(dcs_frame, 43))
|
|
s->y_resolution = T4_Y_RESOLUTION_SUPERFINE;
|
|
else if (test_ctrl_bit(dcs_frame, 42))
|
|
s->y_resolution = T4_Y_RESOLUTION_300;
|
|
else if (test_ctrl_bit(dcs_frame, 15))
|
|
s->y_resolution = T4_Y_RESOLUTION_FINE;
|
|
else
|
|
s->y_resolution = T4_Y_RESOLUTION_STANDARD;
|
|
|
|
if (s->x_resolution == T4_X_RESOLUTION_1200)
|
|
i = 5;
|
|
else if (s->x_resolution == T4_X_RESOLUTION_600)
|
|
i = 4;
|
|
else if (s->x_resolution == T4_X_RESOLUTION_R16)
|
|
i = 3;
|
|
else if (s->x_resolution == T4_X_RESOLUTION_300)
|
|
i = 2;
|
|
else if (s->x_resolution == T4_X_RESOLUTION_R4)
|
|
i = 0;
|
|
else
|
|
i = 1;
|
|
|
|
s->image_width = widths[i][dcs_frame[5] & (DISBIT2 | DISBIT1)];
|
|
|
|
/* Check which compression we will use. */
|
|
if (test_ctrl_bit(dcs_frame, 31))
|
|
s->line_encoding = T4_COMPRESSION_ITU_T6;
|
|
else if (test_ctrl_bit(dcs_frame, 16))
|
|
s->line_encoding = T4_COMPRESSION_ITU_T4_2D;
|
|
else
|
|
s->line_encoding = T4_COMPRESSION_ITU_T4_1D;
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Selected compression %d\n", s->line_encoding);
|
|
if (!test_ctrl_bit(dcs_frame, 10))
|
|
span_log(&s->logging, SPAN_LOG_PROTOCOL_WARNING, "Remote is not requesting receive in DCS\n");
|
|
|
|
if ((s->current_fallback = find_fallback_entry(dcs_frame[4] & (DISBIT6 | DISBIT5 | DISBIT4 | DISBIT3))) < 0)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Remote asked for a modem standard we do not support\n");
|
|
return -1;
|
|
}
|
|
s->error_correcting_mode = (test_ctrl_bit(dcs_frame, 27) != 0);
|
|
|
|
if (s->phase_b_handler)
|
|
{
|
|
new_status = s->phase_b_handler(s, s->phase_b_user_data, msg[2]);
|
|
if (new_status != T30_ERR_OK)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Application rejected DCS - '%s'\n", t30_completion_code_to_str(new_status));
|
|
s->current_status = new_status;
|
|
/* TODO: If FNV is allowed, process it here */
|
|
send_dcn(s);
|
|
return -1;
|
|
}
|
|
}
|
|
/* Start document reception */
|
|
span_log(&s->logging,
|
|
SPAN_LOG_FLOW,
|
|
"Get document at %dbps, modem %d\n",
|
|
fallback_sequence[s->current_fallback].bit_rate,
|
|
fallback_sequence[s->current_fallback].modem_type);
|
|
if (s->rx_file[0] == '\0')
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "No document to receive\n");
|
|
s->current_status = T30_ERR_FILEERROR;
|
|
send_dcn(s);
|
|
return -1;
|
|
}
|
|
if (!s->in_message)
|
|
{
|
|
if (t4_rx_init(&s->t4, s->rx_file, s->output_encoding) == NULL)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_WARNING, "Cannot open target TIFF file '%s'\n", s->rx_file);
|
|
s->current_status = T30_ERR_FILEERROR;
|
|
send_dcn(s);
|
|
return -1;
|
|
}
|
|
s->operation_in_progress = OPERATION_IN_PROGRESS_T4_RX;
|
|
}
|
|
if (!(s->iaf & T30_IAF_MODE_NO_TCF))
|
|
{
|
|
/* TCF is always sent with long training */
|
|
s->short_train = FALSE;
|
|
set_state(s, T30_STATE_F_TCF);
|
|
queue_phase(s, T30_PHASE_C_NON_ECM_RX);
|
|
timer_t2_start(s);
|
|
}
|
|
return 0;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int send_deferred_pps_response(t30_state_t *s)
|
|
{
|
|
queue_phase(s, T30_PHASE_D_TX);
|
|
if (s->ecm_first_bad_frame >= s->ecm_frames)
|
|
{
|
|
/* Everything was OK. We can accept the data and move on. */
|
|
t30_ecm_commit_partial_page(s);
|
|
switch (s->last_pps_fcf2)
|
|
{
|
|
case T30_NULL:
|
|
/* We can confirm this partial page. */
|
|
break;
|
|
default:
|
|
/* We can confirm the whole page. */
|
|
s->next_rx_step = s->last_pps_fcf2;
|
|
rx_end_page(s);
|
|
if (s->phase_d_handler)
|
|
s->phase_d_handler(s, s->phase_d_user_data, s->last_pps_fcf2);
|
|
rx_start_page(s);
|
|
break;
|
|
}
|
|
set_state(s, T30_STATE_F_POST_RCP_MCF);
|
|
send_simple_frame(s, T30_MCF);
|
|
}
|
|
else
|
|
{
|
|
/* We need to send the PPR frame we have created, to try to fill in the missing/bad data. */
|
|
set_state(s, T30_STATE_F_POST_RCP_PPR);
|
|
s->ecm_frame_map[0] = 0xFF;
|
|
s->ecm_frame_map[1] = 0x13;
|
|
s->ecm_frame_map[2] = (uint8_t) (T30_PPR | s->dis_received);
|
|
send_frame(s, s->ecm_frame_map, 3 + 32);
|
|
}
|
|
return 0;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int process_rx_pps(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
int page;
|
|
int block;
|
|
int frames;
|
|
int i;
|
|
int j;
|
|
int frame_no;
|
|
|
|
if (len < 7)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Bad PPS message length %d.\n", len);
|
|
return -1;
|
|
}
|
|
s->last_pps_fcf2 = msg[3] & 0xFE;
|
|
page = msg[4];
|
|
block = msg[5];
|
|
|
|
/* The frames count is not well specified in T.30. In practice it seems it might be the
|
|
number of frames in the current block, or it might be the number of frames in the
|
|
current burst of transmission. For a burst of resent frames this would make it smaller
|
|
than the actual size of the block. If we only accept the number when it exceeds
|
|
previous values, we should get the real number of frames in the block. */
|
|
frames = msg[6] + 1;
|
|
if (s->ecm_frames < 0)
|
|
{
|
|
/* First time. Take the number and believe in it. */
|
|
s->ecm_frames = frames;
|
|
}
|
|
else
|
|
{
|
|
/* If things have gone wrong, the far end might try to send us zero FCD
|
|
frames. It can't represent zero in the block count field, so it might
|
|
put zero there, or it might simplistically insert (blocks - 1), and put
|
|
0xFF there. Beware of this. */
|
|
if (frames == 0xFF)
|
|
{
|
|
/* This is probably zero, erroneously rolled over to the maximum count */
|
|
frames = 0;
|
|
}
|
|
}
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Received PPS + %s - page %d, block %d, %d frames\n", t30_frametype(msg[3]), page, block, frames);
|
|
if (page != s->ecm_rx_page)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "ECM rx page mismatch - expected %d, but received %d.\n", s->ecm_rx_page, page);
|
|
}
|
|
if (block != s->ecm_block)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "ECM rx block mismatch - expected %d, but received %d.\n", s->ecm_block, block);
|
|
}
|
|
/* Build a bit map of which frames we now have stored OK */
|
|
s->ecm_first_bad_frame = 256;
|
|
for (i = 0; i < 32; i++)
|
|
{
|
|
s->ecm_frame_map[i + 3] = 0;
|
|
for (j = 0; j < 8; j++)
|
|
{
|
|
frame_no = (i << 3) + j;
|
|
if (s->ecm_len[frame_no] < 0)
|
|
{
|
|
s->ecm_frame_map[i + 3] |= (1 << j);
|
|
if (frame_no < s->ecm_first_bad_frame)
|
|
s->ecm_first_bad_frame = frame_no;
|
|
if (frame_no < s->ecm_frames)
|
|
s->error_correcting_mode_retries++;
|
|
}
|
|
}
|
|
}
|
|
/* Are there any bad frames, or does our scan represent things being OK? */
|
|
switch (s->last_pps_fcf2)
|
|
{
|
|
case T30_NULL:
|
|
case T30_EOP:
|
|
case T30_PRI_EOP:
|
|
case T30_EOM:
|
|
case T30_PRI_EOM:
|
|
case T30_EOS:
|
|
case T30_MPS:
|
|
case T30_PRI_MPS:
|
|
if (s->receiver_not_ready_count > 0)
|
|
{
|
|
s->receiver_not_ready_count--;
|
|
queue_phase(s, T30_PHASE_D_TX);
|
|
set_state(s, T30_STATE_F_POST_RCP_RNR);
|
|
send_simple_frame(s, T30_RNR);
|
|
}
|
|
else
|
|
{
|
|
send_deferred_pps_response(s);
|
|
}
|
|
break;
|
|
default:
|
|
unexpected_final_frame(s, msg, len);
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void process_rx_ppr(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
int i;
|
|
int j;
|
|
int frame_no;
|
|
uint8_t frame[4];
|
|
|
|
if (len != 3 + 32)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Bad length for PPR bits - %d\n", len);
|
|
/* TODO: probably should send DCN */
|
|
return;
|
|
}
|
|
/* Check which frames are OK, and mark them as OK. */
|
|
for (i = 0; i < 32; i++)
|
|
{
|
|
for (j = 0; j < 8; j++)
|
|
{
|
|
frame_no = (i << 3) + j;
|
|
/* Tick off the frames they are not complaining about as OK */
|
|
if ((msg[i + 3] & (1 << j)) == 0)
|
|
{
|
|
if (s->ecm_len[frame_no] >= 0)
|
|
s->ecm_progress++;
|
|
s->ecm_len[frame_no] = -1;
|
|
}
|
|
else
|
|
{
|
|
if (frame_no < s->ecm_frames)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Frame %d to be resent\n", frame_no);
|
|
s->error_correcting_mode_retries++;
|
|
}
|
|
#if 0
|
|
/* Diagnostic: See if the other end is complaining about something we didn't even send this time. */
|
|
if (s->ecm_len[frame_no] < 0)
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "PPR contains complaint about frame %d, which was not sent\n", frame_no);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
if (++s->ppr_count >= PPR_LIMIT_BEFORE_CTC_OR_EOR)
|
|
{
|
|
/* Continue to correct? */
|
|
/* Continue only if we have been making progress */
|
|
s->ppr_count = 0;
|
|
if (s->ecm_progress)
|
|
{
|
|
s->ecm_progress = 0;
|
|
queue_phase(s, T30_PHASE_D_TX);
|
|
set_state(s, T30_STATE_IV_CTC);
|
|
send_simple_frame(s, T30_CTC);
|
|
}
|
|
else
|
|
{
|
|
set_state(s, T30_STATE_IV_EOR);
|
|
queue_phase(s, T30_PHASE_D_TX);
|
|
frame[0] = 0xFF;
|
|
frame[1] = 0x13;
|
|
frame[2] = (uint8_t) (T30_EOR | s->dis_received);
|
|
frame[3] = (s->ecm_at_page_end) ? ((uint8_t) (s->next_tx_step | s->dis_received)) : T30_NULL;
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Sending EOR + %s\n", t30_frametype(frame[3]));
|
|
send_frame(s, frame, 4);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Initiate resending of the remainder of the frames. */
|
|
set_state(s, T30_STATE_IV);
|
|
queue_phase(s, T30_PHASE_C_ECM_TX);
|
|
send_first_ecm_frame(s);
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void process_rx_fcd(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
int frame_no;
|
|
|
|
/* Facsimile coded data */
|
|
switch (s->state)
|
|
{
|
|
case T30_STATE_F_DOC_ECM:
|
|
if (len <= 4 + 256)
|
|
{
|
|
frame_no = msg[3];
|
|
/* Just store the actual image data, and record its length */
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Storing ECM frame %d, length %d\n", frame_no, len - 4);
|
|
memcpy(&s->ecm_data[frame_no][0], &msg[4], len - 4);
|
|
s->ecm_len[frame_no] = (int16_t) (len - 4);
|
|
/* In case we are just after a CTC/CTR exchange, which kicked us back to long training */
|
|
s->short_train = TRUE;
|
|
}
|
|
else
|
|
{
|
|
unexpected_frame_length(s, msg, len);
|
|
}
|
|
/* We have received something, so any missing carrier status is out of date */
|
|
if (s->current_status == T30_ERR_RX_NOCARRIER)
|
|
s->current_status = T30_ERR_OK;
|
|
break;
|
|
default:
|
|
unexpected_non_final_frame(s, msg, len);
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void process_rx_rcp(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
/* Return to control for partial page. These might come through with or without the final frame tag.
|
|
Here we deal with the "no final frame tag" case. */
|
|
switch (s->state)
|
|
{
|
|
case T30_STATE_F_DOC_ECM:
|
|
set_state(s, T30_STATE_F_POST_DOC_ECM);
|
|
queue_phase(s, T30_PHASE_D_RX);
|
|
timer_t2_start(s);
|
|
/* We have received something, so any missing carrier status is out of date */
|
|
if (s->current_status == T30_ERR_RX_NOCARRIER)
|
|
s->current_status = T30_ERR_OK;
|
|
break;
|
|
case T30_STATE_F_POST_DOC_ECM:
|
|
/* Just ignore this. It must be an extra RCP. Several are usually sent, to maximise the chance
|
|
of receiving a correct one. */
|
|
break;
|
|
default:
|
|
unexpected_non_final_frame(s, msg, len);
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void process_rx_fnv(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
logging_state_t *log;
|
|
const char *x;
|
|
|
|
/* Field not valid */
|
|
/* TODO: analyse the message, as per 5.3.6.2.13 */
|
|
if (!span_log_test(&s->logging, SPAN_LOG_FLOW))
|
|
return;
|
|
log = &s->logging;
|
|
|
|
if ((msg[3] & 0x01))
|
|
span_log(log, SPAN_LOG_FLOW, " Incorrect password (PWD).\n");
|
|
if ((msg[3] & 0x02))
|
|
span_log(log, SPAN_LOG_FLOW, " Selective polling reference (SEP) not known.\n");
|
|
if ((msg[3] & 0x04))
|
|
span_log(log, SPAN_LOG_FLOW, " Sub-address (SUB) not known.\n");
|
|
if ((msg[3] & 0x08))
|
|
span_log(log, SPAN_LOG_FLOW, " Sender identity (SID) not known.\n");
|
|
if ((msg[3] & 0x10))
|
|
span_log(log, SPAN_LOG_FLOW, " Secure fax error.\n");
|
|
if ((msg[3] & 0x20))
|
|
span_log(log, SPAN_LOG_FLOW, " Transmitting subscriber identity (TSI) not accepted.\n");
|
|
if ((msg[3] & 0x40))
|
|
span_log(log, SPAN_LOG_FLOW, " Polled sub-address (PSA) not known.\n");
|
|
if (len > 4 && (msg[3] & DISBIT8))
|
|
{
|
|
if ((msg[4] & 0x01))
|
|
span_log(log, SPAN_LOG_FLOW, " BFT negotiations request not accepted.\n");
|
|
if ((msg[4] & 0x02))
|
|
span_log(log, SPAN_LOG_FLOW, " Internet routing address (IRA) not known.\n");
|
|
if ((msg[4] & 0x04))
|
|
span_log(log, SPAN_LOG_FLOW, " Internet selective polling address (ISP) not known.\n");
|
|
}
|
|
if (len > 5)
|
|
{
|
|
span_log(log, SPAN_LOG_FLOW, " FNV sequence number %d.\n", msg[5]);
|
|
}
|
|
if (len > 6)
|
|
{
|
|
switch (msg[6])
|
|
{
|
|
case T30_PWD:
|
|
x = "Incorrect password (PWD)";
|
|
break;
|
|
case T30_SEP:
|
|
x = "Selective polling reference (SEP) not known";
|
|
break;
|
|
case T30_SUB:
|
|
case T30_SUB | 0x01:
|
|
x = "Sub-address (SUB) not known";
|
|
break;
|
|
case T30_SID:
|
|
case T30_SID | 0x01:
|
|
x = "Sender identity (SID) not known";
|
|
break;
|
|
case T30_SPI:
|
|
x = "Secure fax error";
|
|
break;
|
|
case T30_TSI:
|
|
case T30_TSI | 0x01:
|
|
x = "Transmitting subscriber identity (TSI) not accepted";
|
|
break;
|
|
case T30_PSA:
|
|
x = "Polled sub-address (PSA) not known";
|
|
break;
|
|
default:
|
|
x = "???";
|
|
break;
|
|
}
|
|
span_log(log, SPAN_LOG_FLOW, " FNV diagnostic info type %s.\n", x);
|
|
}
|
|
if (len > 7)
|
|
{
|
|
span_log(log, SPAN_LOG_FLOW, " FNV length %d.\n", msg[7]);
|
|
}
|
|
/* We've decoded it, but we don't yet know how to deal with it, so treat it as unexpected */
|
|
unexpected_final_frame(s, msg, len);
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void process_state_answering(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
switch (msg[2] & 0xFE)
|
|
{
|
|
case T30_DIS:
|
|
/* TODO: This is a fudge to allow for starting up in T.38, where the other end has
|
|
seen DIS by analogue modem means, and has immediately sent DIS/DTC. We might have
|
|
missed useful info, like TSI, but just accept things and carry on form now. */
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "DIS/DTC before DIS\n");
|
|
process_rx_dis_dtc(s, msg, len);
|
|
break;
|
|
case T30_DCS:
|
|
/* TODO: This is a fudge to allow for starting up in T.38, where the other end has
|
|
seen DIS by analogue modem means, and has immediately sent DCS. We might have
|
|
missed useful info, like TSI, but just accept things and carry on form now. */
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "DCS before DIS\n");
|
|
process_rx_dcs(s, msg, len);
|
|
break;
|
|
case T30_DCN:
|
|
s->current_status = T30_ERR_TX_GOTDCN;
|
|
disconnect(s);
|
|
break;
|
|
default:
|
|
/* We don't know what to do with this. */
|
|
unexpected_final_frame(s, msg, len);
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void process_state_b(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
switch (msg[2] & 0xFE)
|
|
{
|
|
case T30_DCN:
|
|
/* Just ignore any DCN's which appear at this stage. */
|
|
break;
|
|
case T30_CRP:
|
|
repeat_last_command(s);
|
|
break;
|
|
case T30_FNV:
|
|
process_rx_fnv(s, msg, len);
|
|
break;
|
|
default:
|
|
/* We don't know what to do with this. */
|
|
unexpected_final_frame(s, msg, len);
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void process_state_c(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
switch (msg[2] & 0xFE)
|
|
{
|
|
case T30_DCN:
|
|
/* Just ignore any DCN's which appear at this stage. */
|
|
break;
|
|
case T30_CRP:
|
|
repeat_last_command(s);
|
|
break;
|
|
case T30_FNV:
|
|
process_rx_fnv(s, msg, len);
|
|
break;
|
|
default:
|
|
/* We don't know what to do with this. */
|
|
unexpected_final_frame(s, msg, len);
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void process_state_d(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
/* We should be sending the DCS sequence right now */
|
|
switch (msg[2] & 0xFE)
|
|
{
|
|
case T30_DCN:
|
|
s->current_status = T30_ERR_TX_BADDCS;
|
|
disconnect(s);
|
|
break;
|
|
case T30_CRP:
|
|
repeat_last_command(s);
|
|
break;
|
|
case T30_FNV:
|
|
process_rx_fnv(s, msg, len);
|
|
break;
|
|
default:
|
|
/* We don't know what to do with this. */
|
|
unexpected_final_frame(s, msg, len);
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void process_state_d_tcf(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
/* We should be sending the TCF data right now */
|
|
switch (msg[2] & 0xFE)
|
|
{
|
|
case T30_DCN:
|
|
s->current_status = T30_ERR_TX_BADDCS;
|
|
disconnect(s);
|
|
break;
|
|
case T30_CRP:
|
|
repeat_last_command(s);
|
|
break;
|
|
case T30_FNV:
|
|
process_rx_fnv(s, msg, len);
|
|
break;
|
|
default:
|
|
/* We don't know what to do with this. */
|
|
unexpected_final_frame(s, msg, len);
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void process_state_d_post_tcf(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
switch (msg[2] & 0xFE)
|
|
{
|
|
case T30_CFR:
|
|
/* Trainability test succeeded. Send the document. */
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Trainability test succeeded\n");
|
|
s->retries = 0;
|
|
s->short_train = TRUE;
|
|
if (s->error_correcting_mode)
|
|
{
|
|
set_state(s, T30_STATE_IV);
|
|
queue_phase(s, T30_PHASE_C_ECM_TX);
|
|
send_first_ecm_frame(s);
|
|
}
|
|
else
|
|
{
|
|
set_state(s, T30_STATE_I);
|
|
queue_phase(s, T30_PHASE_C_NON_ECM_TX);
|
|
}
|
|
break;
|
|
case T30_FTT:
|
|
/* Trainability test failed. Try again. */
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Trainability test failed\n");
|
|
s->retries = 0;
|
|
s->short_train = FALSE;
|
|
if (step_fallback_entry(s) < 0)
|
|
{
|
|
/* We have fallen back as far as we can go. Give up. */
|
|
s->current_fallback = 0;
|
|
s->current_status = T30_ERR_CANNOT_TRAIN;
|
|
send_dcn(s);
|
|
break;
|
|
}
|
|
queue_phase(s, T30_PHASE_B_TX);
|
|
send_dcs_sequence(s, TRUE);
|
|
break;
|
|
case T30_DIS:
|
|
/* It appears they didn't see what we sent - retry the TCF */
|
|
if (++s->retries >= MAX_COMMAND_TRIES)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Too many retries. Giving up.\n");
|
|
s->current_status = T30_ERR_RETRYDCN;
|
|
send_dcn(s);
|
|
break;
|
|
}
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Retry number %d\n", s->retries);
|
|
queue_phase(s, T30_PHASE_B_TX);
|
|
/* TODO: should we reassess the new DIS message, and possibly adjust the DCS we use? */
|
|
send_dcs_sequence(s, TRUE);
|
|
break;
|
|
case T30_DCN:
|
|
s->current_status = T30_ERR_TX_BADDCS;
|
|
disconnect(s);
|
|
break;
|
|
case T30_CRP:
|
|
repeat_last_command(s);
|
|
break;
|
|
case T30_FNV:
|
|
process_rx_fnv(s, msg, len);
|
|
break;
|
|
default:
|
|
/* We don't know what to do with this. */
|
|
unexpected_final_frame(s, msg, len);
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void process_state_f_tcf(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
/* We should be receiving TCF right now, not HDLC messages */
|
|
switch (msg[2] & 0xFE)
|
|
{
|
|
case T30_CRP:
|
|
repeat_last_command(s);
|
|
break;
|
|
case T30_FNV:
|
|
process_rx_fnv(s, msg, len);
|
|
break;
|
|
default:
|
|
/* We don't know what to do with this. */
|
|
unexpected_final_frame(s, msg, len);
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void process_state_f_cfr(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
/* We're waiting for a response to the CFR we sent */
|
|
switch (msg[2] & 0xFE)
|
|
{
|
|
case T30_DCS:
|
|
/* If we received another DCS, they must have missed our CFR */
|
|
process_rx_dcs(s, msg, len);
|
|
break;
|
|
case T30_CRP:
|
|
repeat_last_command(s);
|
|
break;
|
|
case T30_FNV:
|
|
process_rx_fnv(s, msg, len);
|
|
break;
|
|
default:
|
|
/* We don't know what to do with this. */
|
|
unexpected_final_frame(s, msg, len);
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void process_state_f_ftt(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
/* We're waiting for a response to the FTT we sent */
|
|
switch (msg[2] & 0xFE)
|
|
{
|
|
case T30_DCS:
|
|
process_rx_dcs(s, msg, len);
|
|
break;
|
|
case T30_CRP:
|
|
repeat_last_command(s);
|
|
break;
|
|
case T30_FNV:
|
|
process_rx_fnv(s, msg, len);
|
|
break;
|
|
default:
|
|
/* We don't know what to do with this. */
|
|
unexpected_final_frame(s, msg, len);
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void process_state_f_doc_non_ecm(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
/* If we are getting HDLC messages, and we have not moved to the _POST_DOC_NON_ECM
|
|
state, it looks like either:
|
|
- we didn't see the image data carrier properly, or
|
|
- they didn't see our T30_CFR, and are repeating the DCS/TCF sequence.
|
|
- they didn't see out T30_MCF, and are repeating the end of page message. */
|
|
switch (msg[2] & 0xFE)
|
|
{
|
|
case T30_DIS:
|
|
process_rx_dis_dtc(s, msg, len);
|
|
break;
|
|
case T30_DCS:
|
|
process_rx_dcs(s, msg, len);
|
|
break;
|
|
case T30_MPS:
|
|
/* Treat this as a bad quality page. */
|
|
if (s->phase_d_handler)
|
|
s->phase_d_handler(s, s->phase_d_user_data, msg[2] & 0xFE);
|
|
s->next_rx_step = msg[2] & 0xFE;
|
|
queue_phase(s, T30_PHASE_D_TX);
|
|
set_state(s, T30_STATE_III_Q_RTN);
|
|
send_simple_frame(s, T30_RTN);
|
|
break;
|
|
case T30_PRI_MPS:
|
|
/* Treat this as a bad quality page. */
|
|
if (s->phase_d_handler)
|
|
{
|
|
s->phase_d_handler(s, s->phase_d_user_data, msg[2] & 0xFE);
|
|
s->timer_t3 = ms_to_samples(DEFAULT_TIMER_T3);
|
|
}
|
|
s->next_rx_step = msg[2] & 0xFE;
|
|
set_state(s, T30_STATE_III_Q_RTN);
|
|
break;
|
|
case T30_EOM:
|
|
case T30_EOS:
|
|
/* Treat this as a bad quality page. */
|
|
if (s->phase_d_handler)
|
|
s->phase_d_handler(s, s->phase_d_user_data, msg[2] & 0xFE);
|
|
s->next_rx_step = msg[2] & 0xFE;
|
|
/* Return to phase B */
|
|
queue_phase(s, T30_PHASE_B_TX);
|
|
set_state(s, T30_STATE_III_Q_RTN);
|
|
send_simple_frame(s, T30_RTN);
|
|
break;
|
|
case T30_PRI_EOM:
|
|
/* Treat this as a bad quality page. */
|
|
if (s->phase_d_handler)
|
|
{
|
|
s->phase_d_handler(s, s->phase_d_user_data, msg[2] & 0xFE);
|
|
s->timer_t3 = ms_to_samples(DEFAULT_TIMER_T3);
|
|
}
|
|
s->next_rx_step = T30_PRI_EOM;
|
|
set_state(s, T30_STATE_III_Q_RTN);
|
|
break;
|
|
case T30_EOP:
|
|
/* Treat this as a bad quality page. */
|
|
if (s->phase_d_handler)
|
|
s->phase_d_handler(s, s->phase_d_user_data, msg[2] & 0xFE);
|
|
s->next_rx_step = msg[2] & 0xFE;
|
|
queue_phase(s, T30_PHASE_D_TX);
|
|
set_state(s, T30_STATE_III_Q_RTN);
|
|
send_simple_frame(s, T30_RTN);
|
|
break;
|
|
case T30_PRI_EOP:
|
|
/* Treat this as a bad quality page. */
|
|
if (s->phase_d_handler)
|
|
{
|
|
s->phase_d_handler(s, s->phase_d_user_data, msg[2] & 0xFE);
|
|
s->timer_t3 = ms_to_samples(DEFAULT_TIMER_T3);
|
|
}
|
|
s->next_rx_step = msg[2] & 0xFE;
|
|
set_state(s, T30_STATE_III_Q_RTN);
|
|
break;
|
|
case T30_DCN:
|
|
s->current_status = T30_ERR_RX_DCNDATA;
|
|
disconnect(s);
|
|
break;
|
|
case T30_CRP:
|
|
repeat_last_command(s);
|
|
break;
|
|
case T30_FNV:
|
|
process_rx_fnv(s, msg, len);
|
|
break;
|
|
default:
|
|
/* We don't know what to do with this. */
|
|
unexpected_final_frame(s, msg, len);
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void process_state_f_post_doc_non_ecm(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
switch (msg[2] & 0xFE)
|
|
{
|
|
case T30_MPS:
|
|
if (s->phase_d_handler)
|
|
s->phase_d_handler(s, s->phase_d_user_data, msg[2] & 0xFE);
|
|
s->next_rx_step = msg[2] & 0xFE;
|
|
queue_phase(s, T30_PHASE_D_TX);
|
|
switch (copy_quality(s))
|
|
{
|
|
case T30_COPY_QUALITY_PERFECT:
|
|
case T30_COPY_QUALITY_GOOD:
|
|
rx_end_page(s);
|
|
rx_start_page(s);
|
|
set_state(s, T30_STATE_III_Q_MCF);
|
|
send_simple_frame(s, T30_MCF);
|
|
break;
|
|
case T30_COPY_QUALITY_POOR:
|
|
rx_end_page(s);
|
|
rx_start_page(s);
|
|
set_state(s, T30_STATE_III_Q_RTP);
|
|
send_simple_frame(s, T30_RTP);
|
|
break;
|
|
case T30_COPY_QUALITY_BAD:
|
|
rx_start_page(s);
|
|
set_state(s, T30_STATE_III_Q_RTN);
|
|
send_simple_frame(s, T30_RTN);
|
|
break;
|
|
}
|
|
break;
|
|
case T30_PRI_MPS:
|
|
if (s->phase_d_handler)
|
|
{
|
|
s->phase_d_handler(s, s->phase_d_user_data, msg[2] & 0xFE);
|
|
s->timer_t3 = ms_to_samples(DEFAULT_TIMER_T3);
|
|
}
|
|
s->next_rx_step = msg[2] & 0xFE;
|
|
switch (copy_quality(s))
|
|
{
|
|
case T30_COPY_QUALITY_PERFECT:
|
|
case T30_COPY_QUALITY_GOOD:
|
|
rx_end_page(s);
|
|
t4_rx_release(&(s->t4));
|
|
s->operation_in_progress = OPERATION_IN_PROGRESS_NONE;
|
|
s->in_message = FALSE;
|
|
set_state(s, T30_STATE_III_Q_MCF);
|
|
break;
|
|
case T30_COPY_QUALITY_POOR:
|
|
rx_end_page(s);
|
|
t4_rx_release(&(s->t4));
|
|
s->operation_in_progress = OPERATION_IN_PROGRESS_NONE;
|
|
s->in_message = FALSE;
|
|
set_state(s, T30_STATE_III_Q_RTP);
|
|
break;
|
|
case T30_COPY_QUALITY_BAD:
|
|
set_state(s, T30_STATE_III_Q_RTN);
|
|
break;
|
|
}
|
|
break;
|
|
case T30_EOM:
|
|
case T30_EOS:
|
|
if (s->phase_d_handler)
|
|
s->phase_d_handler(s, s->phase_d_user_data, msg[2] & 0xFE);
|
|
s->next_rx_step = msg[2] & 0xFE;
|
|
/* Return to phase B */
|
|
queue_phase(s, T30_PHASE_B_TX);
|
|
switch (copy_quality(s))
|
|
{
|
|
case T30_COPY_QUALITY_PERFECT:
|
|
case T30_COPY_QUALITY_GOOD:
|
|
rx_end_page(s);
|
|
rx_start_page(s);
|
|
set_state(s, T30_STATE_III_Q_MCF);
|
|
send_simple_frame(s, T30_MCF);
|
|
break;
|
|
case T30_COPY_QUALITY_POOR:
|
|
rx_end_page(s);
|
|
rx_start_page(s);
|
|
set_state(s, T30_STATE_III_Q_RTP);
|
|
send_simple_frame(s, T30_RTP);
|
|
break;
|
|
case T30_COPY_QUALITY_BAD:
|
|
rx_start_page(s);
|
|
set_state(s, T30_STATE_III_Q_RTN);
|
|
send_simple_frame(s, T30_RTN);
|
|
break;
|
|
}
|
|
break;
|
|
case T30_PRI_EOM:
|
|
if (s->phase_d_handler)
|
|
{
|
|
s->phase_d_handler(s, s->phase_d_user_data, msg[2] & 0xFE);
|
|
s->timer_t3 = ms_to_samples(DEFAULT_TIMER_T3);
|
|
}
|
|
s->next_rx_step = msg[2] & 0xFE;
|
|
switch (copy_quality(s))
|
|
{
|
|
case T30_COPY_QUALITY_PERFECT:
|
|
case T30_COPY_QUALITY_GOOD:
|
|
rx_end_page(s);
|
|
t4_rx_release(&(s->t4));
|
|
s->operation_in_progress = OPERATION_IN_PROGRESS_NONE;
|
|
s->in_message = FALSE;
|
|
set_state(s, T30_STATE_III_Q_MCF);
|
|
break;
|
|
case T30_COPY_QUALITY_POOR:
|
|
rx_end_page(s);
|
|
t4_rx_release(&(s->t4));
|
|
s->operation_in_progress = OPERATION_IN_PROGRESS_NONE;
|
|
s->in_message = FALSE;
|
|
set_state(s, T30_STATE_III_Q_RTP);
|
|
break;
|
|
case T30_COPY_QUALITY_BAD:
|
|
set_state(s, T30_STATE_III_Q_RTN);
|
|
break;
|
|
}
|
|
break;
|
|
case T30_EOP:
|
|
if (s->phase_d_handler)
|
|
s->phase_d_handler(s, s->phase_d_user_data, msg[2] & 0xFE);
|
|
s->next_rx_step = msg[2] & 0xFE;
|
|
queue_phase(s, T30_PHASE_D_TX);
|
|
switch (copy_quality(s))
|
|
{
|
|
case T30_COPY_QUALITY_PERFECT:
|
|
case T30_COPY_QUALITY_GOOD:
|
|
rx_end_page(s);
|
|
t4_rx_release(&(s->t4));
|
|
s->operation_in_progress = OPERATION_IN_PROGRESS_NONE;
|
|
s->in_message = FALSE;
|
|
set_state(s, T30_STATE_III_Q_MCF);
|
|
send_simple_frame(s, T30_MCF);
|
|
break;
|
|
case T30_COPY_QUALITY_POOR:
|
|
rx_end_page(s);
|
|
t4_rx_release(&(s->t4));
|
|
s->operation_in_progress = OPERATION_IN_PROGRESS_NONE;
|
|
s->in_message = FALSE;
|
|
set_state(s, T30_STATE_III_Q_RTP);
|
|
send_simple_frame(s, T30_RTP);
|
|
break;
|
|
case T30_COPY_QUALITY_BAD:
|
|
set_state(s, T30_STATE_III_Q_RTN);
|
|
send_simple_frame(s, T30_RTN);
|
|
break;
|
|
}
|
|
break;
|
|
case T30_PRI_EOP:
|
|
if (s->phase_d_handler)
|
|
{
|
|
s->phase_d_handler(s, s->phase_d_user_data, msg[2] & 0xFE);
|
|
s->timer_t3 = ms_to_samples(DEFAULT_TIMER_T3);
|
|
}
|
|
s->next_rx_step = msg[2] & 0xFE;
|
|
switch (copy_quality(s))
|
|
{
|
|
case T30_COPY_QUALITY_PERFECT:
|
|
case T30_COPY_QUALITY_GOOD:
|
|
rx_end_page(s);
|
|
t4_rx_release(&(s->t4));
|
|
s->operation_in_progress = OPERATION_IN_PROGRESS_NONE;
|
|
s->in_message = FALSE;
|
|
set_state(s, T30_STATE_III_Q_MCF);
|
|
break;
|
|
case T30_COPY_QUALITY_POOR:
|
|
rx_end_page(s);
|
|
t4_rx_release(&(s->t4));
|
|
s->operation_in_progress = OPERATION_IN_PROGRESS_NONE;
|
|
s->in_message = FALSE;
|
|
set_state(s, T30_STATE_III_Q_RTP);
|
|
break;
|
|
case T30_COPY_QUALITY_BAD:
|
|
set_state(s, T30_STATE_III_Q_RTN);
|
|
break;
|
|
}
|
|
break;
|
|
case T30_DCN:
|
|
s->current_status = T30_ERR_RX_DCNFAX;
|
|
disconnect(s);
|
|
break;
|
|
case T30_CRP:
|
|
repeat_last_command(s);
|
|
break;
|
|
case T30_FNV:
|
|
process_rx_fnv(s, msg, len);
|
|
break;
|
|
default:
|
|
/* We don't know what to do with this. */
|
|
unexpected_final_frame(s, msg, len);
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void process_state_f_doc_and_post_doc_ecm(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
uint8_t fcf2;
|
|
|
|
/* This actually handles 2 states - _DOC_ECM and _POST_DOC_ECM - as they are very similar */
|
|
switch (msg[2] & 0xFE)
|
|
{
|
|
case T30_DIS:
|
|
process_rx_dis_dtc(s, msg, len);
|
|
break;
|
|
case T30_DCS:
|
|
process_rx_dcs(s, msg, len);
|
|
break;
|
|
case T4_RCP:
|
|
/* Return to control for partial page. These might come through with or without the final frame tag.
|
|
Here we deal with the "final frame tag" case. */
|
|
if (s->state == T30_STATE_F_DOC_ECM)
|
|
{
|
|
/* Return to control for partial page */
|
|
set_state(s, T30_STATE_F_POST_DOC_ECM);
|
|
queue_phase(s, T30_PHASE_D_RX);
|
|
timer_t2_start(s);
|
|
/* We have received something, so any missing carrier status is out of date */
|
|
if (s->current_status == T30_ERR_RX_NOCARRIER)
|
|
s->current_status = T30_ERR_OK;
|
|
}
|
|
else
|
|
{
|
|
/* Just ignore this. It must be an extra RCP. Several are usually sent, to maximise the chance
|
|
of receiving a correct one. */
|
|
}
|
|
break;
|
|
case T30_EOR:
|
|
if (len != 4)
|
|
{
|
|
unexpected_frame_length(s, msg, len);
|
|
break;
|
|
}
|
|
fcf2 = msg[3] & 0xFE;
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Received EOR + %s\n", t30_frametype(msg[3]));
|
|
switch (fcf2)
|
|
{
|
|
case T30_PRI_EOP:
|
|
case T30_PRI_EOM:
|
|
case T30_PRI_MPS:
|
|
/* TODO: Alert operator */
|
|
/* Fall through */
|
|
case T30_NULL:
|
|
case T30_EOP:
|
|
case T30_EOM:
|
|
case T30_EOS:
|
|
case T30_MPS:
|
|
s->next_rx_step = fcf2;
|
|
queue_phase(s, T30_PHASE_D_TX);
|
|
set_state(s, T30_STATE_F_DOC_ECM);
|
|
send_simple_frame(s, T30_ERR);
|
|
break;
|
|
default:
|
|
unexpected_final_frame(s, msg, len);
|
|
break;
|
|
}
|
|
break;
|
|
case T30_PPS:
|
|
process_rx_pps(s, msg, len);
|
|
break;
|
|
case T30_CTC:
|
|
/* T.30 says we change back to long training here */
|
|
s->short_train = FALSE;
|
|
queue_phase(s, T30_PHASE_D_TX);
|
|
set_state(s, T30_STATE_F_DOC_ECM);
|
|
send_simple_frame(s, T30_CTR);
|
|
break;
|
|
case T30_RR:
|
|
break;
|
|
case T30_DCN:
|
|
s->current_status = T30_ERR_RX_DCNDATA;
|
|
disconnect(s);
|
|
break;
|
|
case T30_CRP:
|
|
repeat_last_command(s);
|
|
break;
|
|
case T30_FNV:
|
|
process_rx_fnv(s, msg, len);
|
|
break;
|
|
default:
|
|
/* We don't know what to do with this. */
|
|
unexpected_final_frame(s, msg, len);
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void process_state_f_post_rcp_mcf(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
switch (msg[2] & 0xFE)
|
|
{
|
|
case T30_CRP:
|
|
repeat_last_command(s);
|
|
break;
|
|
case T30_FNV:
|
|
process_rx_fnv(s, msg, len);
|
|
break;
|
|
case T30_DCN:
|
|
disconnect(s);
|
|
break;
|
|
default:
|
|
/* We don't know what to do with this. */
|
|
unexpected_final_frame(s, msg, len);
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void process_state_f_post_rcp_ppr(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
switch (msg[2] & 0xFE)
|
|
{
|
|
case T30_CRP:
|
|
repeat_last_command(s);
|
|
break;
|
|
case T30_FNV:
|
|
process_rx_fnv(s, msg, len);
|
|
break;
|
|
default:
|
|
/* We don't know what to do with this. */
|
|
unexpected_final_frame(s, msg, len);
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void process_state_f_post_rcp_rnr(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
switch (msg[2] & 0xFE)
|
|
{
|
|
case T30_RR:
|
|
if (s->receiver_not_ready_count > 0)
|
|
{
|
|
s->receiver_not_ready_count--;
|
|
queue_phase(s, T30_PHASE_D_TX);
|
|
set_state(s, T30_STATE_F_POST_RCP_RNR);
|
|
send_simple_frame(s, T30_RNR);
|
|
}
|
|
else
|
|
{
|
|
send_deferred_pps_response(s);
|
|
}
|
|
break;
|
|
case T30_CRP:
|
|
repeat_last_command(s);
|
|
break;
|
|
case T30_FNV:
|
|
process_rx_fnv(s, msg, len);
|
|
break;
|
|
default:
|
|
/* We don't know what to do with this. */
|
|
unexpected_final_frame(s, msg, len);
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void process_state_r(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
switch (msg[2] & 0xFE)
|
|
{
|
|
case T30_DIS:
|
|
process_rx_dis_dtc(s, msg, len);
|
|
break;
|
|
case T30_DCS:
|
|
process_rx_dcs(s, msg, len);
|
|
break;
|
|
case T30_DCN:
|
|
/* Received a DCN while waiting for a DIS */
|
|
s->current_status = T30_ERR_TX_GOTDCN;
|
|
disconnect(s);
|
|
break;
|
|
case T30_CRP:
|
|
repeat_last_command(s);
|
|
break;
|
|
case T30_FNV:
|
|
process_rx_fnv(s, msg, len);
|
|
break;
|
|
default:
|
|
/* We don't know what to do with this. */
|
|
unexpected_final_frame(s, msg, len);
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void process_state_t(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
switch (msg[2] & 0xFE)
|
|
{
|
|
case T30_DIS:
|
|
process_rx_dis_dtc(s, msg, len);
|
|
break;
|
|
case T30_DCN:
|
|
s->current_status = T30_ERR_RX_DCNWHY;
|
|
disconnect(s);
|
|
break;
|
|
case T30_CRP:
|
|
repeat_last_command(s);
|
|
break;
|
|
case T30_FNV:
|
|
process_rx_fnv(s, msg, len);
|
|
break;
|
|
default:
|
|
/* We don't know what to do with this. */
|
|
unexpected_final_frame(s, msg, len);
|
|
s->current_status = T30_ERR_TX_NODIS;
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void process_state_i(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
switch (msg[2] & 0xFE)
|
|
{
|
|
case T30_CRP:
|
|
repeat_last_command(s);
|
|
break;
|
|
case T30_FNV:
|
|
process_rx_fnv(s, msg, len);
|
|
break;
|
|
default:
|
|
/* We don't know what to do with this. */
|
|
unexpected_final_frame(s, msg, len);
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void process_state_ii(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
switch (msg[2] & 0xFE)
|
|
{
|
|
case T30_CRP:
|
|
repeat_last_command(s);
|
|
break;
|
|
case T30_FNV:
|
|
process_rx_fnv(s, msg, len);
|
|
break;
|
|
default:
|
|
/* We don't know what to do with this. */
|
|
unexpected_final_frame(s, msg, len);
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void process_state_ii_q(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
t4_stats_t stats;
|
|
|
|
switch (msg[2] & 0xFE)
|
|
{
|
|
case T30_MCF:
|
|
switch (s->next_tx_step)
|
|
{
|
|
case T30_MPS:
|
|
case T30_PRI_MPS:
|
|
tx_end_page(s);
|
|
if (s->phase_d_handler)
|
|
s->phase_d_handler(s, s->phase_d_user_data, T30_MCF);
|
|
if (tx_start_page(s))
|
|
{
|
|
/* TODO: recover */
|
|
break;
|
|
}
|
|
set_state(s, T30_STATE_I);
|
|
queue_phase(s, T30_PHASE_C_NON_ECM_TX);
|
|
break;
|
|
case T30_EOM:
|
|
case T30_PRI_EOM:
|
|
case T30_EOS:
|
|
tx_end_page(s);
|
|
if (s->phase_d_handler)
|
|
s->phase_d_handler(s, s->phase_d_user_data, T30_MCF);
|
|
t4_tx_release(&(s->t4));
|
|
s->operation_in_progress = OPERATION_IN_PROGRESS_NONE;
|
|
if (span_log_test(&s->logging, SPAN_LOG_FLOW))
|
|
{
|
|
t4_get_transfer_statistics(&s->t4, &stats);
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Success - delivered %d pages\n", stats.pages_transferred);
|
|
}
|
|
return_to_phase_b(s, FALSE);
|
|
break;
|
|
case T30_EOP:
|
|
case T30_PRI_EOP:
|
|
tx_end_page(s);
|
|
if (s->phase_d_handler)
|
|
s->phase_d_handler(s, s->phase_d_user_data, T30_MCF);
|
|
t4_tx_release(&(s->t4));
|
|
s->operation_in_progress = OPERATION_IN_PROGRESS_NONE;
|
|
send_dcn(s);
|
|
if (span_log_test(&s->logging, SPAN_LOG_FLOW))
|
|
{
|
|
t4_get_transfer_statistics(&s->t4, &stats);
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Success - delivered %d pages\n", stats.pages_transferred);
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
case T30_RTP:
|
|
switch (s->next_tx_step)
|
|
{
|
|
case T30_MPS:
|
|
case T30_PRI_MPS:
|
|
tx_end_page(s);
|
|
if (s->phase_d_handler)
|
|
s->phase_d_handler(s, s->phase_d_user_data, T30_RTP);
|
|
if (tx_start_page(s))
|
|
{
|
|
/* TODO: recover */
|
|
break;
|
|
}
|
|
/* Send fresh training, and then the next page */
|
|
if (step_fallback_entry(s) < 0)
|
|
{
|
|
/* We have fallen back as far as we can go. Give up. */
|
|
s->current_fallback = 0;
|
|
s->current_status = T30_ERR_CANNOT_TRAIN;
|
|
send_dcn(s);
|
|
break;
|
|
}
|
|
queue_phase(s, T30_PHASE_B_TX);
|
|
restart_sending_document(s);
|
|
break;
|
|
case T30_EOM:
|
|
case T30_PRI_EOM:
|
|
case T30_EOS:
|
|
tx_end_page(s);
|
|
if (s->phase_d_handler)
|
|
s->phase_d_handler(s, s->phase_d_user_data, T30_RTP);
|
|
t4_tx_release(&(s->t4));
|
|
/* TODO: should go back to T, and resend */
|
|
return_to_phase_b(s, TRUE);
|
|
break;
|
|
case T30_EOP:
|
|
case T30_PRI_EOP:
|
|
tx_end_page(s);
|
|
if (s->phase_d_handler)
|
|
s->phase_d_handler(s, s->phase_d_user_data, T30_RTP);
|
|
t4_tx_release(&(s->t4));
|
|
s->current_status = T30_ERR_TX_INVALRSP;
|
|
send_dcn(s);
|
|
break;
|
|
}
|
|
break;
|
|
case T30_RTN:
|
|
switch (s->next_tx_step)
|
|
{
|
|
case T30_MPS:
|
|
case T30_PRI_MPS:
|
|
s->retries = 0;
|
|
if (s->phase_d_handler)
|
|
s->phase_d_handler(s, s->phase_d_user_data, T30_RTN);
|
|
/* Send fresh training, and then repeat the last page */
|
|
if (step_fallback_entry(s) < 0)
|
|
{
|
|
/* We have fallen back as far as we can go. Give up. */
|
|
s->current_fallback = 0;
|
|
s->current_status = T30_ERR_CANNOT_TRAIN;
|
|
send_dcn(s);
|
|
break;
|
|
}
|
|
queue_phase(s, T30_PHASE_B_TX);
|
|
restart_sending_document(s);
|
|
break;
|
|
case T30_EOM:
|
|
case T30_PRI_EOM:
|
|
case T30_EOS:
|
|
s->retries = 0;
|
|
if (s->phase_d_handler)
|
|
s->phase_d_handler(s, s->phase_d_user_data, T30_RTN);
|
|
s->current_status = T30_ERR_TX_INVALRSP;
|
|
return_to_phase_b(s, TRUE);
|
|
break;
|
|
case T30_EOP:
|
|
case T30_PRI_EOP:
|
|
s->retries = 0;
|
|
if (s->phase_d_handler)
|
|
s->phase_d_handler(s, s->phase_d_user_data, T30_RTN);
|
|
s->current_status = T30_ERR_TX_INVALRSP;
|
|
send_dcn(s);
|
|
break;
|
|
}
|
|
break;
|
|
case T30_PIP:
|
|
s->retries = 0;
|
|
if (s->phase_d_handler)
|
|
{
|
|
s->phase_d_handler(s, s->phase_d_user_data, T30_PIP);
|
|
s->timer_t3 = ms_to_samples(DEFAULT_TIMER_T3);
|
|
}
|
|
break;
|
|
case T30_PIN:
|
|
s->retries = 0;
|
|
if (s->phase_d_handler)
|
|
{
|
|
s->phase_d_handler(s, s->phase_d_user_data, T30_PIN);
|
|
s->timer_t3 = ms_to_samples(DEFAULT_TIMER_T3);
|
|
}
|
|
break;
|
|
case T30_DCN:
|
|
switch (s->next_tx_step)
|
|
{
|
|
case T30_MPS:
|
|
case T30_PRI_MPS:
|
|
case T30_EOM:
|
|
case T30_PRI_EOM:
|
|
case T30_EOS:
|
|
/* Unexpected DCN after EOM, EOS or MPS sequence */
|
|
s->current_status = T30_ERR_RX_DCNPHD;
|
|
break;
|
|
default:
|
|
s->current_status = T30_ERR_TX_BADPG;
|
|
break;
|
|
}
|
|
disconnect(s);
|
|
break;
|
|
case T30_CRP:
|
|
repeat_last_command(s);
|
|
break;
|
|
case T30_FNV:
|
|
process_rx_fnv(s, msg, len);
|
|
break;
|
|
default:
|
|
/* We don't know what to do with this. */
|
|
unexpected_final_frame(s, msg, len);
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void process_state_iii_q_mcf(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
switch (msg[2] & 0xFE)
|
|
{
|
|
case T30_EOP:
|
|
case T30_EOM:
|
|
case T30_EOS:
|
|
case T30_MPS:
|
|
/* Looks like they didn't see our signal. Repeat it */
|
|
queue_phase(s, T30_PHASE_D_TX);
|
|
set_state(s, T30_STATE_III_Q_MCF);
|
|
send_simple_frame(s, T30_MCF);
|
|
break;
|
|
case T30_DIS:
|
|
if (msg[2] == T30_DTC)
|
|
process_rx_dis_dtc(s, msg, len);
|
|
break;
|
|
case T30_CRP:
|
|
repeat_last_command(s);
|
|
break;
|
|
case T30_FNV:
|
|
process_rx_fnv(s, msg, len);
|
|
break;
|
|
case T30_DCN:
|
|
disconnect(s);
|
|
break;
|
|
default:
|
|
/* We don't know what to do with this. */
|
|
unexpected_final_frame(s, msg, len);
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void process_state_iii_q_rtp(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
switch (msg[2] & 0xFE)
|
|
{
|
|
case T30_EOP:
|
|
case T30_EOM:
|
|
case T30_EOS:
|
|
case T30_MPS:
|
|
/* Looks like they didn't see our signal. Repeat it */
|
|
queue_phase(s, T30_PHASE_D_TX);
|
|
set_state(s, T30_STATE_III_Q_RTP);
|
|
send_simple_frame(s, T30_RTP);
|
|
break;
|
|
case T30_DIS:
|
|
if (msg[2] == T30_DTC)
|
|
process_rx_dis_dtc(s, msg, len);
|
|
break;
|
|
case T30_CRP:
|
|
repeat_last_command(s);
|
|
break;
|
|
case T30_FNV:
|
|
process_rx_fnv(s, msg, len);
|
|
break;
|
|
default:
|
|
/* We don't know what to do with this. */
|
|
unexpected_final_frame(s, msg, len);
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void process_state_iii_q_rtn(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
switch (msg[2] & 0xFE)
|
|
{
|
|
case T30_EOP:
|
|
case T30_EOM:
|
|
case T30_EOS:
|
|
case T30_MPS:
|
|
/* Looks like they didn't see our signal. Repeat it */
|
|
queue_phase(s, T30_PHASE_D_TX);
|
|
set_state(s, T30_STATE_III_Q_RTN);
|
|
send_simple_frame(s, T30_RTN);
|
|
break;
|
|
case T30_DIS:
|
|
if (msg[2] == T30_DTC)
|
|
process_rx_dis_dtc(s, msg, len);
|
|
break;
|
|
case T30_CRP:
|
|
repeat_last_command(s);
|
|
break;
|
|
case T30_FNV:
|
|
process_rx_fnv(s, msg, len);
|
|
break;
|
|
case T30_DCN:
|
|
s->current_status = T30_ERR_RX_DCNNORTN;
|
|
disconnect(s);
|
|
break;
|
|
default:
|
|
/* We don't know what to do with this. */
|
|
unexpected_final_frame(s, msg, len);
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void process_state_iv(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
switch (msg[2] & 0xFE)
|
|
{
|
|
case T30_CRP:
|
|
repeat_last_command(s);
|
|
break;
|
|
case T30_FNV:
|
|
process_rx_fnv(s, msg, len);
|
|
break;
|
|
default:
|
|
/* We don't know what to do with this. */
|
|
unexpected_final_frame(s, msg, len);
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void process_state_iv_pps_null(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
t4_stats_t stats;
|
|
|
|
switch (msg[2] & 0xFE)
|
|
{
|
|
case T30_MCF:
|
|
s->retries = 0;
|
|
s->timer_t5 = 0;
|
|
/* Is there more of the current page to get, or do we move on? */
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Is there more to send? - %d %d\n", s->ecm_frames, s->ecm_len[255]);
|
|
if (!s->ecm_at_page_end && get_partial_ecm_page(s) > 0)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Additional image data to send\n");
|
|
s->ecm_block++;
|
|
set_state(s, T30_STATE_IV);
|
|
queue_phase(s, T30_PHASE_C_ECM_TX);
|
|
send_first_ecm_frame(s);
|
|
}
|
|
else
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Moving on to the next page\n");
|
|
switch (s->next_tx_step)
|
|
{
|
|
case T30_MPS:
|
|
case T30_PRI_MPS:
|
|
tx_end_page(s);
|
|
if (s->phase_d_handler)
|
|
s->phase_d_handler(s, s->phase_d_user_data, T30_MCF);
|
|
if (tx_start_page(s))
|
|
{
|
|
/* TODO: recover */
|
|
break;
|
|
}
|
|
if (get_partial_ecm_page(s) > 0)
|
|
{
|
|
set_state(s, T30_STATE_IV);
|
|
queue_phase(s, T30_PHASE_C_ECM_TX);
|
|
send_first_ecm_frame(s);
|
|
}
|
|
break;
|
|
case T30_EOM:
|
|
case T30_PRI_EOM:
|
|
case T30_EOS:
|
|
tx_end_page(s);
|
|
if (s->phase_d_handler)
|
|
s->phase_d_handler(s, s->phase_d_user_data, T30_MCF);
|
|
t4_tx_release(&(s->t4));
|
|
s->operation_in_progress = OPERATION_IN_PROGRESS_NONE;
|
|
if (span_log_test(&s->logging, SPAN_LOG_FLOW))
|
|
{
|
|
t4_get_transfer_statistics(&s->t4, &stats);
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Success - delivered %d pages\n", stats.pages_transferred);
|
|
}
|
|
return_to_phase_b(s, FALSE);
|
|
break;
|
|
case T30_EOP:
|
|
case T30_PRI_EOP:
|
|
tx_end_page(s);
|
|
if (s->phase_d_handler)
|
|
s->phase_d_handler(s, s->phase_d_user_data, T30_MCF);
|
|
t4_tx_release(&(s->t4));
|
|
s->operation_in_progress = OPERATION_IN_PROGRESS_NONE;
|
|
send_dcn(s);
|
|
if (span_log_test(&s->logging, SPAN_LOG_FLOW))
|
|
{
|
|
t4_get_transfer_statistics(&s->t4, &stats);
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Success - delivered %d pages\n", stats.pages_transferred);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case T30_PPR:
|
|
process_rx_ppr(s, msg, len);
|
|
break;
|
|
case T30_RNR:
|
|
if (s->timer_t5 == 0)
|
|
s->timer_t5 = ms_to_samples(DEFAULT_TIMER_T5);
|
|
queue_phase(s, T30_PHASE_D_TX);
|
|
set_state(s, T30_STATE_IV_PPS_RNR);
|
|
send_rr(s);
|
|
break;
|
|
case T30_DCN:
|
|
s->current_status = T30_ERR_TX_BADPG;
|
|
disconnect(s);
|
|
break;
|
|
case T30_CRP:
|
|
repeat_last_command(s);
|
|
break;
|
|
case T30_FNV:
|
|
process_rx_fnv(s, msg, len);
|
|
break;
|
|
default:
|
|
/* We don't know what to do with this. */
|
|
unexpected_final_frame(s, msg, len);
|
|
s->current_status = T30_ERR_TX_ECMPHD;
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void process_state_iv_pps_q(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
t4_stats_t stats;
|
|
|
|
switch (msg[2] & 0xFE)
|
|
{
|
|
case T30_MCF:
|
|
s->retries = 0;
|
|
s->timer_t5 = 0;
|
|
/* Is there more of the current page to get, or do we move on? */
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Is there more to send? - %d %d\n", s->ecm_frames, s->ecm_len[255]);
|
|
if (!s->ecm_at_page_end && get_partial_ecm_page(s) > 0)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Additional image data to send\n");
|
|
s->ecm_block++;
|
|
set_state(s, T30_STATE_IV);
|
|
queue_phase(s, T30_PHASE_C_ECM_TX);
|
|
send_first_ecm_frame(s);
|
|
}
|
|
else
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Moving on to the next page\n");
|
|
switch (s->next_tx_step)
|
|
{
|
|
case T30_MPS:
|
|
case T30_PRI_MPS:
|
|
tx_end_page(s);
|
|
if (s->phase_d_handler)
|
|
s->phase_d_handler(s, s->phase_d_user_data, T30_MCF);
|
|
if (tx_start_page(s))
|
|
{
|
|
/* TODO: recover */
|
|
break;
|
|
}
|
|
if (get_partial_ecm_page(s) > 0)
|
|
{
|
|
set_state(s, T30_STATE_IV);
|
|
queue_phase(s, T30_PHASE_C_ECM_TX);
|
|
send_first_ecm_frame(s);
|
|
}
|
|
break;
|
|
case T30_EOM:
|
|
case T30_PRI_EOM:
|
|
case T30_EOS:
|
|
tx_end_page(s);
|
|
if (s->phase_d_handler)
|
|
s->phase_d_handler(s, s->phase_d_user_data, T30_MCF);
|
|
t4_tx_release(&(s->t4));
|
|
s->operation_in_progress = OPERATION_IN_PROGRESS_NONE;
|
|
if (span_log_test(&s->logging, SPAN_LOG_FLOW))
|
|
{
|
|
t4_get_transfer_statistics(&s->t4, &stats);
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Success - delivered %d pages\n", stats.pages_transferred);
|
|
}
|
|
return_to_phase_b(s, FALSE);
|
|
break;
|
|
case T30_EOP:
|
|
case T30_PRI_EOP:
|
|
tx_end_page(s);
|
|
if (s->phase_d_handler)
|
|
s->phase_d_handler(s, s->phase_d_user_data, T30_MCF);
|
|
t4_tx_release(&(s->t4));
|
|
s->operation_in_progress = OPERATION_IN_PROGRESS_NONE;
|
|
send_dcn(s);
|
|
if (span_log_test(&s->logging, SPAN_LOG_FLOW))
|
|
{
|
|
t4_get_transfer_statistics(&s->t4, &stats);
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Success - delivered %d pages\n", stats.pages_transferred);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case T30_RNR:
|
|
if (s->timer_t5 == 0)
|
|
s->timer_t5 = ms_to_samples(DEFAULT_TIMER_T5);
|
|
queue_phase(s, T30_PHASE_D_TX);
|
|
set_state(s, T30_STATE_IV_PPS_RNR);
|
|
send_rr(s);
|
|
break;
|
|
case T30_PIP:
|
|
s->retries = 0;
|
|
if (s->phase_d_handler)
|
|
{
|
|
s->phase_d_handler(s, s->phase_d_user_data, T30_PIP);
|
|
s->timer_t3 = ms_to_samples(DEFAULT_TIMER_T3);
|
|
}
|
|
break;
|
|
case T30_PIN:
|
|
s->retries = 0;
|
|
if (s->phase_d_handler)
|
|
{
|
|
s->phase_d_handler(s, s->phase_d_user_data, T30_PIN);
|
|
s->timer_t3 = ms_to_samples(DEFAULT_TIMER_T3);
|
|
}
|
|
break;
|
|
case T30_PPR:
|
|
process_rx_ppr(s, msg, len);
|
|
break;
|
|
case T30_DCN:
|
|
s->current_status = T30_ERR_TX_BADPG;
|
|
disconnect(s);
|
|
break;
|
|
case T30_CRP:
|
|
repeat_last_command(s);
|
|
break;
|
|
case T30_FNV:
|
|
process_rx_fnv(s, msg, len);
|
|
break;
|
|
default:
|
|
/* We don't know what to do with this. */
|
|
unexpected_final_frame(s, msg, len);
|
|
s->current_status = T30_ERR_TX_ECMPHD;
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void process_state_iv_pps_rnr(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
t4_stats_t stats;
|
|
|
|
switch (msg[2] & 0xFE)
|
|
{
|
|
case T30_MCF:
|
|
s->retries = 0;
|
|
s->timer_t5 = 0;
|
|
/* Is there more of the current page to get, or do we move on? */
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Is there more to send? - %d %d\n", s->ecm_frames, s->ecm_len[255]);
|
|
if (!s->ecm_at_page_end && get_partial_ecm_page(s) > 0)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Additional image data to send\n");
|
|
s->ecm_block++;
|
|
set_state(s, T30_STATE_IV);
|
|
queue_phase(s, T30_PHASE_C_ECM_TX);
|
|
send_first_ecm_frame(s);
|
|
}
|
|
else
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Moving on to the next page\n");
|
|
switch (s->next_tx_step)
|
|
{
|
|
case T30_MPS:
|
|
case T30_PRI_MPS:
|
|
tx_end_page(s);
|
|
if (s->phase_d_handler)
|
|
s->phase_d_handler(s, s->phase_d_user_data, T30_MCF);
|
|
if (tx_start_page(s))
|
|
{
|
|
/* TODO: recover */
|
|
break;
|
|
}
|
|
if (get_partial_ecm_page(s) > 0)
|
|
{
|
|
set_state(s, T30_STATE_IV);
|
|
queue_phase(s, T30_PHASE_C_ECM_TX);
|
|
send_first_ecm_frame(s);
|
|
}
|
|
break;
|
|
case T30_EOM:
|
|
case T30_PRI_EOM:
|
|
case T30_EOS:
|
|
tx_end_page(s);
|
|
if (s->phase_d_handler)
|
|
s->phase_d_handler(s, s->phase_d_user_data, T30_MCF);
|
|
t4_tx_release(&(s->t4));
|
|
s->operation_in_progress = OPERATION_IN_PROGRESS_NONE;
|
|
if (span_log_test(&s->logging, SPAN_LOG_FLOW))
|
|
{
|
|
t4_get_transfer_statistics(&s->t4, &stats);
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Success - delivered %d pages\n", stats.pages_transferred);
|
|
}
|
|
return_to_phase_b(s, FALSE);
|
|
break;
|
|
case T30_EOP:
|
|
case T30_PRI_EOP:
|
|
tx_end_page(s);
|
|
if (s->phase_d_handler)
|
|
s->phase_d_handler(s, s->phase_d_user_data, T30_MCF);
|
|
t4_tx_release(&(s->t4));
|
|
s->operation_in_progress = OPERATION_IN_PROGRESS_NONE;
|
|
send_dcn(s);
|
|
if (span_log_test(&s->logging, SPAN_LOG_FLOW))
|
|
{
|
|
t4_get_transfer_statistics(&s->t4, &stats);
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Success - delivered %d pages\n", stats.pages_transferred);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case T30_RNR:
|
|
if (s->timer_t5 == 0)
|
|
s->timer_t5 = ms_to_samples(DEFAULT_TIMER_T5);
|
|
queue_phase(s, T30_PHASE_D_TX);
|
|
set_state(s, T30_STATE_IV_PPS_RNR);
|
|
send_rr(s);
|
|
break;
|
|
case T30_PIP:
|
|
s->retries = 0;
|
|
if (s->phase_d_handler)
|
|
{
|
|
s->phase_d_handler(s, s->phase_d_user_data, T30_PIP);
|
|
s->timer_t3 = ms_to_samples(DEFAULT_TIMER_T3);
|
|
}
|
|
break;
|
|
case T30_PIN:
|
|
s->retries = 0;
|
|
if (s->phase_d_handler)
|
|
{
|
|
s->phase_d_handler(s, s->phase_d_user_data, T30_PIN);
|
|
s->timer_t3 = ms_to_samples(DEFAULT_TIMER_T3);
|
|
}
|
|
break;
|
|
case T30_DCN:
|
|
s->current_status = T30_ERR_RX_DCNRRD;
|
|
disconnect(s);
|
|
break;
|
|
case T30_CRP:
|
|
repeat_last_command(s);
|
|
break;
|
|
case T30_FNV:
|
|
process_rx_fnv(s, msg, len);
|
|
break;
|
|
default:
|
|
/* We don't know what to do with this. */
|
|
unexpected_final_frame(s, msg, len);
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void process_state_iv_ctc(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
switch (msg[2] & 0xFE)
|
|
{
|
|
case T30_CTR:
|
|
/* Valid response to a CTC received */
|
|
/* T.30 says we change back to long training here */
|
|
s->short_train = FALSE;
|
|
/* Initiate resending of the remainder of the frames. */
|
|
set_state(s, T30_STATE_IV);
|
|
queue_phase(s, T30_PHASE_C_ECM_TX);
|
|
send_first_ecm_frame(s);
|
|
break;
|
|
case T30_CRP:
|
|
repeat_last_command(s);
|
|
break;
|
|
case T30_FNV:
|
|
process_rx_fnv(s, msg, len);
|
|
break;
|
|
default:
|
|
/* We don't know what to do with this. */
|
|
unexpected_final_frame(s, msg, len);
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void process_state_iv_eor(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
switch (msg[2] & 0xFE)
|
|
{
|
|
case T30_RNR:
|
|
if (s->timer_t5 == 0)
|
|
s->timer_t5 = ms_to_samples(DEFAULT_TIMER_T5);
|
|
queue_phase(s, T30_PHASE_D_TX);
|
|
set_state(s, T30_STATE_IV_EOR_RNR);
|
|
send_rr(s);
|
|
break;
|
|
case T30_PIN:
|
|
s->retries = 0;
|
|
if (s->phase_d_handler)
|
|
{
|
|
s->phase_d_handler(s, s->phase_d_user_data, T30_PIN);
|
|
s->timer_t3 = ms_to_samples(DEFAULT_TIMER_T3);
|
|
}
|
|
break;
|
|
case T30_ERR:
|
|
/* TODO: Continue with the next message if MPS or EOM? */
|
|
s->timer_t5 = 0;
|
|
send_dcn(s);
|
|
break;
|
|
case T30_CRP:
|
|
repeat_last_command(s);
|
|
break;
|
|
case T30_FNV:
|
|
process_rx_fnv(s, msg, len);
|
|
break;
|
|
default:
|
|
/* We don't know what to do with this. */
|
|
unexpected_final_frame(s, msg, len);
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void process_state_iv_eor_rnr(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
switch (msg[2] & 0xFE)
|
|
{
|
|
case T30_RNR:
|
|
if (s->timer_t5 == 0)
|
|
s->timer_t5 = ms_to_samples(DEFAULT_TIMER_T5);
|
|
queue_phase(s, T30_PHASE_D_TX);
|
|
set_state(s, T30_STATE_IV_EOR_RNR);
|
|
send_rr(s);
|
|
break;
|
|
case T30_PIN:
|
|
s->retries = 0;
|
|
if (s->phase_d_handler)
|
|
{
|
|
s->phase_d_handler(s, s->phase_d_user_data, T30_PIN);
|
|
s->timer_t3 = ms_to_samples(DEFAULT_TIMER_T3);
|
|
}
|
|
break;
|
|
case T30_ERR:
|
|
/* TODO: Continue with the next message if MPS or EOM? */
|
|
s->timer_t5 = 0;
|
|
send_dcn(s);
|
|
break;
|
|
case T30_DCN:
|
|
s->current_status = T30_ERR_RX_DCNRRD;
|
|
disconnect(s);
|
|
break;
|
|
case T30_CRP:
|
|
repeat_last_command(s);
|
|
break;
|
|
case T30_FNV:
|
|
process_rx_fnv(s, msg, len);
|
|
break;
|
|
default:
|
|
/* We don't know what to do with this. */
|
|
unexpected_final_frame(s, msg, len);
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void process_state_call_finished(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
/* Simply ignore anything which comes in when we have declared the call
|
|
to have finished. */
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void process_rx_control_msg(t30_state_t *s, const uint8_t *msg, int len)
|
|
{
|
|
/* We should only get good frames here. */
|
|
print_frame(s, "Rx: ", msg, len);
|
|
if (s->real_time_frame_handler)
|
|
s->real_time_frame_handler(s, s->real_time_frame_user_data, TRUE, msg, len);
|
|
|
|
if ((msg[1] & 0x10) == 0)
|
|
{
|
|
/* This is not a final frame */
|
|
/* It seems we should not restart the command or response timer when exchanging HDLC image
|
|
data. If the modem looses sync in the middle of the image, we should just wait until
|
|
the carrier goes away before proceeding. */
|
|
if (s->phase != T30_PHASE_C_ECM_RX)
|
|
{
|
|
/* Restart the command or response timer, T2 or T4 */
|
|
switch (s->timer_t2_t4_is)
|
|
{
|
|
case TIMER_IS_T1A:
|
|
case TIMER_IS_T2:
|
|
case TIMER_IS_T2A:
|
|
case TIMER_IS_T2B:
|
|
timer_t2a_start(s);
|
|
break;
|
|
case TIMER_IS_T4:
|
|
case TIMER_IS_T4A:
|
|
case TIMER_IS_T4B:
|
|
timer_t4a_start(s);
|
|
break;
|
|
}
|
|
}
|
|
/* The following handles all the message types we expect to get without
|
|
a final frame tag. If we get one that T.30 says we should not expect
|
|
in a particular context, its pretty harmless, so don't worry. */
|
|
switch (msg[2] & 0xFE)
|
|
{
|
|
case (T30_CSI & 0xFE):
|
|
/* Called subscriber identification or Calling subscriber identification (T30_CIG) */
|
|
/* OK in (NSF) (CSI) DIS */
|
|
/* OK in (NSC) (CIG) DTC */
|
|
/* OK in (PWD) (SEP) (CIG) DTC */
|
|
decode_20digit_msg(s, s->rx_info.ident, &msg[2], len - 2);
|
|
break;
|
|
case (T30_NSF & 0xFE):
|
|
if (msg[2] == T30_NSF)
|
|
{
|
|
/* Non-standard facilities */
|
|
/* OK in (NSF) (CSI) DIS */
|
|
t35_decode(&msg[3], len - 3, &s->country, &s->vendor, &s->model);
|
|
if (s->country)
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "The remote was made in '%s'\n", s->country);
|
|
if (s->vendor)
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "The remote was made by '%s'\n", s->vendor);
|
|
if (s->model)
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "The remote is a '%s'\n", s->model);
|
|
}
|
|
else
|
|
{
|
|
/* NSC - Non-standard facilities command */
|
|
/* OK in (NSC) (CIG) DTC */
|
|
}
|
|
break;
|
|
case (T30_PWD & 0xFE):
|
|
if (msg[2] == T30_PWD)
|
|
{
|
|
/* Password */
|
|
/* OK in (SUB) (SID) (SEP) (PWD) (TSI) DCS */
|
|
/* OK in (SUB) (SID) (SEP) (PWD) (CIG) DTC */
|
|
decode_20digit_msg(s, s->rx_info.password, &msg[2], len - 2);
|
|
}
|
|
else
|
|
{
|
|
unexpected_frame(s, msg, len);
|
|
}
|
|
break;
|
|
case (T30_SEP & 0xFE):
|
|
if (msg[2] == T30_SEP)
|
|
{
|
|
/* Selective polling address */
|
|
/* OK in (PWD) (SEP) (CIG) DTC */
|
|
decode_20digit_msg(s, s->rx_info.selective_polling_address, &msg[2], len - 2);
|
|
}
|
|
else
|
|
{
|
|
unexpected_frame(s, msg, len);
|
|
}
|
|
break;
|
|
case (T30_PSA & 0xFE):
|
|
if (msg[2] == T30_PSA)
|
|
{
|
|
/* Polled sub-address */
|
|
decode_20digit_msg(s, s->rx_info.polled_sub_address, &msg[2], len - 2);
|
|
}
|
|
else
|
|
{
|
|
unexpected_frame(s, msg, len);
|
|
}
|
|
break;
|
|
case (T30_CIA & 0xFE):
|
|
if (msg[2] == T30_CIA)
|
|
{
|
|
/* Calling subscriber internet address */
|
|
decode_url_msg(s, NULL, &msg[2], len - 2);
|
|
}
|
|
else
|
|
{
|
|
unexpected_frame(s, msg, len);
|
|
}
|
|
break;
|
|
case (T30_ISP & 0xFE):
|
|
if (msg[2] == T30_ISP)
|
|
{
|
|
/* Internet selective polling address */
|
|
decode_url_msg(s, NULL, &msg[2], len - 2);
|
|
}
|
|
else
|
|
{
|
|
unexpected_frame(s, msg, len);
|
|
}
|
|
break;
|
|
case (T30_TSI & 0xFE):
|
|
/* Transmitting subscriber identity */
|
|
/* OK in (PWD) (SUB) (TSI) DCS */
|
|
decode_20digit_msg(s, s->rx_info.ident, &msg[2], len - 2);
|
|
break;
|
|
case (T30_NSS & 0xFE):
|
|
/* Non-standard facilities set-up */
|
|
break;
|
|
case (T30_SUB & 0xFE):
|
|
/* Sub-address */
|
|
/* OK in (PWD) (SUB) (TSI) DCS */
|
|
decode_20digit_msg(s, s->rx_info.sub_address, &msg[2], len - 2);
|
|
break;
|
|
case (T30_SID & 0xFE):
|
|
/* Sender Identification */
|
|
/* OK in (SUB) (SID) (SEP) (PWD) (TSI) DCS */
|
|
/* OK in (SUB) (SID) (SEP) (PWD) (CIG) DTC */
|
|
decode_20digit_msg(s, s->rx_info.sender_ident, &msg[2], len - 2);
|
|
break;
|
|
case (T30_CSA & 0xFE):
|
|
/* Calling subscriber internet address */
|
|
decode_url_msg(s, NULL, &msg[2], len - 2);
|
|
break;
|
|
case (T30_TSA & 0xFE):
|
|
/* Transmitting subscriber internet address */
|
|
decode_url_msg(s, NULL, &msg[2], len - 2);
|
|
break;
|
|
case (T30_IRA & 0xFE):
|
|
/* Internet routing address */
|
|
decode_url_msg(s, NULL, &msg[2], len - 2);
|
|
break;
|
|
case T4_FCD:
|
|
process_rx_fcd(s, msg, len);
|
|
break;
|
|
case T4_RCP:
|
|
process_rx_rcp(s, msg, len);
|
|
break;
|
|
default:
|
|
unexpected_non_final_frame(s, msg, len);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* This is a final frame */
|
|
/* Once we have any successful message from the far end, we
|
|
cancel timer T1 */
|
|
s->timer_t0_t1 = 0;
|
|
|
|
/* The following handles context sensitive message types, which should
|
|
occur at the end of message sequences. They should, therefore have
|
|
the final frame flag set. */
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "In state %d\n", s->state);
|
|
|
|
switch (s->state)
|
|
{
|
|
case T30_STATE_ANSWERING:
|
|
process_state_answering(s, msg, len);
|
|
break;
|
|
case T30_STATE_B:
|
|
process_state_b(s, msg, len);
|
|
break;
|
|
case T30_STATE_C:
|
|
process_state_c(s, msg, len);
|
|
break;
|
|
case T30_STATE_D:
|
|
process_state_d(s, msg, len);
|
|
break;
|
|
case T30_STATE_D_TCF:
|
|
process_state_d_tcf(s, msg, len);
|
|
break;
|
|
case T30_STATE_D_POST_TCF:
|
|
process_state_d_post_tcf(s, msg, len);
|
|
break;
|
|
case T30_STATE_F_TCF:
|
|
process_state_f_tcf(s, msg, len);
|
|
break;
|
|
case T30_STATE_F_CFR:
|
|
process_state_f_cfr(s, msg, len);
|
|
break;
|
|
case T30_STATE_F_FTT:
|
|
process_state_f_ftt(s, msg, len);
|
|
break;
|
|
case T30_STATE_F_DOC_NON_ECM:
|
|
process_state_f_doc_non_ecm(s, msg, len);
|
|
break;
|
|
case T30_STATE_F_POST_DOC_NON_ECM:
|
|
process_state_f_post_doc_non_ecm(s, msg, len);
|
|
break;
|
|
case T30_STATE_F_DOC_ECM:
|
|
case T30_STATE_F_POST_DOC_ECM:
|
|
process_state_f_doc_and_post_doc_ecm(s, msg, len);
|
|
break;
|
|
case T30_STATE_F_POST_RCP_MCF:
|
|
process_state_f_post_rcp_mcf(s, msg, len);
|
|
break;
|
|
case T30_STATE_F_POST_RCP_PPR:
|
|
process_state_f_post_rcp_ppr(s, msg, len);
|
|
break;
|
|
case T30_STATE_F_POST_RCP_RNR:
|
|
process_state_f_post_rcp_rnr(s, msg, len);
|
|
break;
|
|
case T30_STATE_R:
|
|
process_state_r(s, msg, len);
|
|
break;
|
|
case T30_STATE_T:
|
|
process_state_t(s, msg, len);
|
|
break;
|
|
case T30_STATE_I:
|
|
process_state_i(s, msg, len);
|
|
break;
|
|
case T30_STATE_II:
|
|
process_state_ii(s, msg, len);
|
|
break;
|
|
case T30_STATE_II_Q:
|
|
process_state_ii_q(s, msg, len);
|
|
break;
|
|
case T30_STATE_III_Q_MCF:
|
|
process_state_iii_q_mcf(s, msg, len);
|
|
break;
|
|
case T30_STATE_III_Q_RTP:
|
|
process_state_iii_q_rtp(s, msg, len);
|
|
break;
|
|
case T30_STATE_III_Q_RTN:
|
|
process_state_iii_q_rtn(s, msg, len);
|
|
break;
|
|
case T30_STATE_IV:
|
|
process_state_iv(s, msg, len);
|
|
break;
|
|
case T30_STATE_IV_PPS_NULL:
|
|
process_state_iv_pps_null(s, msg, len);
|
|
break;
|
|
case T30_STATE_IV_PPS_Q:
|
|
process_state_iv_pps_q(s, msg, len);
|
|
break;
|
|
case T30_STATE_IV_PPS_RNR:
|
|
process_state_iv_pps_rnr(s, msg, len);
|
|
break;
|
|
case T30_STATE_IV_CTC:
|
|
process_state_iv_ctc(s, msg, len);
|
|
break;
|
|
case T30_STATE_IV_EOR:
|
|
process_state_iv_eor(s, msg, len);
|
|
break;
|
|
case T30_STATE_IV_EOR_RNR:
|
|
process_state_iv_eor_rnr(s, msg, len);
|
|
break;
|
|
case T30_STATE_CALL_FINISHED:
|
|
process_state_call_finished(s, msg, len);
|
|
break;
|
|
default:
|
|
/* We don't know what to do with this. */
|
|
unexpected_final_frame(s, msg, len);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void queue_phase(t30_state_t *s, int phase)
|
|
{
|
|
if (s->rx_signal_present)
|
|
{
|
|
/* We need to wait for that signal to go away */
|
|
s->next_phase = phase;
|
|
}
|
|
else
|
|
{
|
|
set_phase(s, phase);
|
|
s->next_phase = T30_PHASE_IDLE;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void set_phase(t30_state_t *s, int phase)
|
|
{
|
|
//if (phase = s->phase)
|
|
// return;
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Changing from phase %s to %s\n", phase_names[s->phase], phase_names[phase]);
|
|
/* We may be killing a receiver before it has declared the end of the
|
|
signal. Force the signal present indicator to off, because the
|
|
receiver will never be able to. */
|
|
if (s->phase != T30_PHASE_A_CED && s->phase != T30_PHASE_A_CNG)
|
|
s->rx_signal_present = FALSE;
|
|
s->rx_trained = FALSE;
|
|
s->rx_frame_received = FALSE;
|
|
s->phase = phase;
|
|
switch (phase)
|
|
{
|
|
case T30_PHASE_A_CED:
|
|
if (s->set_rx_type_handler)
|
|
s->set_rx_type_handler(s->set_rx_type_user_data, T30_MODEM_V21, 300, FALSE, TRUE);
|
|
if (s->set_tx_type_handler)
|
|
s->set_tx_type_handler(s->set_tx_type_user_data, T30_MODEM_CED, 0, FALSE, FALSE);
|
|
break;
|
|
case T30_PHASE_A_CNG:
|
|
if (s->set_rx_type_handler)
|
|
s->set_rx_type_handler(s->set_rx_type_user_data, T30_MODEM_V21, 300, FALSE, TRUE);
|
|
if (s->set_tx_type_handler)
|
|
s->set_tx_type_handler(s->set_tx_type_user_data, T30_MODEM_CNG, 0, FALSE, FALSE);
|
|
break;
|
|
case T30_PHASE_B_RX:
|
|
case T30_PHASE_D_RX:
|
|
if (s->set_rx_type_handler)
|
|
s->set_rx_type_handler(s->set_rx_type_user_data, T30_MODEM_V21, 300, FALSE, TRUE);
|
|
if (s->set_tx_type_handler)
|
|
s->set_tx_type_handler(s->set_tx_type_user_data, T30_MODEM_NONE, 0, FALSE, FALSE);
|
|
break;
|
|
case T30_PHASE_B_TX:
|
|
case T30_PHASE_D_TX:
|
|
if (!s->far_end_detected && s->timer_t0_t1 > 0)
|
|
{
|
|
s->timer_t0_t1 = ms_to_samples(DEFAULT_TIMER_T1);
|
|
s->far_end_detected = TRUE;
|
|
}
|
|
if (s->set_rx_type_handler)
|
|
s->set_rx_type_handler(s->set_rx_type_user_data, T30_MODEM_NONE, 0, FALSE, FALSE);
|
|
if (s->set_tx_type_handler)
|
|
s->set_tx_type_handler(s->set_tx_type_user_data, T30_MODEM_V21, 300, FALSE, TRUE);
|
|
break;
|
|
case T30_PHASE_C_NON_ECM_RX:
|
|
if (s->set_rx_type_handler)
|
|
{
|
|
/* Momentarily stop the receive modem, so the next change is forced to happen. If we don't do this
|
|
an HDLC message on the slow modem, which has disabled the fast modem, will prevent the same
|
|
fast modem from restarting. */
|
|
s->set_rx_type_handler(s->set_rx_type_user_data, T30_MODEM_NONE, 0, FALSE, FALSE);
|
|
s->set_rx_type_handler(s->set_rx_type_user_data, fallback_sequence[s->current_fallback].modem_type, fallback_sequence[s->current_fallback].bit_rate, s->short_train, FALSE);
|
|
}
|
|
if (s->set_tx_type_handler)
|
|
s->set_tx_type_handler(s->set_tx_type_user_data, T30_MODEM_NONE, 0, FALSE, FALSE);
|
|
break;
|
|
case T30_PHASE_C_NON_ECM_TX:
|
|
/* Pause before switching from anything to phase C */
|
|
/* Always prime the training count for 1.5s of data at the current rate. Its harmless if
|
|
we prime it and are not doing TCF. */
|
|
s->tcf_test_bits = (3*fallback_sequence[s->current_fallback].bit_rate)/2;
|
|
if (s->set_rx_type_handler)
|
|
{
|
|
/* Momentarily stop the receive modem, so the next change is forced to happen. If we don't do this
|
|
an HDLC message on the slow modem, which has disabled the fast modem, will prevent the same
|
|
fast modem from restarting. */
|
|
s->set_rx_type_handler(s->set_rx_type_user_data, T30_MODEM_NONE, 0, FALSE, FALSE);
|
|
s->set_rx_type_handler(s->set_rx_type_user_data, T30_MODEM_NONE, 0, FALSE, FALSE);
|
|
}
|
|
if (s->set_tx_type_handler)
|
|
s->set_tx_type_handler(s->set_tx_type_user_data, fallback_sequence[s->current_fallback].modem_type, fallback_sequence[s->current_fallback].bit_rate, s->short_train, FALSE);
|
|
break;
|
|
case T30_PHASE_C_ECM_RX:
|
|
if (s->set_rx_type_handler)
|
|
s->set_rx_type_handler(s->set_rx_type_user_data, fallback_sequence[s->current_fallback].modem_type, fallback_sequence[s->current_fallback].bit_rate, s->short_train, TRUE);
|
|
if (s->set_tx_type_handler)
|
|
s->set_tx_type_handler(s->set_tx_type_user_data, T30_MODEM_NONE, 0, FALSE, FALSE);
|
|
break;
|
|
case T30_PHASE_C_ECM_TX:
|
|
/* Pause before switching from anything to phase C */
|
|
if (s->set_rx_type_handler)
|
|
s->set_rx_type_handler(s->set_rx_type_user_data, T30_MODEM_NONE, 0, FALSE, FALSE);
|
|
if (s->set_tx_type_handler)
|
|
s->set_tx_type_handler(s->set_tx_type_user_data, fallback_sequence[s->current_fallback].modem_type, fallback_sequence[s->current_fallback].bit_rate, s->short_train, TRUE);
|
|
break;
|
|
case T30_PHASE_E:
|
|
/* Send a little silence before ending things, to ensure the
|
|
buffers are all flushed through, and the far end has seen
|
|
the last message we sent. */
|
|
s->tcf_test_bits = 0;
|
|
s->tcf_current_zeros = 0;
|
|
s->tcf_most_zeros = 0;
|
|
if (s->set_rx_type_handler)
|
|
s->set_rx_type_handler(s->set_rx_type_user_data, T30_MODEM_NONE, 0, FALSE, FALSE);
|
|
if (s->set_tx_type_handler)
|
|
s->set_tx_type_handler(s->set_tx_type_user_data, T30_MODEM_PAUSE, 0, FINAL_FLUSH_TIME, FALSE);
|
|
break;
|
|
case T30_PHASE_CALL_FINISHED:
|
|
if (s->set_rx_type_handler)
|
|
s->set_rx_type_handler(s->set_rx_type_user_data, T30_MODEM_DONE, 0, FALSE, FALSE);
|
|
if (s->set_tx_type_handler)
|
|
s->set_tx_type_handler(s->set_tx_type_user_data, T30_MODEM_DONE, 0, FALSE, FALSE);
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void set_state(t30_state_t *s, int state)
|
|
{
|
|
if (s->state != state)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Changing from state %d to %d\n", s->state, state);
|
|
s->state = state;
|
|
}
|
|
s->step = 0;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void repeat_last_command(t30_state_t *s)
|
|
{
|
|
s->step = 0;
|
|
if (++s->retries >= MAX_COMMAND_TRIES)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Too many retries. Giving up.\n");
|
|
switch (s->state)
|
|
{
|
|
case T30_STATE_D_POST_TCF:
|
|
/* Received no response to DCS or TCF */
|
|
s->current_status = T30_ERR_TX_PHBDEAD;
|
|
break;
|
|
case T30_STATE_II_Q:
|
|
case T30_STATE_IV_PPS_NULL:
|
|
case T30_STATE_IV_PPS_Q:
|
|
/* No response after sending a page */
|
|
s->current_status = T30_ERR_TX_PHDDEAD;
|
|
break;
|
|
default:
|
|
/* Disconnected after permitted retries */
|
|
s->current_status = T30_ERR_RETRYDCN;
|
|
break;
|
|
}
|
|
send_dcn(s);
|
|
return;
|
|
}
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Retry number %d\n", s->retries);
|
|
switch (s->state)
|
|
{
|
|
case T30_STATE_R:
|
|
s->dis_received = FALSE;
|
|
queue_phase(s, T30_PHASE_B_TX);
|
|
send_dis_or_dtc_sequence(s, TRUE);
|
|
break;
|
|
case T30_STATE_III_Q_MCF:
|
|
queue_phase(s, T30_PHASE_D_TX);
|
|
send_simple_frame(s, T30_MCF);
|
|
break;
|
|
case T30_STATE_III_Q_RTP:
|
|
queue_phase(s, T30_PHASE_D_TX);
|
|
send_simple_frame(s, T30_RTP);
|
|
break;
|
|
case T30_STATE_III_Q_RTN:
|
|
queue_phase(s, T30_PHASE_D_TX);
|
|
send_simple_frame(s, T30_RTN);
|
|
break;
|
|
case T30_STATE_II_Q:
|
|
queue_phase(s, T30_PHASE_D_TX);
|
|
send_simple_frame(s, s->next_tx_step);
|
|
break;
|
|
case T30_STATE_IV_PPS_NULL:
|
|
case T30_STATE_IV_PPS_Q:
|
|
queue_phase(s, T30_PHASE_D_TX);
|
|
send_pps_frame(s);
|
|
break;
|
|
case T30_STATE_IV_PPS_RNR:
|
|
case T30_STATE_IV_EOR_RNR:
|
|
queue_phase(s, T30_PHASE_D_TX);
|
|
send_rr(s);
|
|
break;
|
|
case T30_STATE_D:
|
|
queue_phase(s, T30_PHASE_B_TX);
|
|
send_dcs_sequence(s, TRUE);
|
|
break;
|
|
case T30_STATE_F_FTT:
|
|
queue_phase(s, T30_PHASE_B_TX);
|
|
send_simple_frame(s, T30_FTT);
|
|
break;
|
|
case T30_STATE_F_CFR:
|
|
queue_phase(s, T30_PHASE_B_TX);
|
|
send_cfr_sequence(s, TRUE);
|
|
break;
|
|
case T30_STATE_D_POST_TCF:
|
|
/* Need to send the whole training thing again */
|
|
s->short_train = FALSE;
|
|
queue_phase(s, T30_PHASE_B_TX);
|
|
send_dcs_sequence(s, TRUE);
|
|
break;
|
|
case T30_STATE_F_POST_RCP_RNR:
|
|
/* Just ignore */
|
|
break;
|
|
default:
|
|
span_log(&s->logging,
|
|
SPAN_LOG_FLOW,
|
|
"Repeat command called with nothing to repeat - phase %s, state %d\n",
|
|
phase_names[s->phase],
|
|
s->state);
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void timer_t2_start(t30_state_t *s)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Start T2\n");
|
|
s->timer_t2_t4 = ms_to_samples(DEFAULT_TIMER_T2);
|
|
s->timer_t2_t4_is = TIMER_IS_T2;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void timer_t2a_start(t30_state_t *s)
|
|
{
|
|
/* T.30 Annex A says timeout T1 should be used in ECM phase C to time out the
|
|
first frame after the flags start. This seems a strange reuse of the name T1
|
|
for a different purpose, but there it is. We distinguish it by calling it T1A. */
|
|
if (s->phase == T30_PHASE_C_ECM_RX)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Start T1A\n");
|
|
s->timer_t2_t4 = ms_to_samples(DEFAULT_TIMER_T1A);
|
|
s->timer_t2_t4_is = TIMER_IS_T1A;
|
|
}
|
|
else
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Start T2A\n");
|
|
s->timer_t2_t4 = ms_to_samples(DEFAULT_TIMER_T2A);
|
|
s->timer_t2_t4_is = TIMER_IS_T2A;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void timer_t2b_start(t30_state_t *s)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Start T2B\n");
|
|
s->timer_t2_t4 = ms_to_samples(DEFAULT_TIMER_T2B);
|
|
s->timer_t2_t4_is = TIMER_IS_T2B;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void timer_t4_start(t30_state_t *s)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Start T4\n");
|
|
s->timer_t2_t4 = ms_to_samples(DEFAULT_TIMER_T4);
|
|
s->timer_t2_t4_is = TIMER_IS_T4;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void timer_t4a_start(t30_state_t *s)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Start T4A\n");
|
|
s->timer_t2_t4 = ms_to_samples(DEFAULT_TIMER_T4A);
|
|
s->timer_t2_t4_is = TIMER_IS_T4A;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void timer_t4b_start(t30_state_t *s)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Start T4B\n");
|
|
s->timer_t2_t4 = ms_to_samples(DEFAULT_TIMER_T4B);
|
|
s->timer_t2_t4_is = TIMER_IS_T4B;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void timer_t2_t4_stop(t30_state_t *s)
|
|
{
|
|
const char *tag;
|
|
|
|
switch (s->timer_t2_t4_is)
|
|
{
|
|
case TIMER_IS_IDLE:
|
|
tag = "none";
|
|
break;
|
|
case TIMER_IS_T1A:
|
|
tag = "T1A";
|
|
break;
|
|
case TIMER_IS_T2:
|
|
tag = "T2";
|
|
break;
|
|
case TIMER_IS_T2A:
|
|
tag = "T2A";
|
|
break;
|
|
case TIMER_IS_T2B:
|
|
tag = "T2B";
|
|
break;
|
|
case TIMER_IS_T2C:
|
|
tag = "T2C";
|
|
break;
|
|
case TIMER_IS_T4:
|
|
tag = "T4";
|
|
break;
|
|
case TIMER_IS_T4A:
|
|
tag = "T4A";
|
|
break;
|
|
case TIMER_IS_T4B:
|
|
tag = "T4B";
|
|
break;
|
|
case TIMER_IS_T4C:
|
|
tag = "T4C";
|
|
break;
|
|
default:
|
|
tag = "T2/T4";
|
|
break;
|
|
}
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Stop %s (%d remaining)\n", tag, s->timer_t2_t4);
|
|
s->timer_t2_t4 = 0;
|
|
s->timer_t2_t4_is = TIMER_IS_IDLE;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void timer_t0_expired(t30_state_t *s)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "T0 expired in state %d\n", s->state);
|
|
s->current_status = T30_ERR_T0_EXPIRED;
|
|
/* Just end the call */
|
|
disconnect(s);
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void timer_t1_expired(t30_state_t *s)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "T1 expired in state %d\n", s->state);
|
|
/* The initial connection establishment has timeout out. In other words, we
|
|
have been unable to communicate successfully with a remote machine.
|
|
It is time to abandon the call. */
|
|
s->current_status = T30_ERR_T1_EXPIRED;
|
|
switch (s->state)
|
|
{
|
|
case T30_STATE_T:
|
|
/* Just end the call */
|
|
disconnect(s);
|
|
break;
|
|
case T30_STATE_R:
|
|
/* Send disconnect, and then end the call. Since we have not
|
|
successfully contacted the far end, it is unclear why we should
|
|
send a disconnect message at this point. However, it is what T.30
|
|
says we should do. */
|
|
send_dcn(s);
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void timer_t2_expired(t30_state_t *s)
|
|
{
|
|
if (s->timer_t2_t4_is != TIMER_IS_T2B)
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "T2 expired in phase %s, state %d\n", phase_names[s->phase], s->state);
|
|
switch (s->state)
|
|
{
|
|
case T30_STATE_III_Q_MCF:
|
|
case T30_STATE_III_Q_RTP:
|
|
case T30_STATE_III_Q_RTN:
|
|
case T30_STATE_F_POST_RCP_PPR:
|
|
case T30_STATE_F_POST_RCP_MCF:
|
|
switch (s->next_rx_step)
|
|
{
|
|
case T30_EOM:
|
|
case T30_PRI_EOM:
|
|
case T30_EOS:
|
|
/* We didn't receive a response to our T30_MCF after T30_EOM, so we must be OK
|
|
to proceed to phase B, and pretty act like its the beginning of a call. */
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Returning to phase B after %s\n", t30_frametype(s->next_rx_step));
|
|
set_phase(s, T30_PHASE_B_TX);
|
|
timer_t2_start(s);
|
|
s->dis_received = FALSE;
|
|
send_dis_or_dtc_sequence(s, TRUE);
|
|
return;
|
|
}
|
|
break;
|
|
case T30_STATE_F_TCF:
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "No TCF data received\n");
|
|
set_phase(s, T30_PHASE_B_TX);
|
|
set_state(s, T30_STATE_F_FTT);
|
|
send_simple_frame(s, T30_FTT);
|
|
return;
|
|
case T30_STATE_F_DOC_ECM:
|
|
case T30_STATE_F_DOC_NON_ECM:
|
|
/* While waiting for FAX page */
|
|
s->current_status = T30_ERR_RX_T2EXPFAX;
|
|
break;
|
|
case T30_STATE_F_POST_DOC_ECM:
|
|
case T30_STATE_F_POST_DOC_NON_ECM:
|
|
/* While waiting for next FAX page */
|
|
s->current_status = T30_ERR_RX_T2EXPMPS;
|
|
break;
|
|
#if 0
|
|
case ??????:
|
|
/* While waiting for DCN */
|
|
s->current_status = T30_ERR_RX_T2EXPDCN;
|
|
break;
|
|
case ??????:
|
|
/* While waiting for phase D */
|
|
s->current_status = T30_ERR_RX_T2EXPD;
|
|
break;
|
|
#endif
|
|
case T30_STATE_IV_PPS_RNR:
|
|
case T30_STATE_IV_EOR_RNR:
|
|
/* While waiting for RR command */
|
|
s->current_status = T30_ERR_RX_T2EXPRR;
|
|
break;
|
|
case T30_STATE_R:
|
|
/* While waiting for NSS, DCS or MCF */
|
|
s->current_status = T30_ERR_RX_T2EXP;
|
|
break;
|
|
case T30_STATE_F_FTT:
|
|
break;
|
|
}
|
|
queue_phase(s, T30_PHASE_B_TX);
|
|
start_receiving_document(s);
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void timer_t1a_expired(t30_state_t *s)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "T1A expired in phase %s, state %d. An HDLC frame lasted too long.\n", phase_names[s->phase], s->state);
|
|
s->current_status = T30_ERR_HDLC_CARRIER;
|
|
disconnect(s);
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void timer_t2a_expired(t30_state_t *s)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "T2A expired in phase %s, state %d. An HDLC frame lasted too long.\n", phase_names[s->phase], s->state);
|
|
s->current_status = T30_ERR_HDLC_CARRIER;
|
|
disconnect(s);
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void timer_t2b_expired(t30_state_t *s)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "T2B expired in phase %s, state %d. The line is now quiet.\n", phase_names[s->phase], s->state);
|
|
timer_t2_expired(s);
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void timer_t3_expired(t30_state_t *s)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "T3 expired in phase %s, state %d\n", phase_names[s->phase], s->state);
|
|
s->current_status = T30_ERR_T3_EXPIRED;
|
|
disconnect(s);
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void timer_t4_expired(t30_state_t *s)
|
|
{
|
|
/* There was no response (or only a corrupt response) to a command,
|
|
within the T4 timeout period. */
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "T4 expired in phase %s, state %d\n", phase_names[s->phase], s->state);
|
|
/* Of course, things might just be a little late, especially if there are T.38
|
|
links in the path. There is no point in simply timing out, and resending,
|
|
if we are currently receiving something from the far end - its a half-duplex
|
|
path, so the two transmissions will conflict. Our best strategy is to wait
|
|
until there is nothing being received, or give up after a long backstop timeout.
|
|
In the meantime, if we get a meaningful, if somewhat delayed, response, we
|
|
should accept it and carry on. */
|
|
repeat_last_command(s);
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void timer_t4a_expired(t30_state_t *s)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "T4A expired in phase %s, state %d. An HDLC frame lasted too long.\n", phase_names[s->phase], s->state);
|
|
s->current_status = T30_ERR_HDLC_CARRIER;
|
|
disconnect(s);
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void timer_t4b_expired(t30_state_t *s)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "T4B expired in phase %s, state %d. The line is now quiet.\n", phase_names[s->phase], s->state);
|
|
timer_t4_expired(s);
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void timer_t5_expired(t30_state_t *s)
|
|
{
|
|
/* Give up waiting for the receiver to become ready in error correction mode */
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "T5 expired in phase %s, state %d\n", phase_names[s->phase], s->state);
|
|
s->current_status = T30_ERR_TX_T5EXP;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void decode_20digit_msg(t30_state_t *s, char *msg, const uint8_t *pkt, int len)
|
|
{
|
|
int p;
|
|
int k;
|
|
char text[T30_MAX_IDENT_LEN + 1];
|
|
|
|
if (msg == NULL)
|
|
msg = text;
|
|
if (len > T30_MAX_IDENT_LEN + 1)
|
|
{
|
|
unexpected_frame_length(s, pkt, len);
|
|
msg[0] = '\0';
|
|
return;
|
|
}
|
|
p = len;
|
|
/* Strip trailing spaces */
|
|
while (p > 1 && pkt[p - 1] == ' ')
|
|
p--;
|
|
/* The string is actually backwards in the message */
|
|
k = 0;
|
|
while (p > 1)
|
|
msg[k++] = pkt[--p];
|
|
msg[k] = '\0';
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Remote gave %s as: \"%s\"\n", t30_frametype(pkt[0]), msg);
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void decode_url_msg(t30_state_t *s, char *msg, const uint8_t *pkt, int len)
|
|
{
|
|
char text[77 + 1];
|
|
|
|
/* TODO: decode properly, as per T.30 5.3.6.2.12 */
|
|
if (msg == NULL)
|
|
msg = text;
|
|
if (len < 3 || len > 77 + 3 || len != pkt[2] + 3)
|
|
{
|
|
unexpected_frame_length(s, pkt, len);
|
|
msg[0] = '\0';
|
|
return;
|
|
}
|
|
/* First octet is the sequence number of the packet.
|
|
Bit 7 = 1 for more follows, 0 for last packet in the sequence.
|
|
Bits 6-0 = The sequence number, 0 to 0x7F
|
|
Second octet is the type of internet address.
|
|
Bits 7-4 = reserved
|
|
Bits 3-0 = type:
|
|
0 = reserved
|
|
1 = e-mail address
|
|
2 = URL
|
|
3 = TCP/IP V4
|
|
4 = TCP/IP V6
|
|
5 = international phone number, in the usual +... format
|
|
6-15 = reserved
|
|
Third octet is the length of the internet address
|
|
Bit 7 = 1 for more follows, 0 for last packet in the sequence.
|
|
Bits 6-0 = length
|
|
*/
|
|
memcpy(msg, &pkt[3], len - 3);
|
|
msg[len - 3] = '\0';
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Remote fax gave %s as: %d, %d, \"%s\"\n", t30_frametype(pkt[0]), pkt[0], pkt[1], msg);
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void t30_non_ecm_rx_status(void *user_data, int status)
|
|
{
|
|
t30_state_t *s;
|
|
int was_trained;
|
|
|
|
s = (t30_state_t *) user_data;
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Non-ECM signal status is %s (%d) in state %d\n", signal_status_to_str(status), status, s->state);
|
|
switch (status)
|
|
{
|
|
case SIG_STATUS_TRAINING_IN_PROGRESS:
|
|
break;
|
|
case SIG_STATUS_TRAINING_FAILED:
|
|
s->rx_trained = FALSE;
|
|
break;
|
|
case SIG_STATUS_TRAINING_SUCCEEDED:
|
|
/* The modem is now trained */
|
|
/* In case we are in trainability test mode... */
|
|
s->tcf_test_bits = 0;
|
|
s->tcf_current_zeros = 0;
|
|
s->tcf_most_zeros = 0;
|
|
s->rx_signal_present = TRUE;
|
|
s->rx_trained = TRUE;
|
|
timer_t2_t4_stop(s);
|
|
break;
|
|
case SIG_STATUS_CARRIER_UP:
|
|
break;
|
|
case SIG_STATUS_CARRIER_DOWN:
|
|
was_trained = s->rx_trained;
|
|
s->rx_signal_present = FALSE;
|
|
s->rx_trained = FALSE;
|
|
switch (s->state)
|
|
{
|
|
case T30_STATE_F_TCF:
|
|
/* Only respond if we managed to actually sync up with the source. We don't
|
|
want to respond just because we saw a click. These often occur just
|
|
before the real signal, with many modems. Presumably this is due to switching
|
|
within the far end modem. We also want to avoid the possibility of responding
|
|
to the tail end of any slow modem signal. If there was a genuine data signal
|
|
which we failed to train on it should not matter. If things are that bad, we
|
|
do not stand much chance of good quality communications. */
|
|
if (was_trained)
|
|
{
|
|
/* Although T.30 says the training test should be 1.5s of all 0's, some FAX
|
|
machines send a burst of all 1's before the all 0's. Tolerate this. */
|
|
if (s->tcf_current_zeros > s->tcf_most_zeros)
|
|
s->tcf_most_zeros = s->tcf_current_zeros;
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Trainability (TCF) test result - %d total bits. longest run of zeros was %d\n", s->tcf_test_bits, s->tcf_most_zeros);
|
|
if (s->tcf_most_zeros < fallback_sequence[s->current_fallback].bit_rate)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Trainability (TCF) test failed - longest run of zeros was %d\n", s->tcf_most_zeros);
|
|
set_phase(s, T30_PHASE_B_TX);
|
|
set_state(s, T30_STATE_F_FTT);
|
|
send_simple_frame(s, T30_FTT);
|
|
}
|
|
else
|
|
{
|
|
/* The training went OK */
|
|
s->short_train = TRUE;
|
|
s->in_message = TRUE;
|
|
rx_start_page(s);
|
|
set_phase(s, T30_PHASE_B_TX);
|
|
set_state(s, T30_STATE_F_CFR);
|
|
send_cfr_sequence(s, TRUE);
|
|
}
|
|
}
|
|
break;
|
|
case T30_STATE_F_POST_DOC_NON_ECM:
|
|
/* Page ended cleanly */
|
|
if (s->current_status == T30_ERR_RX_NOCARRIER)
|
|
s->current_status = T30_ERR_OK;
|
|
break;
|
|
default:
|
|
/* We should be receiving a document right now, but it did not end cleanly. */
|
|
if (was_trained)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_WARNING, "Page did not end cleanly\n");
|
|
/* We trained OK, so we should have some kind of received page, even though
|
|
it did not end cleanly. */
|
|
set_state(s, T30_STATE_F_POST_DOC_NON_ECM);
|
|
set_phase(s, T30_PHASE_D_RX);
|
|
timer_t2_start(s);
|
|
if (s->current_status == T30_ERR_RX_NOCARRIER)
|
|
s->current_status = T30_ERR_OK;
|
|
}
|
|
else
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_WARNING, "Non-ECM carrier not found\n");
|
|
s->current_status = T30_ERR_RX_NOCARRIER;
|
|
}
|
|
break;
|
|
}
|
|
if (s->next_phase != T30_PHASE_IDLE)
|
|
{
|
|
set_phase(s, s->next_phase);
|
|
s->next_phase = T30_PHASE_IDLE;
|
|
}
|
|
break;
|
|
default:
|
|
span_log(&s->logging, SPAN_LOG_WARNING, "Unexpected non-ECM rx status - %d!\n", status);
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
SPAN_DECLARE_NONSTD(void) t30_non_ecm_put_bit(void *user_data, int bit)
|
|
{
|
|
t30_state_t *s;
|
|
|
|
if (bit < 0)
|
|
{
|
|
t30_non_ecm_rx_status(user_data, bit);
|
|
return;
|
|
}
|
|
s = (t30_state_t *) user_data;
|
|
switch (s->state)
|
|
{
|
|
case T30_STATE_F_TCF:
|
|
/* Trainability test */
|
|
s->tcf_test_bits++;
|
|
if (bit)
|
|
{
|
|
if (s->tcf_current_zeros > s->tcf_most_zeros)
|
|
s->tcf_most_zeros = s->tcf_current_zeros;
|
|
s->tcf_current_zeros = 0;
|
|
}
|
|
else
|
|
{
|
|
s->tcf_current_zeros++;
|
|
}
|
|
break;
|
|
case T30_STATE_F_DOC_NON_ECM:
|
|
/* Document transfer */
|
|
if (t4_rx_put_bit(&s->t4, bit))
|
|
{
|
|
/* That is the end of the document */
|
|
set_state(s, T30_STATE_F_POST_DOC_NON_ECM);
|
|
queue_phase(s, T30_PHASE_D_RX);
|
|
timer_t2_start(s);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
SPAN_DECLARE(void) t30_non_ecm_put_byte(void *user_data, int byte)
|
|
{
|
|
t30_state_t *s;
|
|
|
|
s = (t30_state_t *) user_data;
|
|
switch (s->state)
|
|
{
|
|
case T30_STATE_F_TCF:
|
|
/* Trainability test */
|
|
/* This makes counting zeros fast, but approximate. That really doesn't matter */
|
|
s->tcf_test_bits += 8;
|
|
if (byte)
|
|
{
|
|
if (s->tcf_current_zeros > s->tcf_most_zeros)
|
|
s->tcf_most_zeros = s->tcf_current_zeros;
|
|
s->tcf_current_zeros = 0;
|
|
}
|
|
else
|
|
{
|
|
s->tcf_current_zeros += 8;
|
|
}
|
|
break;
|
|
case T30_STATE_F_DOC_NON_ECM:
|
|
/* Document transfer */
|
|
if (t4_rx_put_byte(&s->t4, (uint8_t) byte))
|
|
{
|
|
/* That is the end of the document */
|
|
set_state(s, T30_STATE_F_POST_DOC_NON_ECM);
|
|
queue_phase(s, T30_PHASE_D_RX);
|
|
timer_t2_start(s);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
SPAN_DECLARE(void) t30_non_ecm_put_chunk(void *user_data, const uint8_t buf[], int len)
|
|
{
|
|
t30_state_t *s;
|
|
int i;
|
|
|
|
s = (t30_state_t *) user_data;
|
|
switch (s->state)
|
|
{
|
|
case T30_STATE_F_TCF:
|
|
/* Trainability test */
|
|
/* This makes counting zeros fast, but approximate. That really doesn't matter */
|
|
s->tcf_test_bits += 8*len;
|
|
for (i = 0; i < len; i++)
|
|
{
|
|
if (buf[i])
|
|
{
|
|
if (s->tcf_current_zeros > s->tcf_most_zeros)
|
|
s->tcf_most_zeros = s->tcf_current_zeros;
|
|
s->tcf_current_zeros = 0;
|
|
}
|
|
else
|
|
{
|
|
s->tcf_current_zeros += 8;
|
|
}
|
|
}
|
|
break;
|
|
case T30_STATE_F_DOC_NON_ECM:
|
|
/* Document transfer */
|
|
if (t4_rx_put_chunk(&s->t4, buf, len))
|
|
{
|
|
/* That is the end of the document */
|
|
set_state(s, T30_STATE_F_POST_DOC_NON_ECM);
|
|
queue_phase(s, T30_PHASE_D_RX);
|
|
timer_t2_start(s);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
SPAN_DECLARE_NONSTD(int) t30_non_ecm_get_bit(void *user_data)
|
|
{
|
|
int bit;
|
|
t30_state_t *s;
|
|
|
|
s = (t30_state_t *) user_data;
|
|
switch (s->state)
|
|
{
|
|
case T30_STATE_D_TCF:
|
|
/* Trainability test. */
|
|
bit = 0;
|
|
if (s->tcf_test_bits-- < 0)
|
|
{
|
|
/* Finished sending training test. */
|
|
bit = SIG_STATUS_END_OF_DATA;
|
|
}
|
|
break;
|
|
case T30_STATE_I:
|
|
/* Transferring real data. */
|
|
bit = t4_tx_get_bit(&(s->t4));
|
|
break;
|
|
case T30_STATE_D_POST_TCF:
|
|
case T30_STATE_II_Q:
|
|
/* We should be padding out a block of samples if we are here */
|
|
bit = 0;
|
|
break;
|
|
default:
|
|
span_log(&s->logging, SPAN_LOG_WARNING, "t30_non_ecm_get_bit in bad state %d\n", s->state);
|
|
bit = SIG_STATUS_END_OF_DATA;
|
|
break;
|
|
}
|
|
return bit;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
SPAN_DECLARE(int) t30_non_ecm_get_byte(void *user_data)
|
|
{
|
|
int byte;
|
|
t30_state_t *s;
|
|
|
|
s = (t30_state_t *) user_data;
|
|
switch (s->state)
|
|
{
|
|
case T30_STATE_D_TCF:
|
|
/* Trainability test. */
|
|
byte = 0;
|
|
if ((s->tcf_test_bits -= 8) < 0)
|
|
{
|
|
/* Finished sending training test. */
|
|
byte = 0x100;
|
|
}
|
|
break;
|
|
case T30_STATE_I:
|
|
/* Transferring real data. */
|
|
byte = t4_tx_get_byte(&(s->t4));
|
|
break;
|
|
case T30_STATE_D_POST_TCF:
|
|
case T30_STATE_II_Q:
|
|
/* We should be padding out a block of samples if we are here */
|
|
byte = 0;
|
|
break;
|
|
default:
|
|
span_log(&s->logging, SPAN_LOG_WARNING, "t30_non_ecm_get_byte in bad state %d\n", s->state);
|
|
byte = 0x100;
|
|
break;
|
|
}
|
|
return byte;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
SPAN_DECLARE(int) t30_non_ecm_get_chunk(void *user_data, uint8_t buf[], int max_len)
|
|
{
|
|
int len;
|
|
t30_state_t *s;
|
|
|
|
s = (t30_state_t *) user_data;
|
|
switch (s->state)
|
|
{
|
|
case T30_STATE_D_TCF:
|
|
/* Trainability test. */
|
|
for (len = 0; len < max_len; len++)
|
|
{
|
|
buf[len] = 0;
|
|
if ((s->tcf_test_bits -= 8) < 0)
|
|
break;
|
|
}
|
|
break;
|
|
case T30_STATE_I:
|
|
/* Transferring real data. */
|
|
len = t4_tx_get_chunk(&s->t4, buf, max_len);
|
|
break;
|
|
case T30_STATE_D_POST_TCF:
|
|
case T30_STATE_II_Q:
|
|
/* We should be padding out a block of samples if we are here */
|
|
len = 0;
|
|
break;
|
|
default:
|
|
span_log(&s->logging, SPAN_LOG_WARNING, "t30_non_ecm_get_chunk in bad state %d\n", s->state);
|
|
len = 0;
|
|
break;
|
|
}
|
|
return len;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void t30_hdlc_rx_status(void *user_data, int status)
|
|
{
|
|
t30_state_t *s;
|
|
int was_trained;
|
|
|
|
s = (t30_state_t *) user_data;
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "HDLC signal status is %s (%d) in state %d\n", signal_status_to_str(status), status, s->state);
|
|
switch (status)
|
|
{
|
|
case SIG_STATUS_TRAINING_IN_PROGRESS:
|
|
break;
|
|
case SIG_STATUS_TRAINING_FAILED:
|
|
s->rx_trained = FALSE;
|
|
break;
|
|
case SIG_STATUS_TRAINING_SUCCEEDED:
|
|
/* The modem is now trained */
|
|
s->rx_signal_present = TRUE;
|
|
s->rx_trained = TRUE;
|
|
break;
|
|
case SIG_STATUS_CARRIER_UP:
|
|
s->rx_signal_present = TRUE;
|
|
switch (s->timer_t2_t4_is)
|
|
{
|
|
case TIMER_IS_T2B:
|
|
timer_t2_t4_stop(s);
|
|
s->timer_t2_t4_is = TIMER_IS_T2C;
|
|
break;
|
|
case TIMER_IS_T4B:
|
|
timer_t2_t4_stop(s);
|
|
s->timer_t2_t4_is = TIMER_IS_T4C;
|
|
break;
|
|
}
|
|
break;
|
|
case SIG_STATUS_CARRIER_DOWN:
|
|
was_trained = s->rx_trained;
|
|
s->rx_signal_present = FALSE;
|
|
s->rx_trained = FALSE;
|
|
/* If a phase change has been queued to occur after the receive signal drops,
|
|
its time to change. */
|
|
if (s->state == T30_STATE_F_DOC_ECM)
|
|
{
|
|
/* We should be receiving a document right now, but we haven't seen an RCP at the end of
|
|
transmission. */
|
|
if (was_trained)
|
|
{
|
|
/* We trained OK, so we should have some kind of received page, possibly with
|
|
zero good HDLC frames. It just did'nt end cleanly with an RCP. */
|
|
span_log(&s->logging, SPAN_LOG_WARNING, "ECM signal did not end cleanly\n");
|
|
/* Fake the existance of an RCP, and proceed */
|
|
set_state(s, T30_STATE_F_POST_DOC_ECM);
|
|
queue_phase(s, T30_PHASE_D_RX);
|
|
timer_t2_start(s);
|
|
/* We at least trained, so any missing carrier status is out of date */
|
|
if (s->current_status == T30_ERR_RX_NOCARRIER)
|
|
s->current_status = T30_ERR_OK;
|
|
}
|
|
else
|
|
{
|
|
/* Either there was no image carrier, or we failed to train to it. */
|
|
span_log(&s->logging, SPAN_LOG_WARNING, "ECM carrier not found\n");
|
|
s->current_status = T30_ERR_RX_NOCARRIER;
|
|
}
|
|
}
|
|
if (s->next_phase != T30_PHASE_IDLE)
|
|
{
|
|
/* The appropriate timer for the next phase should already be in progress */
|
|
set_phase(s, s->next_phase);
|
|
s->next_phase = T30_PHASE_IDLE;
|
|
}
|
|
else
|
|
{
|
|
switch (s->timer_t2_t4_is)
|
|
{
|
|
case TIMER_IS_T1A:
|
|
case TIMER_IS_T2A:
|
|
case TIMER_IS_T2C:
|
|
timer_t2b_start(s);
|
|
break;
|
|
case TIMER_IS_T4A:
|
|
case TIMER_IS_T4C:
|
|
timer_t4b_start(s);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case SIG_STATUS_FRAMING_OK:
|
|
if (!s->far_end_detected && s->timer_t0_t1 > 0)
|
|
{
|
|
s->timer_t0_t1 = ms_to_samples(DEFAULT_TIMER_T1);
|
|
s->far_end_detected = TRUE;
|
|
if (s->phase == T30_PHASE_A_CED || s->phase == T30_PHASE_A_CNG)
|
|
set_phase(s, T30_PHASE_B_RX);
|
|
}
|
|
/* 5.4.3.1 Timer T2 is reset if flag is received. Timer T2A must be started. */
|
|
/* Unstated, but implied, is that timer T4 and T4A are handled the same way. */
|
|
if (s->timer_t2_t4 > 0)
|
|
{
|
|
switch(s->timer_t2_t4_is)
|
|
{
|
|
case TIMER_IS_T1A:
|
|
case TIMER_IS_T2:
|
|
case TIMER_IS_T2A:
|
|
timer_t2a_start(s);
|
|
break;
|
|
case TIMER_IS_T4:
|
|
case TIMER_IS_T4A:
|
|
timer_t4a_start(s);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case SIG_STATUS_ABORT:
|
|
/* Just ignore these */
|
|
break;
|
|
default:
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Unexpected HDLC special length - %d!\n", status);
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
SPAN_DECLARE_NONSTD(void) t30_hdlc_accept(void *user_data, const uint8_t *msg, int len, int ok)
|
|
{
|
|
t30_state_t *s;
|
|
|
|
if (len < 0)
|
|
{
|
|
t30_hdlc_rx_status(user_data, len);
|
|
return;
|
|
}
|
|
|
|
s = (t30_state_t *) user_data;
|
|
/* The spec. says a command or response is not valid if:
|
|
- any of the frames, optional or mandatory, have an FCS error.
|
|
- any single frame exceeds 3s +- 15% (i.e. no frame should exceed 2.55s)
|
|
- the final frame is not tagged as a final frame
|
|
- the final frame is not a recognised one.
|
|
The first point seems benign. If we accept an optional frame, and a later
|
|
frame is bad, having accepted the optional frame should be harmless.
|
|
The 2.55s maximum seems to limit signalling frames to no more than 95 octets,
|
|
including FCS, and flag octets (assuming the use of V.21).
|
|
*/
|
|
if (!ok)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Bad CRC received\n");
|
|
if (s->phase != T30_PHASE_C_ECM_RX)
|
|
{
|
|
/* We either force a resend, or we wait until a resend occurs through a timeout. */
|
|
if ((s->supported_t30_features & T30_SUPPORT_COMMAND_REPEAT))
|
|
{
|
|
s->step = 0;
|
|
if (s->phase == T30_PHASE_B_RX)
|
|
queue_phase(s, T30_PHASE_B_TX);
|
|
else
|
|
queue_phase(s, T30_PHASE_D_TX);
|
|
send_simple_frame(s, T30_CRP);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (len < 3)
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Bad HDLC frame length - %d\n", len);
|
|
return;
|
|
}
|
|
if (msg[0] != 0xFF || !(msg[1] == 0x03 || msg[1] == 0x13))
|
|
{
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Bad HDLC frame header - %02x %02x\n", msg[0], msg[1]);
|
|
return;
|
|
}
|
|
s->rx_frame_received = TRUE;
|
|
/* Cancel the command or response timer (if one is running) */
|
|
timer_t2_t4_stop(s);
|
|
process_rx_control_msg(s, msg, len);
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
SPAN_DECLARE(void) t30_front_end_status(void *user_data, int status)
|
|
{
|
|
t30_state_t *s;
|
|
|
|
s = (t30_state_t *) user_data;
|
|
|
|
switch (status)
|
|
{
|
|
case T30_FRONT_END_SEND_STEP_COMPLETE:
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Send complete in phase %s, state %d\n", phase_names[s->phase], s->state);
|
|
/* We have finished sending our messages, so move on to the next operation. */
|
|
switch (s->state)
|
|
{
|
|
case T30_STATE_ANSWERING:
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Starting answer mode\n");
|
|
set_phase(s, T30_PHASE_B_TX);
|
|
timer_t2_start(s);
|
|
s->dis_received = FALSE;
|
|
send_dis_or_dtc_sequence(s, TRUE);
|
|
break;
|
|
case T30_STATE_R:
|
|
if (send_dis_or_dtc_sequence(s, FALSE))
|
|
{
|
|
/* Wait for an acknowledgement. */
|
|
set_phase(s, T30_PHASE_B_RX);
|
|
timer_t4_start(s);
|
|
}
|
|
break;
|
|
case T30_STATE_F_CFR:
|
|
if (s->step == 0)
|
|
{
|
|
/* Shut down HDLC transmission. */
|
|
if (s->send_hdlc_handler)
|
|
s->send_hdlc_handler(s->send_hdlc_user_data, NULL, 0);
|
|
s->step++;
|
|
}
|
|
else
|
|
{
|
|
if (s->error_correcting_mode)
|
|
{
|
|
set_state(s, T30_STATE_F_DOC_ECM);
|
|
queue_phase(s, T30_PHASE_C_ECM_RX);
|
|
}
|
|
else
|
|
{
|
|
set_state(s, T30_STATE_F_DOC_NON_ECM);
|
|
queue_phase(s, T30_PHASE_C_NON_ECM_RX);
|
|
}
|
|
timer_t2_start(s);
|
|
s->next_rx_step = T30_MPS;
|
|
}
|
|
break;
|
|
case T30_STATE_F_FTT:
|
|
if (s->step == 0)
|
|
{
|
|
/* Shut down HDLC transmission. */
|
|
if (s->send_hdlc_handler)
|
|
s->send_hdlc_handler(s->send_hdlc_user_data, NULL, 0);
|
|
s->step++;
|
|
}
|
|
else
|
|
{
|
|
set_phase(s, T30_PHASE_B_RX);
|
|
timer_t2_start(s);
|
|
}
|
|
break;
|
|
case T30_STATE_III_Q_MCF:
|
|
case T30_STATE_III_Q_RTP:
|
|
case T30_STATE_III_Q_RTN:
|
|
case T30_STATE_F_POST_RCP_PPR:
|
|
case T30_STATE_F_POST_RCP_MCF:
|
|
if (s->step == 0)
|
|
{
|
|
/* Shut down HDLC transmission. */
|
|
if (s->send_hdlc_handler)
|
|
s->send_hdlc_handler(s->send_hdlc_user_data, NULL, 0);
|
|
s->step++;
|
|
}
|
|
else
|
|
{
|
|
switch (s->next_rx_step)
|
|
{
|
|
case T30_MPS:
|
|
case T30_PRI_MPS:
|
|
/* We should now start to get another page */
|
|
if (s->error_correcting_mode)
|
|
{
|
|
set_state(s, T30_STATE_F_DOC_ECM);
|
|
queue_phase(s, T30_PHASE_C_ECM_RX);
|
|
}
|
|
else
|
|
{
|
|
set_state(s, T30_STATE_F_DOC_NON_ECM);
|
|
queue_phase(s, T30_PHASE_C_NON_ECM_RX);
|
|
}
|
|
timer_t2_start(s);
|
|
break;
|
|
case T30_EOM:
|
|
case T30_PRI_EOM:
|
|
case T30_EOS:
|
|
/* See if we get something back, before moving to phase B. */
|
|
timer_t2_start(s);
|
|
set_phase(s, T30_PHASE_D_RX);
|
|
break;
|
|
case T30_EOP:
|
|
case T30_PRI_EOP:
|
|
/* Wait for a DCN. */
|
|
set_phase(s, T30_PHASE_D_RX);
|
|
timer_t4_start(s);
|
|
break;
|
|
default:
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Unknown next rx step - %d\n", s->next_rx_step);
|
|
disconnect(s);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case T30_STATE_II_Q:
|
|
case T30_STATE_IV_PPS_NULL:
|
|
case T30_STATE_IV_PPS_Q:
|
|
case T30_STATE_IV_PPS_RNR:
|
|
case T30_STATE_IV_EOR_RNR:
|
|
case T30_STATE_F_POST_RCP_RNR:
|
|
case T30_STATE_IV_EOR:
|
|
case T30_STATE_IV_CTC:
|
|
if (s->step == 0)
|
|
{
|
|
/* Shut down HDLC transmission. */
|
|
if (s->send_hdlc_handler)
|
|
s->send_hdlc_handler(s->send_hdlc_user_data, NULL, 0);
|
|
s->step++;
|
|
}
|
|
else
|
|
{
|
|
/* We have finished sending the post image message. Wait for an
|
|
acknowledgement. */
|
|
set_phase(s, T30_PHASE_D_RX);
|
|
timer_t4_start(s);
|
|
}
|
|
break;
|
|
case T30_STATE_B:
|
|
/* We have now allowed time for the last message to flush
|
|
through the system, so it is safe to report the end of the
|
|
call. */
|
|
if (s->phase_e_handler)
|
|
s->phase_e_handler(s, s->phase_e_user_data, s->current_status);
|
|
set_state(s, T30_STATE_CALL_FINISHED);
|
|
set_phase(s, T30_PHASE_CALL_FINISHED);
|
|
release_resources(s);
|
|
break;
|
|
case T30_STATE_C:
|
|
if (s->step == 0)
|
|
{
|
|
/* Shut down HDLC transmission. */
|
|
if (s->send_hdlc_handler)
|
|
s->send_hdlc_handler(s->send_hdlc_user_data, NULL, 0);
|
|
s->step++;
|
|
}
|
|
else
|
|
{
|
|
/* We just sent the disconnect message. Now it is time to disconnect. */
|
|
disconnect(s);
|
|
}
|
|
break;
|
|
case T30_STATE_D:
|
|
if (send_dcs_sequence(s, FALSE))
|
|
{
|
|
if ((s->iaf & T30_IAF_MODE_NO_TCF))
|
|
{
|
|
/* Skip the trainability test */
|
|
s->retries = 0;
|
|
s->short_train = TRUE;
|
|
if (s->error_correcting_mode)
|
|
{
|
|
set_state(s, T30_STATE_IV);
|
|
queue_phase(s, T30_PHASE_C_ECM_TX);
|
|
}
|
|
else
|
|
{
|
|
set_state(s, T30_STATE_I);
|
|
queue_phase(s, T30_PHASE_C_NON_ECM_TX);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Do the trainability test */
|
|
/* TCF is always sent with long training */
|
|
s->short_train = FALSE;
|
|
set_state(s, T30_STATE_D_TCF);
|
|
set_phase(s, T30_PHASE_C_NON_ECM_TX);
|
|
}
|
|
}
|
|
break;
|
|
case T30_STATE_D_TCF:
|
|
/* Finished sending training test. Listen for the response. */
|
|
set_phase(s, T30_PHASE_B_RX);
|
|
timer_t4_start(s);
|
|
set_state(s, T30_STATE_D_POST_TCF);
|
|
break;
|
|
case T30_STATE_I:
|
|
/* Send the end of page message */
|
|
set_phase(s, T30_PHASE_D_TX);
|
|
set_state(s, T30_STATE_II_Q);
|
|
/* We might need to resend the page we are on, but we need to check if there
|
|
are any more pages to send, so we can send the correct signal right now. */
|
|
send_simple_frame(s, s->next_tx_step = check_next_tx_step(s));
|
|
break;
|
|
case T30_STATE_IV:
|
|
/* We have finished sending an FCD frame */
|
|
if (s->step == 0)
|
|
{
|
|
if (send_next_ecm_frame(s))
|
|
{
|
|
/* Shut down HDLC transmission. */
|
|
if (s->send_hdlc_handler)
|
|
s->send_hdlc_handler(s->send_hdlc_user_data, NULL, 0);
|
|
s->step++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Send the end of page or partial page message */
|
|
set_phase(s, T30_PHASE_D_TX);
|
|
s->next_tx_step = check_next_tx_step(s);
|
|
if (send_pps_frame(s) == T30_NULL)
|
|
set_state(s, T30_STATE_IV_PPS_NULL);
|
|
else
|
|
set_state(s, T30_STATE_IV_PPS_Q);
|
|
}
|
|
break;
|
|
case T30_STATE_F_DOC_ECM:
|
|
/* This should be the end of a CTR being sent. */
|
|
if (s->step == 0)
|
|
{
|
|
/* Shut down HDLC transmission. */
|
|
if (s->send_hdlc_handler)
|
|
s->send_hdlc_handler(s->send_hdlc_user_data, NULL, 0);
|
|
s->step++;
|
|
}
|
|
else
|
|
{
|
|
/* We have finished sending the CTR. Wait for image data again. */
|
|
queue_phase(s, T30_PHASE_C_ECM_RX);
|
|
timer_t2_start(s);
|
|
}
|
|
break;
|
|
case T30_STATE_CALL_FINISHED:
|
|
/* Just ignore anything that happens now. We might get here if a premature
|
|
disconnect from the far end overlaps something. */
|
|
break;
|
|
default:
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Bad state for send complete in t30_front_end_status - %d\n", s->state);
|
|
break;
|
|
}
|
|
break;
|
|
case T30_FRONT_END_RECEIVE_COMPLETE:
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "Receive complete in phase %s, state %d\n", phase_names[s->phase], s->state);
|
|
/* Usually receive complete is notified by a carrier down signal. However,
|
|
in cases like a T.38 packet stream dying in the middle of reception
|
|
there needs to be a means to stop things. */
|
|
switch (s->phase)
|
|
{
|
|
case T30_PHASE_C_NON_ECM_RX:
|
|
t30_non_ecm_rx_status(s, SIG_STATUS_CARRIER_DOWN);
|
|
break;
|
|
default:
|
|
t30_hdlc_rx_status(s, SIG_STATUS_CARRIER_DOWN);
|
|
break;
|
|
}
|
|
break;
|
|
case T30_FRONT_END_SIGNAL_PRESENT:
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "A signal is present\n");
|
|
/* The front end is explicitly telling us the signal we expect is present. This might
|
|
be a premature indication from a T.38 implementation, but we have to believe it.
|
|
if we don't we can time out improperly. For example, we might get an image modem
|
|
carrier signal, but the first HDLC frame might only occur several seconds later.
|
|
Many ECM senders idle on HDLC flags while waiting for the paper or filing system
|
|
to become ready. T.38 offers no specific indication of correct carrier training, so
|
|
if we don't kill the timer on the initial carrier starting signal, we will surely
|
|
time out quite often before the next thing we receive. */
|
|
switch (s->phase)
|
|
{
|
|
case T30_PHASE_A_CED:
|
|
case T30_PHASE_A_CNG:
|
|
case T30_PHASE_B_RX:
|
|
case T30_PHASE_D_RX:
|
|
/* We are running a V.21 receive modem, where an explicit training indication
|
|
will not occur. */
|
|
t30_hdlc_rx_status(s, SIG_STATUS_CARRIER_UP);
|
|
t30_hdlc_rx_status(s, SIG_STATUS_FRAMING_OK);
|
|
break;
|
|
default:
|
|
/* Cancel any receive timeout, and declare that a receive signal is present,
|
|
since the front end is explicitly telling us we have seen something. */
|
|
s->rx_signal_present = TRUE;
|
|
timer_t2_t4_stop(s);
|
|
break;
|
|
}
|
|
break;
|
|
case T30_FRONT_END_SIGNAL_ABSENT:
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "No signal is present\n");
|
|
/* TODO: Should we do anything here? */
|
|
break;
|
|
case T30_FRONT_END_CED_PRESENT:
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "CED tone is present\n");
|
|
/* TODO: Should we do anything here? */
|
|
break;
|
|
case T30_FRONT_END_CNG_PRESENT:
|
|
span_log(&s->logging, SPAN_LOG_FLOW, "CNG tone is present\n");
|
|
/* TODO: Should we do anything here? */
|
|
break;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
SPAN_DECLARE(void) t30_timer_update(t30_state_t *s, int samples)
|
|
{
|
|
if (s->timer_t0_t1 > 0)
|
|
{
|
|
if ((s->timer_t0_t1 -= samples) <= 0)
|
|
{
|
|
if (s->far_end_detected)
|
|
timer_t1_expired(s);
|
|
else
|
|
timer_t0_expired(s);
|
|
}
|
|
}
|
|
if (s->timer_t3 > 0)
|
|
{
|
|
if ((s->timer_t3 -= samples) <= 0)
|
|
timer_t3_expired(s);
|
|
}
|
|
if (s->timer_t2_t4 > 0)
|
|
{
|
|
if ((s->timer_t2_t4 -= samples) <= 0)
|
|
{
|
|
switch (s->timer_t2_t4_is)
|
|
{
|
|
case TIMER_IS_T1A:
|
|
timer_t1a_expired(s);
|
|
break;
|
|
case TIMER_IS_T2:
|
|
timer_t2_expired(s);
|
|
break;
|
|
case TIMER_IS_T2A:
|
|
timer_t2a_expired(s);
|
|
break;
|
|
case TIMER_IS_T2B:
|
|
timer_t2b_expired(s);
|
|
break;
|
|
case TIMER_IS_T4:
|
|
timer_t4_expired(s);
|
|
break;
|
|
case TIMER_IS_T4A:
|
|
timer_t4a_expired(s);
|
|
break;
|
|
case TIMER_IS_T4B:
|
|
timer_t4b_expired(s);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (s->timer_t5 > 0)
|
|
{
|
|
if ((s->timer_t5 -= samples) <= 0)
|
|
timer_t5_expired(s);
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
SPAN_DECLARE(void) t30_terminate(t30_state_t *s)
|
|
{
|
|
if (s->phase != T30_PHASE_CALL_FINISHED)
|
|
{
|
|
/* The far end disconnected early, but was it just a tiny bit too early,
|
|
as we were just tidying up, or seriously early as in a failure? */
|
|
switch (s->state)
|
|
{
|
|
case T30_STATE_C:
|
|
/* We were sending the final disconnect, so just hussle things along. */
|
|
disconnect(s);
|
|
break;
|
|
case T30_STATE_B:
|
|
/* We were in the final wait for everything to flush through, so just
|
|
hussle things along. */
|
|
break;
|
|
default:
|
|
/* The call terminated prematurely. */
|
|
s->current_status = T30_ERR_CALLDROPPED;
|
|
break;
|
|
}
|
|
if (s->phase_e_handler)
|
|
s->phase_e_handler(s, s->phase_e_user_data, s->current_status);
|
|
set_state(s, T30_STATE_CALL_FINISHED);
|
|
set_phase(s, T30_PHASE_CALL_FINISHED);
|
|
release_resources(s);
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
SPAN_DECLARE(void) t30_get_transfer_statistics(t30_state_t *s, t30_stats_t *t)
|
|
{
|
|
t4_stats_t stats;
|
|
|
|
t->bit_rate = fallback_sequence[s->current_fallback].bit_rate;
|
|
t->error_correcting_mode = s->error_correcting_mode;
|
|
t->error_correcting_mode_retries = s->error_correcting_mode_retries;
|
|
t4_get_transfer_statistics(&s->t4, &stats);
|
|
t->pages_tx = s->ecm_tx_page;
|
|
t->pages_rx = s->ecm_rx_page;
|
|
t->pages_in_file = stats.pages_in_file;
|
|
t->width = stats.width;
|
|
t->length = stats.length;
|
|
t->bad_rows = stats.bad_rows;
|
|
t->longest_bad_row_run = stats.longest_bad_row_run;
|
|
t->x_resolution = stats.x_resolution;
|
|
t->y_resolution = stats.y_resolution;
|
|
t->encoding = stats.encoding;
|
|
t->image_size = stats.line_image_size;
|
|
t->current_status = s->current_status;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
SPAN_DECLARE(void) t30_local_interrupt_request(t30_state_t *s, int state)
|
|
{
|
|
if (s->timer_t3 > 0)
|
|
{
|
|
/* Accept the far end's outstanding request for interrupt. */
|
|
/* TODO: */
|
|
send_simple_frame(s, (state) ? T30_PIP : T30_PIN);
|
|
}
|
|
s->local_interrupt_pending = state;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
SPAN_DECLARE(int) t30_restart(t30_state_t *s)
|
|
{
|
|
s->phase = T30_PHASE_IDLE;
|
|
s->next_phase = T30_PHASE_IDLE;
|
|
s->current_fallback = 0;
|
|
s->rx_signal_present = FALSE;
|
|
s->rx_trained = FALSE;
|
|
s->rx_frame_received = FALSE;
|
|
s->current_status = T30_ERR_OK;
|
|
s->ppr_count = 0;
|
|
s->ecm_progress = 0;
|
|
s->receiver_not_ready_count = 0;
|
|
s->far_dis_dtc_len = 0;
|
|
memset(&s->far_dis_dtc_frame, 0, sizeof(s->far_dis_dtc_frame));
|
|
t30_build_dis_or_dtc(s);
|
|
memset(&s->rx_info, 0, sizeof(s->rx_info));
|
|
release_resources(s);
|
|
/* The ECM page number is only reset at call establishment */
|
|
s->ecm_rx_page = 0;
|
|
s->ecm_tx_page = 0;
|
|
s->far_end_detected = FALSE;
|
|
s->timer_t0_t1 = ms_to_samples(DEFAULT_TIMER_T0);
|
|
if (s->calling_party)
|
|
{
|
|
set_state(s, T30_STATE_T);
|
|
set_phase(s, T30_PHASE_A_CNG);
|
|
}
|
|
else
|
|
{
|
|
set_state(s, T30_STATE_ANSWERING);
|
|
set_phase(s, T30_PHASE_A_CED);
|
|
}
|
|
return 0;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
SPAN_DECLARE(t30_state_t *) t30_init(t30_state_t *s,
|
|
int calling_party,
|
|
t30_set_handler_t *set_rx_type_handler,
|
|
void *set_rx_type_user_data,
|
|
t30_set_handler_t *set_tx_type_handler,
|
|
void *set_tx_type_user_data,
|
|
t30_send_hdlc_handler_t *send_hdlc_handler,
|
|
void *send_hdlc_user_data)
|
|
{
|
|
if (s == NULL)
|
|
{
|
|
if ((s = (t30_state_t *) malloc(sizeof(*s))) == NULL)
|
|
return NULL;
|
|
}
|
|
memset(s, 0, sizeof(*s));
|
|
s->calling_party = calling_party;
|
|
s->set_rx_type_handler = set_rx_type_handler;
|
|
s->set_rx_type_user_data = set_rx_type_user_data;
|
|
s->set_tx_type_handler = set_tx_type_handler;
|
|
s->set_tx_type_user_data = set_tx_type_user_data;
|
|
s->send_hdlc_handler = send_hdlc_handler;
|
|
s->send_hdlc_user_data = send_hdlc_user_data;
|
|
|
|
/* Default to the basic modems. */
|
|
s->supported_modems = T30_SUPPORT_V27TER | T30_SUPPORT_V29 | T30_SUPPORT_V17;
|
|
s->supported_compressions = T30_SUPPORT_T4_1D_COMPRESSION | T30_SUPPORT_T4_2D_COMPRESSION;
|
|
s->supported_resolutions = T30_SUPPORT_STANDARD_RESOLUTION | T30_SUPPORT_FINE_RESOLUTION | T30_SUPPORT_SUPERFINE_RESOLUTION
|
|
| T30_SUPPORT_R8_RESOLUTION;
|
|
s->supported_image_sizes = T30_SUPPORT_US_LETTER_LENGTH | T30_SUPPORT_US_LEGAL_LENGTH | T30_SUPPORT_UNLIMITED_LENGTH
|
|
| T30_SUPPORT_215MM_WIDTH;
|
|
/* Set the output encoding to something safe. Most things get 1D and 2D
|
|
encoding right. Quite a lot get other things wrong. */
|
|
s->output_encoding = T4_COMPRESSION_ITU_T4_2D;
|
|
s->local_min_scan_time_code = T30_MIN_SCAN_0MS;
|
|
span_log_init(&s->logging, SPAN_LOG_NONE, NULL);
|
|
span_log_set_protocol(&s->logging, "T.30");
|
|
t30_restart(s);
|
|
return s;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
SPAN_DECLARE(int) t30_release(t30_state_t *s)
|
|
{
|
|
/* Make sure any FAX in progress is tidied up. If the tidying up has
|
|
already happened, repeating it here is harmless. */
|
|
switch (s->operation_in_progress)
|
|
{
|
|
case OPERATION_IN_PROGRESS_T4_TX:
|
|
t4_tx_release(&(s->t4));
|
|
break;
|
|
case OPERATION_IN_PROGRESS_T4_RX:
|
|
t4_rx_release(&(s->t4));
|
|
break;
|
|
}
|
|
s->operation_in_progress = OPERATION_IN_PROGRESS_NONE;
|
|
return 0;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
SPAN_DECLARE(int) t30_free(t30_state_t *s)
|
|
{
|
|
t30_release(s);
|
|
free(s);
|
|
return 0;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
SPAN_DECLARE(int) t30_call_active(t30_state_t *s)
|
|
{
|
|
return (s->phase != T30_PHASE_CALL_FINISHED);
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
/*- End of file ------------------------------------------------------------*/
|