Included SpanDSP functions for T.38 fax, local aim being TIFF over TFTP
authorRick van Rein <vanrein@hwdev.(none)>
Fri, 16 Dec 2011 17:31:59 +0000 (17:31 +0000)
committerRick van Rein <vanrein@hwdev.(none)>
Fri, 16 Dec 2011 17:31:59 +0000 (17:31 +0000)
57 files changed:
doc/top2bottom.rst
include/0cpm/snd.h
include/bottom/grandstream.h
src/codec/README
src/codec/l8l16.c [new file with mode: 0644]
src/codec/rtt/Makefile.desktop
src/codec/rtt/README
src/codec/rtt/TODO
src/codec/rtt/desktop.c
src/codec/spandsp/src/async.c [new file with mode: 0644]
src/codec/spandsp/src/bit_operations.c [new file with mode: 0644]
src/codec/spandsp/src/logging.c [new file with mode: 0644]
src/codec/spandsp/src/spandsp/0cpm.h [new file with mode: 0644]
src/codec/spandsp/src/spandsp/async.h [new file with mode: 0644]
src/codec/spandsp/src/spandsp/private/async.h [new file with mode: 0644]
src/codec/spandsp/src/spandsp/private/logging.h [new file with mode: 0644]
src/codec/spandsp/src/spandsp/private/t30.h [new file with mode: 0644]
src/codec/spandsp/src/spandsp/private/t38_core.h [new file with mode: 0644]
src/codec/spandsp/src/spandsp/private/t38_terminal.h [new file with mode: 0644]
src/codec/spandsp/src/spandsp/private/t4_rx.h [new file with mode: 0644]
src/codec/spandsp/src/spandsp/private/t4_t6_decode.h [new file with mode: 0644]
src/codec/spandsp/src/spandsp/private/t4_t6_encode.h [new file with mode: 0644]
src/codec/spandsp/src/spandsp/private/t4_tx.h [new file with mode: 0644]
src/codec/spandsp/src/spandsp/private/timezone.h [new file with mode: 0644]
src/codec/spandsp/src/spandsp/t30.h [new file with mode: 0644]
src/codec/spandsp/src/spandsp/t30_api.h [new file with mode: 0644]
src/codec/spandsp/src/spandsp/t30_fcf.h [new file with mode: 0644]
src/codec/spandsp/src/spandsp/t30_logging.h [new file with mode: 0644]
src/codec/spandsp/src/spandsp/t35.h [new file with mode: 0644]
src/codec/spandsp/src/spandsp/t38_core.h [new file with mode: 0644]
src/codec/spandsp/src/spandsp/t38_terminal.h [new file with mode: 0644]
src/codec/spandsp/src/spandsp/t4_rx.h [new file with mode: 0644]
src/codec/spandsp/src/spandsp/t4_t6_decode.h [new file with mode: 0644]
src/codec/spandsp/src/spandsp/t4_t6_encode.h [new file with mode: 0644]
src/codec/spandsp/src/spandsp/t4_tx.h [new file with mode: 0644]
src/codec/spandsp/src/spandsp/timezone.h [new file with mode: 0644]
src/codec/spandsp/src/t30.c [new file with mode: 0644]
src/codec/spandsp/src/t30_api.c [new file with mode: 0644]
src/codec/spandsp/src/t30_logging.c [new file with mode: 0644]
src/codec/spandsp/src/t35.c [new file with mode: 0644]
src/codec/spandsp/src/t38_core.c [new file with mode: 0644]
src/codec/spandsp/src/t38_terminal.c [new file with mode: 0644]
src/codec/spandsp/src/t4_rx.c [new file with mode: 0644]
src/codec/spandsp/src/t4_tx.c [new file with mode: 0644]
src/codec/spandsp/src/timezone.c [new file with mode: 0644]
src/codec/wrap.c
src/driver/tic55x/grandstream-bt20x.c
src/driver/tlv320aic2x.c
src/function/Kconfig.devel
src/function/Makefile
src/function/bootloader.c
src/function/develtest/echo.c
src/function/develtest/keys2display.c
src/function/develtest/netconsole.c
src/function/develtest/switch2led.c
src/function/develtest/timer2led.c
src/function/sip6phone/topmain.c

index 484ccf1..34daba9 100644 (file)
@@ -34,6 +34,11 @@ interact in the manner described below.
         |  Hardware                       |
        +---------------------------------+
 
+The hardware drivers ideally consists of two kinds of
+module: chip-drivers that work the same in any device,
+and phone-specific functions to handle the interconnect,
+that is, the addressing of the generic chips on the
+concrete printed circuit board.
 
 
 Making changes to this phone firmware
@@ -726,8 +731,8 @@ Sound
 
 **Channels.**
 A phone can have a number of sound channels, and the configuration
-of the platform defines which are available.  The possible channels
-are:
+of the platform defines which are available.  Each of the channels
+usually has a device attacheed; the possible devices are:
 
 * Handset.  This is the only obliged channel for sound I/O.
 * Speaker.  This usually combines with a microphone, although it
@@ -736,19 +741,32 @@ are:
   hide any such choices.
 * Headset.
 
-In all cases, it is a good idea if the bottom half implements echo
-cancellation, because it has more insight in the construction of the
-sound channels than the top half.
+Conceptually, a channel has an input and output aspect.  These are
+handled in same-sized blocks of samples.  An input and output sample
+is imagined to be clocked at the same instant, even if this might in
+practice deviate somewhat; but having such a conceptual model is a
+great advantage to echo cancellation.
+
+So, the normal procedure of playing sound is to run the decoder for
+the playing codec, clock out a block of sound and at the same time
+clock in a block of recorded sound.  The recorded sound has echo
+cancellation applied, and is subsequently fed into the encoder for
+the recording codec.
+
+Echo cancellation and codec algorithms are *principally* run in the
+top-half, but it is recommended to override them (or their most-used
+parts) with machine code written specifically for the target platform;
+overrides exist to support that.  TODO:WHICH?HOW?
 
 The top half can make the following downcall to instruct the bottom
 half about the current sound channel to use.  This implies dropping
 any channel currently in use::
 
        void bottom_soundchannel_device (uint8_t chan, sounddev_t dev);
-       bottom_soundchannel_device (0, SOUNDDEV_NONE);
-       bottom_soundchannel_device (0, SOUNDDEV_HANDSET);
-       bottom_soundchannel_device (0, SOUNDDEV_SPEAKER);
-       bottom_soundchannel_device (0, SOUNDDEV_HEADSET);
+       bottom_soundchannel_device (PHONE_CHANNEL_TELEPHONY, SOUNDDEV_NONE);
+       bottom_soundchannel_device (PHONE_CHANNEL_TELEPHONY, SOUNDDEV_HANDSET);
+       bottom_soundchannel_device (PHONE_CHANNEL_TELEPHONY, SOUNDDEV_SPEAKER);
+       bottom_soundchannel_device (PHONE_CHANNEL_TELEPHONY, SOUNDDEV_HEADSET);
 
 The first argument represents the sound channel index.  It is
 currently assumed that one such channel exists, but future versions
@@ -759,9 +777,6 @@ may well pave the way for additional functions for the hardware.
 Naturally, the bottom half will never be asked to support a sound
 channel that it has not made available in the phone's configuration.
 
-Note that handling any buttons for speakerphone access and such are
-usually done by the top half.
-
 **Volume.**
 Every sound channel has its own volume setting.  This value may
 vary depending on the current use of the channel; if it plays a
@@ -808,7 +823,14 @@ what a cruel joke, as it usually very hard to get all these
 factors into the operating frequency of a general purpose device,
 and so there is going to be some delay from time to time.
 
-The best approach is probably to set a slightly lower frequency
+It is highly recommended to pay attention to the direction of
+rounding the divisor(s) for the sample frequency; in case no
+exact divisor exists, it may be harmful to round one way or the
+other, depending on the hardware architecture.  The bottom half
+is the place where such knowledge resides, and should be isolated
+from the top half.
+
+For example, the best approach may be to set a slightly lower frequency
 and use counters to detect when a single sample should be
 tossed out of the mix.  If 44000 Hz is achievable, this would
 mean that 1 out of every 441 samples would have to be dropped.
@@ -820,113 +842,200 @@ concealed in the bottom layer which is aware of the hardware
 involved.
 
 The result is that every possible sample rate may be set, but
-that choosing common ones is still a good idea.  The calls to
-set the sample rate for playback or recording of sound are::
-
-       void bottom_codec_play_samplerate   (uint8_t chan, uint32_t samplerate);
-       void bottom_codec_record_samplerate (uint8_t chan, uint32_t samplerate);
-
-It is not safe in general to assume that playing and recording
-can be done at different sample rates, so always call both
-these functions with the same values -- we just want to
-keep the option open to separate the sample rates on platforms
-that support, perhaps in a future version of the firmware.
-
-The list of codecs that are offered from bottom to top half
-suggests samplerates that are known to work well for those
-codecs.
+that choosing common ones is still a good idea.  To generalise
+this a bit, the bottom half exports two functions that help to
+determine if a samplerate would be merely acceptable, or if it
+would actually be preferred.  All preferred sample rates are
+always acceptable::
+
+       bool bottom_soundchannel_acceptable_samplerate (uint8_t chan, uint32_t samplerate);
+       bool bottom_soundchannel_preferred_samplerate  (uint8_t chan, uint32_t samplerate);
+
+The bottom must be configured so that at least the commonly used
+sample rate of 8000 Hz is preferred; and inasfar as they are
+acceptable, the additional sample rates of 16000 Hz and 32000 Hz
+should also be preferred -- but that is not a must, as additional
+parameters such as coding efficiency may influence such choices.
+
+During SDP negotiations, the top half can first try to find an
+offer with a preferred sample rate and, failing that, fall back
+to the reduced quality of an acceptable sample rate.  An example
+of this behaviour could be a phone acting as a web radio; it may
+be offered the popular sample rate 44100 Hz that is impossible to
+configure accurately on most phones; this may still be played if
+it is an acceptable sample rate, but if 48000 Hz is also offered
+and setup as a preferred frequency because it can be configured
+accurately, then that stream would be preferred.  That rate is
+much more likely to be preferred on common phone hardware, which
+is usually engineered to support 8000 Hz rates, and a limited
+list of multiples.
+
+Since playback and recording of sound occur in lockstep, there
+is one bottom call to set it for both playback and recording::
+
+       void bottom_soundchannel_set_samplerate (uint8_t chan,
+               uint32_t samplerate, uint8_t blocksize,
+               uint8_t upsample_play, uint8_t downsample_record);
+
+The ``samplerate`` is denominated in Hz, so in samples per seconnd.
+It should only be set to values that are known to be at least
+acceptable to the bottom drivers and hardware.
+The ``blocksize`` indicates how many raw samples should be treated
+as a block; this is likely to be influenced by the codec(s) used.
+The bottom half will usually contain a buffer for playback and
+recording, and its size will be divided by the blocksize parameter
+to determine how many blocks are available in the buffer.  It is
+advised that the ``blocksize`` is a multiple of 40, possibly even
+of 80; the latter represents 10 ms of sound at 8 kHz, so a common
+standard size of block to handle at once in generally used codecs.
+
+In the future, we may choose to support upsampling the play
+and/or downsampling the record side, for instance to support
+playback at 16 kHz and recording at 8 kHz; but at
+present, the values of ``downsample_play`` and ``downsample_record``
+must be set to 1 to avoid using that functionality.  Current
+bottom implementations may simply ignore these values, or require
+that they are 1.
 
 **Codec play/record.**
 For the actual exchange of sound, a mapping from codec format to
 the internal format used for playback (usually 16-bit samples)
 is required.  This is the work of the codec, which principally is
-part of the bottom half because that enables the use of (existing)
-assembly code for the target processor, and possibly even support
-in hardware.
-
-The internal sound buffer has a concealed size, and top and bottom
-exchange how many samples they would like to put in there.  When
-a given amount of space is available (usually for a particular
-time period), the bottom will inform the top that it can map a
-certain number of samples into the bottom buffer.  This may or
-may not cover a complete network packet.  Upon completion, the
-notified routine returns a suggestion about the number of
-additional samples it suggests for next time.  If the bottom
-does not find the required samples in time, it will try to insert
-sensible samples, effectively delaying the delivery of sound.
-To handle noticeable network dropouts, the top can register
-non-delivery of sound packets with a special function.
-
-::
-
-       uint16_t top_codec_can_play   (uint8_t chan, uint16_t samples);
-       uint16_t top_codec_can_record (uint8_t chan, uint16_t samples);
-
-These functions indicate to the top half that sound packets may
-now be played or recorded, as soon as they are available.  The
-return value suggests the number of samples to allow on the
-next call, but the bottom is not required to follow that
-suggestion.  As a result, the top must always be willing to
-deliver parts of packets.  It does not need to combine packets,
-as it can return the length of a sequel packet as a suggested
-size for next time.
-
-::
-       int16_t bottom_codec_play   (uint8_t chan,
-                                    codec_t codec,
-                                    uint8_t *coded_samples, 
-                                    uint16_t coded_bytes,
-                                    uint16_t samples);
-
-       int16_t bottom_codec_record (uint8_t chan,
-                                    codec_t codec,
-                                    uint8_t *coded_samples,
-                                    uint16_t coded_bytes,
-                                    uint16_t samples);
-
-TODO: Why repeat ``codec`` for every piece of data?  It could
-change between packets, but not according to SIP?  Plus, it will
-probably not work on drivers that have lagging-behind effects
-such as DMA currently going on and being setup for later.
-
-These functions request the bottom half to employ a codec for
-mapping encoded bytes (with a given start pointer and maximum
-length) to the given number of samples.  The ``codec`` is a
-code that defines the mapping; the architecture defines a
-mapping from codec names to ``codec_t`` values.  When used as
-an argument to ``bottom_codec_record``, the encoding side of the
-codec will be used; in ``bottom_codec_play``, the codec will be
-used for decoding.
-
-The return value of these functions is as complex as the possible
-outcomes of these functions:
-
-* return value 0 means that ``coded_bytes`` have been consumed to
-  generate precisely ``samples`` samples.
-* return values <0 mean that ``coded_bytes`` are all used but
-  there are still samples missing; the return value indicates how
-  much to subtract from ``samples`` to get the achieved result.
-  Note that it is assumed that all ``coded_bytes`` are used up
-  at this point.  If that means keeping internal state in the
-  bottom half, then so be it.  (But this is not likely to occur,
-  because it would dramatically complicate packet loss handling.)
-* return values >>0 mean that all ``samples`` samples were
-  generated, but there are still the returned number of bytes
-  left from ``codec_bytes``.
+part of the top half because it is generic application logic;
+however, it is quite possible that a more optimal implementation
+exists, using knowledge only available in the bottom half.  The
+top-half code contains mechanisms to replace the generic code
+from the top half with any such more optimal code.
+
+A structure is defined in ``<0cpm/codec.h>`` to contain the inner
+data storage of any codec selected, and to point to the functions
+that initialise, encode/decode, and finalise the codec.  These
+structures are used as a generic API through which to access the
+codec's facilities.  The data storage ensures that the top layer
+can abstract from details such as pointers midway a bitfield.  As
+far as the top level is concerned, encoded bytes are consumed or
+produced in portions of whole bytes, and samples are produced or
+consumed in portions of an entire signed 16-bit integer.
+
+Bytes setup for playback go through the following phases:
+
+1. They are setup in the playback buffer by a decoder
+
+2. They are surrendered to the playback facility
+
+3. During playback, the same-timed samples are also recorded
+
+4. The playback buffer and recorded buffer are used for echo suppression
+
+6. The recorded samples are processed by an encoder
+
+6. The playback buffer and recording buffer are released
+
+In practice, there will be a playback process and a recording
+process; the playback buffer claims access to additional blocks
+in the buffer and the recording buffer causes blocks to be freed
+after they have been processed.  Internally, a block may either
+be free for playback/recording, be actively read/written by DMA,
+or may be locked for echo cancellation and recorded-sound
+processing.  The allocation of play and recording buffers happen
+in lockstep.
+
+The encoder and decoder are aware of the blocksize that has been
+claimed for the channel; so they will claim whole blocks, process
+them and release them.  To aid efficiency, there are no access
+mechsnisms for individual samples.  The corresponding downcalls
+to administer claiming and releasing sample blocks are::
+
+       int16_t *bottom_play_claim (uint8_t chan);
+       int16_t *bottom_echo_claim (uint8_t chan);
+       int16_t *bottom_record_claim (uint8_t chan);
+       void bottom_play_release (uint8_t chan);
+       void bottom_record_release (uint8_t chan);
+
+The claim routines return a pointer to a block-sized array of
+samples, each as a signed 16-bit integer.  For playback, these
+samples can be written; for echo and record these can be read.
+The difference between the last two is that the echo values
+return the input to echo cancellation, while the recording claim
+returns the microphone input that synchronises with the echo
+data.  Note that the claiming routines return NULL if they have
+nothing to offer; the notification functions below resolve this
+sitiation if it arises.  The release routines report that work on
+the claimed region has ended; note that ``bottom_record_release``
+applies to an optional echo claim as well as to the record claim.
+
+For each channel, it is only possible to claim a single block at
+a time.  The reason for still having support for multiple blocks
+is to permit the hardware drivers to arrange a scheme where some
+buffers are being played, while others are being setup by the
+firmware.
+
+The reason to have separate calls for the playing side and the
+recording side is that they will usually be implemented by
+different processes; the reason for permitting access to the
+echo-source signal is also to simplify separate processes.  It
+also ensures that the echo cancellation software receives
+properly timed material, regardless of any variations in timing
+between the playback and recording processes.
+
+Note that zero is a valid respons from the ``bottom_playback_claim``
+function; it indicates that no more blocks are available.
+Furthermore, since the block sizes 
+
+The bottom half makes upcalls to indicate a positive change or,
+as the bottom implementors see fit, every time a block becomes
+available for claiming.  The upcalls for the two sides of the
+sound channel are::
+
+       void top_codec_can_play   (uint8_t chan);
+       void top_codec_can_record (uint8_t chan);
+
+These routines will normally kick a (possibly waiting) task, or
+perform another action that makes the top-half codec handlers do
+their thing.  These top-half handlers will subsequently try to make
+a claim and do their thing.  The routines will certainly be
+called after a NULL value has been returned from
+``bottom_play_claim`` and ``bottom_record_claim``, respectively.
+
+A trivial example of using these routines would be::
+
+       bool play_welcome = false;
+
+       void top_codec_can_play (uint8_t chan) {
+               play_welcome = true;
+       }
 
-::
-       void bottom_codec_play_skip (codec_t codec, uint16_t samples);
+       void top_main (void) {
+               bottom_critical_region_end ();
+               while (___more_to_write___) {
+                       int16_t *outbuf;
+                       do {
+                               play_welcome = false;
+                               outbuf = bottom_play_claim (PHONE_CHANNEL_TELEPHONY);
+                               if (outbuf != NULL) {
+                                       ___send_samples_to_outbuf___;
+                                       bottom_play_release (PHONE_CHANNEL_TELEPHONY);
+                               }
+                       } while (outbuf != NULL);
+                       while (!play_welcome) {
+                               ___awful_example_of_polling___;
+                       }
+               }
+       }
 
-This instructs the playing half of the codec to skip the given
-number of samples on playback.  This is required when network
-traffic has gone missing.  If this function is not called to
-indicate having detected such out-of-order delivery, the bottom
-half would assume that the network path has incurred a new delay,
-for which it would insert extra samples to fill up and cause the
-phone to playback with the added delay.  The bottom half must be
-clever enough to handle accidental skipping until samples are
-played in the future.  A small delay before a packet is actually
-played may be healthy to avoid future sound glitches.
+Of course, this is not a practical example.  It wastes time polling
+and has only a single main loop.  But it is noteworthy how there is
+no need to block interrupts.  The trick is to reset the flag that is
+set by the ``top_codec_can_play()`` before calling ``bottom_play_claim()``
+and not after; this avoids ever missing the top-call due to race
+conditions between the main program and background sample playback.
+The only thing that can happen is that the flag is set just before
+the block is claimed that can actually be used for playback; this
+is easily detected on the next attempt to claim a block, and should
+be tolerated when this technique is used.  The only reason why the
+flag is reset before *every* call to ``bottom_play_claim()`` is to
+minimise the number of such spurious calls; it is primarily an
+optimisation.
 
 
 Entropy
@@ -964,6 +1073,7 @@ reading, as the service is not truely random, but pseudo-random:
 best-effort suffices for telephony applications.
 
 ::
+
        void bottom_rndseed (void);
        void bottom_rnd_pseudo (uint8_t *rnd, uint8_t len);
        //TBD// void bottom_rnd_strong (uint8_t *rnd, uint8_t len);
index a6988b7..eae132f 100644 (file)
@@ -59,21 +59,24 @@ void bottom_soundchannel_device (uint8_t chan, sounddev_t dev);
 void bottom_soundchannel_setvolume (uint8_t chan, uint8_t vol);
 uint8_t bottom_soundchannel_getvolume (uint8_t chan);
 
-/* Upcalls to indicate that sound can be inserted */
-
-uint16_t top_codec_can_play   (uint8_t chan, uint16_t samples);
-uint16_t top_codec_can_record (uint8_t chan, uint16_t samples);
+bool bottom_soundchannel_acceptable_samplerate (uint8_t chan, uint32_t samplerate);
+bool bottom_soundchannel_preferred_samplerate  (uint8_t chan, uint32_t samplerate);
+void bottom_soundchannel_set_samplerate (uint8_t chan,
+                uint32_t samplerate, uint8_t blocksize,
+                uint8_t upsample_play, uint8_t downsample_record);
 
-/* Calls to play or record through a codec */
-
-void bottom_codec_play_samplerate   (uint8_t chan, uint32_t samplerate);
-void bottom_codec_record_samplerate (uint8_t chan, uint32_t samplerate);
+/* Upcalls to indicate that sound can be inserted */
 
-int16_t bottom_codec_play   (uint8_t chan, codec_t codec, uint8_t *coded_samples, uint16_t coded_bytes, uint16_t samples);
+void top_codec_can_play   (uint8_t chan);
+void top_codec_can_record (uint8_t chan);
 
-int16_t bottom_codec_record (uint8_t chan, codec_t codec, uint8_t *coded_samples, uint16_t coded_bytes, uint16_t samples);
+/* Calls to access the buffer space for playback and recording */
 
-void bottom_codec_play_skip (codec_t codec, uint16_t samples);
+int16_t *bottom_play_claim (uint8_t chan);
+int16_t *bottom_echo_claim (uint8_t chan);
+int16_t *bottom_record_claim (uint8_t chan);
+void bottom_play_release (uint8_t chan);
+void bottom_record_release (uint8_t chan);
 
 
 /* Definitions for sound channels and devices.
index 5c80775..a884edf 100644 (file)
 #define NEED_KBD_SCANNER_DURING_KEYPRESS 1
 
 
+inline bool bottom_soundchannel_acceptable_samplerate (uint8_t chan, uint32_t samplerate) {
+       return (samplerate >= 1875) && (samplerate <= 52000);
+}
+
+inline bool bottom_soundchannel_preferred_samplerate (uint8_t chan, uint32_t samplerate) {
+       return bottom_acceptable_samplerate (chan, samplerate)
+               && ( ( ( 30720000 / 16 ) % ( 1 * 2 * samplerate)) == 0);
+}
+
+
 #ifdef BOTTOM
 
 
index 5ca0b6d..ef29bce 100644 (file)
@@ -23,6 +23,7 @@ Codecs that are large or contraversial are optional:
 * Ogg decoding is computationally heavy, and could be done by a central node
 * Speex encoder/decoder consumes about 100 kB of code size
 * L16 as it might not be to everyone's taste to have an open sound player next to them
+* T.38 over UDP is the best FoIP method; it locally integrates with TFTP over LLC1
 
 Note that RTT is hardly contraversial -- it is about 1 kB of code, and can add great
 benefit to all users -- not just the deaf or speech impaired.  Making it available
@@ -30,3 +31,4 @@ by default not only creates a World were handicaps cause less isolation, but it
 sends a strong signal to PBX developers to start texting their IVR menus so we can
 get on wading through lists of options.  The only case where RTT is not to be built
 into the 0cpm Firmerware is actually when there is no display to use.
+
diff --git a/src/codec/l8l16.c b/src/codec/l8l16.c
new file mode 100644 (file)
index 0000000..e9b9cda
--- /dev/null
@@ -0,0 +1,71 @@
+/* l8l16.c -- Codec support for L8 and L16 -- plain samples.
+ *
+ * This file is part of 0cpm Firmerware.
+ *
+ * 0cpm Firmerware is Copyright (c)2011 Rick van Rein, OpenFortress.
+ *
+ * 0cpm Firmerware is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 3.
+ *
+ * 0cpm Firmerware 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0cpm Firmerware.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <0cpm/codec.h>
+
+
+/** \ingroup codec
+ * The L8 and L16 codecs are part of the audio/video profile for RTP,
+ * and simply describe samples that are encoded directly, without any
+ * further compression.  L8 is an 8-bit codec of unsigned values after
+ * adding a 0x80 offset; L16 describes signed 16-bit integers.
+ */
+
+
+void l8_decode (struct codec *hdl, int16_t *pcm, uint16_t *pcmlen, uint8_t *pkt, uint16_t *pktlen) {
+       while ((*pktlen >= 1) && (*pcmlen >= 1)) {
+               // Note: The 0xff masks away bits from 16-bit DSPs like tic55x
+               *pcm++ = (((*pkt++) & 0xff) ^ 0x80) << 8;
+               *pcmlen--;
+               *pktlen--;
+       }
+}
+
+void l8_encode (struct codec *hdl, int16_t *pcm, uint16_t *pcmlen, uint8_t *pkt, uint16_t *pktlen) {
+       while ((*pktlen >= 1) && (*pcmlen >= 1)) {
+               // Note: The 0xff masks away bits from 16-bit DSPs like tic55x
+               *pkt++ = (((*pcm++) >> 8) ^ 0x80) & 0xff;
+               *pcmlen--;
+               *pktlen--;
+       }
+}
+
+void l16_decode (struct codec *hdl, int16_t *pcm, uint16_t *pcmlen, uint8_t *pkt, uint16_t *pktlen) {
+       while ((*pktlen >= 2) && (*pcmlen >= 1)) {
+               // Note: The 0xff masks away bits from 16-bit DSPs like tic55x
+               *pcm++ = (int16_t) (((*pkt++) << 8) | ((*pkt++) & 0xff));
+               *pcmlen--;
+               *pktlen -= 2;
+       }
+}
+
+void l16_encode (struct codec *hdl, int16_t *pcm, uint16_t *pcmlen, uint8_t *pkt, uint16_t *pktlen) {
+       while ((*pktlen >= 2) && (*pcmlen >= 1)) {
+               // Note: The 0xff masks away bits from 16-bit DSPs like tic55x
+               *pkt++ = ((*pcm) >> 8) & 0xff;
+               *pkt++ = (*pcm++) & 0xff;
+               *pcmlen--;
+               *pktlen -= 2;
+       }
+}
+
index 152054a..60d7f9e 100644 (file)
@@ -1,8 +1,8 @@
 TARGET=rtt_desktop_test
 OBJS=recvkeys.o sendkeys.o desktop.o
 
-CFLAGS=-m32 -L.
-# CFLAGS=-m64
+# CFLAGS=-m32 -L.
+CFLAGS=-m64
 
 # CFLAGS+=-ggdb3
 
index 97fea7b..88886d9 100644 (file)
@@ -48,14 +48,10 @@ To build, run::
 
 To test, run a command like this on each side::
 
-       ./rtt_desktop_test /dev/tty3 2001:db8:123::45 1212 2001:db8:99::6 1313
+       ./rtt_desktop_test 2001:db8:123::45 1212 2001:db8:99::6 1313
 
 The parameters are:
 
-* ``/dev/tty3`` is the terminal to which output should be sent.  The terminal
-  on which this is run will become the input terminal.  Using different
-  terminals for input and output avoids cluttered output.
-
 * ``2001:db8:123::45`` and ``1212`` are the local address and port to bind to.
 
 * ``2001:db8:99::6`` and ``1313`` are the remote address and port to connect to.
index 86975e2..378a92e 100644 (file)
@@ -1 +1,3 @@
-X bs-key zendt als DEL
++ bs-key zendt als DEL
++ split the screen
+- limit characters per second
index 57e94e5..f4ba330 100644 (file)
@@ -252,8 +252,10 @@ int main (int argc, char *argv []) {
        // Test arguments
        if (argc != 5) {
                fprintf (stderr, "Usage: %s myAddr myPort remoteAddr remotePort\n"
-                               "   Where is an output terminal, and addresses are IPv6 (as RTP would deliver).\n"
-                               "   This is not a fancy interface, but rather demonstrates the protocol.\n",
+                               "   This is how RTP would setup this connection after SDP exchange over SIP.\n"
+                               "   The addresses myAddr and remoteAddr are IPv6 addresses.  You could use\n"
+                               "   the IPv4-syntax ::123.45.67.89 but firewall traversal would probably fail.\n"
+                               "   Please expect nothing fancy, this is just a protocol demonstration!\n",
                        argv [0]);
                exit (1);
        }
diff --git a/src/codec/spandsp/src/async.c b/src/codec/spandsp/src/async.c
new file mode 100644 (file)
index 0000000..e24d843
--- /dev/null
@@ -0,0 +1,292 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * async.c - Asynchronous serial bit stream encoding and decoding
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2003 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.
+ */
+
+/*! \file */
+
+#if defined(HAVE_CONFIG_H)
+#include "config.h"
+#endif
+
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "spandsp/telephony.h"
+#include "spandsp/async.h"
+
+#include "spandsp/private/async.h"
+
+SPAN_DECLARE(const char *) signal_status_to_str(int status)
+{
+    switch (status)
+    {
+    case SIG_STATUS_CARRIER_DOWN:
+        return "Carrier down";
+    case SIG_STATUS_CARRIER_UP:
+        return "Carrier up";
+    case SIG_STATUS_TRAINING_IN_PROGRESS:
+        return "Training in progress";
+    case SIG_STATUS_TRAINING_SUCCEEDED:
+        return "Training succeeded";
+    case SIG_STATUS_TRAINING_FAILED:
+        return "Training failed";
+    case SIG_STATUS_FRAMING_OK:
+        return "Framing OK";
+    case SIG_STATUS_END_OF_DATA:
+        return "End of data";
+    case SIG_STATUS_ABORT:
+        return "Abort";
+    case SIG_STATUS_BREAK:
+        return "Break";
+    case SIG_STATUS_SHUTDOWN_COMPLETE:
+        return "Shutdown complete";
+    case SIG_STATUS_OCTET_REPORT:
+        return "Octet report";
+    case SIG_STATUS_POOR_SIGNAL_QUALITY:
+        return "Poor signal quality";
+    case SIG_STATUS_MODEM_RETRAIN_OCCURRED:
+        return "Modem retrain occurred";
+    }
+    return "???";
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(async_rx_state_t *) async_rx_init(async_rx_state_t *s,
+                                               int data_bits,
+                                               int parity,
+                                               int stop_bits,
+                                               int use_v14,
+                                               put_byte_func_t put_byte,
+                                               void *user_data)
+{
+    if (s == NULL)
+    {
+        if ((s = (async_rx_state_t *) malloc(sizeof(*s))) == NULL)
+            return NULL;
+    }
+    s->data_bits = data_bits;
+    s->parity = parity;
+    s->stop_bits = stop_bits;
+    s->use_v14 = use_v14;
+
+    s->put_byte = put_byte;
+    s->user_data = user_data;
+
+    s->byte_in_progress = 0;
+    s->bitpos = 0;
+    s->parity_bit = 0;
+
+    s->parity_errors = 0;
+    s->framing_errors = 0;
+    return s;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) async_rx_release(async_rx_state_t *s)
+{
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) async_rx_free(async_rx_state_t *s)
+{
+    free(s);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE_NONSTD(void) async_rx_put_bit(void *user_data, int bit)
+{
+    async_rx_state_t *s;
+
+    s = (async_rx_state_t *) user_data;
+    if (bit < 0)
+    {
+        /* Special conditions */
+        switch (bit)
+        {
+        case SIG_STATUS_CARRIER_UP:
+        case SIG_STATUS_CARRIER_DOWN:
+        case SIG_STATUS_TRAINING_IN_PROGRESS:
+        case SIG_STATUS_TRAINING_SUCCEEDED:
+        case SIG_STATUS_TRAINING_FAILED:
+        case SIG_STATUS_END_OF_DATA:
+            s->put_byte(s->user_data, bit);
+            s->bitpos = 0;
+            s->byte_in_progress = 0;
+            break;
+        default:
+            //printf("Eh!\n");
+            break;
+        }
+        return;
+    }
+    if (s->bitpos == 0)
+    {
+        /* Search for the start bit */
+        s->bitpos += (bit ^ 1);
+        s->parity_bit = 0;
+        s->byte_in_progress = 0;
+    }
+    else if (s->bitpos <= s->data_bits)
+    {
+        s->byte_in_progress = (s->byte_in_progress >> 1) | (bit << 7);
+        s->parity_bit ^= bit;
+        s->bitpos++;
+    }
+    else if (s->parity  &&  s->bitpos == s->data_bits + 1)
+    {
+        if (s->parity == ASYNC_PARITY_ODD)
+            s->parity_bit ^= 1;
+
+        if (s->parity_bit != bit)
+            s->parity_errors++;
+        s->bitpos++;
+    }
+    else
+    {
+        /* Stop bit */
+        if (bit == 1)
+        {
+            /* Align the received value */
+            if (s->data_bits < 8)
+                s->byte_in_progress >>= (8 - s->data_bits);
+            s->put_byte(s->user_data, s->byte_in_progress);
+            s->bitpos = 0;
+        }
+        else if (s->use_v14)
+        {
+            /* This is actually the start bit for the next character, and
+               the stop bit has been dropped from the stream. This is the
+               rate adaption specified in V.14 */
+            /* Align the received value */
+            if (s->data_bits < 8)
+                s->byte_in_progress >>= (8 - s->data_bits);
+            s->put_byte(s->user_data, s->byte_in_progress);
+            s->bitpos = 1;
+            s->parity_bit = 0;
+            s->byte_in_progress = 0;
+        }
+        else
+        {
+            s->framing_errors++;
+            s->bitpos = 0;
+        }
+    }
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(async_tx_state_t *) async_tx_init(async_tx_state_t *s,
+                                               int data_bits,
+                                               int parity,
+                                               int stop_bits,
+                                               int use_v14,
+                                               get_byte_func_t get_byte,
+                                               void *user_data)
+{
+    if (s == NULL)
+    {
+        if ((s = (async_tx_state_t *) malloc(sizeof(*s))) == NULL)
+            return NULL;
+    }
+    /* We have a use_v14 parameter for completeness, but right now V.14 only
+       applies to the receive side. We are unlikely to have an application where
+       flow control does not exist, so V.14 stuffing is not needed. */
+    s->data_bits = data_bits;
+    s->parity = parity;
+    s->stop_bits = stop_bits;
+    if (parity != ASYNC_PARITY_NONE)
+        s->stop_bits++;
+        
+    s->get_byte = get_byte;
+    s->user_data = user_data;
+
+    s->byte_in_progress = 0;
+    s->bitpos = 0;
+    s->parity_bit = 0;
+    return s;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) async_tx_release(async_tx_state_t *s)
+{
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) async_tx_free(async_tx_state_t *s)
+{
+    free(s);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE_NONSTD(int) async_tx_get_bit(void *user_data)
+{
+    async_tx_state_t *s;
+    int bit;
+    
+    s = (async_tx_state_t *) user_data;
+    if (s->bitpos == 0)
+    {
+        if ((s->byte_in_progress = s->get_byte(s->user_data)) < 0)
+        {
+            /* No more data */
+            bit = SIG_STATUS_END_OF_DATA;
+        }
+        else
+        {
+            /* Start bit */
+            bit = 0;
+            s->parity_bit = 0;
+            s->bitpos++;
+        }
+    }
+    else if (s->bitpos <= s->data_bits)
+    {
+        bit = s->byte_in_progress & 1;
+        s->byte_in_progress >>= 1;
+        s->parity_bit ^= bit;
+        s->bitpos++;
+    }
+    else if (s->parity  &&  s->bitpos == s->data_bits + 1)
+    {
+        if (s->parity == ASYNC_PARITY_ODD)
+            s->parity_bit ^= 1;
+        bit = s->parity_bit;
+        s->bitpos++;
+    }
+    else
+    {
+        /* Stop bit(s) */
+        bit = 1;
+        if (++s->bitpos > s->data_bits + s->stop_bits)
+            s->bitpos = 0;
+    }
+    return bit;
+}
+/*- End of function --------------------------------------------------------*/
+/*- End of file ------------------------------------------------------------*/
diff --git a/src/codec/spandsp/src/bit_operations.c b/src/codec/spandsp/src/bit_operations.c
new file mode 100644 (file)
index 0000000..3b6bee3
--- /dev/null
@@ -0,0 +1,181 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * bit_operations.c - Various bit level operations, such as bit reversal
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2006 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.
+ */
+
+/*! \file */
+
+#if defined(HAVE_CONFIG_H)
+#include "config.h"
+#endif
+
+#include <inttypes.h>
+#include <stdlib.h>
+//TODO// #include <fcntl.h>
+#include <string.h>
+#include <assert.h>
+#include <memory.h>
+
+#include "spandsp/0cpm.h"
+
+#include "spandsp/telephony.h"
+#include "spandsp/bit_operations.h"
+
+SPAN_DECLARE(uint16_t) bit_reverse16(uint16_t x)
+{
+    x = (x >> 8) | (x << 8);
+    x = ((x & 0xF0F0) >> 4) | ((x & 0x0F0F) << 4);
+    x = ((x & 0xCCCC) >> 2) | ((x & 0x3333) << 2);
+    return ((x & 0xAAAA) >> 1) | ((x & 0x5555) << 1);
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(uint32_t) bit_reverse32(uint32_t x)
+{
+    x = (x >> 16) | (x << 16);
+    x = ((x & 0xFF00FF00) >> 8) | ((x & 0x00FF00FF) << 8);
+    x = ((x & 0xF0F0F0F0) >> 4) | ((x & 0x0F0F0F0F) << 4);
+    x = ((x & 0xCCCCCCCC) >> 2) | ((x & 0x33333333) << 2);
+    return ((x & 0xAAAAAAAA) >> 1) | ((x & 0x55555555) << 1);
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(uint32_t) bit_reverse_4bytes(uint32_t x)
+{
+    x = ((x & 0xF0F0F0F0) >> 4) | ((x & 0x0F0F0F0F) << 4);
+    x = ((x & 0xCCCCCCCC) >> 2) | ((x & 0x33333333) << 2);
+    return ((x & 0xAAAAAAAA) >> 1) | ((x & 0x55555555) << 1);
+}
+/*- End of function --------------------------------------------------------*/
+
+#if defined(__x86_64__)
+SPAN_DECLARE(uint64_t) bit_reverse_8bytes(uint64_t x)
+{
+    x = ((x & 0xF0F0F0F0F0F0F0F0LLU) >> 4) | ((x & 0x0F0F0F0F0F0F0F0FLLU) << 4);
+    x = ((x & 0xCCCCCCCCCCCCCCCCLLU) >> 2) | ((x & 0x3333333333333333LLU) << 2);
+    return ((x & 0xAAAAAAAAAAAAAAAALLU) >> 1) | ((x & 0x5555555555555555LLU) << 1);
+}
+/*- End of function --------------------------------------------------------*/
+#endif
+
+SPAN_DECLARE(void) bit_reverse(uint8_t to[], const uint8_t from[], int len)
+{
+#if defined(SPANDSP_MISALIGNED_ACCESS_FAILS)
+    int i;
+#else
+    const uint8_t *y1;
+    uint8_t *z1;
+    const uint32_t *y4;
+    uint32_t *z4;
+    uint32_t x4;
+#if defined(__x86_64__)
+    const uint64_t *y8;
+    uint64_t *z8;
+    uint64_t x8;
+#endif
+#endif
+
+#if defined(SPANDSP_MISALIGNED_ACCESS_FAILS)
+    /* This code works byte by byte, so it works on machines where misalignment
+       is either desperately slow (its a bit slow on practically any machine, but
+       some machines make it desparately slow) or fails. */
+    for (i = 0;  i < len;  i++)
+        to[i] = bit_reverse8(from[i]);
+#else
+    /* This code is this is based on the woolly assumption that the start of the buffers
+       is memory aligned. If it isn't, the routine will be less efficient on some machines,
+       but might not work at all on others. */
+#if defined(__x86_64__)
+    y8 = (const uint64_t *) from;
+    z8 = (uint64_t *) to;
+    while (len >= sizeof(uint64_t))
+    {
+        x8 = *y8++;
+        x8 = ((x8 & 0xF0F0F0F0F0F0F0F0LLU) >> 4) | ((x8 & 0x0F0F0F0F0F0F0F0FLLU) << 4);
+        x8 = ((x8 & 0xCCCCCCCCCCCCCCCCLLU) >> 2) | ((x8 & 0x3333333333333333LLU) << 2);
+        *z8++ = ((x8 & 0xAAAAAAAAAAAAAAAALLU) >> 1) | ((x8 & 0x5555555555555555LLU) << 1);
+        len -= sizeof(uint64_t);
+    }
+    y4 = (const uint32_t *) y8;
+    z4 = (uint32_t *) z8;
+#else
+    y4 = (const uint32_t *) from;
+    z4 = (uint32_t *) to;
+#endif
+    while (len >= sizeof(uint32_t))
+    {
+        x4 = *y4++;
+        x4 = ((x4 & 0xF0F0F0F0) >> 4) | ((x4 & 0x0F0F0F0F) << 4);
+        x4 = ((x4 & 0xCCCCCCCC) >> 2) | ((x4 & 0x33333333) << 2);
+        *z4++ = ((x4 & 0xAAAAAAAA) >> 1) | ((x4 & 0x55555555) << 1);
+        len -= sizeof(uint32_t);
+    }
+    y1 = (const uint8_t *) y4;
+    z1 = (uint8_t *) z4;
+    while (len-- > 0)
+        *z1++ = bit_reverse8(*y1++);
+#endif
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) one_bits32(uint32_t x)
+{
+    x = x - ((x >> 1) & 0x55555555);
+    /* We now have 16 2-bit counts */
+    x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
+    /* We now have 8 4-bit counts */
+    x = (x + (x >> 4)) & 0x0F0F0F0F;
+    /* We now have 4 8-bit counts */
+#if defined(__i386__)  ||  defined(__x86_64__)  ||  defined(__ppc__)  ||  defined(__powerpc__)
+    /* If multiply is fast */
+    return (x*0x01010101) >> 24;
+#else
+    /* If multiply is slow */
+    x += (x >> 8);
+    x += (x >> 16);
+    return (x & 0x0000003F);
+#endif
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(uint32_t) make_mask32(uint32_t x)
+{
+    x |= (x >> 1);
+    x |= (x >> 2);
+    x |= (x >> 4);
+    x |= (x >> 8);
+    x |= (x >> 16);
+    return x;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(uint16_t) make_mask16(uint16_t x)
+{
+    x |= (x >> 1);
+    x |= (x >> 2);
+    x |= (x >> 4);
+    x |= (x >> 8);
+    return x;
+}
+/*- End of function --------------------------------------------------------*/
+/*- End of file ------------------------------------------------------------*/
diff --git a/src/codec/spandsp/src/logging.c b/src/codec/spandsp/src/logging.c
new file mode 100644 (file)
index 0000000..74f3e64
--- /dev/null
@@ -0,0 +1,276 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * logging.c - error and debug logging.
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2005 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.
+ */
+
+/*! \file */
+
+#if defined(HAVE_CONFIG_H)
+#include "config.h"
+#endif
+
+#include <limits.h>
+//TODO// #include <stdio.h>
+#include <stdarg.h>
+//TODO// #include <fcntl.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+//TODO// #include <sys/time.h>
+#include <time.h>
+
+#include "spandsp/0cpm.h"
+
+#include "spandsp/telephony.h"
+#include "spandsp/logging.h"
+
+#include "spandsp/private/logging.h"
+
+static void default_message_handler(int level, const char *text);
+
+static message_handler_func_t __span_message = &default_message_handler;
+static error_handler_func_t __span_error = NULL;
+
+/* Note that this list *must* match the enum definition in logging.h */
+static const char *severities[] =
+{
+    "NONE",
+    "ERROR",
+    "WARNING",
+    "PROTOCOL_ERROR",
+    "PROTOCOL_WARNING",
+    "FLOW",
+    "FLOW 2",
+    "FLOW 3",
+    "DEBUG 1",
+    "DEBUG 2",
+    "DEBUG 3"
+};
+
+static void default_message_handler(int level, const char *text)
+{
+    fprintf(stderr, "%s", text);
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) span_log_test(logging_state_t *s, int level)
+{
+    if (s  &&  (s->level & SPAN_LOG_SEVERITY_MASK) >= (level & SPAN_LOG_SEVERITY_MASK))
+        return TRUE;
+    return FALSE;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) span_log(logging_state_t *s, int level, const char *format, ...)
+{
+    char msg[1024 + 1];
+    va_list arg_ptr;
+    int len;
+    struct tm *tim;
+    struct timeval nowx;
+    time_t now;
+
+    if (span_log_test(s, level))
+    {
+        va_start(arg_ptr, format);
+        len = 0;
+        if ((level & SPAN_LOG_SUPPRESS_LABELLING) == 0)
+        {
+            if ((s->level & SPAN_LOG_SHOW_DATE))
+            {
+                gettimeofday(&nowx, NULL);
+                now = nowx.tv_sec;
+                tim = gmtime(&now);
+                len += snprintf(msg + len,
+                                1024 - len,
+                                "%04d/%02d/%02d %02d:%02d:%02d.%03d ",
+                                tim->tm_year + 1900,
+                                tim->tm_mon + 1,
+                                tim->tm_mday,
+                                tim->tm_hour,
+                                tim->tm_min,
+                                tim->tm_sec,
+                                (int) nowx.tv_usec/1000);
+            }
+            /*endif*/
+            if ((s->level & SPAN_LOG_SHOW_SAMPLE_TIME))
+            {
+                now = s->elapsed_samples/s->samples_per_second;
+                tim = gmtime(&now);
+                len += snprintf(msg + len,
+                                1024 - len,
+                                "%02d:%02d:%02d.%03d ",
+                                tim->tm_hour,
+                                tim->tm_min,
+                                tim->tm_sec,
+                                (int) (s->elapsed_samples%s->samples_per_second)*1000/s->samples_per_second);
+            }
+            /*endif*/
+            if ((s->level & SPAN_LOG_SHOW_SEVERITY)  &&  (level & SPAN_LOG_SEVERITY_MASK) <= SPAN_LOG_DEBUG_3)
+                len += snprintf(msg + len, 1024 - len, "%s ", severities[level & SPAN_LOG_SEVERITY_MASK]);
+            /*endif*/
+            if ((s->level & SPAN_LOG_SHOW_PROTOCOL)  &&  s->protocol)
+                len += snprintf(msg + len, 1024 - len, "%s ", s->protocol);
+            /*endif*/
+            if ((s->level & SPAN_LOG_SHOW_TAG)  &&  s->tag)
+                len += snprintf(msg + len, 1024 - len, "%s ", s->tag);
+            /*endif*/
+        }
+        /*endif*/
+        len += vsnprintf(msg + len, 1024 - len, format, arg_ptr);
+        if (s->span_error  &&  level == SPAN_LOG_ERROR)
+            s->span_error(msg);
+        else if (__span_error  &&  level == SPAN_LOG_ERROR)
+            __span_error(msg);
+        else if (s->span_message)
+            s->span_message(level, msg);
+        else if (__span_message)
+            __span_message(level, msg);
+        /*endif*/
+        va_end(arg_ptr);
+        return  1;
+    }
+    /*endif*/
+    return  0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) span_log_buf(logging_state_t *s, int level, const char *tag, const uint8_t *buf, int len)
+{
+    char msg[1024];
+    int i;
+    int msg_len;
+
+    if (span_log_test(s, level))
+    {
+        msg_len = 0;
+        if (tag)
+            msg_len += snprintf(msg + msg_len, 1024 - msg_len, "%s", tag);
+        for (i = 0;  i < len  &&  msg_len < 800;  i++)
+            msg_len += snprintf(msg + msg_len, 1024 - msg_len, " %02x", buf[i]);
+        msg_len += snprintf(msg + msg_len, 1024 - msg_len, "\n");
+        return span_log(s, level, msg);
+    }
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) span_log_set_level(logging_state_t *s, int level)
+{
+    s->level = level;
+
+    return  0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) span_log_set_tag(logging_state_t *s, const char *tag)
+{
+    s->tag = tag;
+
+    return  0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) span_log_set_protocol(logging_state_t *s, const char *protocol)
+{
+    s->protocol = protocol;
+
+    return  0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) span_log_set_sample_rate(logging_state_t *s, int samples_per_second)
+{
+    s->samples_per_second = samples_per_second;
+
+    return  0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) span_log_bump_samples(logging_state_t *s, int samples)
+{
+    s->elapsed_samples += samples;
+
+    return  0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) span_log_set_message_handler(logging_state_t *s, message_handler_func_t func)
+{
+    s->span_message = func;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) span_log_set_error_handler(logging_state_t *s, error_handler_func_t func)
+{
+    s->span_error = func;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) span_set_message_handler(message_handler_func_t func)
+{
+    __span_message = func;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) span_set_error_handler(error_handler_func_t func)
+{
+    __span_error = func;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(logging_state_t *) span_log_init(logging_state_t *s, int level, const char *tag)
+{
+    if (s == NULL)
+    {
+        if ((s = (logging_state_t *) malloc(sizeof(*s))) == NULL)
+            return NULL;
+    }
+    s->span_error = __span_error;
+    s->span_message = __span_message;
+    s->level = level;
+    s->tag = tag;
+    s->protocol = NULL;
+    s->samples_per_second = SAMPLE_RATE;
+    s->elapsed_samples = 0;
+
+    return s;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) span_log_release(logging_state_t *s)
+{
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) span_log_free(logging_state_t *s)
+{
+    if (s)
+        free(s);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+/*- End of file ------------------------------------------------------------*/
diff --git a/src/codec/spandsp/src/spandsp/0cpm.h b/src/codec/spandsp/src/spandsp/0cpm.h
new file mode 100644 (file)
index 0000000..6b938f0
--- /dev/null
@@ -0,0 +1,56 @@
+/* Extra header for use with 0cpm */
+
+#include <stdbool.h>
+#include <config.h>
+
+#define __inline__ inline
+
+#ifndef INT32_MAX
+#define INT32_MAX 2147483647
+#endif
+
+#ifndef INT32_MIN
+#define INT32_MIN -2147483648
+#endif
+
+#ifndef UINT32_MAX
+#define UINT32_MAX 4294967295
+#endif
+
+#ifndef UINT32_MIN
+#define UINT32_MIN 0
+#endif
+
+#ifndef INT16_MAX
+#define INT16_MAX 32767
+#endif
+
+#ifndef INT16_MIN
+#define INT16_MIN -32768
+#endif
+
+#ifndef UINT16_MAX
+#define UINT16_MAX 65535
+#endif
+
+#ifndef UINT16_MIN
+#define UINT16_MIN 0
+#endif
+
+#ifndef INT8_MAX
+#define INT8_MAX 127
+#endif
+
+#ifndef INT8_MIN
+#define INT8_MIN -128
+#endif
+
+#ifndef UINT8_MAX
+#define UINT8_MAX 255
+#endif
+
+#ifndef UINT8_MIN
+#define UINT8_MIN 0
+#endif
+
+
diff --git a/src/codec/spandsp/src/spandsp/async.h b/src/codec/spandsp/src/spandsp/async.h
new file mode 100644 (file)
index 0000000..9c6c66c
--- /dev/null
@@ -0,0 +1,211 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * async.h - Asynchronous serial bit stream encoding and decoding
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2003 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.
+ */
+
+/*! \file */
+
+/*! \page async_page Asynchronous bit stream processing
+\section async_page_sec_1 What does it do?
+The asynchronous serial bit stream processing module provides
+generation and decoding facilities for most asynchronous data
+formats. It supports:
+ - 1 or 2 stop bits.
+ - Odd, even or no parity.
+ - 5, 6, 7, or 8 bit characters.
+ - V.14 rate adaption.
+The input to this module is a bit stream. This means any symbol synchronisation
+and decoding must occur before data is fed to this module.
+
+\section async_page_sec_2 The transmitter
+???.
+
+\section async_page_sec_3 The receiver
+???.
+*/
+
+#if !defined(_SPANDSP_ASYNC_H_)
+#define _SPANDSP_ASYNC_H_
+
+/*! Special "bit" values for the bitstream put and get functions, and the signal status functions. */
+enum
+{
+    /*! \brief The carrier signal has dropped. */
+    SIG_STATUS_CARRIER_DOWN = -1,
+    /*! \brief The carrier signal is up. This merely indicates that carrier
+         energy has been seen. It is not an indication that the carrier is either
+         valid, or of the expected type. */
+    SIG_STATUS_CARRIER_UP = -2,
+    /*! \brief The modem is training. This is an early indication that the
+        signal seems to be of the right type. This may be needed in time critical
+        applications, like T.38, to forward an early indication of what is happening
+        on the wire. */
+    SIG_STATUS_TRAINING_IN_PROGRESS = -3,
+    /*! \brief The modem has trained, and is ready for data exchange. */
+    SIG_STATUS_TRAINING_SUCCEEDED = -4,
+    /*! \brief The modem has failed to train. */
+    SIG_STATUS_TRAINING_FAILED = -5,
+    /*! \brief Packet framing (e.g. HDLC framing) is OK. */
+    SIG_STATUS_FRAMING_OK = -6,
+    /*! \brief The data stream has ended. */
+    SIG_STATUS_END_OF_DATA = -7,
+    /*! \brief An abort signal (e.g. an HDLC abort) has been received. */
+    SIG_STATUS_ABORT = -8,
+    /*! \brief A break signal (e.g. an async break) has been received. */
+    SIG_STATUS_BREAK = -9,
+    /*! \brief A modem has completed its task, and shut down. */
+    SIG_STATUS_SHUTDOWN_COMPLETE = -10,
+    /*! \brief Regular octet report for things like HDLC to the MTP standards. */
+    SIG_STATUS_OCTET_REPORT = -11,
+    /*! \brief Notification that a modem has detected signal quality degradation. */
+    SIG_STATUS_POOR_SIGNAL_QUALITY = -12,
+    /*! \brief Notification that a modem retrain has occurred. */
+    SIG_STATUS_MODEM_RETRAIN_OCCURRED = -13
+};
+
+/*! Message put function for data pumps */
+typedef void (*put_msg_func_t)(void *user_data, const uint8_t *msg, int len);
+
+/*! Message get function for data pumps */
+typedef int (*get_msg_func_t)(void *user_data, uint8_t *msg, int max_len);
+
+/*! Byte put function for data pumps */
+typedef void (*put_byte_func_t)(void *user_data, int byte);
+
+/*! Byte get function for data pumps */
+typedef int (*get_byte_func_t)(void *user_data);
+
+/*! Bit put function for data pumps */
+typedef void (*put_bit_func_t)(void *user_data, int bit);
+
+/*! Bit get function for data pumps */
+typedef int (*get_bit_func_t)(void *user_data);
+
+/*! Completion callback function for tx data pumps */
+typedef void (*modem_tx_status_func_t)(void *user_data, int status);
+
+/*! Completion callback function for rx data pumps */
+typedef void (*modem_rx_status_func_t)(void *user_data, int status);
+
+enum
+{
+    /*! No parity bit should be used */
+    ASYNC_PARITY_NONE = 0,
+    /*! An even parity bit will exist, after the data bits */
+    ASYNC_PARITY_EVEN,
+    /*! An odd parity bit will exist, after the data bits */
+    ASYNC_PARITY_ODD
+};
+
+/*!
+    Asynchronous data transmit descriptor. This defines the state of a single
+    working instance of a byte to asynchronous serial converter, for use
+    in FSK modems.
+*/
+typedef struct async_tx_state_s async_tx_state_t;
+
+/*!
+    Asynchronous data receive descriptor. This defines the state of a single
+    working instance of an asynchronous serial to byte converter, for use
+    in FSK modems.
+*/
+typedef struct async_rx_state_s async_rx_state_t;
+
+#if defined(__cplusplus)
+extern "C"
+{
+#endif
+
+/*! Convert a signal status to a short text description.
+    \brief Convert a signal status to a short text description.
+    \param status The modem signal status.
+    \return A pointer to the description. */
+SPAN_DECLARE(const char *) signal_status_to_str(int status);
+
+/*! Initialise an asynchronous data transmit context.
+    \brief Initialise an asynchronous data transmit context.
+    \param s The transmitter context.
+    \param data_bits The number of data bit.
+    \param parity_bits The type of parity.
+    \param stop_bits The number of stop bits.
+    \param use_v14 TRUE if V.14 rate adaption processing should be used.
+    \param get_byte The callback routine used to get the data to be transmitted.
+    \param user_data An opaque pointer.
+    \return A pointer to the initialised context, or NULL if there was a problem. */
+SPAN_DECLARE(async_tx_state_t *) async_tx_init(async_tx_state_t *s,
+                                               int data_bits,
+                                               int parity_bits,
+                                               int stop_bits,
+                                               int use_v14,
+                                               get_byte_func_t get_byte,
+                                               void *user_data);
+
+SPAN_DECLARE(int) async_tx_release(async_tx_state_t *s);
+
+SPAN_DECLARE(int) async_tx_free(async_tx_state_t *s);
+
+/*! Get the next bit of a transmitted serial bit stream.
+    \brief Get the next bit of a transmitted serial bit stream.
+    \param user_data An opaque point which must point to a transmitter context.
+    \return the next bit, or PUTBIT_END_OF_DATA to indicate the data stream has ended. */
+SPAN_DECLARE_NONSTD(int) async_tx_get_bit(void *user_data);
+
+/*! Initialise an asynchronous data receiver context.
+    \brief Initialise an asynchronous data receiver context.
+    \param s The receiver context.
+    \param data_bits The number of data bits.
+    \param parity_bits The type of parity.
+    \param stop_bits The number of stop bits.
+    \param use_v14 TRUE if V.14 rate adaption processing should be used.
+    \param put_byte The callback routine used to put the received data.
+    \param user_data An opaque pointer.
+    \return A pointer to the initialised context, or NULL if there was a problem. */
+SPAN_DECLARE(async_rx_state_t *) async_rx_init(async_rx_state_t *s,
+                                               int data_bits,
+                                               int parity_bits,
+                                               int stop_bits,
+                                               int use_v14,
+                                               put_byte_func_t put_byte,
+                                               void *user_data);
+
+SPAN_DECLARE(int) async_rx_release(async_rx_state_t *s);
+
+SPAN_DECLARE(int) async_rx_free(async_rx_state_t *s);
+
+/*! Accept a bit from a received serial bit stream
+    \brief Accept a bit from a received serial bit stream
+    \param user_data An opaque point which must point to a receiver context.
+    \param bit The new bit. Some special values are supported for this field.
+        - SIG_STATUS_CARRIER_UP
+        - SIG_STATUS_CARRIER_DOWN
+        - SIG_STATUS_TRAINING_SUCCEEDED
+        - SIG_STATUS_TRAINING_FAILED
+        - SIG_STATUS_END_OF_DATA */
+SPAN_DECLARE_NONSTD(void) async_rx_put_bit(void *user_data, int bit);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif
+/*- End of file ------------------------------------------------------------*/
diff --git a/src/codec/spandsp/src/spandsp/private/async.h b/src/codec/spandsp/src/spandsp/private/async.h
new file mode 100644 (file)
index 0000000..c383764
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * private/async.h - Asynchronous serial bit stream encoding and decoding
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2003 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.
+ */
+
+#if !defined(_SPANDSP_PRIVATE_ASYNC_H_)
+#define _SPANDSP_PRIVATE_ASYNC_H_
+
+/*!
+    Asynchronous data transmit descriptor. This defines the state of a single
+    working instance of a byte to asynchronous serial converter, for use
+    in FSK modems.
+*/
+struct async_tx_state_s
+{
+    /*! \brief The number of data bits per character. */
+    int data_bits;
+    /*! \brief The type of parity. */
+    int parity;
+    /*! \brief The number of stop bits per character. */
+    int stop_bits;
+    /*! \brief A pointer to the callback routine used to get characters to be transmitted. */
+    get_byte_func_t get_byte;
+    /*! \brief An opaque pointer passed when calling get_byte. */
+    void *user_data;
+
+    /*! \brief A current, partially transmitted, character. */
+    int byte_in_progress;
+    /*! \brief The current bit position within a partially transmitted character. */
+    int bitpos;
+    /*! \brief Parity bit. */
+    int parity_bit;
+};
+
+/*!
+    Asynchronous data receive descriptor. This defines the state of a single
+    working instance of an asynchronous serial to byte converter, for use
+    in FSK modems.
+*/
+struct async_rx_state_s
+{
+    /*! \brief The number of data bits per character. */
+    int data_bits;
+    /*! \brief The type of parity. */
+    int parity;
+    /*! \brief The number of stop bits per character. */
+    int stop_bits;
+    /*! \brief TRUE if V.14 rate adaption processing should be performed. */
+    int use_v14;
+    /*! \brief A pointer to the callback routine used to handle received characters. */
+    put_byte_func_t put_byte;
+    /*! \brief An opaque pointer passed when calling put_byte. */
+    void *user_data;
+
+    /*! \brief A current, partially complete, character. */
+    int byte_in_progress;
+    /*! \brief The current bit position within a partially complete character. */
+    int bitpos;
+    /*! \brief Parity bit. */
+    int parity_bit;
+
+    /*! A count of the number of parity errors seen. */
+    int parity_errors;
+    /*! A count of the number of character framing errors seen. */
+    int framing_errors;
+};
+
+#endif
+/*- End of file ------------------------------------------------------------*/
diff --git a/src/codec/spandsp/src/spandsp/private/logging.h b/src/codec/spandsp/src/spandsp/private/logging.h
new file mode 100644 (file)
index 0000000..0e59d26
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * private/logging.h - definitions for error and debug logging.
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2005 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.
+ */
+
+#if !defined(_SPANDSP_PRIVATE_LOGGING_H_)
+#define _SPANDSP_PRIVATE_LOGGING_H_
+
+/*!
+    Logging descriptor. This defines the working state for a single instance of
+    the logging facility for spandsp.
+*/
+struct logging_state_s
+{
+    int level;
+    int samples_per_second;
+    int64_t elapsed_samples;
+    const char *tag;
+    const char *protocol;
+
+    message_handler_func_t span_message;
+    error_handler_func_t span_error;
+};
+
+#endif
+/*- End of file ------------------------------------------------------------*/
diff --git a/src/codec/spandsp/src/spandsp/private/t30.h b/src/codec/spandsp/src/spandsp/private/t30.h
new file mode 100644 (file)
index 0000000..b8a2e8b
--- /dev/null
@@ -0,0 +1,295 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * private/t30.h - definitions for T.30 fax processing
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2003 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.
+ */
+
+/*! \file */
+
+#if !defined(_SPANDSP_PRIVATE_T30_H_)
+#define _SPANDSP_PRIVATE_T30_H_
+
+/*!
+    T.30 FAX channel descriptor. This defines the state of a single working
+    instance of a T.30 FAX channel.
+*/
+struct t30_state_s
+{
+    /*! \brief T.4 context for reading or writing image data. */
+    union
+    {
+        t4_state_t rx;
+        t4_state_t tx;
+    } t4;
+    /*! \brief The type of FAX operation currently in progress */
+    int operation_in_progress;
+
+    /*! \brief TRUE if behaving as the calling party */
+    int calling_party;
+    
+    /*! \brief Internet aware FAX mode bit mask. */
+    int iaf;
+    /*! \brief A bit mask of the currently supported modem types. */
+    int supported_modems;
+    /*! \brief A bit mask of the currently supported image compression modes. */
+    int supported_compressions;
+    /*! \brief A bit mask of the currently supported image resolutions. */
+    int supported_resolutions;
+    /*! \brief A bit mask of the currently supported image sizes. */
+    int supported_image_sizes;
+    /*! \brief A bit mask of the currently supported T.30 special features. */
+    int supported_t30_features;
+    /*! \brief TRUE is ECM mode handling is enabled. */
+    int ecm_allowed;
+    /*! \brief TRUE if we are capable of retransmitting pages */
+    int retransmit_capable;
+
+    /*! \brief The received DCS, formatted as an ASCII string, for inclusion
+               in the TIFF file. */
+    char rx_dcs_string[T30_MAX_DIS_DTC_DCS_LEN*3 + 1];
+    /*! \brief The text which will be used in FAX page header. No text results
+               in no header line. */
+    char header_info[T30_MAX_PAGE_HEADER_INFO + 1];
+    /*! \brief TRUE for FAX page headers to overlay (i.e. replace) the beginning of the
+               page image. FALSE for FAX page headers to add to the overall length of
+               the page. */
+    int header_overlays_image;
+    /*! \brief TRUE if remote T.30 procedural interrupts are allowed. */
+    int remote_interrupts_allowed;
+
+    /*! \brief The information fields received. */
+    t30_exchanged_info_t rx_info;
+    /*! \brief The information fields to be transmitted. */
+    t30_exchanged_info_t tx_info;
+    /*! \brief The country of origin of the remote machine, if known, else NULL. */
+    const char *country;
+    /*! \brief The vendor of the remote machine, if known, else NULL. */
+    const char *vendor;
+    /*! \brief The model of the remote machine, if known, else NULL. */
+    const char *model;
+
+    /*! \brief A pointer to a callback routine to be called when phase B events
+        occur. */
+    t30_phase_b_handler_t *phase_b_handler;
+    /*! \brief An opaque pointer supplied in event B callbacks. */
+    void *phase_b_user_data;
+    /*! \brief A pointer to a callback routine to be called when phase D events
+        occur. */
+    t30_phase_d_handler_t *phase_d_handler;
+    /*! \brief An opaque pointer supplied in event D callbacks. */
+    void *phase_d_user_data;
+    /*! \brief A pointer to a callback routine to be called when phase E events
+        occur. */
+    t30_phase_e_handler_t *phase_e_handler;
+    /*! \brief An opaque pointer supplied in event E callbacks. */
+    void *phase_e_user_data;
+    /*! \brief A pointer to a callback routine to be called when frames are
+        exchanged. */
+    t30_real_time_frame_handler_t *real_time_frame_handler;
+    /*! \brief An opaque pointer supplied in real time frame callbacks. */
+    void *real_time_frame_user_data;
+
+    /*! \brief A pointer to a callback routine to be called when document events
+        (e.g. end of transmitted document) occur. */
+    t30_document_handler_t *document_handler;
+    /*! \brief An opaque pointer supplied in document callbacks. */
+    void *document_user_data;
+
+    /*! \brief The handler for changes to the receive mode */
+    t30_set_handler_t *set_rx_type_handler;
+    /*! \brief An opaque pointer passed to the handler for changes to the receive mode */
+    void *set_rx_type_user_data;
+    /*! \brief The handler for changes to the transmit mode */
+    t30_set_handler_t *set_tx_type_handler;
+    /*! \brief An opaque pointer passed to the handler for changes to the transmit mode */
+    void *set_tx_type_user_data;
+
+    /*! \brief The transmitted HDLC frame handler. */
+    t30_send_hdlc_handler_t *send_hdlc_handler;
+    /*! \brief An opaque pointer passed to the transmitted HDLC frame handler. */
+    void *send_hdlc_user_data;
+
+    /*! \brief The DIS code for the minimum scan row time we require. This is usually 0ms,
+        but if we are trying to simulate another type of FAX machine, we may need a non-zero
+        value here. */
+    uint8_t local_min_scan_time_code;
+
+    /*! \brief The current T.30 phase. */
+    int phase;
+    /*! \brief The T.30 phase to change to when the current phase ends. */
+    int next_phase;
+    /*! \brief The current state of the T.30 state machine. */
+    int state;
+    /*! \brief The step in sending a sequence of HDLC frames. */
+    int step;
+
+    /*! \brief The preparation buffer for the DCS message to be transmitted. */
+    uint8_t dcs_frame[T30_MAX_DIS_DTC_DCS_LEN];
+    /*! \brief The length of the DCS message to be transmitted. */
+    int dcs_len;
+    /*! \brief The preparation buffer for DIS or DTC message to be transmitted. */
+    uint8_t local_dis_dtc_frame[T30_MAX_DIS_DTC_DCS_LEN];
+    /*! \brief The length of the DIS or DTC message to be transmitted. */
+    int local_dis_dtc_len;
+    /*! \brief The last DIS or DTC message received form the far end. */
+    uint8_t far_dis_dtc_frame[T30_MAX_DIS_DTC_DCS_LEN];
+    /*! \brief The length of the last DIS or DTC message received form the far end. */
+    int far_dis_dtc_len;
+    /*! \brief TRUE if a valid DIS has been received from the far end. */
+    int dis_received;
+
+    /*! \brief TRUE if the short training sequence should be used. */
+    int short_train;
+
+    /*! \brief A count of the number of bits in the trainability test. This counts down to zero when
+        sending TCF, and counts up when receiving it. */
+    int tcf_test_bits;
+    /*! \brief The current count of consecutive received zero bits, during the trainability test. */
+    int tcf_current_zeros;
+    /*! \brief The maximum consecutive received zero bits seen to date, during the trainability test. */
+    int tcf_most_zeros;
+
+    /*! \brief The current fallback step for the fast message transfer modem. */
+    int current_fallback;
+    /*! \brief The subset of supported modems allowed at the current time, allowing for negotiation. */
+    int current_permitted_modems;
+    /*! \brief TRUE if a carrier is present. Otherwise FALSE. */
+    int rx_signal_present;
+    /*! \brief TRUE if a modem has trained correctly. */
+    int rx_trained;
+    /*! \brief TRUE if a valid HDLC frame has been received in the current reception period. */
+    int rx_frame_received;
+
+    /*! \brief Current reception mode. */
+    int current_rx_type;
+    /*! \brief Current transmission mode. */
+    int current_tx_type;
+
+    /*! \brief T0 is the answer timeout when calling another FAX machine.
+        Placing calls is handled outside the FAX processing, but this timeout keeps
+        running until V.21 modulation is sent or received.
+        T1 is the remote terminal identification timeout (in audio samples). */
+    int timer_t0_t1;
+    /*! \brief T2, T2A and T2B are the HDLC command timeouts.
+               T4, T4A and T4B are the HDLC response timeouts (in audio samples). */
+    int timer_t2_t4;
+    /*! \brief A value specifying which of the possible timers is currently running in timer_t2_t4 */
+    int timer_t2_t4_is;
+    /*! \brief Procedural interrupt timeout (in audio samples). */
+    int timer_t3;
+    /*! \brief This is only used in error correcting mode. */
+    int timer_t5;
+    /*! \brief This is only used in full duplex (e.g. ISDN) modes. */
+    int timer_t6;
+    /*! \brief This is only used in full duplex (e.g. ISDN) modes. */
+    int timer_t7;
+    /*! \brief This is only used in full duplex (e.g. ISDN) modes. */
+    int timer_t8;
+
+    /*! \brief TRUE once the far end FAX entity has been detected. */
+    int far_end_detected;
+
+    /*! \brief TRUE if a local T.30 interrupt is pending. */
+    int local_interrupt_pending;
+    /*! \brief The image coding being used on the line. */
+    int line_encoding;
+    /*! \brief The image coding being used for output files. */
+    int output_encoding;
+    /*! \brief The current DCS message minimum scan time code. */
+    uint8_t min_scan_time_code;
+    /*! \brief The X direction resolution of the current image, in pixels per metre. */
+    int x_resolution;
+    /*! \brief The Y direction resolution of the current image, in pixels per metre. */
+    int y_resolution;
+    /*! \brief The width of the current image, in pixels. */
+    t4_image_width_t image_width;
+    /*! \brief Current number of retries of the action in progress. */
+    int retries;
+    /*! \brief TRUE if error correcting mode is used. */
+    int error_correcting_mode;
+    /*! \brief The number of HDLC frame retries, if error correcting mode is used. */
+    int error_correcting_mode_retries;
+    /*! \brief The current count of consecutive T30_PPR messages. */
+    int ppr_count;
+    /*! \brief The current count of consecutive T30_RNR messages. */
+    int receiver_not_ready_count;
+    /*! \brief The number of octets to be used per ECM frame. */
+    int octets_per_ecm_frame;
+    /*! \brief The ECM partial page buffer. */
+    uint8_t ecm_data[256][260];
+    /*! \brief The lengths of the frames in the ECM partial page buffer. */
+    int16_t ecm_len[256];
+    /*! \brief A bit map of the OK ECM frames, constructed as a PPR frame. */
+    uint8_t ecm_frame_map[3 + 32];
+    
+    /*! \brief The current page number for receiving, in ECM or non-ECM mode. This is reset at the start of a call. */
+    int rx_page_number;
+    /*! \brief The current page number for sending, in ECM or non-ECM mode. This is reset at the start of a call. */
+    int tx_page_number;
+    /*! \brief The current block number, in ECM mode */
+    int ecm_block;
+    /*! \brief The number of frames in the current block number, in ECM mode */
+    int ecm_frames;
+    /*! \brief The number of frames sent in the current burst of image transmission, in ECM mode */
+    int ecm_frames_this_tx_burst;
+    /*! \brief The current ECM frame, during ECM transmission. */
+    int ecm_current_tx_frame;
+    /*! \brief TRUE if we are at the end of an ECM page to se sent - i.e. there are no more
+        partial pages still to come. */
+    int ecm_at_page_end;
+
+    /*! \brief The transmission step queued to follow the one in progress. */
+    int next_tx_step;
+    /*! \brief The FCF for the next receive step. */
+    uint8_t next_rx_step;
+    /*! \brief Image file name for image reception. */
+    char rx_file[256];
+    /*! \brief The last page we are prepared accept for a received image file. -1 means no restriction. */
+    int rx_stop_page;
+    /*! \brief Image file name to be sent. */
+    char tx_file[256];
+    /*! \brief The first page to be sent from the image file. -1 means no restriction. */
+    int tx_start_page;
+    /*! \brief The last page to be sent from the image file. -1 means no restriction. */
+    int tx_stop_page;
+    /*! \brief The current completion status. */
+    int current_status;
+
+    /*! \brief The number of RTP events */
+    int rtp_events;
+    /*! \brief The number of RTN events */
+    int rtn_events;
+
+    /*! \brief the FCF2 field of the last PPS message we received. */
+    uint8_t last_pps_fcf2;
+    /*! \brief TRUE if all frames of the current received ECM block are now OK */
+    int rx_ecm_block_ok;
+    /*! \brief A count of successfully received ECM frames, to assess progress as a basis for
+        deciding whether to continue error correction when PPRs keep repeating. */
+    int ecm_progress;
+
+    /*! \brief Error and flow logging control */
+    logging_state_t logging;
+};
+
+#endif
+/*- End of file ------------------------------------------------------------*/
diff --git a/src/codec/spandsp/src/spandsp/private/t38_core.h b/src/codec/spandsp/src/spandsp/private/t38_core.h
new file mode 100644 (file)
index 0000000..8f71321
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * private/t38_core.h - An implementation of T.38, less the packet exchange part
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2005 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.
+ */
+
+#if !defined(_SPANDSP_PRIVATE_T38_CORE_H_)
+#define _SPANDSP_PRIVATE_T38_CORE_H_
+
+/*!
+    Core T.38 state, common to all modes of T.38.
+*/
+struct t38_core_state_s
+{
+    /*! \brief Handler routine to transmit IFP packets generated by the T.38 protocol engine */
+    t38_tx_packet_handler_t *tx_packet_handler;
+    /*! \brief An opaque pointer passed to tx_packet_handler */
+    void *tx_packet_user_data;
+
+    /*! \brief Handler routine to process received indicator packets */
+    t38_rx_indicator_handler_t *rx_indicator_handler;
+    /*! \brief Handler routine to process received data packets */
+    t38_rx_data_handler_t *rx_data_handler;
+    /*! \brief Handler routine to process the missing packet condition */
+    t38_rx_missing_handler_t *rx_missing_handler;
+    /*! \brief An opaque pointer passed to any of the above receive handling routines */
+    void *rx_user_data;
+
+    /*! NOTE - Bandwidth reduction shall only be done on suitable Phase C data, i.e., MH, MR
+        and - in the case of transcoding to JBIG - MMR. MMR and JBIG require reliable data
+        transport such as that provided by TCP. When transcoding is selected, it shall be
+        applied to every suitable page in a call. */
+
+    /*! \brief Method 1: Local generation of TCF (required for use with TCP).
+               Method 2: Transfer of TCF is required for use with UDP (UDPTL or RTP).
+               Method 2 is not recommended for use with TCP. */
+    int data_rate_management_method;
+    
+    /*! \brief The emitting gateway may indicate a preference for either UDP/UDPTL, or
+               UDP/RTP, or TCP for transport of T.38 IFP Packets. The receiving device
+               selects the transport protocol. */
+    int data_transport_protocol;
+
+    /*! \brief Indicates the capability to remove and insert fill bits in Phase C, non-ECM
+        data to reduce bandwidth in the packet network. */
+    int fill_bit_removal;
+
+    /*! \brief Indicates the ability to convert to/from MMR from/to the line format to
+        improve the compression of the data, and reduce the bandwidth, in the
+        packet network. */
+    int mmr_transcoding;
+
+    /*! \brief Indicates the ability to convert to/from JBIG to reduce bandwidth. */
+    int jbig_transcoding;
+
+    /*! \brief For UDP (UDPTL or RTP) modes, this option indicates the maximum
+               number of octets that can be stored on the remote device before an
+               overflow condition occurs. It is the responsibility of the transmitting
+               application to limit the transfer rate to prevent an overflow. The
+               negotiated data rate should be used to determine the rate at which
+               data is being removed from the buffer. */
+    int max_buffer_size;
+
+    /*! \brief This option indicates the maximum size of a UDPTL packet or the
+               maximum size of the payload within an RTP packet that can be accepted 
+               by the remote device. */
+    int max_datagram_size;
+
+    /*! \brief This is the version number of ITU-T Rec. T.38. New versions shall be
+               compatible with previous versions. */
+    int t38_version;
+
+    /*! \brief Allow time for TEP playout */
+    int allow_for_tep;
+
+    /*! \brief The fastest data rate supported by the T.38 channel. */
+    int fastest_image_data_rate;
+
+    /*! \brief The number of times each packet type will be sent (low byte). The 
+               depth of redundancy (2nd byte). Higher numbers may increase reliability
+               for UDP transmission. Zero is valid for the indicator packet category,
+               to suppress all indicator packets (typicaly for TCP transmission). */
+    int category_control[5];
+
+    /*! \brief TRUE if IFP packet sequence numbers are relevant. For some transports, like TPKT
+               over TCP they are not relevent. */
+    int check_sequence_numbers;
+
+    /*! \brief The sequence number for the next packet to be transmitted */
+    int tx_seq_no;
+    /*! \brief The sequence number expected in the next received packet */
+    int rx_expected_seq_no;
+
+    /*! \brief The current receive indicator - i.e. the last indicator received */
+    int current_rx_indicator;
+    /*! \brief The current receive data type - i.e. the last data type received */
+    int current_rx_data_type;
+    /*! \brief The current receive field type - i.e. the last field_type received */
+    int current_rx_field_type;
+    /*! \brief The current transmit indicator - i.e. the last indicator transmitted */
+    int current_tx_indicator;
+    /*! \brief The bit rate for V.34 operation */
+    int v34_rate;
+
+    /*! A count of missing receive packets. This count might not be accurate if the
+        received packet numbers jump wildly. */
+    int missing_packets;
+
+    /*! \brief Error and flow logging control */
+    logging_state_t logging;
+};
+
+#endif
+/*- End of file ------------------------------------------------------------*/
diff --git a/src/codec/spandsp/src/spandsp/private/t38_terminal.h b/src/codec/spandsp/src/spandsp/private/t38_terminal.h
new file mode 100644 (file)
index 0000000..6681f93
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * private/t38_terminal.h - T.38 termination, less the packet exchange part
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2005 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.
+ */
+
+/*! \file */
+
+#if !defined(_SPANDSP_PRIVATE_T38_TERMINAL_H_)
+#define _SPANDSP_PRIVATE_T38_TERMINAL_H_
+
+typedef struct
+{
+    /*! \brief Internet Aware FAX mode bit mask. */
+    int iaf;
+    /*! \brief Required time between T.38 transmissions, in ms. */
+    int ms_per_tx_chunk;
+    /*! \brief Bit fields controlling the way data is packed into chunked for transmission. */
+    int chunking_modes;
+
+    /*! \brief Core T.38 IFP support */
+    t38_core_state_t t38;
+
+    /*! \brief The current transmit step being timed */
+    int timed_step;
+
+    /*! \brief TRUE is there has been some T.38 data missed (i.e. lost packets) in the current
+               reception period. */
+    int rx_data_missing;
+
+    /*! \brief The number of octets to send in each image packet (non-ECM or ECM) at the current
+               rate and the current specified packet interval. */
+    int octets_per_data_packet;
+
+    struct
+    {
+        /*! \brief HDLC receive buffer */
+        uint8_t buf[T38_MAX_HDLC_LEN];
+        /*! \brief The length of the contents of the HDLC receive buffer */
+        int len;
+    } hdlc_rx;
+
+    struct
+    {
+        /*! \brief HDLC transmit buffer */
+        uint8_t buf[T38_MAX_HDLC_LEN];
+        /*! \brief The length of the contents of the HDLC transmit buffer */
+        int len;
+        /*! \brief Current pointer within the contents of the HDLC transmit buffer */
+        int ptr;
+        /*! \brief The number of extra bits in a fully stuffed version of the
+                   contents of the HDLC transmit buffer. This is needed to accurately
+                   estimate the playout time for this frame, through an analogue modem. */
+        int extra_bits;
+    } hdlc_tx;
+
+    /*! \brief Counter for trailing non-ECM bytes, used to flush out the far end's modem. */
+    int non_ecm_trailer_bytes;
+
+    /*! \brief The next T.38 indicator queued for transmission. */
+    int next_tx_indicator;
+    /*! \brief The current T.38 data type being transmitted. */
+    int current_tx_data_type;
+
+    /*! \brief TRUE if a carrier is present. Otherwise FALSE. */
+    int rx_signal_present;
+
+    /*! \brief The current operating mode of the receiver. */
+    int current_rx_type;
+    /*! \brief The current operating mode of the transmitter. */
+    int current_tx_type;
+
+    /*! \brief Current transmission bit rate. */
+    int tx_bit_rate;
+    /*! \brief A "sample" count, used to time events. */
+    int32_t samples;
+    /*! \brief The value for samples at the next transmission point. */
+    int32_t next_tx_samples;
+    /*! \brief The current transmit timeout. */
+    //int32_t timeout_tx_samples;
+    /*! \brief The current receive timeout. */
+    int32_t timeout_rx_samples;
+} t38_terminal_front_end_state_t;
+
+/*!
+    T.38 terminal state.
+*/
+struct t38_terminal_state_s
+{
+    /*! \brief The T.30 back-end */
+    t30_state_t t30;
+
+    /*! \brief The T.38 front-end */
+    t38_terminal_front_end_state_t t38_fe;
+
+    /*! \brief Error and flow logging control */
+    logging_state_t logging;
+};
+
+#endif
+/*- End of file ------------------------------------------------------------*/
diff --git a/src/codec/spandsp/src/spandsp/private/t4_rx.h b/src/codec/spandsp/src/spandsp/private/t4_rx.h
new file mode 100644 (file)
index 0000000..ba0d4ca
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * private/t4_rx.h - definitions for T.4 FAX receive processing
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2003 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.
+ */
+
+#if !defined(_SPANDSP_PRIVATE_T4_RX_H_)
+#define _SPANDSP_PRIVATE_T4_RX_H_
+
+/*!
+    TIFF specific state information to go with T.4 compression or decompression handling.
+*/
+typedef struct
+{
+    /*! \brief The current file name. */
+    const char *file;
+    /*! \brief The libtiff context for the current TIFF file */
+    TIFF *tiff_file;
+
+    /*! \brief The compression type for output to the TIFF file. */
+    int32_t output_compression;
+    /*! \brief The TIFF photometric setting for the current page. */
+    uint16_t photo_metric;
+    /*! \brief The TIFF fill order setting for the current page. */
+    uint16_t fill_order;
+    /*! \brief The TIFF G3 FAX options. */
+    int32_t output_t4_options;
+
+    /*! \brief The number of pages in the current image file. */
+    int pages_in_file;
+
+    /* "Background" information about the FAX, which can be stored in the image file. */
+    /*! \brief The vendor of the machine which produced the file. */ 
+    const char *vendor;
+    /*! \brief The model of machine which produced the file. */ 
+    const char *model;
+    /*! \brief The local ident string. */ 
+    const char *local_ident;
+    /*! \brief The remote end's ident string. */ 
+    const char *far_ident;
+    /*! \brief The FAX sub-address. */ 
+    const char *sub_address;
+    /*! \brief The FAX DCS information, as an ASCII string. */ 
+    const char *dcs;
+
+    /*! \brief The first page to transfer. -1 to start at the beginning of the file. */
+    int start_page;
+    /*! \brief The last page to transfer. -1 to continue to the end of the file. */
+    int stop_page;
+} t4_tiff_state_t;
+
+#endif
+/*- End of file ------------------------------------------------------------*/
diff --git a/src/codec/spandsp/src/spandsp/private/t4_t6_decode.h b/src/codec/spandsp/src/spandsp/private/t4_t6_decode.h
new file mode 100644 (file)
index 0000000..1b15fc7
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * private/t4_t6_decode.h - definitions for T.4/T.6 fax decoding
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2003, 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.
+ */
+
+#if !defined(_SPANDSP_PRIVATE_T4_T6_DECODE_H_)
+#define _SPANDSP_PRIVATE_T4_T6_DECODE_H_
+
+/*!
+    T.4 1D, T4 2D and T6 decompressor state.
+*/
+struct t4_t6_decode_state_s
+{
+    /*! \brief The type of compression used between the FAX machines. */
+    //int encoding;
+    /*! \brief Width of the current page, in pixels. */
+    //int image_width;
+
+    /*! \brief Callback function to write a row of pixels to the image destination. */
+    t4_row_write_handler_t row_write_handler;
+    /*! \brief Opaque pointer passed to row_write_handler. */
+    void *row_write_user_data;
+
+    /*! \brief A pointer into the image buffer indicating where the last row begins */
+    int last_row_starts_at;
+
+    /*! \brief This variable is used to count the consecutive EOLS we have seen. If it
+               reaches six, this is the end of the image. It is initially set to -1 for
+               1D and 2D decoding, as an indicator that we must wait for the first EOL,
+               before decoding any image data. */
+    int consecutive_eols;
+
+    /*! \brief The reference or starting changing element on the coding line. At the
+               start of the coding line, a0 is set on an imaginary white changing element
+               situated just before the first element on the line. During the coding of
+               the coding line, the position of a0 is defined by the previous coding mode.
+               (See T.4/4.2.1.3.2.). */
+    int a0;
+    /*! \brief The first changing element on the reference line to the right of a0 and of
+               opposite colour to a0. */
+    int b1;
+    /*! \brief The length of the in-progress run of black or white. */
+    int run_length;
+    /*! \brief 2D horizontal mode control. */
+    int black_white;
+    /*! \brief TRUE if the current run is black */
+    int its_black;
+
+    /*! \brief The current step into the current row run-lengths buffer. */
+    int a_cursor;
+    /*! \brief The current step into the reference row run-lengths buffer. */
+    int b_cursor;
+
+    /*! \brief Incoming bit buffer for decompression. */
+    uint32_t rx_bitstream;
+    /*! \brief The number of bits currently in rx_bitstream. */
+    int rx_bits;
+    /*! \brief The number of bits to be skipped before trying to match the next code word. */
+    int rx_skip_bits;
+
+    /*! \brief Decoded pixel buffer. */
+    //uint32_t pixel_stream;
+    /*! \brief The number of bits currently in pixel_stream. */
+    //int tx_bits;
+
+    /*! \brief Current pixel row number. */
+    //int row;
+
+    /*! \brief The current number of consecutive bad rows. */
+    int curr_bad_row_run;
+    /*! \brief The longest run of consecutive bad rows seen in the current page. */
+    int longest_bad_row_run;
+    /*! \brief The total number of bad rows in the current page. */
+    int bad_rows;
+
+    /*! \brief Error and flow logging control */
+    //logging_state_t logging;
+};
+
+#endif
+/*- End of file ------------------------------------------------------------*/
diff --git a/src/codec/spandsp/src/spandsp/private/t4_t6_encode.h b/src/codec/spandsp/src/spandsp/private/t4_t6_encode.h
new file mode 100644 (file)
index 0000000..40d750b
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * private/t4_t6_encode.h - definitions for T.4/T.6 fax compression
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2003 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.
+ */
+
+#if !defined(_SPANDSP_PRIVATE_T4_T6_ENCODE_H_)
+#define _SPANDSP_PRIVATE_T4_T6_ENCODE_H_
+
+/*!
+    T.4 1D, T4 2D and T6 compressor state.
+*/
+struct t4_t6_encode_state_s
+{
+    /*! \brief The minimum number of encoded bits per row. This is a timing thing
+               for hardware FAX machines. */
+    int min_bits_per_row;
+    /*! \brief The current maximum contiguous rows that may be 2D encoded. */
+    int max_rows_to_next_1d_row;
+
+    /*! \brief Number of rows left that can be 2D encoded, before a 1D encoded row
+               must be used. */
+    int rows_to_next_1d_row;
+
+    /*! \brief The number of runs currently in the reference row. */
+    int ref_steps;
+
+    /*! \brief Pointer to the byte containing the next image bit to transmit. */
+    int bit_pos;
+    /*! \brief Pointer to the bit within the byte containing the next image bit to transmit. */
+    int bit_ptr;
+
+    /*! \brief Callback function to read a row of pixels from the image source. */
+    t4_row_read_handler_t row_read_handler;
+    /*! \brief Opaque pointer passed to row_read_handler. */
+    void *row_read_user_data;
+};
+
+#endif
+/*- End of file ------------------------------------------------------------*/
diff --git a/src/codec/spandsp/src/spandsp/private/t4_tx.h b/src/codec/spandsp/src/spandsp/private/t4_tx.h
new file mode 100644 (file)
index 0000000..236523a
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * private/t4_tx.h - definitions for T.4 FAX transmit processing
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2003 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.
+ */
+
+#if !defined(_SPANDSP_PRIVATE_T4_TX_H_)
+#define _SPANDSP_PRIVATE_T4_TX_H_
+
+/*!
+    T.4 FAX compression/decompression descriptor. This defines the working state
+    for a single instance of a T.4 FAX compression or decompression channel.
+*/
+struct t4_state_s
+{
+    /*! \brief The same structure is used for T.4 transmit and receive. This variable
+               records which mode is in progress. */
+    int rx;
+
+    /*! \brief The type of compression used between the FAX machines. */
+    int line_encoding;
+
+    /*! \brief The time at which handling of the current page began. */
+    time_t page_start_time;
+
+    /*! \brief The text which will be used in FAX page header. No text results
+               in no header line. */
+    const char *header_info;
+    /*! \brief Optional per instance time zone for the FAX pager header timestamp. */
+    struct tz_s *tz;
+
+    /*! \brief The size of the compressed image on the line side, in bits. */
+    int line_image_size;
+
+    /*! \brief The current number of bytes per row of uncompressed image data. */
+    int bytes_per_row;
+    /*! \brief The size of the image in the image buffer, in bytes. */
+    int image_size;
+    /*! \brief The current size of the image buffer. */
+    int image_buffer_size;
+    /*! \brief A point to the image buffer. */
+    uint8_t *image_buffer;
+
+    /*! \brief The number of pages transferred to date. */
+    int current_page;
+    /*! \brief Column-to-column (X) resolution in pixels per metre. */
+    int x_resolution;
+    /*! \brief Row-to-row (Y) resolution in pixels per metre. */
+    int y_resolution;
+    /*! \brief Width of the current page, in pixels. */
+    int image_width;
+    /*! \brief Length of the current page, in pixels. */
+    int image_length;
+    /*! \brief Current pixel row number. */
+    int row;
+
+    /*! \brief This variable is set if we are treating the current row as a 2D encoded
+               one. */
+    int row_is_2d;
+    /*! \brief The current length of the current row. */
+    int row_len;
+
+    /*! \brief Black and white run-lengths for the current row. */
+    uint32_t *cur_runs;
+    /*! \brief Black and white run-lengths for the reference row. */
+    uint32_t *ref_runs;
+    /*! \brief Pointer to the buffer for the current pixel row. */
+    uint8_t *row_buf;
+
+    /*! \brief Encoded data bits buffer. */
+    uint32_t tx_bitstream;
+    /*! \brief The number of bits currently in tx_bitstream. */
+    int tx_bits;
+
+    /*! \brief The current number of bits in the current encoded row. */
+    int row_bits;
+    /*! \brief The minimum bits in any row of the current page. For monitoring only. */
+    int min_row_bits;
+    /*! \brief The maximum bits in any row of the current page. For monitoring only. */
+    int max_row_bits;
+
+    /*! \brief Error and flow logging control */
+    logging_state_t logging;
+
+    /*! \brief All TIFF file specific state information for the T.4 context. */
+    t4_tiff_state_t tiff;
+    t4_t6_decode_state_t t4_t6_rx;
+    t4_t6_encode_state_t t4_t6_tx;
+};
+
+#endif
+/*- End of file ------------------------------------------------------------*/
diff --git a/src/codec/spandsp/src/spandsp/private/timezone.h b/src/codec/spandsp/src/spandsp/private/timezone.h
new file mode 100644 (file)
index 0000000..9f28cc3
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * private/timezone.h - Timezone handling for time interpretation
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2010 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.
+ */
+
+#if !defined(_SPANDSP_PRIVATE_TIMEZONE_H_)
+#define _SPANDSP_PRIVATE_TIMEZONE_H_
+
+#define TZ_MAX_CHARS            50      /* Maximum number of abbreviation characters */
+
+#define TZ_MAX_LEAPS            50      /* Maximum number of leap second corrections */
+
+#define TZNAME_MAX              255
+
+/* The TZ_MAX_TIMES value below is enough to handle a bit more than a
+ * year's worth of solar time (corrected daily to the nearest second) or
+ * 138 years of Pacific Presidential Election time
+ * (where there are three time zone transitions every fourth year). */
+#define TZ_MAX_TIMES            370
+
+#if !defined(NOSOLAR)
+#define TZ_MAX_TYPES            256     /* Limited by what (unsigned char)'s can hold */
+#else
+/* Must be at least 14 for Europe/Riga as of Jan 12 1995,
+ * as noted by Earl Chew <earl@hpato.aus.hp.com>. */
+#define TZ_MAX_TYPES            20      /* Maximum number of local time types */
+#endif
+
+#define TZ_BIGGEST(a, b)        (((a) > (b)) ? (a) : (b))
+
+/* Time type information */
+struct tz_ttinfo_s
+{
+    int32_t gmtoff;             /* UTC offset in seconds */
+    int isdst;                  /* Used to set tm_isdst */
+    int abbrind;                /* Abbreviation list index */
+    int ttisstd;                /* TRUE if transition is std time */
+    int ttisgmt;                /* TRUE if transition is UTC */
+};
+
+/* Leap second information */
+struct tz_lsinfo_s
+{
+    time_t trans;               /* Transition time */
+    int32_t corr;               /* Correction to apply */
+};
+
+struct tz_state_s
+{
+    int leapcnt;
+    int timecnt;
+    int typecnt;
+    int charcnt;
+    time_t ats[TZ_MAX_TIMES];
+    uint8_t types[TZ_MAX_TIMES];
+    struct tz_ttinfo_s ttis[TZ_MAX_TYPES];
+    char chars[TZ_BIGGEST(TZ_MAX_CHARS + 1, (2*(TZNAME_MAX + 1)))];
+    struct tz_lsinfo_s lsis[TZ_MAX_LEAPS];
+};
+
+struct tz_s
+{
+    struct tz_state_s state;
+    char lcl_tzname[TZNAME_MAX + 1];
+    int lcl_is_set;
+    const char *tzname[2];
+};
+
+#endif
+/*- End of file ------------------------------------------------------------*/
diff --git a/src/codec/spandsp/src/spandsp/t30.h b/src/codec/spandsp/src/spandsp/t30.h
new file mode 100644 (file)
index 0000000..a2fff2d
--- /dev/null
@@ -0,0 +1,696 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * t30.h - definitions for T.30 fax processing
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2003 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.
+ */
+
+/*! \file */
+
+#if !defined(_SPANDSP_T30_H_)
+#define _SPANDSP_T30_H_
+
+/*! \page t30_page T.30 FAX protocol handling
+
+\section t30_page_sec_1 What does it do?
+The T.30 protocol is the core protocol used for FAX transmission. This module
+implements most of its key featrues. It does not interface to the outside work.
+Seperate modules do that for T.38, analogue line, and other forms of FAX
+communication.
+
+Current features of this module include:
+
+    - FAXing to and from multi-page TIFF/F files, whose images are one of the standard
+      FAX sizes.
+    - V.27ter, V.29 and V.17 modes (2400bps, to 14,400bps).
+    - T.4 1D (MH), T.4 2D,(MR) and T.6 (MMR) compression.
+    - Error correction mode (ECM).
+    - All standard horizonal resolutions (R8, R16, 300dpi, 600dpi, 800dpi, 1200dpi).
+    - All standard vertical resolutions (standard, fine, superfine, 300dpi, 600dpi, 800dpi, 1200dpi).
+    - All standard page widths (A4, B4, A3).
+    - All standard page lengths (A4, B4, North American letter, North American legal, continuous).
+    - Monitoring and sending identifier strings (CSI, TSI, and CIG).
+    - Monitoring and sending sub-address strings (SUB).
+    - Monitoring and sending polling sub-addresses (SEP).
+    - Monitoring and sending polled sub-addresses (PSA).
+    - Monitoring and sending sender identifications (SID).
+    - Monitoring and sending passwords (PWD).
+    - Monitoring of non-standard facility frames (NSF, NSC, and NSS).
+    - Sending custom non-standard facility frames (NSF, NSC, and NSS).
+    - Analogue modem and T.38 operation.
+
+\section t30_page_sec_2 How does it work?
+
+Some of the following is paraphrased from some notes found a while ago on the Internet.
+I cannot remember exactly where they came from, but they are useful.
+
+\subsection t30_page_sec_2a The answer (CED) tone
+
+The T.30 standard says an answering fax device must send CED (a 2100Hz tone) for
+approximately 3 seconds before sending the first handshake message. Some machines
+send an 1100Hz or 1850Hz tone, and some send no tone at all. In fact, this answer
+tone is so unpredictable, it cannot really be used. It should, however, always be
+generated according to the specification.
+
+\subsection t30_page_sec_2b Common Timing Deviations
+
+The T.30 spec. specifies a number of time-outs. For example, after dialing a number,
+a calling fax system should listen for a response for 35 seconds before giving up.
+These time-out periods are as follows: 
+
+    - T1 - 35+-5s: the maximum time for which two fax system will attempt to identify each other
+    - T2 - 6+-1s:  a time-out used to start the sequence for changing transmit parameters
+    - T3 - 10+-5s: a time-out used in handling operator interrupts
+    - T5 - 60+-5s: a time-out used in error correction mode
+
+These time-outs are sometimes misinterpreted. In addition, they are routinely
+ignored, sometimes with good reason. For example, after placing a call, the
+calling fax system is supposed to wait for 35 seconds before giving up. If the
+answering unit does not answer on the first ring or if a voice answering machine
+is connected to the line, or if there are many delays through the network,
+the delay before answer can be much longer than 35 seconds. 
+
+Fax units that support error correction mode (ECM) can respond to a post-image
+handshake message with a receiver not ready (RNR) message. The calling unit then
+queries the receiving fax unit with a receiver ready (RR) message. If the
+answering unit is still busy (printing for example), it will repeat the RNR
+message. According to the T.30 standard, this sequence (RR/RNR RR/RNR) can be
+repeated for up to the end of T5 (60+-5s). However, many fax systems
+ignore the time-out and will continue the sequence indefinitely, unless the user
+manually overrides. 
+
+All the time-outs are subject to alteration, and sometimes misuse. Good T.30
+implementations must do the right thing, and tolerate others doing the wrong thing.
+
+\subsection t30_page_sec_2c Variations in the inter-carrier gap
+
+T.30 specifies 75+-20ms of silence between signals using different modulation
+schemes. Examples are between the end of a DCS signal and the start of a TCF signal,
+and between the end of an image and the start of a post-image signal. Many fax systems
+violate this requirement, especially for the silent period between DCS and TCF.
+This may be stretched to well over 100ms. If this period is too long, it can interfere with
+handshake signal error recovery, should a packet be corrupted on the line. Systems
+should ensure they stay within the prescribed T.30 limits, and be tolerant of others
+being out of spec.. 
+
+\subsection t30_page_sec_2d Other timing variations
+
+Testing is required to determine the ability of a fax system to handle
+variations in the duration of pauses between unacknowledged handshake message
+repetitions, and also in the pauses between the receipt of a handshake command and
+the start of a response to that command. In order to reduce the total
+transmission time, many fax systems start sending a response message before the
+end of the command has been received. 
+
+\subsection t30_page_sec_2e Other deviations from the T.30 standard
+
+There are many other commonly encountered variations between machines, including:
+
+    - frame sequence deviations
+    - preamble and flag sequence variations
+    - improper EOM usage
+    - unusual data rate fallback sequences
+    - common training pattern detection algorithms
+    - image transmission deviations
+    - use of the talker echo protect tone
+    - image padding and short lines
+    - RTP/RTN handshake message usage
+    - long duration lines
+    - nonstandard disconnect sequences
+    - DCN usage
+*/
+
+/*! The maximum length of a DIS, DTC or DCS frame */
+#define T30_MAX_DIS_DTC_DCS_LEN     22
+/*! The maximum length of the body of an ident string */
+#define T30_MAX_IDENT_LEN           20
+/*! The maximum length of the user string to insert in page headers */
+#define T30_MAX_PAGE_HEADER_INFO    50
+
+typedef struct t30_state_s t30_state_t;
+
+/*!
+    T.30 phase B callback handler. This handler can be used to process addition
+    information available in some FAX calls, such as passwords. The callback handler
+    can access whatever additional information might have been received, using
+    t30_get_received_info().
+    \brief T.30 phase B callback handler.
+    \param s The T.30 context.
+    \param user_data An opaque pointer.
+    \param result The phase B event code.
+    \return The new status. Normally, T30_ERR_OK is returned.
+*/
+typedef int (t30_phase_b_handler_t)(t30_state_t *s, void *user_data, int result);
+
+/*!
+    T.30 phase D callback handler.
+    \brief T.30 phase D callback handler.
+    \param s The T.30 context.
+    \param user_data An opaque pointer.
+    \param result The phase D event code.
+    \return The new status. Normally, T30_ERR_OK is returned.
+*/
+typedef int (t30_phase_d_handler_t)(t30_state_t *s, void *user_data, int result);
+
+/*!
+    T.30 phase E callback handler.
+    \brief T.30 phase E callback handler.
+    \param s The T.30 context.
+    \param user_data An opaque pointer.
+    \param completion_code The phase E completion code.
+*/
+typedef void (t30_phase_e_handler_t)(t30_state_t *s, void *user_data, int completion_code);
+
+/*!
+    T.30 real time frame handler.
+    \brief T.30 real time frame handler.
+    \param s The T.30 context.
+    \param user_data An opaque pointer.
+    \param direction TRUE for incoming, FALSE for outgoing.
+    \param msg The HDLC message.
+    \param len The length of the message.
+*/
+typedef void (t30_real_time_frame_handler_t)(t30_state_t *s,
+                                             void *user_data,
+                                             int direction,
+                                             const uint8_t msg[],
+                                             int len);
+
+/*!
+    T.30 document handler.
+    \brief T.30 document handler.
+    \param s The T.30 context.
+    \param user_data An opaque pointer.
+    \param result The document event code.
+*/
+typedef int (t30_document_handler_t)(t30_state_t *s, void *user_data, int status);
+
+/*!
+    T.30 set a receive or transmit type handler.
+    \brief T.30 set a receive or transmit type handler.
+    \param user_data An opaque pointer.
+    \param type The modem, tone or silence to be sent or received.
+    \param bit_rate The bit rate of the modem to be sent or received.
+    \param short_train TRUE if the short training sequence should be used (where one exists).
+    \param use_hdlc FALSE for bit stream, TRUE for HDLC framing.
+*/
+typedef void (t30_set_handler_t)(void *user_data, int type, int bit_rate, int short_train, int use_hdlc);
+
+/*!
+    T.30 send HDLC handler.
+    \brief T.30 send HDLC handler.
+    \param user_data An opaque pointer.
+    \param msg The HDLC message.
+    \param len The length of the message.
+*/
+typedef void (t30_send_hdlc_handler_t)(void *user_data, const uint8_t msg[], int len);
+
+/*!
+    T.30 protocol completion codes, at phase E.
+*/
+enum
+{
+    T30_ERR_OK = 0,             /*! OK */
+
+    /* Link problems */
+    T30_ERR_CEDTONE,            /*! The CED tone exceeded 5s */
+    T30_ERR_T0_EXPIRED,         /*! Timed out waiting for initial communication */
+    T30_ERR_T1_EXPIRED,         /*! Timed out waiting for the first message */
+    T30_ERR_T3_EXPIRED,         /*! Timed out waiting for procedural interrupt */
+    T30_ERR_HDLC_CARRIER,       /*! The HDLC carrier did not stop in a timely manner */
+    T30_ERR_CANNOT_TRAIN,       /*! Failed to train with any of the compatible modems */
+    T30_ERR_OPER_INT_FAIL,      /*! Operator intervention failed */
+    T30_ERR_INCOMPATIBLE,       /*! Far end is not compatible */
+    T30_ERR_RX_INCAPABLE,       /*! Far end is not able to receive */
+    T30_ERR_TX_INCAPABLE,       /*! Far end is not able to transmit */
+    T30_ERR_NORESSUPPORT,       /*! Far end cannot receive at the resolution of the image */
+    T30_ERR_NOSIZESUPPORT,      /*! Far end cannot receive at the size of image */
+    T30_ERR_UNEXPECTED,         /*! Unexpected message received */
+
+    /* Phase E status values returned to a transmitter */
+    T30_ERR_TX_BADDCS,          /*! Received bad response to DCS or training */
+    T30_ERR_TX_BADPG,           /*! Received a DCN from remote after sending a page */
+    T30_ERR_TX_ECMPHD,          /*! Invalid ECM response received from receiver */
+    T30_ERR_TX_GOTDCN,          /*! Received a DCN while waiting for a DIS */
+    T30_ERR_TX_INVALRSP,        /*! Invalid response after sending a page */
+    T30_ERR_TX_NODIS,           /*! Received other than DIS while waiting for DIS */
+    T30_ERR_TX_PHBDEAD,         /*! Received no response to DCS, training or TCF */
+    T30_ERR_TX_PHDDEAD,         /*! No response after sending a page */
+    T30_ERR_TX_T5EXP,           /*! Timed out waiting for receiver ready (ECM mode) */
+
+    /* Phase E status values returned to a receiver */
+    T30_ERR_RX_ECMPHD,          /*! Invalid ECM response received from transmitter */
+    T30_ERR_RX_GOTDCS,          /*! DCS received while waiting for DTC */
+    T30_ERR_RX_INVALCMD,        /*! Unexpected command after page received */
+    T30_ERR_RX_NOCARRIER,       /*! Carrier lost during fax receive */
+    T30_ERR_RX_NOEOL,           /*! Timed out while waiting for EOL (end Of line) */
+    T30_ERR_RX_NOFAX,           /*! Timed out while waiting for first line */
+    T30_ERR_RX_T2EXPDCN,        /*! Timer T2 expired while waiting for DCN */
+    T30_ERR_RX_T2EXPD,          /*! Timer T2 expired while waiting for phase D */
+    T30_ERR_RX_T2EXPFAX,        /*! Timer T2 expired while waiting for fax page */
+    T30_ERR_RX_T2EXPMPS,        /*! Timer T2 expired while waiting for next fax page */
+    T30_ERR_RX_T2EXPRR,         /*! Timer T2 expired while waiting for RR command */
+    T30_ERR_RX_T2EXP,           /*! Timer T2 expired while waiting for NSS, DCS or MCF */
+    T30_ERR_RX_DCNWHY,          /*! Unexpected DCN while waiting for DCS or DIS */
+    T30_ERR_RX_DCNDATA,         /*! Unexpected DCN while waiting for image data */
+    T30_ERR_RX_DCNFAX,          /*! Unexpected DCN while waiting for EOM, EOP or MPS */
+    T30_ERR_RX_DCNPHD,          /*! Unexpected DCN after EOM or MPS sequence */
+    T30_ERR_RX_DCNRRD,          /*! Unexpected DCN after RR/RNR sequence */
+    T30_ERR_RX_DCNNORTN,        /*! Unexpected DCN after requested retransmission */
+
+    /* TIFF file problems */
+    T30_ERR_FILEERROR,          /*! TIFF/F file cannot be opened */
+    T30_ERR_NOPAGE,             /*! TIFF/F page not found */
+    T30_ERR_BADTIFF,            /*! TIFF/F format is not compatible */
+    T30_ERR_BADPAGE,            /*! TIFF/F page number tag missing */
+    T30_ERR_BADTAG,             /*! Incorrect values for TIFF/F tags */
+    T30_ERR_BADTIFFHDR,         /*! Bad TIFF/F header - incorrect values in fields */
+    T30_ERR_NOMEM,              /*! Cannot allocate memory for more pages */
+    
+    /* General problems */
+    T30_ERR_RETRYDCN,           /*! Disconnected after permitted retries */
+    T30_ERR_CALLDROPPED,        /*! The call dropped prematurely */
+    
+    /* Feature negotiation issues */
+    T30_ERR_NOPOLL,             /*! Poll not accepted */
+    T30_ERR_IDENT_UNACCEPTABLE, /*! Far end's ident is not acceptable */
+    T30_ERR_SUB_UNACCEPTABLE,   /*! Far end's sub-address is not acceptable */
+    T30_ERR_SEP_UNACCEPTABLE,   /*! Far end's selective polling address is not acceptable */
+    T30_ERR_PSA_UNACCEPTABLE,   /*! Far end's polled sub-address is not acceptable */
+    T30_ERR_SID_UNACCEPTABLE,   /*! Far end's sender identification is not acceptable */
+    T30_ERR_PWD_UNACCEPTABLE,   /*! Far end's password is not acceptable */
+    T30_ERR_TSA_UNACCEPTABLE,   /*! Far end's transmitting subscriber internet address is not acceptable */
+    T30_ERR_IRA_UNACCEPTABLE,   /*! Far end's internet routing address is not acceptable */
+    T30_ERR_CIA_UNACCEPTABLE,   /*! Far end's calling subscriber internet address is not acceptable */
+    T30_ERR_ISP_UNACCEPTABLE,   /*! Far end's internet selective polling address is not acceptable */
+    T30_ERR_CSA_UNACCEPTABLE    /*! Far end's called subscriber internet address is not acceptable */
+};
+
+/*!
+    I/O modes for the T.30 protocol.
+    These are allocated such that the lower 4 bits represents the variant of the modem - e.g. the
+    particular bit rate selected.
+*/
+enum
+{
+    T30_MODEM_NONE = 0,
+    T30_MODEM_PAUSE,
+    T30_MODEM_CED,
+    T30_MODEM_CNG,
+    T30_MODEM_V21,
+    T30_MODEM_V27TER,
+    T30_MODEM_V29,
+    T30_MODEM_V17,
+    T30_MODEM_V34HDX,
+    T30_MODEM_DONE
+};
+
+enum
+{
+    T30_FRONT_END_SEND_STEP_COMPLETE = 0,
+    /*! The current receive has completed. This is only needed to report an
+        unexpected end of the receive operation, as might happen with T.38
+        dying. */
+    T30_FRONT_END_RECEIVE_COMPLETE,
+    T30_FRONT_END_SIGNAL_PRESENT,
+    T30_FRONT_END_SIGNAL_ABSENT,
+    T30_FRONT_END_CED_PRESENT,
+    T30_FRONT_END_CNG_PRESENT
+};
+
+enum
+{
+    /*! Support the V.27ter modem (2400, and 4800bps) for image transfer. */
+    T30_SUPPORT_V27TER = 0x01,
+    /*! Support the V.29 modem (9600, and 7200bps) for image transfer. */
+    T30_SUPPORT_V29 = 0x02,
+    /*! Support the V.17 modem (14400, 12000, 9600 and 7200bps) for image transfer. */
+    T30_SUPPORT_V17 = 0x04,
+    /*! Support the V.34 modem (up to 33,600bps) for image transfer. */
+    T30_SUPPORT_V34HDX = 0x08,
+    /*! Support the Internet aware FAX mode (no bit rate limit) for image transfer. */
+    T30_SUPPORT_IAF = 0x10
+};
+
+enum
+{
+    /*! No compression */
+    T30_SUPPORT_NO_COMPRESSION = 0x01,
+    /*! T.1 1D compression */
+    T30_SUPPORT_T4_1D_COMPRESSION = 0x02,
+    /*! T.4 2D compression */
+    T30_SUPPORT_T4_2D_COMPRESSION = 0x04,
+    /*! T.6 2D compression */
+    T30_SUPPORT_T6_COMPRESSION = 0x08,
+    /*! T.85 monochrome JBIG compression, with fixed L0 */
+    T30_SUPPORT_T85_COMPRESSION = 0x10,
+    /*! T.85 monochrome JBIG compression, with variable L0 */
+    T30_SUPPORT_T85_L0_COMPRESSION = 0x20,
+    /*! T.43 colour JBIG compression */
+    T30_SUPPORT_T43_COMPRESSION = 0x40,
+    /*! T.45 run length colour compression */
+    T30_SUPPORT_T45_COMPRESSION = 0x80,
+    /*! T.81 + T.30 Annex E colour JPEG compression */
+    T30_SUPPORT_T81_COMPRESSION = 0x100,
+    /*! T.81 + T.30 Annex K colour sYCC-JPEG compression */
+    T30_SUPPORT_SYCC_T81_COMPRESSION = 0x200,
+    /*! T.88 monochrome JBIG2 compression */
+    T30_SUPPORT_T88_COMPRESSION = 0x400
+};
+
+enum
+{
+    /*! Support standard FAX Y-resolution 98/100dpi */
+    T30_SUPPORT_STANDARD_RESOLUTION = 0x01,
+    /*! Support fine FAX Y-resolution 196/200dpi */
+    T30_SUPPORT_FINE_RESOLUTION = 0x02,
+    /*! Support super-fine FAX Y-resolution 392/400dpi */
+    T30_SUPPORT_SUPERFINE_RESOLUTION = 0x04,
+
+    /*! Support half FAX X-resolution 100/102dpi */
+    T30_SUPPORT_R4_RESOLUTION = 0x10000,
+    /*! Support standard FAX X-resolution 200/204dpi */
+    T30_SUPPORT_R8_RESOLUTION = 0x20000,
+    /*! Support double FAX X-resolution 400dpi */
+    T30_SUPPORT_R16_RESOLUTION = 0x40000,
+
+    /*! Support 300dpi x 300 dpi */
+    T30_SUPPORT_300_300_RESOLUTION = 0x100000,
+    /*! Support 400dpi x 400 dpi */
+    T30_SUPPORT_400_400_RESOLUTION = 0x200000,
+    /*! Support 600dpi x 600 dpi */
+    T30_SUPPORT_600_600_RESOLUTION = 0x400000,
+    /*! Support 1200dpi x 1200 dpi */
+    T30_SUPPORT_1200_1200_RESOLUTION = 0x800000,
+    /*! Support 300dpi x 600 dpi */
+    T30_SUPPORT_300_600_RESOLUTION = 0x1000000,
+    /*! Support 400dpi x 800 dpi */
+    T30_SUPPORT_400_800_RESOLUTION = 0x2000000,
+    /*! Support 600dpi x 1200 dpi */
+    T30_SUPPORT_600_1200_RESOLUTION = 0x4000000
+};
+
+enum
+{
+    T30_SUPPORT_215MM_WIDTH = 0x01,
+    T30_SUPPORT_255MM_WIDTH = 0x02,
+    T30_SUPPORT_303MM_WIDTH = 0x04,
+
+    T30_SUPPORT_UNLIMITED_LENGTH = 0x10000,
+    T30_SUPPORT_A4_LENGTH = 0x20000,
+    T30_SUPPORT_B4_LENGTH = 0x40000,
+    T30_SUPPORT_US_LETTER_LENGTH = 0x80000,
+    T30_SUPPORT_US_LEGAL_LENGTH = 0x100000
+};
+
+enum
+{
+    /*! Enable support of identification, through the SID and/or PWD frames. */
+    T30_SUPPORT_IDENTIFICATION = 0x01,
+    /*! Enable support of selective polling, through the SEP frame. */
+    T30_SUPPORT_SELECTIVE_POLLING = 0x02,
+    /*! Enable support of polling sub-addressing, through the PSA frame. */
+    T30_SUPPORT_POLLED_SUB_ADDRESSING = 0x04,
+    /*! Enable support of multiple selective polling, through repeated used of the SEP and PSA frames. */
+    T30_SUPPORT_MULTIPLE_SELECTIVE_POLLING = 0x08,
+    /*! Enable support of sub-addressing, through the SUB frame. */
+    T30_SUPPORT_SUB_ADDRESSING = 0x10,
+    /*! Enable support of transmitting subscriber internet address, through the TSA frame. */
+    T30_SUPPORT_TRANSMITTING_SUBSCRIBER_INTERNET_ADDRESS = 0x20,
+    /*! Enable support of internet routing address, through the IRA frame. */
+    T30_SUPPORT_INTERNET_ROUTING_ADDRESS = 0x40,
+    /*! Enable support of calling subscriber internet address, through the CIA frame. */
+    T30_SUPPORT_CALLING_SUBSCRIBER_INTERNET_ADDRESS = 0x80,
+    /*! Enable support of internet selective polling address, through the ISP frame. */
+    T30_SUPPORT_INTERNET_SELECTIVE_POLLING_ADDRESS = 0x100,
+    /*! Enable support of called subscriber internet address, through the CSA frame. */
+    T30_SUPPORT_CALLED_SUBSCRIBER_INTERNET_ADDRESS = 0x200,
+    /*! Enable support of the field not valid (FNV) frame. */
+    T30_SUPPORT_FIELD_NOT_VALID = 0x400,
+    /*! Enable support of the command repeat (CRP) frame. */
+    T30_SUPPORT_COMMAND_REPEAT = 0x800
+};
+
+enum
+{
+    T30_IAF_MODE_T37 = 0x01,
+    T30_IAF_MODE_T38 = 0x02,
+    T30_IAF_MODE_FLOW_CONTROL = 0x04,
+    /*! Continuous flow mode means data is sent as fast as possible, usually across
+        the Internet, where speed is not constrained by a PSTN modem. */
+    T30_IAF_MODE_CONTINUOUS_FLOW = 0x08,
+    /*! No TCF means TCF is not exchanged. The end points must sort out usable speed
+        issues locally. */
+    T30_IAF_MODE_NO_TCF = 0x10,
+    /*! No fill bits means do not insert fill bits, even if the T.30 messages request
+        them. */
+    T30_IAF_MODE_NO_FILL_BITS = 0x20,
+    /*! No indicators means do not send indicator messages when using T.38. */
+    T30_IAF_MODE_NO_INDICATORS = 0x40,
+    /*! Use relaxed timers for T.38. This is appropriate when using TCP/TPKT for T.38,
+        as there is no point in anything but a long backstop timeout in such a mode. */
+    T30_IAF_MODE_RELAXED_TIMERS = 0x80
+};
+
+typedef struct
+{
+    /*! \brief The identifier string (CSI, TSI, CIG). */
+    char ident[T30_MAX_IDENT_LEN + 1];
+    /*! \brief The sub-address string (SUB). */
+    char sub_address[T30_MAX_IDENT_LEN + 1];
+    /*! \brief The selective polling sub-address (SEP). */
+    char selective_polling_address[T30_MAX_IDENT_LEN + 1];
+    /*! \brief The polled sub-address (PSA). */
+    char polled_sub_address[T30_MAX_IDENT_LEN + 1];
+    /*! \brief The sender identification (SID). */
+    char sender_ident[T30_MAX_IDENT_LEN + 1];
+    /*! \brief The password (PWD). */
+    char password[T30_MAX_IDENT_LEN + 1];
+    /*! \brief Non-standard facilities (NSF). */
+    uint8_t *nsf;
+    size_t nsf_len;
+    /*! \brief Non-standard facilities command (NSC). */
+    uint8_t *nsc;
+    size_t nsc_len;
+    /*! \brief Non-standard facilities set-up (NSS). */
+    uint8_t *nss;
+    size_t nss_len;
+    /*! \brief Transmitting subscriber internet address (TSA). */
+    int tsa_type;
+    char *tsa;
+    size_t tsa_len;
+    /*! \brief Internet routing address (IRA). */
+    int ira_type;
+    char *ira;
+    size_t ira_len;
+    /*! \brief Calling subscriber internet address (CIA). */
+    int cia_type;
+    char *cia;
+    size_t cia_len;
+    /*! \brief Internet selective polling address (ISP). */
+    int isp_type;
+    char *isp;
+    size_t isp_len;
+    /*! \brief Called subscriber internet address (CSA). */
+    int csa_type;
+    char *csa;
+    size_t csa_len;
+} t30_exchanged_info_t;
+
+typedef struct
+{
+    /*! \brief The current bit rate for image transfer. */
+    int bit_rate;
+    /*! \brief TRUE if error correcting mode is used. */
+    int error_correcting_mode;
+    /*! \brief The number of pages sent so far. */
+    int pages_tx;
+    /*! \brief The number of pages received so far. */
+    int pages_rx;
+    /*! \brief The number of pages in the file (<0 if not known). */
+    int pages_in_file;
+    /*! \brief The horizontal column-to-column resolution of the most recent page, in pixels per metre */
+    int x_resolution;
+    /*! \brief The vertical row-to-row resolution of the most recent page, in pixels per metre */
+    int y_resolution;
+    /*! \brief The number of horizontal pixels in the most recent page. */
+    int width;
+    /*! \brief The number of vertical pixels in the most recent page. */
+    int length;
+    /*! \brief The size of the image, in bytes */
+    int image_size;
+    /*! \brief The type of compression used between the FAX machines */
+    int encoding;
+    /*! \brief The number of bad pixel rows in the most recent page. */
+    int bad_rows;
+    /*! \brief The largest number of bad pixel rows in a block in the most recent page. */
+    int longest_bad_row_run;
+    /*! \brief The number of HDLC frame retries, if error correcting mode is used. */
+    int error_correcting_mode_retries;
+    /*! \brief Current status. */
+    int current_status;
+#if 0
+    /*! \brief The number of RTP events in this call. */
+    int rtp_events;
+    /*! \brief The number of RTN events in this call. */
+    int rtn_events;
+#endif
+} t30_stats_t;
+
+#if defined(__cplusplus)
+extern "C"
+{
+#endif
+
+/*! Initialise a T.30 context.
+    \brief Initialise a T.30 context.
+    \param s The T.30 context.
+    \param calling_party TRUE if the context is for a calling party. FALSE if the
+           context is for an answering party.
+    \param set_rx_type_handler
+    \param set_rx_type_user_data
+    \param set_tx_type_handler
+    \param set_tx_type_user_data
+    \param send_hdlc_handler
+    \param send_hdlc_user_data
+    \return A pointer to the context, or NULL if there was a problem. */
+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);
+
+/*! Release a T.30 context.
+    \brief Release a T.30 context.
+    \param s The T.30 context.
+    \return 0 for OK, else -1. */
+SPAN_DECLARE(int) t30_release(t30_state_t *s);
+
+/*! Free a T.30 context.
+    \brief Free a T.30 context.
+    \param s The T.30 context.
+    \return 0 for OK, else -1. */
+SPAN_DECLARE(int) t30_free(t30_state_t *s);
+
+/*! Restart a T.30 context.
+    \brief Restart a T.30 context.
+    \param s The T.30 context.
+    \return 0 for OK, else -1. */
+SPAN_DECLARE(int) t30_restart(t30_state_t *s);
+
+/*! Check if a T.30 call is still active. This may be used to regularly poll
+    if the job has finished.
+    \brief Check if a T.30 call is still active.
+    \param s The T.30 context.
+    \return TRUE for call still active, or FALSE for call completed. */
+SPAN_DECLARE(int) t30_call_active(t30_state_t *s);
+
+/*! Cleanup a T.30 context if the call terminates.
+    \brief Cleanup a T.30 context if the call terminates.
+    \param s The T.30 context. */
+SPAN_DECLARE(void) t30_terminate(t30_state_t *s);
+
+/*! Inform the T.30 engine of a status change in the front end (end of tx, rx signal change, etc.).
+    \brief Inform the T.30 engine of a status change in the front end (end of tx, rx signal change, etc.).
+    \param user_data The T.30 context.
+    \param status The type of status change which occured. */
+SPAN_DECLARE(void) t30_front_end_status(void *user_data, int status);
+
+/*! Get a bit of received non-ECM image data.
+    \brief Get a bit of received non-ECM image data.
+    \param user_data An opaque pointer, which must point to the T.30 context.
+    \return The next bit to transmit. */
+SPAN_DECLARE_NONSTD(int) t30_non_ecm_get_bit(void *user_data);
+
+/*! Get a byte of received non-ECM image data.
+    \brief Get a byte of received non-ECM image data.
+    \param user_data An opaque pointer, which must point to the T.30 context.
+    \return The next byte to transmit. */
+SPAN_DECLARE(int) t30_non_ecm_get_byte(void *user_data);
+
+/*! Get a chunk of received non-ECM image data.
+    \brief Get a bit of received non-ECM image data.
+    \param user_data An opaque pointer, which must point to the T.30 context.
+    \param buf The buffer to contain the data.
+    \param max_len The maximum length of the chunk.
+    \return The actual length of the chunk. */
+SPAN_DECLARE(int) t30_non_ecm_get_chunk(void *user_data, uint8_t buf[], int max_len);
+
+/*! Process a bit of received non-ECM image data.
+    \brief Process a bit of received non-ECM image data
+    \param user_data An opaque pointer, which must point to the T.30 context.
+    \param bit The received bit. */
+SPAN_DECLARE_NONSTD(void) t30_non_ecm_put_bit(void *user_data, int bit);
+
+/*! Process a byte of received non-ECM image data.
+    \brief Process a byte of received non-ECM image data
+    \param user_data An opaque pointer, which must point to the T.30 context.
+    \param byte The received byte. */
+SPAN_DECLARE(void) t30_non_ecm_put_byte(void *user_data, int byte);
+
+/*! Process a chunk of received non-ECM image data.
+    \brief Process a chunk of received non-ECM image data
+    \param user_data An opaque pointer, which must point to the T.30 context.
+    \param buf The buffer containing the received data.
+    \param len The length of the data in buf. */
+SPAN_DECLARE(void) t30_non_ecm_put_chunk(void *user_data, const uint8_t buf[], int len);
+
+/*! Process a received HDLC frame.
+    \brief Process a received HDLC frame.
+    \param user_data The T.30 context.
+    \param msg The HDLC message.
+    \param len The length of the message, in octets.
+    \param ok TRUE if the frame was received without error. */
+SPAN_DECLARE_NONSTD(void) t30_hdlc_accept(void *user_data, const uint8_t msg[], int len, int ok);
+
+/*! Report the passage of time to the T.30 engine.
+    \brief Report the passage of time to the T.30 engine.
+    \param s The T.30 context.
+    \param samples The time change in 1/8000th second steps. */
+SPAN_DECLARE(void) t30_timer_update(t30_state_t *s, int samples);
+
+/*! Get the current transfer statistics for the file being sent or received.
+    \brief Get the current transfer statistics.
+    \param s The T.30 context.
+    \param t A pointer to a buffer for the statistics. */
+SPAN_DECLARE(void) t30_get_transfer_statistics(t30_state_t *s, t30_stats_t *t);
+
+/*! Request a local interrupt of FAX exchange.
+    \brief Request a local interrupt of FAX exchange.
+    \param s The T.30 context.
+    \param state TRUE to enable interrupt request, else FALSE. */
+SPAN_DECLARE(void) t30_local_interrupt_request(t30_state_t *s, int state);
+
+/*! Allow remote interrupts of FAX exchange.
+    \brief Allow remote interrupts of FAX exchange.
+    \param s The T.30 context.
+    \param state TRUE to allow interruptd, else FALSE. */
+SPAN_DECLARE(void) t30_remote_interrupts_allowed(t30_state_t *s, int state);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif
+/*- End of file ------------------------------------------------------------*/
diff --git a/src/codec/spandsp/src/spandsp/t30_api.h b/src/codec/spandsp/src/spandsp/t30_api.h
new file mode 100644 (file)
index 0000000..d758c8e
--- /dev/null
@@ -0,0 +1,553 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * t30_api.h - definitions for T.30 fax processing
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2003 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.
+ */
+
+/*! \file */
+
+#if !defined(_SPANDSP_T30_API_H_)
+#define _SPANDSP_T30_API_H_
+
+#if defined(__cplusplus)
+extern "C"
+{
+#endif
+
+/*! Set the transmitted NSF frame to be associated with a T.30 context.
+    \brief Set the transmitted NSF frame to be associated with a T.30 context.
+    \param s The T.30 context.
+    \param nsf A pointer to the frame.
+    \param len The length of the frame.
+    \return 0 for OK, else -1. */
+SPAN_DECLARE(int) t30_set_tx_nsf(t30_state_t *s, const uint8_t *nsf, int len);
+
+/*! Get an NSF frame to be associated with a T.30 context.
+    \brief Set an NSF frame to be associated with a T.30 context.
+    \param s The T.30 context.
+    \param nsf A pointer to the frame.
+    \return the length of the NSF message. */
+SPAN_DECLARE(size_t) t30_get_tx_nsf(t30_state_t *s, const uint8_t *nsf[]);
+
+/*! Get an NSF frame to be associated with a T.30 context.
+    \brief Set an NSF frame to be associated with a T.30 context.
+    \param s The T.30 context.
+    \param nsf A pointer to the frame.
+    \return the length of the NSF message. */
+SPAN_DECLARE(size_t) t30_get_rx_nsf(t30_state_t *s, const uint8_t *nsf[]);
+
+/*! Set the transmitted NSC frame to be associated with a T.30 context.
+    \brief Set the transmitted NSC frame to be associated with a T.30 context.
+    \param s The T.30 context.
+    \param nsc A pointer to the frame.
+    \param len The length of the frame.
+    \return 0 for OK, else -1. */
+SPAN_DECLARE(int) t30_set_tx_nsc(t30_state_t *s, const uint8_t *nsc, int len);
+
+/*! Get an NSC frame to be associated with a T.30 context.
+    \brief Set an NSC frame to be associated with a T.30 context.
+    \param s The T.30 context.
+    \param nsc A pointer to the frame.
+    \return the length of the NSC message. */
+SPAN_DECLARE(size_t) t30_get_tx_nsc(t30_state_t *s, const uint8_t *nsc[]);
+
+/*! Get an NSC frame to be associated with a T.30 context.
+    \brief Set an NSC frame to be associated with a T.30 context.
+    \param s The T.30 context.
+    \param nsc A pointer to the frame.
+    \return the length of the NSC message. */
+SPAN_DECLARE(size_t) t30_get_rx_nsc(t30_state_t *s, const uint8_t *nsc[]);
+
+/*! Set the transmitted NSS frame to be associated with a T.30 context.
+    \brief Set the transmitted NSS frame to be associated with a T.30 context.
+    \param s The T.30 context.
+    \param nss A pointer to the frame.
+    \param len The length of the frame.
+    \return 0 for OK, else -1. */
+SPAN_DECLARE(int) t30_set_tx_nss(t30_state_t *s, const uint8_t *nss, int len);
+
+/*! Get an NSS frame to be associated with a T.30 context.
+    \brief Set an NSS frame to be associated with a T.30 context.
+    \param s The T.30 context.
+    \param nss A pointer to the frame.
+    \return the length of the NSS message. */
+SPAN_DECLARE(size_t) t30_get_tx_nss(t30_state_t *s, const uint8_t *nss[]);
+
+/*! Get an NSS frame to be associated with a T.30 context.
+    \brief Set an NSS frame to be associated with a T.30 context.
+    \param s The T.30 context.
+    \param nss A pointer to the frame.
+    \return the length of the NSS message. */
+SPAN_DECLARE(size_t) t30_get_rx_nss(t30_state_t *s, const uint8_t *nss[]);
+
+/*! Set the transmitted identifier associated with a T.30 context.
+    \brief Set the transmitted identifier associated with a T.30 context.
+    \param s The T.30 context.
+    \param id A pointer to the identifier.
+    \return 0 for OK, else -1. */
+SPAN_DECLARE(int) t30_set_tx_ident(t30_state_t *s, const char *id);
+
+/*! Get the transmitted identifier associated with a T.30 context.
+    \brief Set the transmitted identifier associated with a T.30 context.
+    \param s The T.30 context.
+    \return A pointer to the identifier. */
+SPAN_DECLARE(const char *) t30_get_tx_ident(t30_state_t *s);
+
+/*! Get the transmitted identifier associated with a T.30 context.
+    \brief Set the transmitted identifier associated with a T.30 context.
+    \param s The T.30 context.
+    \return A pointer to the identifier. */
+SPAN_DECLARE(const char *) t30_get_rx_ident(t30_state_t *s);
+
+/*! Set the transmitted sub-address associated with a T.30 context.
+    \brief Set the transmitted sub-address associated with a T.30 context.
+    \param s The T.30 context.
+    \param sub_address A pointer to the sub-address.
+    \return 0 for OK, else -1. */
+SPAN_DECLARE(int) t30_set_tx_sub_address(t30_state_t *s, const char *sub_address);
+
+/*! Get the received sub-address associated with a T.30 context.
+    \brief Get the received sub-address associated with a T.30 context.
+    \param s The T.30 context.
+    \return A pointer to the sub-address. */
+SPAN_DECLARE(const char *) t30_get_tx_sub_address(t30_state_t *s);
+
+/*! Get the received sub-address associated with a T.30 context.
+    \brief Get the received sub-address associated with a T.30 context.
+    \param s The T.30 context.
+    \return A pointer to the sub-address. */
+SPAN_DECLARE(const char *) t30_get_rx_sub_address(t30_state_t *s);
+
+/*! Set the transmitted selective polling address (i.e. the one we will send to the far
+    end) associated with a T.30 context.
+    \brief Set the transmitted selective polling address associated with a T.30 context.
+    \param s The T.30 context.
+    \param selective_polling_address A pointer to the selective polling address.
+    \return 0 for OK, else -1. */
+SPAN_DECLARE(int) t30_set_tx_selective_polling_address(t30_state_t *s, const char *selective_polling_address);
+
+/*! Get the received selective polling address (i.e. the one we will send to the far
+    end) associated with a T.30 context.
+    \brief Get the received selective polling address associated with a T.30 context.
+    \param s The T.30 context.
+    \return A pointer to the selective polling address. */
+SPAN_DECLARE(const char *) t30_get_tx_selective_polling_address(t30_state_t *s);
+
+/*! Get the received selective polling address (i.e. the one we will send to the far
+    end) associated with a T.30 context.
+    \brief Get the received selective polling address associated with a T.30 context.
+    \param s The T.30 context.
+    \return A pointer to the selective polling address. */
+SPAN_DECLARE(const char *) t30_get_rx_selective_polling_address(t30_state_t *s);
+
+/*! Set the transmitted polled sub-address (i.e. the one we will send to the far
+    end) associated with a T.30 context.
+    \brief Set the transmitted polled sub-address associated with a T.30 context.
+    \param s The T.30 context.
+    \param polled_sub_address A pointer to the polled sub-address.
+    \return 0 for OK, else -1. */
+SPAN_DECLARE(int) t30_set_tx_polled_sub_address(t30_state_t *s, const char *polled_sub_address);
+
+/*! Get the received polled sub-address (i.e. the one we will send to the far
+    end) associated with a T.30 context.
+    \brief Get the received polled sub-address associated with a T.30 context.
+    \param s The T.30 context.
+    \return A pointer to the polled sub-address. */
+SPAN_DECLARE(const char *) t30_get_tx_polled_sub_address(t30_state_t *s);
+
+/*! Get the received polled sub-address (i.e. the one we will send to the far
+    end) associated with a T.30 context.
+    \brief Get the received polled sub-address associated with a T.30 context.
+    \param s The T.30 context.
+    \return A pointer to the polled sub-address. */
+SPAN_DECLARE(const char *) t30_get_rx_polled_sub_address(t30_state_t *s);
+
+/*! Set the transmitted sender ident (i.e. the one we will send to the far
+    end) associated with a T.30 context.
+    \brief Set the transmitted sender ident associated with a T.30 context.
+    \param s The T.30 context.
+    \param sender_ident A pointer to the sender ident.
+    \return 0 for OK, else -1. */
+SPAN_DECLARE(int) t30_set_tx_sender_ident(t30_state_t *s, const char *sender_ident);
+
+/*! Get the received sender ident (i.e. the one we will send to the far
+    end) associated with a T.30 context.
+    \brief Get the received sender ident associated with a T.30 context.
+    \param s The T.30 context.
+    \return A pointer to the sender ident. */
+SPAN_DECLARE(const char *) t30_get_tx_sender_ident(t30_state_t *s);
+
+/*! Get the received sender ident (i.e. the one we will send to the far
+    end) associated with a T.30 context.
+    \brief Get the received sender ident associated with a T.30 context.
+    \param s The T.30 context.
+    \return A pointer to the sender ident. */
+SPAN_DECLARE(const char *) t30_get_rx_sender_ident(t30_state_t *s);
+
+/*! Set the transmitted password (i.e. the one we will send to the far
+    end) associated with a T.30 context.
+    \brief Set the transmitted password associated with a T.30 context.
+    \param s The T.30 context.
+    \param password A pointer to the password.
+    \return 0 for OK, else -1. */
+SPAN_DECLARE(int) t30_set_tx_password(t30_state_t *s, const char *password);
+
+/*! Get the received password (i.e. the one we will send to the far
+    end) associated with a T.30 context.
+    \brief Get the received password associated with a T.30 context.
+    \param s The T.30 context.
+    \return A pointer to the password. */
+SPAN_DECLARE(const char *) t30_get_tx_password(t30_state_t *s);
+
+/*! Get the received password (i.e. the one we will send to the far
+    end) associated with a T.30 context.
+    \brief Get the received password associated with a T.30 context.
+    \param s The T.30 context.
+    \return A pointer to the password. */
+SPAN_DECLARE(const char *) t30_get_rx_password(t30_state_t *s);
+
+/*! Set the transmitted TSA (i.e. the one we will send to the far
+    end) associated with a T.30 context.
+    \brief Set the transmitted TSA associated with a T.30 context.
+    \param s The T.30 context.
+    \param type The type of address.
+    \param address A pointer to the address.
+    \param len The length of the address.
+    \return 0 for OK, else -1. */
+SPAN_DECLARE(int) t30_set_tx_tsa(t30_state_t *s, int type, const char *address, int len);
+
+/*! Get the transmitted TSA (i.e. the one we will send to the far
+    end) associated with a T.30 context.
+    \brief Get the received TSA associated with a T.30 context.
+    \param s The T.30 context.
+    \param type The type of address.
+    \param address A pointer to the address.
+    \return The length of the address. */
+SPAN_DECLARE(size_t) t30_get_tx_tsa(t30_state_t *s, int *type, const char *address[]);
+
+/*! Get the received TSA associated with a T.30 context.
+    \brief Get the received TSA associated with a T.30 context.
+    \param s The T.30 context.
+    \param type The type of address.
+    \param address A pointer to the address.
+    \return The length of the address. */
+SPAN_DECLARE(size_t) t30_get_rx_tsa(t30_state_t *s, int *type, const char *address[]);
+
+/*! Set the transmitted IRA (i.e. the one we will send to the far
+    end) associated with a T.30 context.
+    \brief Set the transmitted IRA associated with a T.30 context.
+    \param s The T.30 context.
+    \param type The type of address.
+    \param address A pointer to the address.
+    \param len The length of the address.
+    \return 0 for OK, else -1. */
+SPAN_DECLARE(int) t30_set_tx_ira(t30_state_t *s, int type, const char *address, int len);
+
+/*! Get the transmitted IRA (i.e. the one we will send to the far
+    end) associated with a T.30 context.
+    \brief Get the received IRA associated with a T.30 context.
+    \param s The T.30 context.
+    \param type The type of address.
+    \param address A pointer to the address.
+    \return The length of the address. */
+SPAN_DECLARE(size_t) t30_get_tx_ira(t30_state_t *s, int *type, const char *address[]);
+
+/*! Get the received IRA associated with a T.30 context.
+    \brief Get the received IRA associated with a T.30 context.
+    \param s The T.30 context.
+    \param type The type of address.
+    \param address A pointer to the address.
+    \return The length of the address. */
+SPAN_DECLARE(size_t) t30_get_rx_ira(t30_state_t *s, int *type, const char *address[]);
+
+/*! Set the transmitted CIA (i.e. the one we will send to the far
+    end) associated with a T.30 context.
+    \brief Set the transmitted CIA associated with a T.30 context.
+    \param s The T.30 context.
+    \param type The type of address.
+    \param address A pointer to the address.
+    \param len The length of the address.
+    \return 0 for OK, else -1. */
+SPAN_DECLARE(int) t30_set_tx_cia(t30_state_t *s, int type, const char *address, int len);
+
+/*! Get the transmitted CIA (i.e. the one we will send to the far
+    end) associated with a T.30 context.
+    \brief Get the received CIA associated with a T.30 context.
+    \param s The T.30 context.
+    \param type The type of address.
+    \param address A pointer to the address.
+    \return The length of the address. */
+SPAN_DECLARE(size_t) t30_get_tx_cia(t30_state_t *s, int *type, const char *address[]);
+
+/*! Get the received CIA associated with a T.30 context.
+    \brief Get the received CIA associated with a T.30 context.
+    \param s The T.30 context.
+    \param type The type of address.
+    \param address A pointer to the address.
+    \return 0 for OK, else -1. */
+SPAN_DECLARE(size_t) t30_get_rx_cia(t30_state_t *s, int *type, const char *address[]);
+
+/*! Set the transmitted ISP (i.e. the one we will send to the far
+    end) associated with a T.30 context.
+    \brief Set the transmitted ISP associated with a T.30 context.
+    \param s The T.30 context.
+    \param type The type of address.
+    \param address A pointer to the address.
+    \param len The length of the address.
+    \return 0 for OK, else -1. */
+SPAN_DECLARE(int) t30_set_tx_isp(t30_state_t *s, int type, const char *address, int len);
+
+/*! Get the transmitted ISP (i.e. the one we will send to the far
+    end) associated with a T.30 context.
+    \brief Get the received ISP associated with a T.30 context.
+    \param s The T.30 context.
+    \param type The type of address.
+    \param address A pointer to the address.
+    \return 0 for OK, else -1. */
+SPAN_DECLARE(size_t) t30_get_tx_isp(t30_state_t *s, int *type, const char *address[]);
+
+/*! Get the received ISP associated with a T.30 context.
+    \brief Get the received ISP associated with a T.30 context.
+    \param s The T.30 context.
+    \param type The type of address.
+    \param address A pointer to the address.
+    \return 0 for OK, else -1. */
+SPAN_DECLARE(size_t) t30_get_rx_isp(t30_state_t *s, int *type, const char *address[]);
+
+/*! Set the transmitted CSA (i.e. the one we will send to the far
+    end) associated with a T.30 context.
+    \brief Set the transmitted CSA associated with a T.30 context.
+    \param s The T.30 context.
+    \param type The type of address.
+    \param address A pointer to the address.
+    \param len The length of the address.
+    \return 0 for OK, else -1. */
+SPAN_DECLARE(int) t30_set_tx_csa(t30_state_t *s, int type, const char *address, int len);
+
+/*! Get the transmitted CSA (i.e. the one we will send to the far
+    end) associated with a T.30 context.
+    \brief Get the received CSA associated with a T.30 context.
+    \param s The T.30 context.
+    \param type The type of address.
+    \param address A pointer to the address.
+    \return The length of the address. */
+SPAN_DECLARE(size_t) t30_get_tx_csa(t30_state_t *s, int *type, const char *address[]);
+
+/*! Get the received CSA associated with a T.30 context.
+    \brief Get the received CSA associated with a T.30 context.
+    \param s The T.30 context.
+    \param type The type of address.
+    \param address A pointer to the address.
+    \return 0 for OK, else -1. */
+SPAN_DECLARE(size_t) t30_get_rx_csa(t30_state_t *s, int *type, const char *address[]);
+
+/*! Set page header extends or overlays the image mode.
+    \brief Set page header overlay mode.
+    \param s The T.30 context.
+    \param header_overlays_image TRUE for overlay, or FALSE for extend the page. */
+SPAN_DECLARE(int) t30_set_tx_page_header_overlays_image(t30_state_t *s, int header_overlays_image);
+
+/*! Set the transmitted header information associated with a T.30 context.
+    \brief Set the transmitted header information associated with a T.30 context.
+    \param s The T.30 context.
+    \param info A pointer to the information string.
+    \return 0 for OK, else -1. */
+SPAN_DECLARE(int) t30_set_tx_page_header_info(t30_state_t *s, const char *info);
+
+/*! Set the transmitted header timestamp timezone associated with a T.30 context.
+    \brief Set the transmitted header timestamp timezone associated with a T.30 context.
+    \param s The T.30 context.
+    \param info A pointer to the POSIZ timezone string.
+    \return 0 for OK, else -1. */
+SPAN_DECLARE(int) t30_set_tx_page_header_tz(t30_state_t *s, const char *tzstring);
+
+/*! Get the header information associated with a T.30 context.
+    \brief Get the header information associated with a T.30 context.
+    \param s The T.30 context.
+    \param info A pointer to a buffer for the header information.  The buffer
+           should be at least 51 bytes long.
+    \return the length of the string. */
+SPAN_DECLARE(size_t) t30_get_tx_page_header_info(t30_state_t *s, char *info);
+
+/*! Get the country of origin of the remote FAX machine associated with a T.30 context.
+    \brief Get the country of origin of the remote FAX machine associated with a T.30 context.
+    \param s The T.30 context.
+    \return a pointer to the country name, or NULL if the country is not known. */
+SPAN_DECLARE(const char *) t30_get_rx_country(t30_state_t *s);
+
+/*! Get the name of the vendor of the remote FAX machine associated with a T.30 context.
+    \brief Get the name of the vendor of the remote FAX machine associated with a T.30 context.
+    \param s The T.30 context.
+    \return a pointer to the vendor name, or NULL if the vendor is not known. */
+SPAN_DECLARE(const char *) t30_get_rx_vendor(t30_state_t *s);
+
+/*! Get the name of the model of the remote FAX machine associated with a T.30 context.
+    \brief Get the name of the model of the remote FAX machine associated with a T.30 context.
+    \param s The T.30 context.
+    \return a pointer to the model name, or NULL if the model is not known. */
+SPAN_DECLARE(const char *) t30_get_rx_model(t30_state_t *s);
+
+/*! Specify the file name of the next TIFF file to be received by a T.30
+    context.
+    \brief Set next receive file name.
+    \param s The T.30 context.
+    \param file The file name
+    \param stop_page The maximum page to receive. -1 for no restriction. */
+SPAN_DECLARE(void) t30_set_rx_file(t30_state_t *s, const char *file, int stop_page);
+
+/*! Specify the file name of the next TIFF file to be transmitted by a T.30
+    context.
+    \brief Set next transmit file name.
+    \param s The T.30 context.
+    \param file The file name
+    \param start_page The first page to send. -1 for no restriction.
+    \param stop_page The last page to send. -1 for no restriction. */
+SPAN_DECLARE(void) t30_set_tx_file(t30_state_t *s, const char *file, int start_page, int stop_page);
+
+/*! Set Internet aware FAX (IAF) mode.
+    \brief Set Internet aware FAX (IAF) mode.
+    \param s The T.30 context.
+    \param iaf TRUE for IAF, or FALSE for non-IAF. */
+SPAN_DECLARE(void) t30_set_iaf_mode(t30_state_t *s, int iaf);
+
+/*! Specify if error correction mode (ECM) is allowed by a T.30 context.
+    \brief Select ECM capability.
+    \param s The T.30 context.
+    \param enabled TRUE for ECM capable, FALSE for not ECM capable.
+    \return 0 if OK, else -1. */
+SPAN_DECLARE(int) t30_set_ecm_capability(t30_state_t *s, int enabled);
+
+/*! Specify the output encoding for TIFF files created during FAX reception.
+    \brief Specify the output encoding for TIFF files created during FAX reception.
+    \param s The T.30 context.
+    \param encoding The coding required. The options are T4_COMPRESSION_ITU_T4_1D,
+           T4_COMPRESSION_ITU_T4_2D, T4_COMPRESSION_ITU_T6. T6 is usually the
+           densest option, but support for it is broken in a number of software
+           packages.
+    \return 0 if OK, else -1. */
+SPAN_DECLARE(int) t30_set_rx_encoding(t30_state_t *s, int encoding);
+
+/*! Specify the minimum scan line time supported by a T.30 context.
+    \brief Specify minimum scan line time.
+    \param s The T.30 context.
+    \param min_time The minimum permitted scan line time, in milliseconds.
+    \return 0 if OK, else -1. */
+SPAN_DECLARE(int) t30_set_minimum_scan_line_time(t30_state_t *s, int min_time);
+
+/*! Specify which modem types are supported by a T.30 context.
+    \brief Specify supported modems.
+    \param s The T.30 context.
+    \param supported_modems Bit field list of the supported modems.
+    \return 0 if OK, else -1. */
+SPAN_DECLARE(int) t30_set_supported_modems(t30_state_t *s, int supported_modems);
+
+/*! Specify which compression types are supported by a T.30 context.
+    \brief Specify supported compression types.
+    \param s The T.30 context.
+    \param supported_compressions Bit field list of the supported compression types.
+    \return 0 if OK, else -1. */
+SPAN_DECLARE(int) t30_set_supported_compressions(t30_state_t *s, int supported_compressions);
+
+/*! Specify which resolutions are supported by a T.30 context.
+    \brief Specify supported resolutions.
+    \param s The T.30 context.
+    \param supported_resolutions Bit field list of the supported resolutions.
+    \return 0 if OK, else -1. */
+SPAN_DECLARE(int) t30_set_supported_resolutions(t30_state_t *s, int supported_resolutions);
+
+/*! Specify which images sizes are supported by a T.30 context.
+    \brief Specify supported image sizes.
+    \param s The T.30 context.
+    \param supported_image_sizes Bit field list of the supported widths and lengths.
+    \return 0 if OK, else -1. */
+SPAN_DECLARE(int) t30_set_supported_image_sizes(t30_state_t *s, int supported_image_sizes);
+
+/*! Specify which special T.30 features are supported by a T.30 context.
+    \brief Specify supported T.30 features.
+    \param s The T.30 context.
+    \param supported_t30_features Bit field list of the supported features.
+    \return 0 if OK, else -1. */
+SPAN_DECLARE(int) t30_set_supported_t30_features(t30_state_t *s, int supported_t30_features);
+
+/*! Set T.30 status. This may be used to adjust the status from within
+    the phase B and phase D callbacks.
+    \brief Set T.30 status.
+    \param s The T.30 context.
+    \param status The new status. */
+SPAN_DECLARE(void) t30_set_status(t30_state_t *s, int status);
+
+/*! Specify a period of responding with receiver not ready.
+    \brief Specify a period of responding with receiver not ready.
+    \param s The T.30 context.
+    \param count The number of times to report receiver not ready.
+    \return 0 if OK, else -1. */
+SPAN_DECLARE(int) t30_set_receiver_not_ready(t30_state_t *s, int count);
+
+/*! Set a callback function for T.30 phase B handling.
+    \brief Set a callback function for T.30 phase B handling.
+    \param s The T.30 context.
+    \param handler The callback function.
+    \param user_data An opaque pointer passed to the callback function. */
+SPAN_DECLARE(void) t30_set_phase_b_handler(t30_state_t *s, t30_phase_b_handler_t *handler, void *user_data);
+
+/*! Set a callback function for T.30 phase D handling.
+    \brief Set a callback function for T.30 phase D handling.
+    \param s The T.30 context.
+    \param handler The callback function.
+    \param user_data An opaque pointer passed to the callback function. */
+SPAN_DECLARE(void) t30_set_phase_d_handler(t30_state_t *s, t30_phase_d_handler_t *handler, void *user_data);
+
+/*! Set a callback function for T.30 phase E handling.
+    \brief Set a callback function for T.30 phase E handling.
+    \param s The T.30 context.
+    \param handler The callback function.
+    \param user_data An opaque pointer passed to the callback function. */
+SPAN_DECLARE(void) t30_set_phase_e_handler(t30_state_t *s, t30_phase_e_handler_t *handler, void *user_data);
+
+/*! Set a callback function for T.30 end of document handling.
+    \brief Set a callback function for T.30 end of document handling.
+    \param s The T.30 context.
+    \param handler The callback function.
+    \param user_data An opaque pointer passed to the callback function. */
+SPAN_DECLARE(void) t30_set_document_handler(t30_state_t *s, t30_document_handler_t *handler, void *user_data);
+
+/*! Set a callback function for T.30 frame exchange monitoring. This is called from the heart
+    of the signal processing, so don't take too long in the handler routine.
+    \brief Set a callback function for T.30 frame exchange monitoring.
+    \param s The T.30 context.
+    \param handler The callback function.
+    \param user_data An opaque pointer passed to the callback function. */
+SPAN_DECLARE(void) t30_set_real_time_frame_handler(t30_state_t *s, t30_real_time_frame_handler_t *handler, void *user_data);
+
+/*! Get a pointer to the logging context associated with a T.30 context.
+    \brief Get a pointer to the logging context associated with a T.30 context.
+    \param s The T.30 context.
+    \return A pointer to the logging context, or NULL.
+*/
+SPAN_DECLARE(logging_state_t *) t30_get_logging_state(t30_state_t *s);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif
+/*- End of file ------------------------------------------------------------*/
diff --git a/src/codec/spandsp/src/spandsp/t30_fcf.h b/src/codec/spandsp/src/spandsp/t30_fcf.h
new file mode 100644 (file)
index 0000000..7414497
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * t30_fcf.h - ITU T.30 fax control field definitions
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2003 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.
+ */
+
+/*! \file */
+
+#if !defined(_SPANDSP_T30_FCF_H_)
+#define _SPANDSP_T30_FCF_H_
+
+enum
+{
+    /*! Initial identification messages */
+    /*! From the called to the calling terminal. */
+    T30_DIS = 0x80,         /*! [0000 0001] Digital identification signal */
+    T30_CSI = 0x40,         /*! [0000 0010] Called subscriber identification */
+    T30_NSF = 0x20,         /*! [0000 0100] Non-standard facilities */
+
+    /*! Commands to send */
+    /*! From a calling terminal wishing to be a receiver, to a called terminal
+        which is capable of transmitting. */
+    T30_DTC = 0x81,         /*! [1000 0001] Digital transmit command */
+    T30_CIG = 0x41,         /*! [1000 0010] Calling subscriber identification */
+    T30_NSC = 0x21,         /*! [1000 0100] Non-standard facilities command */
+    T30_PWD = 0xC1,         /*! [1000 0011] Password */
+    T30_SEP = 0xA1,         /*! [1000 0101] Selective polling */
+    T30_PSA = 0x61,         /*! [1000 0110] Polled subaddress */
+    T30_CIA = 0xE1,         /*! [1000 0111] Calling subscriber internet address */
+    T30_ISP = 0x11,         /*! [1000 1000] Internet selective polling address */
+
+    /*! Commands to receive */
+    /*! From a calling terminal wishing to be a transmitter, to a called terminal
+        which is capable of receiving. */
+    T30_DCS = 0x82,         /*! [X100 0001] Digital command signal */
+    T30_TSI = 0x42,         /*! [X100 0010] Transmitting subscriber information */
+    T30_NSS = 0x22,         /*! [X100 0100] Non-standard facilities set-up */
+    T30_SUB = 0xC2,         /*! [X100 0011] Sub-address */
+    T30_SID = 0xA2,         /*! [X100 0101] Sender identification */
+    /*! T30_TCF - Training check is a burst of 1.5s of zeros sent using the image modem */
+    T30_CTC = 0x12,         /*! [X100 1000] Continue to correct */
+    T30_TSA = 0x62,         /*! [X100 0110] Transmitting subscriber internet address */
+    T30_IRA = 0xE2,         /*! [X100 0111] Internet routing address */
+
+    /*! Pre-message response signals */
+    /*! From the receiver to the transmitter. */
+    T30_CFR = 0x84,         /*! [X010 0001] Confirmation to receive */
+    T30_FTT = 0x44,         /*! [X010 0010] Failure to train */
+    T30_CTR = 0xC4,         /*! [X010 0011] Response for continue to correct */
+    T30_CSA = 0x24,         /*! [X010 0100] Called subscriber internet address */
+
+    /*! Post-message commands */
+    T30_EOM = 0x8E,         /*! [X111 0001] End of message */
+    T30_MPS = 0x4E,         /*! [X111 0010] Multipage signal */
+    T30_EOP = 0x2E,         /*! [X111 0100] End of procedure */
+    T30_PRI_EOM = 0x9E,     /*! [X111 1001] Procedure interrupt - end of procedure */
+    T30_PRI_MPS = 0x5E,     /*! [X111 1010] Procedure interrupt - multipage signal */
+    T30_PRI_EOP = 0x3E,     /*! [X111 1100] Procedure interrupt - end of procedure */
+    T30_EOS = 0x1E,         /*! [X111 1000] End of selection */
+    T30_PPS = 0xBE,         /*! [X111 1101] Partial page signal */
+    T30_EOR = 0xCE,         /*! [X111 0011] End of retransmission */
+    T30_RR = 0x6E,          /*! [X111 0110] Receiver ready */
+
+    /*! Post-message responses */
+    T30_MCF = 0x8C,         /*! [X011 0001] Message confirmation */
+    T30_RTP = 0xCC,         /*! [X011 0011] Retrain positive */
+    T30_RTN = 0x4C,         /*! [X011 0010] Retrain negative */
+    T30_PIP = 0xAC,         /*! [X011 0101] Procedure interrupt positive */
+    T30_PIN = 0x2C,         /*! [X011 0100] Procedure interrupt negative */
+    T30_PPR = 0xBC,         /*! [X011 1101] Partial page request */
+    T30_RNR = 0xEC,         /*! [X011 0111] Receive not ready */
+    T30_ERR = 0x1C,         /*! [X011 1000] Response for end of retransmission */
+    T30_FDM = 0xFC,         /*! [X011 1111] File diagnostics message */
+
+    /*! Other line control signals */
+    T30_DCN = 0xFA,         /*! [X101 1111] Disconnect */
+    T30_CRP = 0x1A,         /*! [X101 1000] Command repeat */
+    T30_FNV = 0xCA,         /*! [X101 0011] Field not valid */
+    T30_TNR = 0xEA,         /*! [X101 0111] Transmitter not ready */
+    T30_TR = 0x6A,          /*! [X101 0110] Transmitter ready */
+    T30_TK = 0x4B,          /*! [1101 0010] Transmitter keys */
+    T30_RK = 0x4A,          /*! [0101 0010] Receiver keys */
+    T30_PSS = 0x1F,         /*! [1111 1000] Present signature signal (used only as FCF2) */
+    T30_DES = 0xA0,         /*! [0000 0101] Digital extended signal */
+    T30_DEC = 0x93,         /*! [1100 1001] Digital extended command */
+    T30_DER = 0x53,         /*! [1100 1010] Digital extended request */
+    T30_DTR = 0x11,         /*! [1000 1000] Digital turnaround request (conflicts with ISP) */
+    T30_DNK = 0x9A,         /*! [X101 1001] Digital not acknowledge */
+    T30_PID = 0x6C,         /*! [X011 0110] Procedure interrupt disconnect */
+    T30_SPI = 0x10,         /*! [0000 1000] Security page indicator */
+    T30_SPT = 0x80,         /*! [0000 0001] Security page type */
+
+    /*! Something only use as a secondary value in error correcting mode */
+    T30_NULL = 0x00,        /*! [0000 0000] Nothing to say */
+
+    /*! Information frame types used for error correction mode, in T.4 */
+    T4_FCD = 0x06,          /*! [0110 0000] Facsimile coded data */
+    T4_RCP = 0x86           /*! [0110 0001] Return to control for partial page */
+};
+
+#endif
+/*- End of file ------------------------------------------------------------*/
diff --git a/src/codec/spandsp/src/spandsp/t30_logging.h b/src/codec/spandsp/src/spandsp/t30_logging.h
new file mode 100644 (file)
index 0000000..2dc9bb3
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * t30_logging.h - definitions for T.30 fax processing
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2003 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.
+ */
+
+/*! \file */
+
+#if !defined(_SPANDSP_T30_LOGGING_H_)
+#define _SPANDSP_T30_LOGGING_H_
+
+#if defined(__cplusplus)
+extern "C"
+{
+#endif
+
+/*! Return a text name for a T.30 frame type.
+    \brief Return a text name for a T.30 frame type.
+    \param x The frametype octet.
+    \return A pointer to the text name for the frame type. If the frame type is
+            not value, the string "???" is returned. */
+SPAN_DECLARE(const char *) t30_frametype(uint8_t x);
+
+/*! Decode a DIS, DTC or DCS frame, and log the contents.
+    \brief Decode a DIS, DTC or DCS frame, and log the contents.
+    \param s The T.30 context.
+    \param dis A pointer to the frame to be decoded.
+    \param len The length of the frame. */
+SPAN_DECLARE(void) t30_decode_dis_dtc_dcs(t30_state_t *s, const uint8_t *dis, int len);
+
+/*! Convert a phase E completion code to a short text description.
+    \brief Convert a phase E completion code to a short text description.
+    \param result The result code.
+    \return A pointer to the description. */
+SPAN_DECLARE(const char *) t30_completion_code_to_str(int result);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif
+/*- End of file ------------------------------------------------------------*/
diff --git a/src/codec/spandsp/src/spandsp/t35.h b/src/codec/spandsp/src/spandsp/t35.h
new file mode 100644 (file)
index 0000000..553b621
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * t35.h - ITU T.35 FAX non-standard facility processing.
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2003 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.
+ */
+
+/*! \file */
+
+#if !defined(_SPANDSP_T35_H_)
+#define _SPANDSP_T35_H_
+
+/*! \page t35_page T.35 manufacturer specific processing for FAX machines
+\section t35_page_sec_1 What does it do?
+???.
+
+\section t35_page_sec_2 How does it work?
+???.
+*/
+
+/*! A table of the country names associated with each possible value of the T.35 country code
+    selector octet. */
+extern const char *t35_country_codes[256];
+
+#if defined(__cplusplus)
+extern "C"
+{
+#endif
+
+/*! Decode an NSF field to try to determine the make and model of the
+    remote machine.
+    \brief Decode an NSF field.
+    \param msg The NSF message.
+    \param len The length of the NSF message.
+    \param country A pointer which will be pointed to the identified country of origin.
+           If a NULL pointer is given, the country of origin will not be returned.
+           If the country of origin is not identified, NULL will be returned.
+    \param vendor A pointer which will be pointed to the identified vendor.
+           If a NULL pointer is given, the vendor ID will not be returned.
+           If the vendor is not identified, NULL will be returned.
+    \param model A pointer which will be pointed to the identified model.
+           If a NULL pointer is given, the model will not be returned.
+           If the model is not identified, NULL will be returned.
+    \return TRUE if the machine was identified, otherwise FALSE.
+*/
+SPAN_DECLARE(int) t35_decode(const uint8_t *msg, int len, const char **country, const char **vendor, const char **model);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif
+/*- End of file ------------------------------------------------------------*/
diff --git a/src/codec/spandsp/src/spandsp/t38_core.h b/src/codec/spandsp/src/spandsp/t38_core.h
new file mode 100644 (file)
index 0000000..1476969
--- /dev/null
@@ -0,0 +1,413 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * t38_core.h - An implementation of T.38, less the packet exchange part
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2005 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.
+ */
+
+/*! \file */
+
+#if !defined(_SPANDSP_T38_CORE_H_)
+#define _SPANDSP_T38_CORE_H_
+
+/*! \page t38_core_page T.38 real time FAX over IP message handling
+There are two ITU recommendations which address sending FAXes over IP networks. T.37 specifies a
+method of encapsulating FAX images in e-mails, and transporting them to the recipient (an e-mail
+box, or another FAX machine) in a store-and-forward manner. T.38 defines a protocol for
+transmitting a FAX across an IP network in real time. The core T.38 modules implements the basic
+message handling for the T.38, real time, FAX over IP (FoIP) protocol.
+
+The T.38 protocol can operate between:
+    - Internet-aware FAX terminals, which connect directly to an IP network. The T.38 terminal module
+      extends this module to provide a complete T.38 terminal.
+    - FAX gateways, which allow traditional PSTN FAX terminals to communicate through the Internet.
+      The T.38 gateway module extends this module to provide a T.38 gateway.
+    - A combination of terminals and gateways.
+
+T.38 is the only standardised protocol which exists for real-time FoIP. Reliably transporting a
+FAX between PSTN FAX terminals, through an IP network, requires use of the T.38 protocol at FAX
+gateways. VoIP connections are not robust for modem use, including FAX modem use. Most use low
+bit rate codecs, which cannot convey the modem signals accurately. Even when high bit rate
+codecs are used, VoIP connections suffer dropouts and timing adjustments, which modems cannot
+tolerate. In a LAN environment the dropout rate may be very low, but the timing adjustments which
+occur in VoIP connections still make modem operation unreliable. T.38 FAX gateways deal with the
+delays, timing jitter, and packet loss experienced in packet networks, and isolate the PSTN FAX
+terminals from these as far as possible. In addition, by sending FAXes as image data, rather than
+digitised audio, they reduce the required bandwidth of the IP network.
+
+\section t38_core_page_sec_1 What does it do?
+
+\section t38_core_page_sec_2 How does it work?
+
+Timing differences and jitter between two T.38 entities can be a serious problem, if one of those
+entities is a PSTN gateway.
+
+Flow control for non-ECM image data takes advantage of several features of the T.30 specification.
+First, an unspecified number of 0xFF octets may be sent at the start of transmission. This means we
+can add endless extra 0xFF bytes at this point, without breaking the T.30 spec. In practice, we
+cannot add too many, or we will affect the timing tolerance of the T.30 protocol by delaying the
+response at the end of each image. Secondly, just before an end of line (EOL) marker we can pad
+with zero bits. Again, the number is limited only by need to avoid upsetting the timing of the
+step following the non-ECM data.
+*/
+
+/*! T.38 indicator types */
+enum t30_indicator_types_e
+{
+    T38_IND_NO_SIGNAL = 0,
+    T38_IND_CNG,
+    T38_IND_CED,
+    T38_IND_V21_PREAMBLE,
+    T38_IND_V27TER_2400_TRAINING,
+    T38_IND_V27TER_4800_TRAINING,
+    T38_IND_V29_7200_TRAINING,
+    T38_IND_V29_9600_TRAINING,
+    T38_IND_V17_7200_SHORT_TRAINING,
+    T38_IND_V17_7200_LONG_TRAINING,
+    T38_IND_V17_9600_SHORT_TRAINING,
+    T38_IND_V17_9600_LONG_TRAINING,
+    T38_IND_V17_12000_SHORT_TRAINING,
+    T38_IND_V17_12000_LONG_TRAINING,
+    T38_IND_V17_14400_SHORT_TRAINING,
+    T38_IND_V17_14400_LONG_TRAINING,
+    T38_IND_V8_ANSAM,
+    T38_IND_V8_SIGNAL,
+    T38_IND_V34_CNTL_CHANNEL_1200,
+    T38_IND_V34_PRI_CHANNEL,
+    T38_IND_V34_CC_RETRAIN,
+    T38_IND_V33_12000_TRAINING,
+    T38_IND_V33_14400_TRAINING
+};
+
+/*! T.38 data types */
+enum t38_data_types_e
+{
+    T38_DATA_NONE = -1,
+    T38_DATA_V21 = 0,
+    T38_DATA_V27TER_2400,
+    T38_DATA_V27TER_4800,
+    T38_DATA_V29_7200,
+    T38_DATA_V29_9600,
+    T38_DATA_V17_7200,
+    T38_DATA_V17_9600,
+    T38_DATA_V17_12000,
+    T38_DATA_V17_14400,
+    T38_DATA_V8,
+    T38_DATA_V34_PRI_RATE,
+    T38_DATA_V34_CC_1200,
+    T38_DATA_V34_PRI_CH,
+    T38_DATA_V33_12000,
+    T38_DATA_V33_14400
+};
+
+/*! T.38 data field types */
+enum t38_field_types_e
+{
+    T38_FIELD_HDLC_DATA = 0,
+    T38_FIELD_HDLC_SIG_END,
+    T38_FIELD_HDLC_FCS_OK,
+    T38_FIELD_HDLC_FCS_BAD,
+    T38_FIELD_HDLC_FCS_OK_SIG_END,
+    T38_FIELD_HDLC_FCS_BAD_SIG_END,
+    T38_FIELD_T4_NON_ECM_DATA,
+    T38_FIELD_T4_NON_ECM_SIG_END,
+    T38_FIELD_CM_MESSAGE,
+    T38_FIELD_JM_MESSAGE,
+    T38_FIELD_CI_MESSAGE,
+    T38_FIELD_V34RATE
+};
+
+/*! T.38 field classes */
+enum t38_field_classes_e
+{
+    T38_FIELD_CLASS_NONE = 0,
+    T38_FIELD_CLASS_HDLC,
+    T38_FIELD_CLASS_NON_ECM
+};
+
+/*! T.38 message types */
+enum t38_message_types_e
+{
+    T38_TYPE_OF_MSG_T30_INDICATOR = 0,
+    T38_TYPE_OF_MSG_T30_DATA
+};
+
+/*! T.38 transport types */
+enum t38_transport_types_e
+{
+    T38_TRANSPORT_UDPTL = 0,
+    T38_TRANSPORT_RTP,
+    T38_TRANSPORT_TCP
+};
+
+/*! T.38 TCF management types */
+enum t38_data_rate_management_types_e
+{
+    T38_DATA_RATE_MANAGEMENT_LOCAL_TCF = 1,
+    T38_DATA_RATE_MANAGEMENT_TRANSFERRED_TCF = 2
+};
+
+/*! T.38 Packet categories used for setting the redundancy level and packet repeat
+    counts on a packet by packet basis. */
+enum t38_packet_categories_e
+{
+    /*! \brief Indicator packet */
+    T38_PACKET_CATEGORY_INDICATOR = 0,
+    /*! \brief Control data packet */
+    T38_PACKET_CATEGORY_CONTROL_DATA = 1,
+    /*! \brief Terminating control data packet */
+    T38_PACKET_CATEGORY_CONTROL_DATA_END = 2,
+    /*! \brief Image data packet */
+    T38_PACKET_CATEGORY_IMAGE_DATA = 3,
+    /*! \brief Terminating image data packet */
+    T38_PACKET_CATEGORY_IMAGE_DATA_END = 4
+};
+
+#define T38_RX_BUF_LEN  2048
+#define T38_TX_BUF_LEN  16384
+
+/*! T.38 data field */
+typedef struct
+{
+    /*! Field type */
+    int field_type;
+    /*! Field contents */
+    const uint8_t *field;
+    /*! Field length */
+    int field_len;
+} t38_data_field_t;
+
+/*!
+    Core T.38 state, common to all modes of T.38.
+*/
+typedef struct t38_core_state_s t38_core_state_t;
+
+typedef int (t38_tx_packet_handler_t)(t38_core_state_t *s, void *user_data, const uint8_t *buf, int len, int count);
+
+typedef int (t38_rx_indicator_handler_t)(t38_core_state_t *s, void *user_data, int indicator);
+typedef int (t38_rx_data_handler_t)(t38_core_state_t *s, void *user_data, int data_type, int field_type, const uint8_t *buf, int len);
+typedef int (t38_rx_missing_handler_t)(t38_core_state_t *s, void *user_data, int rx_seq_no, int expected_seq_no);
+
+#if defined(__cplusplus)
+extern "C"
+{
+#endif
+
+/*! \brief Convert the code for an indicator to a short text name.
+    \param indicator The type of indicator.
+    \return A pointer to a short text name for the indicator. */
+SPAN_DECLARE(const char *) t38_indicator_to_str(int indicator);
+
+/*! \brief Convert the code for a type of data to a short text name.
+    \param data_type The data type.
+    \return A pointer to a short text name for the data type. */
+SPAN_DECLARE(const char *) t38_data_type_to_str(int data_type);
+
+/*! \brief Convert the code for a type of data field to a short text name.
+    \param field_type The field type.
+    \return A pointer to a short text name for the field type. */
+SPAN_DECLARE(const char *) t38_field_type_to_str(int field_type);
+
+/*! \brief Convert the code for a CM profile code to text description.
+    \param profile The profile code from a CM message.
+    \return A pointer to a short text description of the profile. */
+SPAN_DECLARE(const char *) t38_cm_profile_to_str(int profile);
+
+/*! \brief Convert a JM message code to text description.
+    \param data The data field of the message.
+    \param len The length of the data field.
+    \return A pointer to a short text description of the profile. */
+SPAN_DECLARE(const char *) t38_jm_to_str(const uint8_t *data, int len);
+
+/*! \brief Convert a V34rate message to an actual bit rate.
+    \param data The data field of the message.
+    \param len The length of the data field.
+    \return The bit rate, or -1 for a bad message. */
+SPAN_DECLARE(int) t38_v34rate_to_bps(const uint8_t *data, int len);
+
+/*! \brief Send an indicator packet
+    \param s The T.38 context.
+    \param indicator The indicator to send.
+    \return The delay to allow after this indicator is sent. */
+SPAN_DECLARE(int) t38_core_send_indicator(t38_core_state_t *s, int indicator);
+
+/*! \brief Find the delay to allow for HDLC flags after sending an indicator
+    \param s The T.38 context.
+    \param indicator The indicator to check.
+    \return The delay to allow for initial HDLC flags after this indicator is sent. */
+SPAN_DECLARE(int) t38_core_send_flags_delay(t38_core_state_t *s, int indicator);
+
+/*! \brief Find the delay to allow for modem training after sending an indicator
+    \param s The T.38 context.
+    \param indicator The indicator to check.
+    \return The delay to allow for modem training after this indicator is sent. */
+SPAN_DECLARE(int) t38_core_send_training_delay(t38_core_state_t *s, int indicator);
+
+/*! \brief Send a data packet
+    \param s The T.38 context.
+    \param data_type The packet's data type.
+    \param field_type The packet's field type.
+    \param field The message data content for the packet.
+    \param field_len The length of the message data, in bytes.
+    \param category The category of the packet being sent. This should be one of the values defined for t38_packet_categories_e.
+    \return ??? */
+SPAN_DECLARE(int) t38_core_send_data(t38_core_state_t *s, int data_type, int field_type, const uint8_t field[], int field_len, int category);
+
+/*! \brief Send a data packet
+    \param s The T.38 context.
+    \param data_type The packet's data type.
+    \param field The list of fields.
+    \param fields The number of fields in the list.
+    \param category The category of the packet being sent. This should be one of the values defined for t38_packet_categories_e.
+    \return ??? */
+SPAN_DECLARE(int) t38_core_send_data_multi_field(t38_core_state_t *s, int data_type, const t38_data_field_t field[], int fields, int category);
+
+/*! \brief Process a received T.38 IFP packet.
+    \param s The T.38 context.
+    \param buf The packet contents.
+    \param len The length of the packet contents.
+    \param seq_no The packet sequence number.
+    \return 0 for OK, else -1. */
+SPAN_DECLARE(int) t38_core_rx_ifp_packet(t38_core_state_t *s, const uint8_t *buf, int len, uint16_t seq_no);
+
+/*! Set the method to be used for data rate management, as per the T.38 spec.
+    \param s The T.38 context.
+    \param method 1 for pass TCF across the T.38 link, 2 for handle TCF locally.
+*/
+SPAN_DECLARE(void) t38_set_data_rate_management_method(t38_core_state_t *s, int method);
+
+/*! Set the data transport protocol.
+    \param s The T.38 context.
+    \param data_transport_protocol UDPTL, RTP or TPKT.
+*/
+SPAN_DECLARE(void) t38_set_data_transport_protocol(t38_core_state_t *s, int data_transport_protocol);
+
+/*! Set the non-ECM fill bit removal mode.
+    \param s The T.38 context.
+    \param fill_bit_removal TRUE to remove fill bits across the T.38 link, else FALSE.
+*/
+SPAN_DECLARE(void) t38_set_fill_bit_removal(t38_core_state_t *s, int fill_bit_removal);
+
+/*! Set the MMR transcoding mode.
+    \param s The T.38 context.
+    \param mmr_transcoding TRUE to transcode to MMR across the T.38 link, else FALSE.
+*/
+SPAN_DECLARE(void) t38_set_mmr_transcoding(t38_core_state_t *s, int mmr_transcoding);
+
+/*! Set the JBIG transcoding mode.
+    \param s The T.38 context.
+    \param jbig_transcoding TRUE to transcode to JBIG across the T.38 link, else FALSE.
+*/
+SPAN_DECLARE(void) t38_set_jbig_transcoding(t38_core_state_t *s, int jbig_transcoding);
+
+/*! Set the maximum buffer size for received data at the far end.
+    \param s The T.38 context.
+    \param max_buffer_size The maximum buffer size.
+*/
+SPAN_DECLARE(void) t38_set_max_buffer_size(t38_core_state_t *s, int max_buffer_size);
+
+/*! Set the maximum size of an IFP packet that is acceptable by the far end.
+    \param s The T.38 context.
+    \param max_datagram_size The maximum IFP packet length, in bytes.
+*/
+SPAN_DECLARE(void) t38_set_max_datagram_size(t38_core_state_t *s, int max_datagram_size);
+
+/*! \brief Send a data packet
+    \param s The T.38 context.
+    \param category The category of the packet being sent. This should be one of the values defined for t38_packet_categories_e.
+    \param setting The repeat count for the category. This should be at least one for all categories other an indicator packets.
+                   Zero is valid for indicator packets, as it suppresses the sending of indicator packets, as an application using
+                   TCP for the transport would require. As the setting is passed through to the transmission channel, additional
+                   information may be encoded in it, such as the redundancy depth for the particular packet category. */
+SPAN_DECLARE(void) t38_set_redundancy_control(t38_core_state_t *s, int category, int setting);
+
+SPAN_DECLARE(void) t38_set_fastest_image_data_rate(t38_core_state_t *s, int max_rate);
+
+SPAN_DECLARE(int) t38_get_fastest_image_data_rate(t38_core_state_t *s);
+
+/*! Set the T.38 version to be emulated.
+    \param s The T.38 context.
+    \param t38_version Version number, as in the T.38 spec.
+*/
+SPAN_DECLARE(void) t38_set_t38_version(t38_core_state_t *s, int t38_version);
+
+/*! Set the sequence number handling option.
+    \param s The T.38 context.
+    \param check TRUE to check sequence numbers, and handle gaps reasonably. FALSE
+           for no sequence number processing (e.g. for TPKT over TCP transport).
+*/
+SPAN_DECLARE(void) t38_set_sequence_number_handling(t38_core_state_t *s, int check);
+
+/*! Set the TEP handling option.
+    \param s The T.38 context.
+    \param allow_for_tep TRUE to allow for TEP playout, else FALSE.
+*/
+SPAN_DECLARE(void) t38_set_tep_handling(t38_core_state_t *s, int allow_for_tep);
+
+/*! Get a pointer to the logging context associated with a T.38 context.
+    \brief Get a pointer to the logging context associated with a T.38 context.
+    \param s The T.38 context.
+    \return A pointer to the logging context, or NULL.
+*/
+SPAN_DECLARE(logging_state_t *) t38_core_get_logging_state(t38_core_state_t *s);
+
+/*! Restart a T.38 core context.
+    \brief Restart a T.38 core context.
+    \param s The T.38 context.
+    \return 0 for OK, else -1. */
+SPAN_DECLARE(int) t38_core_restart(t38_core_state_t *s);
+
+/*! Initialise a T.38 core context.
+    \brief Initialise a T.38 core context.
+    \param s The T.38 context.
+    \param rx_indicator_handler Receive indicator handling routine.
+    \param rx_data_handler Receive data packet handling routine.
+    \param rx_rx_missing_handler Missing receive packet handling routine.
+    \param rx_packet_user_data An opaque pointer passed to the rx packet handling routines.
+    \param tx_packet_handler Packet transmit handling routine.
+    \param tx_packet_user_data An opaque pointer passed to the tx_packet_handler.
+    \return A pointer to the T.38 context, or NULL if there was a problem. */
+SPAN_DECLARE(t38_core_state_t *) t38_core_init(t38_core_state_t *s,
+                                               t38_rx_indicator_handler_t *rx_indicator_handler,
+                                               t38_rx_data_handler_t *rx_data_handler,
+                                               t38_rx_missing_handler_t *rx_missing_handler,
+                                               void *rx_user_data,
+                                               t38_tx_packet_handler_t *tx_packet_handler,
+                                               void *tx_packet_user_data);
+
+/*! Release a signaling tone transmitter context.
+    \brief Release a signaling tone transmitter context.
+    \param s The T.38 context.
+    \return 0 for OK */
+SPAN_DECLARE(int) t38_core_release(t38_core_state_t *s);
+
+/*! Free a signaling tone transmitter context.
+    \brief Free a signaling tone transmitter context.
+    \param s The T.38 context.
+    \return 0 for OK */
+SPAN_DECLARE(int) t38_core_free(t38_core_state_t *s);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif
+/*- End of file ------------------------------------------------------------*/
diff --git a/src/codec/spandsp/src/spandsp/t38_terminal.h b/src/codec/spandsp/src/spandsp/t38_terminal.h
new file mode 100644 (file)
index 0000000..c751b47
--- /dev/null
@@ -0,0 +1,143 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * t38_terminal.h - T.38 termination, less the packet exchange part
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2005 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.
+ */
+
+/*! \file */
+
+#if !defined(_SPANDSP_T38_TERMINAL_H_)
+#define _SPANDSP_T38_TERMINAL_H_
+
+/*! \page t38_terminal_page T.38 real time FAX over IP termination
+\section t38_terminal_page_sec_1 What does it do?
+
+\section t38_terminal_page_sec_2 How does it work?
+*/
+
+/* Make sure the HDLC frame buffers are big enough for ECM frames. */
+#define T38_MAX_HDLC_LEN        260
+
+enum
+{
+    /*! This option enables the continuous streaming of FAX data, with no allowance for
+        FAX machine speeds. This is usually used with TCP/TPKT transmission of T.38 FAXes */
+    T38_TERMINAL_OPTION_NO_PACING = 0x01,
+    /*! This option enables the regular repeat transmission of indicator signals,
+        during periods when no FAX signal transmission occurs. */
+    T38_TERMINAL_OPTION_REGULAR_INDICATORS = 0x02,
+    /*! This option enables the regular repeat transmission of indicator signals for the 
+        first 2s, during periods when no FAX signal transmission occurs. */
+    T38_TERMINAL_OPTION_2S_REPEATING_INDICATORS = 0x04
+};
+
+typedef struct t38_terminal_state_s t38_terminal_state_t;
+
+#if defined(__cplusplus)
+extern "C"
+{
+#endif
+
+SPAN_DECLARE(int) t38_terminal_send_timeout(t38_terminal_state_t *s, int samples);
+
+/*! Set configuration options.
+    \brief Set configuration options.
+    \param s The T.38 context.
+    \param config A combinations of T38_TERMINAL_OPTION_* bits.
+*/
+SPAN_DECLARE(void) t38_terminal_set_config(t38_terminal_state_t *s, int config);
+
+/*! Select whether the time for talker echo protection tone will be allowed for when sending.
+    \brief Select whether TEP time will be allowed for.
+    \param s The T.38 context.
+    \param use_tep TRUE if TEP should be allowed for.
+*/
+SPAN_DECLARE(void) t38_terminal_set_tep_mode(t38_terminal_state_t *s, int use_tep);
+
+/*! Select whether non-ECM fill bits are to be removed during transmission.
+    \brief Select whether non-ECM fill bits are to be removed during transmission.
+    \param s The T.38 context.
+    \param remove TRUE if fill bits are to be removed.
+*/
+SPAN_DECLARE(void) t38_terminal_set_fill_bit_removal(t38_terminal_state_t *s, int remove);
+
+/*! Get a pointer to the T.30 engine associated with a termination mode T.38 context.
+    \brief Get a pointer to the T.30 engine associated with a T.38 context.
+    \param s The T.38 context.
+    \return A pointer to the T.30 context, or NULL.
+*/
+SPAN_DECLARE(t30_state_t *) t38_terminal_get_t30_state(t38_terminal_state_t *s);
+
+/*! Get a pointer to the T.38 core IFP packet engine associated with a
+    termination mode T.38 context.
+    \brief Get a pointer to the T.38 core IFP packet engine associated
+           with a T.38 context.
+    \param s The T.38 context.
+    \return A pointer to the T.38 core context, or NULL.
+*/
+SPAN_DECLARE(t38_core_state_t *) t38_terminal_get_t38_core_state(t38_terminal_state_t *s);
+
+/*! Get a pointer to the logging context associated with a T.38 context.
+    \brief Get a pointer to the logging context associated with a T.38 context.
+    \param s The T.38 context.
+    \return A pointer to the logging context, or NULL.
+*/
+SPAN_DECLARE(logging_state_t *) t38_terminal_get_logging_state(t38_terminal_state_t *s);
+
+/*! \brief Reinitialise a termination mode T.38 context.
+    \param s The T.38 context.
+    \param calling_party TRUE if the context is for a calling party. FALSE if the
+           context is for an answering party.
+    \return 0 for OK, else -1. */
+SPAN_DECLARE(int) t38_terminal_restart(t38_terminal_state_t *s,
+                                       int calling_party);
+
+/*! \brief Initialise a termination mode T.38 context.
+    \param s The T.38 context.
+    \param calling_party TRUE if the context is for a calling party. FALSE if the
+           context is for an answering party.
+    \param tx_packet_handler A callback routine to encapsulate and transmit T.38 packets.
+    \param tx_packet_user_data An opaque pointer passed to the tx_packet_handler routine.
+    \return A pointer to the termination mode T.38 context, or NULL if there was a problem. */
+SPAN_DECLARE(t38_terminal_state_t *) t38_terminal_init(t38_terminal_state_t *s,
+                                                       int calling_party,
+                                                       t38_tx_packet_handler_t *tx_packet_handler,
+                                                       void *tx_packet_user_data);
+
+/*! Release a termination mode T.38 context.
+    \brief Release a T.38 context.
+    \param s The T.38 context.
+    \return 0 for OK, else -1. */
+SPAN_DECLARE(int) t38_terminal_release(t38_terminal_state_t *s);
+
+/*! Free a a termination mode T.38 context.
+    \brief Free a T.38 context.
+    \param s The T.38 context.
+    \return 0 for OK, else -1. */
+SPAN_DECLARE(int) t38_terminal_free(t38_terminal_state_t *s);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif
+/*- End of file ------------------------------------------------------------*/
diff --git a/src/codec/spandsp/src/spandsp/t4_rx.h b/src/codec/spandsp/src/spandsp/t4_rx.h
new file mode 100644 (file)
index 0000000..b1b0a96
--- /dev/null
@@ -0,0 +1,344 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * t4_rx.h - definitions for T.4 FAX receive processing
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2003 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.
+ */
+
+/*! \file */
+
+#if !defined(_SPANDSP_T4_RX_H_)
+#define _SPANDSP_T4_RX_H_
+
+/*! \page t4_page T.4 image compression and decompression
+
+\section t4_page_sec_1 What does it do?
+The T.4 image compression and decompression routines implement the 1D and 2D
+encoding methods defined in ITU specification T.4. They also implement the pure
+2D encoding method defined in T.6. These are image compression algorithms used
+for FAX transmission.
+
+\section t4_page_sec_1 How does it work?
+*/
+
+typedef int (*t4_row_write_handler_t)(void *user_data, const uint8_t buf[], size_t len);
+
+/*! Supported compression modes. */
+typedef enum
+{
+    /*! No compression */
+    T4_COMPRESSION_NONE = 0,
+    /*! T.1 1D compression */
+    T4_COMPRESSION_ITU_T4_1D = 1,
+    /*! T.4 2D compression */
+    T4_COMPRESSION_ITU_T4_2D = 2,
+    /*! T.6 2D compression */
+    T4_COMPRESSION_ITU_T6 = 3,
+    /*! T.85 monochrome JBIG coding with L0 fixed. */
+    T4_COMPRESSION_ITU_T85 = 4,
+    /*! T.85 monochrome JBIG coding with L0 variable. */
+    T4_COMPRESSION_ITU_T85_L0 = 5,
+    /*! T.43 colour JBIG coding */
+    T4_COMPRESSION_ITU_T43 = 6,
+    /*! T.45 run length colour compression */
+    T4_COMPRESSION_ITU_T45 = 7,
+    /*! T.81 + T.30 Annex E colour JPEG coding */
+    T4_COMPRESSION_ITU_T81 = 8,
+    /*! T.81 + T.30 Annex K colour sYCC-JPEG coding */
+    T4_COMPRESSION_ITU_SYCC_T81 = 9
+} t4_image_compression_t;
+
+/*! Supported X resolutions, in pixels per metre. */
+typedef enum
+{
+    T4_X_RESOLUTION_R4 = 4016,
+    T4_X_RESOLUTION_R8 = 8031,
+    T4_X_RESOLUTION_300 = 11811,
+    T4_X_RESOLUTION_R16 = 16063,
+    T4_X_RESOLUTION_600 = 23622,
+    T4_X_RESOLUTION_800 = 31496,
+    T4_X_RESOLUTION_1200 = 47244
+} t4_image_x_resolution_t;
+
+/*! Supported Y resolutions, in pixels per metre. */
+typedef enum
+{
+    T4_Y_RESOLUTION_STANDARD = 3850,
+    T4_Y_RESOLUTION_FINE = 7700,
+    T4_Y_RESOLUTION_300 = 11811,
+    T4_Y_RESOLUTION_SUPERFINE = 15400,  /* 400 is 15748 */
+    T4_Y_RESOLUTION_600 = 23622,
+    T4_Y_RESOLUTION_800 = 31496,
+    T4_Y_RESOLUTION_1200 = 47244
+} t4_image_y_resolution_t;
+
+/*!
+    Exact widths in PELs for the difference resolutions, and page widths.
+    Note:
+        The A4 widths also apply to North American letter and legal.
+        The R4 resolution widths are not supported in recent versions of T.30
+        Only images of exactly these widths are acceptable for FAX transmisson.
+
+    R4    864 pels/215mm for ISO A4, North American Letter and Legal
+    R4   1024 pels/255mm for ISO B4
+    R4   1216 pels/303mm for ISO A3
+    R8   1728 pels/215mm for ISO A4, North American Letter and Legal
+    R8   2048 pels/255mm for ISO B4
+    R8   2432 pels/303mm for ISO A3
+    R16  3456 pels/215mm for ISO A4, North American Letter and Legal
+    R16  4096 pels/255mm for ISO B4
+    R16  4864 pels/303mm for ISO A3
+*/
+typedef enum
+{
+    T4_WIDTH_R4_A4 = 864,
+    T4_WIDTH_R4_B4 = 1024,
+    T4_WIDTH_R4_A3 = 1216,
+    T4_WIDTH_R8_A4 = 1728,
+    T4_WIDTH_R8_B4 = 2048,
+    T4_WIDTH_R8_A3 = 2432,
+    T4_WIDTH_300_A4 = 2592,
+    T4_WIDTH_300_B4 = 3072,
+    T4_WIDTH_300_A3 = 3648,
+    T4_WIDTH_R16_A4 = 3456,
+    T4_WIDTH_R16_B4 = 4096,
+    T4_WIDTH_R16_A3 = 4864,
+    T4_WIDTH_600_A4 = 5184,
+    T4_WIDTH_600_B4 = 6144,
+    T4_WIDTH_600_A3 = 7296,
+    T4_WIDTH_1200_A4 = 10368,
+    T4_WIDTH_1200_B4 = 12288,
+    T4_WIDTH_1200_A3 = 14592
+} t4_image_width_t;
+
+/*!
+    Length of the various supported paper sizes, in pixels at the various Y resolutions.
+    Paper sizes are
+        A4 (215mm x 297mm)
+        B4 (255mm x 364mm)
+        A3 (303mm x 418.56mm)
+        North American Letter (215.9mm x 279.4mm)
+        North American Legal (215.9mm x 355.6mm)
+        Unlimited
+
+    T.4 does not accurately define the maximum number of scan lines in a page. A wide
+    variety of maximum row counts are used in the real world. It is important not to
+    set our sending limit too high, or a receiving machine might split pages. It is
+    important not to set it too low, or we might clip pages.
+
+    Values seen for standard resolution A4 pages include 1037, 1045, 1109, 1126 and 1143.
+    1109 seems the most-popular.  At fine res 2150, 2196, 2200, 2237, 2252-2262, 2264,
+    2286, and 2394 are used. 2255 seems the most popular. We try to use balanced choices
+    here.
+*/
+typedef enum
+{
+    /* A4 is 297mm long */
+    T4_LENGTH_STANDARD_A4 = 1143,
+    T4_LENGTH_FINE_A4 = 2286,
+    T4_LENGTH_300_A4 = 4665,
+    T4_LENGTH_SUPERFINE_A4 = 4573,
+    T4_LENGTH_600_A4 = 6998,
+    T4_LENGTH_800_A4 = 9330,
+    T4_LENGTH_1200_A4 = 13996,
+    /* B4 is 364mm long */
+    T4_LENGTH_STANDARD_B4 = 1401,
+    T4_LENGTH_FINE_B4 = 2802,
+    T4_LENGTH_300_B4 = 0,
+    T4_LENGTH_SUPERFINE_B4 = 5605,
+    T4_LENGTH_600_B4 = 0,
+    T4_LENGTH_800_B4 = 0,
+    T4_LENGTH_1200_B4 = 0,
+    /* North American letter is 279.4mm long */
+    T4_LENGTH_STANDARD_US_LETTER = 1075,
+    T4_LENGTH_FINE_US_LETTER = 2151,
+    T4_LENGTH_300_US_LETTER = 0,
+    T4_LENGTH_SUPERFINE_US_LETTER = 4302,
+    T4_LENGTH_600_US_LETTER = 0,
+    T4_LENGTH_800_US_LETTER = 0,
+    T4_LENGTH_1200_US_LETTER = 0,
+    /* North American legal is 355.6mm long */
+    T4_LENGTH_STANDARD_US_LEGAL = 1369,
+    T4_LENGTH_FINE_US_LEGAL = 2738,
+    T4_LENGTH_300_US_LEGAL = 0,
+    T4_LENGTH_SUPERFINE_US_LEGAL = 5476,
+    T4_LENGTH_600_US_LEGAL = 0,
+    T4_LENGTH_800_US_LEGAL = 0,
+    T4_LENGTH_1200_US_LEGAL = 0
+} t4_image_length_t;
+
+/*!
+    T.4 FAX compression/decompression descriptor. This defines the working state
+    for a single instance of a T.4 FAX compression or decompression channel.
+*/
+typedef struct t4_state_s t4_state_t;
+
+/*!
+    T.4 FAX compression/decompression statistics.
+*/
+typedef struct
+{
+    /*! \brief The number of pages transferred so far. */
+    int pages_transferred;
+    /*! \brief The number of pages in the file (<0 if unknown). */
+    int pages_in_file;
+    /*! \brief The number of horizontal pixels in the most recent page. */
+    int width;
+    /*! \brief The number of vertical pixels in the most recent page. */
+    int length;
+    /*! \brief The number of bad pixel rows in the most recent page. */
+    int bad_rows;
+    /*! \brief The largest number of bad pixel rows in a block in the most recent page. */
+    int longest_bad_row_run;
+    /*! \brief The horizontal resolution of the page in pixels per metre */
+    int x_resolution;
+    /*! \brief The vertical resolution of the page in pixels per metre */
+    int y_resolution;
+    /*! \brief The type of compression used between the FAX machines */
+    int encoding;
+    /*! \brief The size of the image on the line, in bytes */
+    int line_image_size;
+} t4_stats_t;
+    
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+/*! \brief Prepare for reception of a document.
+    \param s The T.4 context.
+    \param file The name of the file to be received.
+    \param output_encoding The output encoding.
+    \return A pointer to the context, or NULL if there was a problem. */
+SPAN_DECLARE(t4_state_t *) t4_rx_init(t4_state_t *s, const char *file, int output_encoding);
+
+/*! \brief Prepare to receive the next page of the current document.
+    \param s The T.4 context.
+    \return zero for success, -1 for failure. */
+SPAN_DECLARE(int) t4_rx_start_page(t4_state_t *s);
+
+/*! \brief Put a bit of the current document page.
+    \param s The T.4 context.
+    \param bit The data bit.
+    \return TRUE when the bit ends the document page, otherwise FALSE. */
+SPAN_DECLARE(int) t4_rx_put_bit(t4_state_t *s, int bit);
+
+/*! \brief Put a byte of the current document page.
+    \param s The T.4 context.
+    \param byte The data byte.
+    \return TRUE when the byte ends the document page, otherwise FALSE. */
+SPAN_DECLARE(int) t4_rx_put_byte(t4_state_t *s, uint8_t byte);
+
+/*! \brief Put a byte of the current document page.
+    \param s The T.4 context.
+    \param buf The buffer containing the chunk.
+    \param len The length of the chunk.
+    \return TRUE when the byte ends the document page, otherwise FALSE. */
+SPAN_DECLARE(int) t4_rx_put_chunk(t4_state_t *s, const uint8_t buf[], int len);
+
+/*! \brief Complete the reception of a page.
+    \param s The T.4 receive context.
+    \return 0 for success, otherwise -1. */
+SPAN_DECLARE(int) t4_rx_end_page(t4_state_t *s);
+
+/*! \brief End reception of a document. Tidy up and close the file.
+           This should be used to end T.4 reception started with
+           t4_rx_init.
+    \param s The T.4 receive context.
+    \return 0 for success, otherwise -1. */
+SPAN_DECLARE(int) t4_rx_release(t4_state_t *s);
+
+/*! \brief End reception of a document. Tidy up, close the file and
+           free the context. This should be used to end T.4 reception
+           started with t4_rx_init.
+    \param s The T.4 receive context.
+    \return 0 for success, otherwise -1. */
+SPAN_DECLARE(int) t4_rx_free(t4_state_t *s);
+
+/*! \brief Set the row write handler for a T.4 receive context.
+    \param s The T.4 receive context.
+    \param handler A pointer to the handler routine.
+    \param user_data An opaque pointer passed to the handler routine.
+    \return 0 for success, otherwise -1. */
+SPAN_DECLARE(int) t4_rx_set_row_write_handler(t4_state_t *s, t4_row_write_handler_t handler, void *user_data);
+
+/*! \brief Set the encoding for the received data.
+    \param s The T.4 context.
+    \param encoding The encoding. */
+SPAN_DECLARE(void) t4_rx_set_rx_encoding(t4_state_t *s, int encoding);
+
+/*! \brief Set the expected width of the received image, in pixel columns.
+    \param s The T.4 context.
+    \param width The number of pixels across the image. */
+SPAN_DECLARE(void) t4_rx_set_image_width(t4_state_t *s, int width);
+
+/*! \brief Set the row-to-row (y) resolution to expect for a received image.
+    \param s The T.4 context.
+    \param resolution The resolution, in pixels per metre. */
+SPAN_DECLARE(void) t4_rx_set_y_resolution(t4_state_t *s, int resolution);
+
+/*! \brief Set the column-to-column (x) resolution to expect for a received image.
+    \param s The T.4 context.
+    \param resolution The resolution, in pixels per metre. */
+SPAN_DECLARE(void) t4_rx_set_x_resolution(t4_state_t *s, int resolution);
+
+/*! \brief Set the DCS information of the fax, for inclusion in the file.
+    \param s The T.4 context.
+    \param dcs The DCS information, formatted as an ASCII string. */
+SPAN_DECLARE(void) t4_rx_set_dcs(t4_state_t *s, const char *dcs);
+
+/*! \brief Set the sub-address of the fax, for inclusion in the file.
+    \param s The T.4 context.
+    \param sub_address The sub-address string. */
+SPAN_DECLARE(void) t4_rx_set_sub_address(t4_state_t *s, const char *sub_address);
+
+/*! \brief Set the identity of the remote machine, for inclusion in the file.
+    \param s The T.4 context.
+    \param ident The identity string. */
+SPAN_DECLARE(void) t4_rx_set_far_ident(t4_state_t *s, const char *ident);
+
+/*! \brief Set the vendor of the remote machine, for inclusion in the file.
+    \param s The T.4 context.
+    \param vendor The vendor string, or NULL. */
+SPAN_DECLARE(void) t4_rx_set_vendor(t4_state_t *s, const char *vendor);
+
+/*! \brief Set the model of the remote machine, for inclusion in the file.
+    \param s The T.4 context.
+    \param model The model string, or NULL. */
+SPAN_DECLARE(void) t4_rx_set_model(t4_state_t *s, const char *model);
+
+/*! Get the current image transfer statistics. 
+    \brief Get the current transfer statistics.
+    \param s The T.4 context.
+    \param t A pointer to a statistics structure. */
+SPAN_DECLARE(void) t4_rx_get_transfer_statistics(t4_state_t *s, t4_stats_t *t);
+
+/*! Get the short text name of an encoding format. 
+    \brief Get the short text name of an encoding format.
+    \param encoding The encoding type.
+    \return A pointer to the string. */
+SPAN_DECLARE(const char *) t4_encoding_to_str(int encoding);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif
+/*- End of file ------------------------------------------------------------*/
diff --git a/src/codec/spandsp/src/spandsp/t4_t6_decode.h b/src/codec/spandsp/src/spandsp/t4_t6_decode.h
new file mode 100644 (file)
index 0000000..860d4ec
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * t4_t6_decode.h - definitions for T.4/T.6 fax decoding
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2003, 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.
+ */
+
+/*! \file */
+
+#if !defined(_SPANDSP_T4_T6_DECODE_H_)
+#define _SPANDSP_T4_T6_DECODE_H_
+
+/*! \page t4_t6_decode_page T.4 and T.6 FAX image decompression
+
+\section t4_t6_decode_page_sec_1 What does it do?
+The T.4 image compression and decompression routines implement the 1D and 2D
+encoding methods defined in ITU specification T.4. They also implement the pure
+2D encoding method defined in T.6. These are image compression algorithms used
+for FAX transmission.
+
+\section t4_t6_decode_page_sec_1 How does it work?
+*/
+
+typedef struct t4_t6_decode_state_s t4_t6_decode_state_t;
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif
+/*- End of file ------------------------------------------------------------*/
diff --git a/src/codec/spandsp/src/spandsp/t4_t6_encode.h b/src/codec/spandsp/src/spandsp/t4_t6_encode.h
new file mode 100644 (file)
index 0000000..72a3598
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * t4_t6_encode.h - definitions for T.4/T.6 fax encoding
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2003, 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.
+ */
+
+/*! \file */
+
+#if !defined(_SPANDSP_T4_T6_ENCODE_H_)
+#define _SPANDSP_T4_T6_ENCODE_H_
+
+typedef struct t4_t6_encode_state_s t4_t6_encode_state_t;
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif
+/*- End of file ------------------------------------------------------------*/
diff --git a/src/codec/spandsp/src/spandsp/t4_tx.h b/src/codec/spandsp/src/spandsp/t4_tx.h
new file mode 100644 (file)
index 0000000..0d32297
--- /dev/null
@@ -0,0 +1,190 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * t4_tx.h - definitions for T.4 FAX transmit processing
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2003 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.
+ */
+
+/*! \file */
+
+#if !defined(_SPANDSP_T4_TX_H_)
+#define _SPANDSP_T4_TX_H_
+
+typedef int (*t4_row_read_handler_t)(void *user_data, uint8_t buf[], size_t len);
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+/*! \brief Prepare for transmission of a document.
+    \param s The T.4 context.
+    \param file The name of the file to be sent.
+    \param start_page The first page to send. -1 for no restriction.
+    \param stop_page The last page to send. -1 for no restriction.
+    \return A pointer to the context, or NULL if there was a problem. */
+SPAN_DECLARE(t4_state_t *) t4_tx_init(t4_state_t *s, const char *file, int start_page, int stop_page);
+
+/*! \brief Prepare to send the next page of the current document.
+    \param s The T.4 context.
+    \return zero for success, -1 for failure. */
+SPAN_DECLARE(int) t4_tx_start_page(t4_state_t *s);
+
+/*! \brief Prepare the current page for a resend.
+    \param s The T.4 context.
+    \return zero for success, -1 for failure. */
+SPAN_DECLARE(int) t4_tx_restart_page(t4_state_t *s);
+
+/*! \brief Check for the existance of the next page, and whether its format is like the
+    current one. This information can be needed before it is determined that the current
+    page is finished with.
+    \param s The T.4 context.
+    \return 0 for next page found with the same format as the current page.
+            1 for next page found with different format from the current page.
+            -1 for no page found, or file failure. */
+SPAN_DECLARE(int) t4_tx_next_page_has_different_format(t4_state_t *s);
+
+/*! \brief Complete the sending of a page.
+    \param s The T.4 context.
+    \return zero for success, -1 for failure. */
+SPAN_DECLARE(int) t4_tx_end_page(t4_state_t *s);
+
+/*! \brief Return the next bit of the current document page, without actually
+           moving forward in the buffer. The document will be padded for the
+           current minimum scan line time.
+    \param s The T.4 context.
+    \return The next bit (i.e. 0 or 1). For the last bit of data, bit 1 is
+            set (i.e. the returned value is 2 or 3). */
+SPAN_DECLARE(int) t4_tx_check_bit(t4_state_t *s);
+
+/*! \brief Get the next bit of the current document page. The document will
+           be padded for the current minimum scan line time.
+    \param s The T.4 context.
+    \return The next bit (i.e. 0 or 1). For the last bit of data, bit 1 is
+            set (i.e. the returned value is 2 or 3). */
+SPAN_DECLARE(int) t4_tx_get_bit(t4_state_t *s);
+
+/*! \brief Get the next byte of the current document page. The document will
+           be padded for the current minimum scan line time.
+    \param s The T.4 context.
+    \return The next byte. For the last byte of data, bit 8 is
+            set. In this case, one or more bits of the byte may be padded with
+            zeros, to complete the byte. */
+SPAN_DECLARE(int) t4_tx_get_byte(t4_state_t *s);
+
+/*! \brief Get the next chunk of the current document page. The document will
+           be padded for the current minimum scan line time.
+    \param s The T.4 context.
+    \param buf The buffer into which the chunk is to written.
+    \param max_len The maximum length of the chunk.
+    \return The actual length of the chunk. If this is less than max_len it 
+            indicates that the end of the document has been reached. */
+SPAN_DECLARE(int) t4_tx_get_chunk(t4_state_t *s, uint8_t buf[], int max_len);
+
+/*! \brief End the transmission of a document. Tidy up and close the file.
+           This should be used to end T.4 transmission started with t4_tx_init.
+    \param s The T.4 context.
+    \return 0 for success, otherwise -1. */
+SPAN_DECLARE(int) t4_tx_release(t4_state_t *s);
+
+/*! \brief End the transmission of a document. Tidy up, close the file and
+           free the context. This should be used to end T.4 transmission
+           started with t4_tx_init.
+    \param s The T.4 context.
+    \return 0 for success, otherwise -1. */
+SPAN_DECLARE(int) t4_tx_free(t4_state_t *s);
+
+/*! \brief Set the encoding for the encoded data.
+    \param s The T.4 context.
+    \param encoding The encoding. */
+SPAN_DECLARE(void) t4_tx_set_tx_encoding(t4_state_t *s, int encoding);
+
+/*! \brief Set the minimum number of encoded bits per row. This allows the
+           makes the encoding process to be set to comply with the minimum row
+           time specified by a remote receiving machine.
+    \param s The T.4 context.
+    \param bits The minimum number of bits per row. */
+SPAN_DECLARE(void) t4_tx_set_min_bits_per_row(t4_state_t *s, int bits);
+
+/*! \brief Set the identity of the local machine, for inclusion in page headers.
+    \param s The T.4 context.
+    \param ident The identity string. */
+SPAN_DECLARE(void) t4_tx_set_local_ident(t4_state_t *s, const char *ident);
+
+/*! Set the info field, included in the header line included in each page of an encoded
+    FAX. This is a string of up to 50 characters. Other information (date, local ident, etc.)
+    are automatically included in the header. If the header info is set to NULL or a zero
+    length string, no header lines will be added to the encoded FAX.
+    \brief Set the header info.
+    \param s The T.4 context.
+    \param info A string, of up to 50 bytes, which will form the info field. */
+SPAN_DECLARE(void) t4_tx_set_header_info(t4_state_t *s, const char *info);
+
+/*! Set the time zone for the time stamp in page header lines. If this function is not used
+    the current time zone of the program's environment is used.
+    \brief Set the header timezone.
+    \param s The T.4 context.
+    \param info A POSIX timezone description string. */
+SPAN_DECLARE(void) t4_tx_set_header_tz(t4_state_t *s, const char *tzstring);
+
+/*! \brief Set the row read handler for a T.4 transmit context.
+    \param s The T.4 transmit context.
+    \param handler A pointer to the handler routine.
+    \param user_data An opaque pointer passed to the handler routine.
+    \return 0 for success, otherwise -1. */
+SPAN_DECLARE(int) t4_tx_set_row_read_handler(t4_state_t *s, t4_row_read_handler_t handler, void *user_data);
+
+/*! \brief Get the row-to-row (y) resolution of the current page.
+    \param s The T.4 context.
+    \return The resolution, in pixels per metre. */
+SPAN_DECLARE(int) t4_tx_get_y_resolution(t4_state_t *s);
+
+/*! \brief Get the column-to-column (x) resolution of the current page.
+    \param s The T.4 context.
+    \return The resolution, in pixels per metre. */
+SPAN_DECLARE(int) t4_tx_get_x_resolution(t4_state_t *s);
+
+/*! \brief Get the width of the current page, in pixel columns.
+    \param s The T.4 context.
+    \return The number of columns. */
+SPAN_DECLARE(int) t4_tx_get_image_width(t4_state_t *s);
+
+/*! \brief Get the number of pages in the file.
+    \param s The T.4 context.
+    \return The number of pages, or -1 if there is an error. */
+SPAN_DECLARE(int) t4_tx_get_pages_in_file(t4_state_t *s);
+
+/*! \brief Get the currnet page number in the file.
+    \param s The T.4 context.
+    \return The page number, or -1 if there is an error. */
+SPAN_DECLARE(int) t4_tx_get_current_page_in_file(t4_state_t *s);
+
+/*! Get the current image transfer statistics. 
+    \brief Get the current transfer statistics.
+    \param s The T.4 context.
+    \param t A pointer to a statistics structure. */
+SPAN_DECLARE(void) t4_tx_get_transfer_statistics(t4_state_t *s, t4_stats_t *t);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif
+/*- End of file ------------------------------------------------------------*/
diff --git a/src/codec/spandsp/src/spandsp/timezone.h b/src/codec/spandsp/src/spandsp/timezone.h
new file mode 100644 (file)
index 0000000..923f5e0
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * timezone.h - Timezone handling for time interpretation
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2010 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.
+ */
+
+/*! \file */
+
+#if !defined(_SPANDSP_TIMEZONE_H_)
+#define _SPANDSP_TIMEZONE_H_
+
+/*! \page timezone_page Timezone handling
+
+\section timezone_sec_1 What does it do?
+
+\section timezone_sec_2 How does it work?
+
+*/
+
+typedef struct tz_s tz_t;
+
+enum
+{
+    TM_SUNDAY = 0,
+    TM_MONDAY,
+    TM_TUESDAY,
+    TM_WEDNESDAY,
+    TM_THURSDAY,
+    TM_FRIDAY,
+    TM_SATURDAY
+};
+
+enum
+{
+    TM_JANUARY = 0,
+    TM_FEBRUARY,
+    TM_MARCH,
+    TM_APRIL,
+    TM_MAY,
+    TM_JUNE,
+    TM_JULY,
+    TM_AUGUST,
+    TM_SEPTEMBER,
+    TM_OCTOBER,
+    TM_NOVEMBER,
+    TM_DECEMBER
+};
+
+#if defined(__cplusplus)
+extern "C"
+{
+#endif
+
+SPAN_DECLARE(tz_t *) tz_init(tz_t *tz, const char *tzstring);
+
+SPAN_DECLARE(int) tz_release(tz_t *tz);
+
+SPAN_DECLARE(int) tz_free(tz_t *tz);
+
+SPAN_DECLARE(int) tz_localtime(tz_t *tz, struct tm *tm, time_t t);
+
+SPAN_DECLARE(const char *) tz_tzname(tz_t *tz, int isdst);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif
+/*- End of file ------------------------------------------------------------*/
diff --git a/src/codec/spandsp/src/t30.c b/src/codec/spandsp/src/t30.c
new file mode 100644 (file)
index 0000000..60e69c4
--- /dev/null
@@ -0,0 +1,6307 @@
+/*
+ * 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.
+ */
+
+/*! \file */
+
+#if defined(HAVE_CONFIG_H)
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+//TODO// #include <stdio.h>
+#include <inttypes.h>
+#include <string.h>
+//TODO// #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"
+//TODO// #include <tiffio.h>
+
+#include "spandsp/0cpm.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_rx.h"
+#include "spandsp/t4_tx.h"
+#if defined(SPANDSP_SUPPORT_T85)
+#include "spandsp/t81_t82_arith_coding.h"
+#include "spandsp/t85.h"
+#endif
+#include "spandsp/t4_t6_decode.h"
+#include "spandsp/t4_t6_encode.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"
+#if defined(SPANDSP_SUPPORT_T85)
+#include "spandsp/private/t81_t82_arith_coding.h"
+#include "spandsp/private/t85.h"
+#endif
+#include "spandsp/private/t4_t6_decode.h"
+#include "spandsp/private/t4_t6_encode.h"
+#include "spandsp/private/t4_rx.h"
+#include "spandsp/private/t4_tx.h"
+#include "spandsp/private/t30.h"
+#include "spandsp/private/t30_dis_dtc_dcs_bits.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
+
+/* 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. This is only relevant to non-ECM operation. */
+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
+    continue, retrain, or abandoning of the call. */
+enum
+{
+    OPERATION_IN_PROGRESS_NONE = 0,
+    OPERATION_IN_PROGRESS_T4_RX,
+    OPERATION_IN_PROGRESS_T4_TX,
+    OPERATION_IN_PROGRESS_POST_T4_RX,
+    OPERATION_IN_PROGRESS_POST_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
+
+/* HDLC message header byte values */
+#define ADDRESS_FIELD                   0xFF
+#define CONTROL_FIELD_NON_FINAL_FRAME   0x03
+#define CONTROL_FIELD_FINAL_FRAME       0x13
+
+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.tx);
+        s->operation_in_progress = OPERATION_IN_PROGRESS_POST_T4_TX;
+        break;
+    case OPERATION_IN_PROGRESS_T4_RX:
+        t4_rx_release(&s->t4.rx);
+        s->operation_in_progress = OPERATION_IN_PROGRESS_POST_T4_RX;
+        break;
+    }
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int tx_start_page(t30_state_t *s)
+{
+    if (t4_tx_start_page(&s->t4.tx))
+    {
+        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->tx_page_number + 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.tx) == 0)
+    {
+        s->tx_page_number++;
+        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.rx, s->image_width);
+    t4_rx_set_sub_address(&s->t4.rx, s->rx_info.sub_address);
+    t4_rx_set_dcs(&s->t4.rx, s->rx_dcs_string);
+    t4_rx_set_far_ident(&s->t4.rx, s->rx_info.ident);
+    t4_rx_set_vendor(&s->t4.rx, s->vendor);
+    t4_rx_set_model(&s->t4.rx, s->model);
+
+    t4_rx_set_rx_encoding(&s->t4.rx, s->line_encoding);
+    t4_rx_set_x_resolution(&s->t4.rx, s->x_resolution);
+    t4_rx_set_y_resolution(&s->t4.rx, s->y_resolution);
+
+    if (t4_rx_start_page(&s->t4.rx))
+        return -1;
+    /* Clear the ECM 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.rx) == 0)
+    {
+        s->rx_page_number++;
+        s->ecm_block = 0;
+    }
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static void report_rx_ecm_page_result(t30_state_t *s)
+{
+    t4_stats_t stats;
+
+    /* This is only used for ECM pages, as copy_quality() does a similar job for non-ECM
+       pages as a byproduct of assessing copy quality. */
+    t4_rx_get_transfer_statistics(&s->t4.rx, &stats);
+    span_log(&s->logging, SPAN_LOG_FLOW, "Page no = %d\n", stats.pages_transferred);
+    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, "Compression = %s (%d)\n", t4_encoding_to_str(stats.encoding), stats.encoding);
+    span_log(&s->logging, SPAN_LOG_FLOW, "Compressed image size = %d bytes\n", stats.line_image_size);
+}
+/*- End of function --------------------------------------------------------*/
+
+static int copy_quality(t30_state_t *s)
+{
+    t4_stats_t stats;
+    int quality;
+
+    t4_rx_get_transfer_statistics(&s->t4.rx, &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
+     */
+    /* This is called before the page is confirmed, so we need to add one to get the page
+       number right */
+    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, "Compression = %s (%d)\n", t4_encoding_to_str(stats.encoding), stats.encoding);
+    span_log(&s->logging, SPAN_LOG_FLOW, "Compressed image size = %d bytes\n", stats.line_image_size);
+    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 report_tx_result(t30_state_t *s, int result)
+{
+    t4_stats_t stats;
+
+    if (span_log_test(&s->logging, SPAN_LOG_FLOW))
+    {
+        t4_tx_get_transfer_statistics(&s->t4.tx, &stats);
+        span_log(&s->logging,
+                 SPAN_LOG_FLOW,
+                 "%s - delivered %d pages\n",
+                 (result)  ?  "Success"  :  "Failure",
+                 stats.pages_transferred);
+    }
+}
+/*- 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.tx);
+    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.tx) + 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, T30_DIS_BIT_MULTIPLE_SELECTIVE_POLLING_CAPABLE))
+        //    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] = ADDRESS_FIELD;
+        s->ecm_data[i][1] = CONTROL_FIELD_NON_FINAL_FRAME;
+        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.tx, &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.tx) & 2) != 0);
+    return 256;
+}
+/*- 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] = ADDRESS_FIELD;
+        frame[1] = CONTROL_FIELD_NON_FINAL_FRAME;
+        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] = ADDRESS_FIELD;
+    frame[1] = CONTROL_FIELD_FINAL_FRAME;
+    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++] = ADDRESS_FIELD;
+    frame[p++] = CONTROL_FIELD_NON_FINAL_FRAME;
+    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] = ADDRESS_FIELD;
+        s->tx_info.nsf[1] = CONTROL_FIELD_NON_FINAL_FRAME;
+        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] = ADDRESS_FIELD;
+        s->tx_info.nss[1] = CONTROL_FIELD_NON_FINAL_FRAME;
+        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] = ADDRESS_FIELD;
+        s->tx_info.nsc[1] = CONTROL_FIELD_NON_FINAL_FRAME;
+        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, T30_DIS_BIT_POLLED_SUBADDRESSING_CAPABLE)  &&  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, T30_DIS_BIT_POLLED_SUBADDRESSING_CAPABLE);
+        return TRUE;
+    }
+    clr_ctrl_bit(s->local_dis_dtc_frame, T30_DIS_BIT_POLLED_SUBADDRESSING_CAPABLE);
+    return FALSE;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int send_sep_frame(t30_state_t *s)
+{
+    if (test_ctrl_bit(s->far_dis_dtc_frame, T30_DIS_BIT_SELECTIVE_POLLING_CAPABLE)  &&  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, T30_DIS_BIT_SELECTIVE_POLLING_CAPABLE);
+        return TRUE;
+    }
+    clr_ctrl_bit(s->local_dis_dtc_frame, T30_DIS_BIT_SELECTIVE_POLLING_CAPABLE);
+    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, T30_DIS_BIT_PASSWORD)  &&  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, T30_DCS_BIT_SENDER_ID_TRANSMISSION);
+        return TRUE;
+    }
+    clr_ctrl_bit(s->dcs_frame, T30_DCS_BIT_SENDER_ID_TRANSMISSION);
+    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, T30_DIS_BIT_PASSWORD)  &&  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, T30_DIS_BIT_PASSWORD);
+        return TRUE;
+    }
+    clr_ctrl_bit(s->local_dis_dtc_frame, T30_DIS_BIT_PASSWORD);
+    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, T30_DIS_BIT_SUBADDRESSING_CAPABLE)  &&  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, T30_DCS_BIT_SUBADDRESS_TRANSMISSION);
+        return TRUE;
+    }
+    clr_ctrl_bit(s->dcs_frame, T30_DCS_BIT_SUBADDRESS_TRANSMISSION);
+    return FALSE;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int send_tsa_frame(t30_state_t *s)
+{
+    if ((test_ctrl_bit(s->far_dis_dtc_frame, T30_DIS_BIT_T37)  ||  test_ctrl_bit(s->far_dis_dtc_frame, T30_DIS_BIT_T38))  &&  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, T30_DIS_BIT_INTERNET_ROUTING_ADDRESS)  &&  0)
+    {
+        span_log(&s->logging, SPAN_LOG_FLOW, "Sending internet routing address '%s'\n", "");
+        set_ctrl_bit(s->dcs_frame, T30_DCS_BIT_INTERNET_ROUTING_ADDRESS_TRANSMISSION);
+        return TRUE;
+    }
+    clr_ctrl_bit(s->dcs_frame, T30_DCS_BIT_INTERNET_ROUTING_ADDRESS_TRANSMISSION);
+    return FALSE;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int send_cia_frame(t30_state_t *s)
+{
+    if ((test_ctrl_bit(s->far_dis_dtc_frame, T30_DIS_BIT_T37)  ||  test_ctrl_bit(s->far_dis_dtc_frame, T30_DIS_BIT_T38))  &&  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, T30_DIS_BIT_INTERNET_SELECTIVE_POLLING_ADDRESS)  &&  0)
+    {
+        span_log(&s->logging, SPAN_LOG_FLOW, "Sending internet selective polling address '%s'\n", "");
+        set_ctrl_bit(s->local_dis_dtc_frame, T30_DIS_BIT_INTERNET_SELECTIVE_POLLING_ADDRESS);
+        return TRUE;
+    }
+    clr_ctrl_bit(s->local_dis_dtc_frame, T30_DIS_BIT_INTERNET_SELECTIVE_POLLING_ADDRESS);
+    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] = ADDRESS_FIELD;
+    frame[1] = CONTROL_FIELD_FINAL_FRAME;
+    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->tx_page_number & 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, T30_DIS_BIT_READY_TO_RECEIVE_FAX_DOCUMENT);
+    else
+        clr_ctrl_bit(s->local_dis_dtc_frame, T30_DIS_BIT_READY_TO_RECEIVE_FAX_DOCUMENT);
+    /* 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, T30_DIS_BIT_READY_TO_TRANSMIT_FAX_DOCUMENT);
+    else
+        clr_ctrl_bit(s->local_dis_dtc_frame, T30_DIS_BIT_READY_TO_TRANSMIT_FAX_DOCUMENT);
+    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] = ADDRESS_FIELD;
+    s->local_dis_dtc_frame[1] = CONTROL_FIELD_FINAL_FRAME;
+    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, T30_DIS_BIT_T37);
+    if ((s->iaf & T30_IAF_MODE_T38))
+        set_ctrl_bit(s->local_dis_dtc_frame, T30_DIS_BIT_T38);
+    /* 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, T30_DIS_BIT_UNCOMPRESSED_CAPABLE);
+    if (s->ecm_allowed)
+    {
+        /* ECM allowed */
+        set_ctrl_bit(s->local_dis_dtc_frame, T30_DIS_BIT_ECM_CAPABLE);
+        /* 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, T30_DIS_BIT_T6_CAPABLE);
+        if ((s->supported_compressions & T30_SUPPORT_T43_COMPRESSION))
+            set_ctrl_bit(s->local_dis_dtc_frame, T30_DIS_BIT_T43_CAPABLE);
+        if ((s->supported_compressions & T30_SUPPORT_T45_COMPRESSION))
+            set_ctrl_bit(s->local_dis_dtc_frame, T30_DIS_BIT_T45_CAPABLE);
+        if ((s->supported_compressions & T30_SUPPORT_T81_COMPRESSION))
+            set_ctrl_bit(s->local_dis_dtc_frame, T30_DIS_BIT_T81_CAPABLE);
+        if ((s->supported_compressions & T30_SUPPORT_SYCC_T81_COMPRESSION))
+            set_ctrl_bit(s->local_dis_dtc_frame, T30_DIS_BIT_SYCC_T81_CAPABLE);
+        if ((s->supported_compressions & T30_SUPPORT_T85_COMPRESSION))
+        {
+            set_ctrl_bit(s->local_dis_dtc_frame, T30_DIS_BIT_T85_CAPABLE);
+            if ((s->supported_compressions & T30_SUPPORT_T85_L0_COMPRESSION))
+                set_ctrl_bit(s->local_dis_dtc_frame, T30_DIS_BIT_T85_L0_CAPABLE);
+        }
+        //if ((s->supported_compressions & T30_SUPPORT_T89_COMPRESSION))
+        //    set_ctrl_bit(s->local_dis_dtc_frame, T30_DIS_BIT_T89_CAPABLE);
+    }
+    if ((s->supported_t30_features & T30_SUPPORT_FIELD_NOT_VALID))
+        set_ctrl_bit(s->local_dis_dtc_frame, T30_DIS_BIT_FNV_CAPABLE);
+    if ((s->supported_t30_features & T30_SUPPORT_MULTIPLE_SELECTIVE_POLLING))
+        set_ctrl_bit(s->local_dis_dtc_frame, T30_DIS_BIT_MULTIPLE_SELECTIVE_POLLING_CAPABLE);
+    if ((s->supported_t30_features & T30_SUPPORT_POLLED_SUB_ADDRESSING))
+        set_ctrl_bit(s->local_dis_dtc_frame, T30_DIS_BIT_POLLED_SUBADDRESSING_CAPABLE);
+    /* 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, T30_DIS_BIT_200_400_CAPABLE);
+    if ((s->supported_resolutions & T30_SUPPORT_300_300_RESOLUTION))
+        set_ctrl_bit(s->local_dis_dtc_frame, T30_DIS_BIT_300_300_CAPABLE);
+    if ((s->supported_resolutions & (T30_SUPPORT_400_400_RESOLUTION | T30_SUPPORT_R16_RESOLUTION)))
+        set_ctrl_bit(s->local_dis_dtc_frame, T30_DIS_BIT_400_400_CAPABLE);
+    /* Metric */ 
+    set_ctrl_bit(s->local_dis_dtc_frame, T30_DIS_BIT_METRIC_RESOLUTION_PREFERRED);
+    /* 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, T30_DIS_BIT_SELECTIVE_POLLING_CAPABLE);
+    if ((s->supported_t30_features & T30_SUPPORT_SUB_ADDRESSING))
+        set_ctrl_bit(s->local_dis_dtc_frame, T30_DIS_BIT_SUBADDRESSING_CAPABLE);
+    if ((s->supported_t30_features & T30_SUPPORT_IDENTIFICATION))
+        set_ctrl_bit(s->local_dis_dtc_frame, T30_DIS_BIT_PASSWORD);
+    /* Ready to transmit a data file (polling) */
+    if (s->tx_file[0])
+        set_ctrl_bit(s->local_dis_dtc_frame, T30_DIS_BIT_READY_TO_TRANSMIT_DATA_FILE);
+    /* 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 (T.4/Annex E) */
+    /* 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, T30_DIS_BIT_NORTH_AMERICAN_LETTER_CAPABLE);
+    if ((s->supported_image_sizes & T30_SUPPORT_US_LEGAL_LENGTH))
+        set_ctrl_bit(s->local_dis_dtc_frame, T30_DIS_BIT_NORTH_AMERICAN_LEGAL_CAPABLE);
+    /* 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, T30_DIS_BIT_INTERNET_SELECTIVE_POLLING_ADDRESS);
+    if ((s->supported_t30_features & T30_SUPPORT_INTERNET_ROUTING_ADDRESS))
+        set_ctrl_bit(s->local_dis_dtc_frame, T30_DIS_BIT_INTERNET_ROUTING_ADDRESS);
+    if ((s->supported_resolutions & T30_SUPPORT_600_600_RESOLUTION))
+        set_ctrl_bit(s->local_dis_dtc_frame, T30_DIS_BIT_600_600_CAPABLE);
+    if ((s->supported_resolutions & T30_SUPPORT_1200_1200_RESOLUTION))
+        set_ctrl_bit(s->local_dis_dtc_frame, T30_DIS_BIT_1200_1200_CAPABLE);
+    if ((s->supported_resolutions & T30_SUPPORT_300_600_RESOLUTION))
+        set_ctrl_bit(s->local_dis_dtc_frame, T30_DIS_BIT_300_600_CAPABLE);
+    if ((s->supported_resolutions & T30_SUPPORT_400_800_RESOLUTION))
+        set_ctrl_bit(s->local_dis_dtc_frame, T30_DIS_BIT_400_800_CAPABLE);
+    if ((s->supported_resolutions & T30_SUPPORT_600_1200_RESOLUTION))
+        set_ctrl_bit(s->local_dis_dtc_frame, T30_DIS_BIT_600_1200_CAPABLE);
+    /* 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, T30_DIS_BIT_T38_FLOW_CONTROL_CAPABLE);
+    /* No k > 4 */
+    if ((s->iaf & T30_IAF_MODE_CONTINUOUS_FLOW))
+        set_ctrl_bit(s->local_dis_dtc_frame, T30_DIS_BIT_T38_FAX_CAPABLE);
+    /* No T.88/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] = ADDRESS_FIELD;
+    s->dcs_frame[1] = CONTROL_FIELD_FINAL_FRAME;
+    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, T30_DIS_BIT_T37))
+        set_ctrl_bit(s->dcs_frame, T30_DCS_BIT_T37);
+    /* Check for T.38 mode. */
+    if (test_ctrl_bit(s->far_dis_dtc_frame, T30_DIS_BIT_T38))
+        set_ctrl_bit(s->dcs_frame, T30_DCS_BIT_T38);
+#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_T85:
+        set_ctrl_bit(s->dcs_frame, T30_DCS_BIT_T85_MODE);
+        clr_ctrl_bit(s->dcs_frame, T30_DCS_BIT_T85_L0_MODE);
+        set_ctrl_bits(s->dcs_frame, T30_MIN_SCAN_0MS, 21);
+        break;
+    case T4_COMPRESSION_ITU_T85_L0:
+        set_ctrl_bit(s->dcs_frame, T30_DCS_BIT_T85_MODE);
+        set_ctrl_bit(s->dcs_frame, T30_DCS_BIT_T85_L0_MODE);
+        set_ctrl_bits(s->dcs_frame, T30_MIN_SCAN_0MS, 21);
+        break;
+    case T4_COMPRESSION_ITU_T6:
+        set_ctrl_bit(s->dcs_frame, T30_DCS_BIT_T6_MODE);
+        set_ctrl_bits(s->dcs_frame, T30_MIN_SCAN_0MS, 21);
+        break;
+    case T4_COMPRESSION_ITU_T4_2D:
+        set_ctrl_bit(s->dcs_frame, T30_DCS_BIT_2D_CODING);
+        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, T30_DCS_BIT_RECEIVE_FAX_DOCUMENT);
+    /* 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, T30_DCS_BIT_600_1200);
+            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, T30_DCS_BIT_1200_1200);
+            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, T30_DCS_BIT_400_800);
+            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, T30_DCS_BIT_300_600);
+            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, T30_DCS_BIT_600_600);
+            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, T30_DCS_BIT_200_400);
+                break;
+            case T4_X_RESOLUTION_R16:
+                set_ctrl_bit(s->dcs_frame, T30_DCS_BIT_400_400);
+                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, T30_DCS_BIT_300_300);
+            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, T30_DCS_BIT_200_200);
+                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;
+    /* The following treats a width field of 11 like 10, which does what note 6 of Table 2/T.30
+       says we should do with the invalid value 11. */
+    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_NOSIZESUPPORT;
+        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_NOSIZESUPPORT;
+        else
+            set_ctrl_bit(s->dcs_frame, 18);
+        break;
+    default:
+        /* T.30 does not support this width */
+        bad = T30_ERR_NOSIZESUPPORT;
+        break;
+    }
+    if (bad != T30_ERR_OK)
+    {
+        s->current_status = bad;
+        span_log(&s->logging, SPAN_LOG_FLOW, "Image width (%d pixels) not an acceptable 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, T30_DIS_BIT_300_300_CAPABLE)  &&  !test_ctrl_bit(s->far_dis_dtc_frame, T30_DIS_BIT_300_600_CAPABLE))
+            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, T30_DIS_BIT_400_400_CAPABLE))
+            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, T30_DIS_BIT_600_600_CAPABLE)  &&  !test_ctrl_bit(s->far_dis_dtc_frame, T30_DIS_BIT_600_1200_CAPABLE))
+            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, T30_DIS_BIT_1200_1200_CAPABLE))
+            bad = T30_ERR_NOSIZESUPPORT;
+        break;
+    default:
+        /* T.30 does not support this width */
+        bad = T30_ERR_NOSIZESUPPORT;
+        break;
+    }
+    if (bad != T30_ERR_OK)
+    {
+        s->current_status = bad;
+        span_log(&s->logging, SPAN_LOG_FLOW, "Image width (%d pixels) not an acceptable 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, T30_DCS_BIT_ECM);
+
+    if ((s->iaf & T30_IAF_MODE_FLOW_CONTROL)  &&  test_ctrl_bit(s->far_dis_dtc_frame, T30_DIS_BIT_T38_FLOW_CONTROL_CAPABLE))
+        set_ctrl_bit(s->dcs_frame, T30_DCS_BIT_T38_FLOW_CONTROL_CAPABLE);
+    if ((s->iaf & T30_IAF_MODE_CONTINUOUS_FLOW)  &&  test_ctrl_bit(s->far_dis_dtc_frame, T30_DIS_BIT_T38_FAX_CAPABLE))
+    {
+        /* Clear the modem type bits, in accordance with note 77 of Table 2/T.30 */
+        clr_ctrl_bit(s->local_dis_dtc_frame, 11);
+        clr_ctrl_bit(s->local_dis_dtc_frame, 12);
+        clr_ctrl_bit(s->local_dis_dtc_frame, 13);
+        clr_ctrl_bit(s->local_dis_dtc_frame, 14);
+        set_ctrl_bit(s->dcs_frame, T30_DCS_BIT_T38_FAX_MODE);
+    }
+    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_bits_per_row(&s->t4.tx, 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 0
+    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);
+    }
+#else
+    if (s->calling_party)
+        set_state(s, T30_STATE_T);
+    else
+        set_state(s, T30_STATE_R);
+#endif
+}
+/*- 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. */
+    terminate_operation_in_progress(s);
+    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, T30_DIS_BIT_200_400_CAPABLE))
+        {
+            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, T30_DIS_BIT_MIN_SCAN_TIME_HALVES))  ?  2  :  1][min_bits_field];
+        break;
+    case T4_Y_RESOLUTION_FINE:
+        if (!test_ctrl_bit(s->far_dis_dtc_frame, T30_DIS_BIT_200_200_CAPABLE))
+        {
+            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.tx, 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.tx);
+    t4_tx_set_tx_encoding(&s->t4.tx, s->line_encoding);
+    t4_tx_set_local_ident(&s->t4.tx, s->tx_info.ident);
+    t4_tx_set_header_info(&s->t4.tx, s->header_info);
+
+    s->x_resolution = t4_tx_get_x_resolution(&s->t4.tx);
+    s->y_resolution = t4_tx_get_y_resolution(&s->t4.tx);
+    /* 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)
+    {
+        terminate_operation_in_progress(s);
+        return -1;
+    }
+    span_log(&s->logging, SPAN_LOG_FLOW, "Minimum bits per row will be %d\n", min_row_bits);
+    t4_tx_set_min_bits_per_row(&s->t4.tx, min_row_bits);
+
+    if (tx_start_page(s))
+        return -1;
+    s->image_width = t4_tx_get_image_width(&s->t4.tx);
+    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.tx);
+    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_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);
+    if (s->current_status == T30_ERR_OK)
+        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 frame in state %d\n", t30_frametype(msg[2]), s->state);
+    if (s->current_status == T30_ERR_OK)
+        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);
+    if (s->current_status == T30_ERR_OK)
+        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_T85_COMPRESSION)
+        &&
+        test_ctrl_bit(s->far_dis_dtc_frame, T30_DIS_BIT_T85_CAPABLE))
+    {
+        if (s->supported_compressions & T30_SUPPORT_T85_L0_COMPRESSION
+            &&
+            test_ctrl_bit(s->far_dis_dtc_frame, T30_DIS_BIT_T85_L0_CAPABLE))
+        {
+            s->line_encoding = T4_COMPRESSION_ITU_T85_L0;
+        }
+        else
+        {
+            s->line_encoding = T4_COMPRESSION_ITU_T85;
+        }
+    }
+    else if (s->error_correcting_mode
+             &&
+             (s->supported_compressions & T30_SUPPORT_T6_COMPRESSION)
+             &&
+             test_ctrl_bit(s->far_dis_dtc_frame, T30_DIS_BIT_T6_CAPABLE))
+    {
+        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, T30_DIS_BIT_2D_CAPABLE))
+    {
+        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 %s (%d)\n", t4_encoding_to_str(s->line_encoding), 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, T30_DIS_BIT_READY_TO_RECEIVE_FAX_DOCUMENT))
+        {
+            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, T30_DIS_BIT_READY_TO_TRANSMIT_FAX_DOCUMENT))
+        {
+            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, T30_DCS_BIT_64_OCTET_ECM_FRAMES)  ?  256  :  64;
+
+    if (test_ctrl_bit(dcs_frame, T30_DCS_BIT_1200_1200))
+        s->x_resolution = T4_X_RESOLUTION_1200;
+    else if (test_ctrl_bit(dcs_frame, T30_DCS_BIT_600_600)  ||  test_ctrl_bit(dcs_frame, T30_DCS_BIT_600_1200))
+        s->x_resolution = T4_X_RESOLUTION_600;
+    else if (test_ctrl_bit(dcs_frame, T30_DCS_BIT_400_400)  ||  test_ctrl_bit(dcs_frame, T30_DCS_BIT_400_800))
+        s->x_resolution = T4_X_RESOLUTION_R16;
+    else if (test_ctrl_bit(dcs_frame, T30_DCS_BIT_300_300)  ||  test_ctrl_bit(dcs_frame, T30_DCS_BIT_300_600))
+        s->x_resolution = T4_X_RESOLUTION_300;
+    else
+        s->x_resolution = T4_X_RESOLUTION_R8;
+
+    if (test_ctrl_bit(dcs_frame, T30_DCS_BIT_1200_1200)  ||  test_ctrl_bit(dcs_frame, T30_DCS_BIT_600_1200))
+        s->y_resolution = T4_Y_RESOLUTION_1200;
+    else if (test_ctrl_bit(dcs_frame, T30_DCS_BIT_400_800))
+        s->y_resolution = T4_Y_RESOLUTION_800;
+    else if (test_ctrl_bit(dcs_frame, T30_DCS_BIT_600_600)  ||  test_ctrl_bit(dcs_frame, T30_DCS_BIT_300_600))
+        s->y_resolution = T4_Y_RESOLUTION_600;
+    else if (test_ctrl_bit(dcs_frame, T30_DCS_BIT_200_400)  ||  test_ctrl_bit(dcs_frame, T30_DCS_BIT_400_400))
+        s->y_resolution = T4_Y_RESOLUTION_SUPERFINE;
+    else if (test_ctrl_bit(dcs_frame, T30_DCS_BIT_300_300))
+        s->y_resolution = T4_Y_RESOLUTION_300;
+    else if (test_ctrl_bit(dcs_frame, T30_DCS_BIT_200_200))
+        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, T30_DCS_BIT_T85_MODE))
+    {
+        if (test_ctrl_bit(dcs_frame, T30_DCS_BIT_T85_L0_MODE))
+            s->line_encoding = T4_COMPRESSION_ITU_T85_L0;
+        else
+            s->line_encoding = T4_COMPRESSION_ITU_T85;
+    }
+    else if (test_ctrl_bit(dcs_frame, T30_DCS_BIT_T6_MODE))
+    {
+        s->line_encoding = T4_COMPRESSION_ITU_T6;
+    }
+    else if (test_ctrl_bit(dcs_frame, T30_DCS_BIT_2D_CODING))
+    {
+        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 %s (%d)\n", t4_encoding_to_str(s->line_encoding), s->line_encoding);
+    if (!test_ctrl_bit(dcs_frame, T30_DCS_BIT_RECEIVE_FAX_DOCUMENT))
+        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, T30_DCS_BIT_ECM) != 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->operation_in_progress != OPERATION_IN_PROGRESS_T4_RX)
+    {
+        if (t4_rx_init(&s->t4.rx, 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_response_to_pps(t30_state_t *s)
+{
+    queue_phase(s, T30_PHASE_D_TX);
+    if (s->rx_ecm_block_ok)
+    {
+        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] = ADDRESS_FIELD;
+        s->ecm_frame_map[1] = CONTROL_FIELD_FINAL_FRAME;
+        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 --------------------------------------------------------*/
+
+#define VET_ALL_FCD_FRAMES
+
+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;
+    int first_bad_frame;
+    int image_ended;
+#if defined(VET_ALL_FCD_FRAMES)
+    int first;
+    int expected_len;
+#endif
+
+    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;
+
+    /* 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;
+    block = msg[5];
+    page = msg[4];
+
+    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);
+    /* Check that we have received the page and block we expected. If the far end missed
+       our last response, it might have repeated the previous chunk. */
+    if ((s->rx_page_number & 0xFF) != page  ||  (s->ecm_block & 0xFF) != block)
+    {
+        span_log(&s->logging,
+                 SPAN_LOG_FLOW,
+                 "ECM rx page/block mismatch - expected %d/%d, but received %d/%d.\n",
+                 (s->rx_page_number & 0xFF),
+                 (s->ecm_block & 0xFF),
+                 page,
+                 block);
+        /* Look for this being a repeat, because the other end missed the last response
+           we sent - which would have been a T30_MCF - If the block is for the previous
+           page, or the previous block of the current page, we can assume we have hit this
+           condition. */
+        if (((s->rx_page_number & 0xFF) == page  &&  (s->ecm_block & 0xFF) == block)
+            ||
+            (((s->rx_page_number - 1) & 0xFF) == page  &&  s->ecm_block == 0))
+        {
+            /* This must be a repeat of the last thing the far end sent, while we are expecting
+               the first transfer of a new block. */
+            span_log(&s->logging, SPAN_LOG_FLOW, "Looks like a repeat from the previous page/block - send MCF again.\n");
+            /* Clear the ECM buffer */
+            for (i = 0;  i < 256;  i++)
+                s->ecm_len[i] = -1;
+            s->ecm_frames = -1;
+            queue_phase(s, T30_PHASE_D_TX);
+            set_state(s, T30_STATE_F_POST_RCP_MCF);
+            send_simple_frame(s, T30_MCF);
+        }
+        else
+        {
+            /* Give up */
+            s->current_status = T30_ERR_RX_ECMPHD;
+            send_dcn(s);
+        }
+        return 0;
+    }
+
+    /* Build a bit map of which frames we now have stored OK */
+    first_bad_frame = 256;
+#if defined(VET_ALL_FCD_FRAMES)
+    first = TRUE;
+    expected_len = 256;
+#endif
+    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 defined(VET_ALL_FCD_FRAMES)
+            if (s->ecm_len[frame_no] >= 0)
+            {
+                if (frame_no < s->ecm_frames - 1)
+                {
+                    if (first)
+                    {
+                        if (s->ecm_len[frame_no] == 64)
+                            expected_len = 64;
+                        first = FALSE;
+                    }
+                    if (s->ecm_len[frame_no] != expected_len)
+                    {
+                        span_log(&s->logging, SPAN_LOG_FLOW, "Bad length ECM frame - %d\n", s->ecm_len[frame_no]);
+                        s->ecm_len[frame_no] = -1;
+                    }
+                }
+            }            
+#endif
+            if (s->ecm_len[frame_no] < 0)
+            {
+                s->ecm_frame_map[i + 3] |= (1 << j);
+                if (frame_no < first_bad_frame)
+                    first_bad_frame = frame_no;
+                if (frame_no < s->ecm_frames)
+                    s->error_correcting_mode_retries++;
+            }
+        }
+    }
+    s->rx_ecm_block_ok = (first_bad_frame >= s->ecm_frames);
+    if (s->rx_ecm_block_ok)
+    {
+        span_log(&s->logging, SPAN_LOG_FLOW, "Partial page OK - committing 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.rx, s->ecm_data[i], s->ecm_len[i]))
+            {
+                /* This is the end of the document */
+                image_ended = TRUE;
+                break;
+            }
+        }
+        /* Clear the ECM buffer */
+        for (i = 0;  i < 256;  i++)
+            s->ecm_len[i] = -1;
+        s->ecm_block++;
+        s->ecm_frames = -1;
+
+        switch (s->last_pps_fcf2)
+        {
+        case T30_NULL:
+            /* We can accept only this partial page. */
+            break;
+        default:
+            /* We can accept and confirm the whole page. */
+            s->next_rx_step = s->last_pps_fcf2;
+            rx_end_page(s);
+            report_rx_ecm_page_result(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;
+        }
+    }
+
+    switch (s->last_pps_fcf2)
+    {
+    case T30_PRI_MPS:
+    case T30_PRI_EOM:
+    case T30_PRI_EOP:
+        if (s->remote_interrupts_allowed)
+        {
+        }
+        /* Fall through */
+    case T30_NULL:
+    case T30_MPS:
+    case T30_EOM:
+    case T30_EOS:
+    case T30_EOP:
+        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_response_to_pps(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] = ADDRESS_FIELD;
+            frame[1] = CONTROL_FIELD_FINAL_FRAME;
+            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)
+        {
+            /* For other frame types we kill the call on an unexpected frame length. For FCD frames it is better to just ignore
+               the frame, and let retries sort things out. */
+            span_log(&s->logging, SPAN_LOG_FLOW, "Unexpected %s frame length - %d\n", t30_frametype(msg[0]), len);
+        }
+        else
+        {
+            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;
+        }
+        /* 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. */
+        timer_t2_start(s);
+        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)
+{
+    uint8_t fcf;
+
+    /* We should be sending the TCF data right now */
+    fcf = msg[2] & 0xFE;
+    switch (fcf)
+    {
+    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)
+{
+    uint8_t fcf;
+
+    fcf = msg[2] & 0xFE;
+    switch (fcf)
+    {
+    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)
+{
+    uint8_t fcf;
+
+    fcf = msg[2] & 0xFE;
+    switch (fcf)
+    {
+    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)
+{
+    uint8_t fcf;
+
+    /* We should be sending the DCS sequence right now */
+    fcf = msg[2] & 0xFE;
+    switch (fcf)
+    {
+    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)
+{
+    uint8_t fcf;
+
+    /* We should be sending the TCF data right now */
+    fcf = msg[2] & 0xFE;
+    switch (fcf)
+    {
+    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)
+{
+    uint8_t fcf;
+
+    fcf = msg[2] & 0xFE;
+    switch (fcf)
+    {
+    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)
+{
+    uint8_t fcf;
+
+    /* We should be receiving TCF right now, not HDLC messages */
+    fcf = msg[2] & 0xFE;
+    switch (fcf)
+    {
+    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)
+{
+    uint8_t fcf;
+
+    /* We're waiting for a response to the CFR we sent */
+    fcf = msg[2] & 0xFE;
+    switch (fcf)
+    {
+    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)
+{
+    uint8_t fcf;
+
+    /* We're waiting for a response to the FTT we sent */
+    fcf = msg[2] & 0xFE;
+    switch (fcf)
+    {
+    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)
+{
+    uint8_t fcf;
+
+    /* 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. */
+    fcf = msg[2] & 0xFE;
+    switch (fcf)
+    {
+    case T30_DIS:
+        process_rx_dis_dtc(s, msg, len);
+        break;
+    case T30_DCS:
+        process_rx_dcs(s, msg, len);
+        break;
+    case T30_PRI_MPS:
+        if (s->remote_interrupts_allowed)
+        {
+        }
+        /* Fall through */
+    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, fcf);
+        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_EOM:
+        if (s->remote_interrupts_allowed)
+        {
+        }
+        /* Fall through */
+    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, fcf);
+        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_EOP:
+        if (s->remote_interrupts_allowed)
+        {
+        }
+        /* Fall through */
+    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, fcf);
+        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_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. */
+        s->current_status = T30_ERR_RX_INVALCMD;
+        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)
+{
+    uint8_t fcf;
+
+    fcf = msg[2] & 0xFE;
+    switch (fcf)
+    {
+    case T30_PRI_MPS:
+        if (s->remote_interrupts_allowed)
+        {
+        }
+        /* Fall through */
+    case T30_MPS:
+        s->next_rx_step = fcf;
+        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);
+            if (s->phase_d_handler)
+                s->phase_d_handler(s, s->phase_d_user_data, fcf);
+            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);
+            if (s->phase_d_handler)
+                s->phase_d_handler(s, s->phase_d_user_data, fcf);
+            rx_start_page(s);
+            set_state(s, T30_STATE_III_Q_RTP);
+            send_simple_frame(s, T30_RTP);
+            break;
+        case T30_COPY_QUALITY_BAD:
+            if (s->phase_d_handler)
+                s->phase_d_handler(s, s->phase_d_user_data, fcf);
+            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->remote_interrupts_allowed)
+        {
+        }
+        /* Fall through */
+    case T30_EOM:
+    case T30_EOS:
+        s->next_rx_step = fcf;
+        /* 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);
+            if (s->phase_d_handler)
+                s->phase_d_handler(s, s->phase_d_user_data, fcf);
+            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);
+            if (s->phase_d_handler)
+                s->phase_d_handler(s, s->phase_d_user_data, fcf);
+            rx_start_page(s);
+            set_state(s, T30_STATE_III_Q_RTP);
+            send_simple_frame(s, T30_RTP);
+            break;
+        case T30_COPY_QUALITY_BAD:
+            if (s->phase_d_handler)
+                s->phase_d_handler(s, s->phase_d_user_data, fcf);
+            rx_start_page(s);
+            set_state(s, T30_STATE_III_Q_RTN);
+            send_simple_frame(s, T30_RTN);
+            break;
+        }
+        break;
+    case T30_PRI_EOP:
+        if (s->remote_interrupts_allowed)
+        {
+        }
+        /* Fall through */
+    case T30_EOP:
+        s->next_rx_step = fcf;
+        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);
+            if (s->phase_d_handler)
+                s->phase_d_handler(s, s->phase_d_user_data, fcf);
+            terminate_operation_in_progress(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);
+            if (s->phase_d_handler)
+                s->phase_d_handler(s, s->phase_d_user_data, fcf);
+            terminate_operation_in_progress(s);
+            set_state(s, T30_STATE_III_Q_RTP);
+            send_simple_frame(s, T30_RTP);
+            break;
+        case T30_COPY_QUALITY_BAD:
+            if (s->phase_d_handler)
+                s->phase_d_handler(s, s->phase_d_user_data, fcf);
+            set_state(s, T30_STATE_III_Q_RTN);
+            send_simple_frame(s, T30_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. */
+        s->current_status = T30_ERR_RX_INVALCMD;
+        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 fcf;
+    uint8_t fcf2;
+    
+    /* This actually handles 2 states - _DOC_ECM and _POST_DOC_ECM - as they are very similar */
+    fcf = msg[2] & 0xFE;
+    switch (fcf)
+    {
+    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. */
+        process_rx_rcp(s, msg, len);
+        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:
+            if (s->remote_interrupts_allowed)
+            {
+                /* 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. */
+        s->current_status = T30_ERR_RX_INVALCMD;
+        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)
+{
+    uint8_t fcf;
+
+    fcf = msg[2] & 0xFE;
+    switch (fcf)
+    {
+    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)
+{
+    uint8_t fcf;
+
+    fcf = msg[2] & 0xFE;
+    switch (fcf)
+    {
+    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)
+{
+    uint8_t fcf;
+
+    fcf = msg[2] & 0xFE;
+    switch (fcf)
+    {
+    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
+        {
+            /* Now we send the deferred response */
+            send_response_to_pps(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)
+{
+    uint8_t fcf;
+
+    fcf = msg[2] & 0xFE;
+    switch (fcf)
+    {
+    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)
+{
+    uint8_t fcf;
+
+    fcf = msg[2] & 0xFE;
+    switch (fcf)
+    {
+    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)
+{
+    uint8_t fcf;
+
+    fcf = msg[2] & 0xFE;
+    switch (fcf)
+    {
+    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)
+{
+    uint8_t fcf;
+
+    fcf = msg[2] & 0xFE;
+    switch (fcf)
+    {
+    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)
+{
+    uint8_t fcf;
+
+    fcf = msg[2] & 0xFE;
+    switch (fcf)
+    {
+    case T30_PIP:
+        if (s->remote_interrupts_allowed)
+        {
+            s->retries = 0;
+            if (s->phase_d_handler)
+            {
+                s->phase_d_handler(s, s->phase_d_user_data, fcf);
+                s->timer_t3 = ms_to_samples(DEFAULT_TIMER_T3);
+            }
+        }
+        /* Fall through */
+    case T30_MCF:
+        switch (s->next_tx_step)
+        {
+        case T30_PRI_MPS:
+        case T30_MPS:
+            tx_end_page(s);
+            if (s->phase_d_handler)
+                s->phase_d_handler(s, s->phase_d_user_data, fcf);
+            /* Transmit the next page */
+            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_PRI_EOM:
+        case T30_EOM:
+        case T30_EOS:
+            tx_end_page(s);
+            if (s->phase_d_handler)
+                s->phase_d_handler(s, s->phase_d_user_data, fcf);
+            terminate_operation_in_progress(s);
+            report_tx_result(s, TRUE);
+            return_to_phase_b(s, FALSE);
+            break;
+        case T30_PRI_EOP:
+        case T30_EOP:
+            tx_end_page(s);
+            if (s->phase_d_handler)
+                s->phase_d_handler(s, s->phase_d_user_data, fcf);
+            terminate_operation_in_progress(s);
+            send_dcn(s);
+            report_tx_result(s, TRUE);
+            break;
+        }
+        break;
+    case T30_RTP:
+        s->rtp_events++;
+        switch (s->next_tx_step)
+        {
+        case T30_PRI_MPS:
+        case T30_MPS:
+            tx_end_page(s);
+            if (s->phase_d_handler)
+                s->phase_d_handler(s, s->phase_d_user_data, fcf);
+            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_PRI_EOM:
+        case T30_EOM:
+        case T30_EOS:
+            tx_end_page(s);
+            if (s->phase_d_handler)
+                s->phase_d_handler(s, s->phase_d_user_data, fcf);
+            t4_tx_release(&s->t4.tx);
+            /* TODO: should go back to T, and resend */
+            return_to_phase_b(s, TRUE);
+            break;
+        case T30_PRI_EOP:
+        case T30_EOP:
+            tx_end_page(s);
+            if (s->phase_d_handler)
+                s->phase_d_handler(s, s->phase_d_user_data, fcf);
+            t4_tx_release(&s->t4.tx);
+            send_dcn(s);
+            break;
+        }
+        break;
+    case T30_PIN:
+        if (s->remote_interrupts_allowed)
+        {
+            s->retries = 0;
+            if (s->phase_d_handler)
+            {
+                s->phase_d_handler(s, s->phase_d_user_data, fcf);
+                s->timer_t3 = ms_to_samples(DEFAULT_TIMER_T3);
+            }
+        }
+        /* Fall through */
+    case T30_RTN:
+        s->rtn_events++;
+        switch (s->next_tx_step)
+        {
+        case T30_PRI_MPS:
+        case T30_MPS:
+            s->retries = 0;
+            if (s->phase_d_handler)
+                s->phase_d_handler(s, s->phase_d_user_data, fcf);
+            if (!s->retransmit_capable)
+            {
+                /* Send the next page, regardless of the problem with the current one. */
+                if (tx_start_page(s))
+                {
+                    /* TODO: recover */
+                    break;
+                }
+            }
+            /* Send fresh training */
+            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_PRI_EOM:
+        case T30_EOM:
+        case T30_EOS:
+            s->retries = 0;
+            if (s->phase_d_handler)
+                s->phase_d_handler(s, s->phase_d_user_data, fcf);
+            if (s->retransmit_capable)
+            {
+                /* Wait for DIS */
+            }
+            else
+            {
+                return_to_phase_b(s, TRUE);
+            }
+            break;
+        case T30_PRI_EOP:
+        case T30_EOP:
+            s->retries = 0;
+            if (s->phase_d_handler)
+                s->phase_d_handler(s, s->phase_d_user_data, fcf);
+            if (s->retransmit_capable)
+            {
+                /* 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);
+            }
+            else
+            {
+                send_dcn(s);
+            }
+            break;
+        }
+        break;
+    case T30_DCN:
+        switch (s->next_tx_step)
+        {
+        case T30_PRI_MPS:
+        case T30_PRI_EOM:
+        case T30_MPS:
+        case T30_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. */
+        s->current_status = T30_ERR_TX_INVALRSP;
+        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)
+{
+    uint8_t fcf;
+
+    fcf = msg[2] & 0xFE;
+    switch (fcf)
+    {
+    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)
+{
+    uint8_t fcf;
+
+    fcf = msg[2] & 0xFE;
+    switch (fcf)
+    {
+    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)
+{
+    uint8_t fcf;
+
+    fcf = msg[2] & 0xFE;
+    switch (fcf)
+    {
+    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)
+{
+    uint8_t fcf;
+
+    fcf = msg[2] & 0xFE;
+    switch (fcf)
+    {
+    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)
+{
+    uint8_t fcf;
+
+    fcf = msg[2] & 0xFE;
+    switch (fcf)
+    {
+    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_PRI_MPS:
+            case T30_MPS:
+                tx_end_page(s);
+                if (s->phase_d_handler)
+                    s->phase_d_handler(s, s->phase_d_user_data, fcf);
+                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_PRI_EOM:
+            case T30_EOM:
+            case T30_EOS:
+                tx_end_page(s);
+                if (s->phase_d_handler)
+                    s->phase_d_handler(s, s->phase_d_user_data, fcf);
+                terminate_operation_in_progress(s);
+                report_tx_result(s, TRUE);
+                return_to_phase_b(s, FALSE);
+                break;
+            case T30_PRI_EOP:
+            case T30_EOP:
+                tx_end_page(s);
+                if (s->phase_d_handler)
+                    s->phase_d_handler(s, s->phase_d_user_data, fcf);
+                terminate_operation_in_progress(s);
+                send_dcn(s);
+                report_tx_result(s, TRUE);
+                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)
+{
+    uint8_t fcf;
+
+    fcf = msg[2] & 0xFE;
+    switch (fcf)
+    {
+    case T30_PIP:
+        if (s->remote_interrupts_allowed)
+        {
+            s->retries = 0;
+            if (s->phase_d_handler)
+            {
+                s->phase_d_handler(s, s->phase_d_user_data, fcf);
+                s->timer_t3 = ms_to_samples(DEFAULT_TIMER_T3);
+            }
+        }
+        /* Fall through */
+    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_PRI_MPS:
+            case T30_MPS:
+                tx_end_page(s);
+                if (s->phase_d_handler)
+                    s->phase_d_handler(s, s->phase_d_user_data, fcf);
+                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_PRI_EOM:
+            case T30_EOM:
+            case T30_EOS:
+                tx_end_page(s);
+                if (s->phase_d_handler)
+                    s->phase_d_handler(s, s->phase_d_user_data, fcf);
+                terminate_operation_in_progress(s);
+                report_tx_result(s, TRUE);
+                return_to_phase_b(s, FALSE);
+                break;
+            case T30_PRI_EOP:
+            case T30_EOP:
+                tx_end_page(s);
+                if (s->phase_d_handler)
+                    s->phase_d_handler(s, s->phase_d_user_data, fcf);
+                terminate_operation_in_progress(s);
+                send_dcn(s);
+                report_tx_result(s, TRUE);
+                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_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;
+    case T30_PIN:
+        if (s->remote_interrupts_allowed)
+        {
+            s->retries = 0;
+            if (s->phase_d_handler)
+            {
+                s->phase_d_handler(s, s->phase_d_user_data, fcf);
+                s->timer_t3 = ms_to_samples(DEFAULT_TIMER_T3);
+            }
+        }
+        /* Fall through */
+    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)
+{
+    uint8_t fcf;
+
+    fcf = msg[2] & 0xFE;
+    switch (fcf)
+    {
+    case T30_PIP:
+        if (s->remote_interrupts_allowed)
+        {
+            s->retries = 0;
+            if (s->phase_d_handler)
+            {
+                s->phase_d_handler(s, s->phase_d_user_data, fcf);
+                s->timer_t3 = ms_to_samples(DEFAULT_TIMER_T3);
+            }
+        }
+        /* Fall through */
+    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_PRI_MPS:
+            case T30_MPS:
+                tx_end_page(s);
+                if (s->phase_d_handler)
+                    s->phase_d_handler(s, s->phase_d_user_data, fcf);
+                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_PRI_EOM:
+            case T30_EOM:
+            case T30_EOS:
+                tx_end_page(s);
+                if (s->phase_d_handler)
+                    s->phase_d_handler(s, s->phase_d_user_data, fcf);
+                terminate_operation_in_progress(s);
+                report_tx_result(s, TRUE);
+                return_to_phase_b(s, FALSE);
+                break;
+            case T30_PRI_EOP:
+            case T30_EOP:
+                tx_end_page(s);
+                if (s->phase_d_handler)
+                    s->phase_d_handler(s, s->phase_d_user_data, fcf);
+                terminate_operation_in_progress(s);
+                send_dcn(s);
+                report_tx_result(s, TRUE);
+                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_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;
+    case T30_PIN:
+        if (s->remote_interrupts_allowed)
+        {
+            s->retries = 0;
+            if (s->phase_d_handler)
+            {
+                s->phase_d_handler(s, s->phase_d_user_data, fcf);
+                s->timer_t3 = ms_to_samples(DEFAULT_TIMER_T3);
+            }
+        }
+        /* Fall through */
+    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)
+{
+    uint8_t fcf;
+
+    fcf = msg[2] & 0xFE;
+    switch (fcf)
+    {
+    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)
+{
+    uint8_t fcf;
+
+    fcf = msg[2] & 0xFE;
+    switch (fcf)
+    {
+    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_ERR:
+        /* TODO: Continue with the next message if MPS or EOM? */
+        s->current_status = T30_ERR_RETRYDCN;
+        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;
+    case T30_PIN:
+        if (s->remote_interrupts_allowed)
+        {
+            s->retries = 0;
+            if (s->phase_d_handler)
+            {
+                s->phase_d_handler(s, s->phase_d_user_data, fcf);
+                s->timer_t3 = ms_to_samples(DEFAULT_TIMER_T3);
+            }
+        }
+        /* Fall through */
+    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)
+{
+    uint8_t fcf;
+
+    fcf = msg[2] & 0xFE;
+    switch (fcf)
+    {
+    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_ERR:
+        /* TODO: Continue with the next message if MPS or EOM? */
+        s->current_status = T30_ERR_RETRYDCN;
+        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;
+    case T30_PIN:
+        if (s->remote_interrupts_allowed)
+        {
+            s->retries = 0;
+            if (s->phase_d_handler)
+            {
+                s->phase_d_handler(s, s->phase_d_user_data, fcf);
+                s->timer_t3 = ms_to_samples(DEFAULT_TIMER_T3);
+            }
+        }
+        /* Fall through */
+    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_non_final_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_non_final_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_non_final_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_non_final_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_non_final_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, "Rx final frame 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_PRI_EOM:
+        case T30_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 much 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 */
+        /* Figure 5-2b/T.30 and note 7 says we should allow 1 to 3 tries at this point.
+           The way we work now is effectively hard coding a 1 try limit */
+        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;
+                    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.rx, 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;
+
+    if (byte < 0)
+    {
+        t30_non_ecm_rx_status(user_data, byte);
+        return;
+    }
+    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.rx, (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.rx, 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.tx);
+        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.tx);
+        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.tx, 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 HDLC 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);
+            }
+            else
+            {
+                /* Cancel the command or response timer (if one is running) */
+                span_log(&s->logging, SPAN_LOG_FLOW, "Bad CRC and timer is %d\n", s->timer_t2_t4_is);
+                if (s->timer_t2_t4_is == TIMER_IS_T2A)
+                    timer_t2_t4_stop(s);
+            }
+        }
+        return;
+    }
+
+    if (len < 3)
+    {
+        span_log(&s->logging, SPAN_LOG_FLOW, "Bad HDLC frame length - %d\n", len);
+        /* Cancel the command or response timer (if one is running) */
+        timer_t2_t4_stop(s);
+        return;
+    }
+    if (msg[0] != ADDRESS_FIELD
+        ||
+        !(msg[1] == CONTROL_FIELD_NON_FINAL_FRAME  ||  msg[1] == CONTROL_FIELD_FINAL_FRAME))
+    {
+        span_log(&s->logging, SPAN_LOG_FLOW, "Bad HDLC frame header - %02x %02x\n", msg[0], msg[1]);
+        /* Cancel the command or response timer (if one is running) */
+        timer_t2_t4_stop(s);
+        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_PRI_MPS:
+                case T30_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_PRI_EOM:
+                case T30_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_PRI_EOP:
+                case T30_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)
+{
+    int previous;
+
+    if (s->timer_t0_t1 > 0)
+    {
+        if ((s->timer_t0_t1 -= samples) <= 0)
+        {
+            s->timer_t0_t1 = 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)
+        {
+            s->timer_t3 = 0;
+            timer_t3_expired(s);
+        }
+    }
+    if (s->timer_t2_t4 > 0)
+    {
+        if ((s->timer_t2_t4 -= samples) <= 0)
+        {
+            previous = s->timer_t2_t4_is;
+            /* Don't allow the count to be left at a small negative number.
+               It looks cosmetically bad in the logs. */
+            s->timer_t2_t4 = 0;
+            s->timer_t2_t4_is = TIMER_IS_IDLE;
+            switch (previous)
+            {
+            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)
+        {
+            s->timer_t5 = 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;
+    switch (s->operation_in_progress)
+    {
+    case OPERATION_IN_PROGRESS_T4_TX:
+    case OPERATION_IN_PROGRESS_POST_T4_TX:
+        t4_tx_get_transfer_statistics(&s->t4.tx, &stats);
+        break;
+    case OPERATION_IN_PROGRESS_T4_RX:
+    case OPERATION_IN_PROGRESS_POST_T4_RX:
+        t4_rx_get_transfer_statistics(&s->t4.rx, &stats);
+        break;
+    default:
+        memset(&stats, 0, sizeof(stats));
+        break;
+    }
+    t->pages_tx = s->tx_page_number;
+    t->pages_rx = s->rx_page_number;
+    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;
+#if 0
+    t->rtn_events = s->rtn_events;
+    t->rtp_events = s->rtp_events;
+#endif
+}
+/*- 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(void) t30_remote_interrupts_allowed(t30_state_t *s, int state)
+{
+    s->remote_interrupts_allowed = 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 page number is only reset at call establishment */
+    s->rx_page_number = 0;
+    s->tx_page_number = 0;
+    s->rtn_events = 0;
+    s->rtp_events = 0;
+    s->local_interrupt_pending = FALSE;
+    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. */
+    terminate_operation_in_progress(s);
+    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 ------------------------------------------------------------*/
diff --git a/src/codec/spandsp/src/t30_api.c b/src/codec/spandsp/src/t30_api.c
new file mode 100644 (file)
index 0000000..e2eaacf
--- /dev/null
@@ -0,0 +1,780 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * t30_api.c - ITU T.30 FAX transfer processing
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2003, 2004, 2005, 2006, 2007 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.
+ */
+
+/*! \file */
+
+#if defined(HAVE_CONFIG_H)
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+//TODO// #include <stdio.h>
+#include <inttypes.h>
+#include <string.h>
+//TODO// #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"
+//TODO// #include <tiffio.h>
+
+#include "spandsp/0cpm.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_rx.h"
+#include "spandsp/t4_tx.h"
+#if defined(SPANDSP_SUPPORT_T85)
+#include "spandsp/t81_t82_arith_coding.h"
+#include "spandsp/t85.h"
+#endif
+#include "spandsp/t4_t6_decode.h"
+#include "spandsp/t4_t6_encode.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"
+#if defined(SPANDSP_SUPPORT_T85)
+#include "spandsp/private/t81_t82_arith_coding.h"
+#include "spandsp/private/t85.h"
+#endif
+#include "spandsp/private/t4_t6_decode.h"
+#include "spandsp/private/t4_t6_encode.h"
+#include "spandsp/private/t4_rx.h"
+#include "spandsp/private/t4_tx.h"
+#include "spandsp/private/t30.h"
+
+#include "t30_local.h"
+
+SPAN_DECLARE(int) t30_set_tx_ident(t30_state_t *s, const char *id)
+{
+    if (id == NULL)
+    {
+        s->tx_info.ident[0] = '\0';
+        return 0;
+    }
+    if (strlen(id) > T30_MAX_IDENT_LEN)
+        return -1;
+    strcpy(s->tx_info.ident, id);
+    t4_tx_set_local_ident(&s->t4.tx, s->tx_info.ident);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(const char *) t30_get_tx_ident(t30_state_t *s)
+{
+    if (s->tx_info.ident[0] == '\0')
+        return NULL;
+    return s->tx_info.ident;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(const char *) t30_get_rx_ident(t30_state_t *s)
+{
+    if (s->rx_info.ident[0] == '\0')
+        return NULL;
+    return s->rx_info.ident;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t30_set_tx_sub_address(t30_state_t *s, const char *sub_address)
+{
+    if (sub_address == NULL)
+    {
+        s->tx_info.sub_address[0] = '\0';
+        return 0;
+    }
+    if (strlen(sub_address) > T30_MAX_IDENT_LEN)
+        return -1;
+    strcpy(s->tx_info.sub_address, sub_address);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(const char *) t30_get_tx_sub_address(t30_state_t *s)
+{
+    if (s->tx_info.sub_address[0] == '\0')
+        return NULL;
+    return s->tx_info.sub_address;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(const char *) t30_get_rx_sub_address(t30_state_t *s)
+{
+    if (s->rx_info.sub_address[0] == '\0')
+        return NULL;
+    return s->rx_info.sub_address;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t30_set_tx_selective_polling_address(t30_state_t *s, const char *selective_polling_address)
+{
+    if (selective_polling_address == NULL)
+    {
+        s->tx_info.selective_polling_address[0] = '\0';
+        return 0;
+    }
+    if (strlen(selective_polling_address) > T30_MAX_IDENT_LEN)
+        return -1;
+    strcpy(s->tx_info.selective_polling_address, selective_polling_address);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(const char *) t30_get_tx_selective_polling_address(t30_state_t *s)
+{
+    if (s->tx_info.selective_polling_address[0] == '\0')
+        return NULL;
+    return s->tx_info.selective_polling_address;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(const char *) t30_get_rx_selective_polling_address(t30_state_t *s)
+{
+    if (s->rx_info.selective_polling_address[0] == '\0')
+        return NULL;
+    return s->rx_info.selective_polling_address;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t30_set_tx_polled_sub_address(t30_state_t *s, const char *polled_sub_address)
+{
+    if (polled_sub_address == NULL)
+    {
+        s->tx_info.polled_sub_address[0] = '\0';
+        return 0;
+    }
+    if (strlen(polled_sub_address) > T30_MAX_IDENT_LEN)
+        return -1;
+    strcpy(s->tx_info.polled_sub_address, polled_sub_address);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(const char *) t30_get_tx_polled_sub_address(t30_state_t *s)
+{
+    if (s->tx_info.polled_sub_address[0] == '\0')
+        return NULL;
+    return s->tx_info.polled_sub_address;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(const char *) t30_get_rx_polled_sub_address(t30_state_t *s)
+{
+    if (s->rx_info.polled_sub_address[0] == '\0')
+        return NULL;
+    return s->rx_info.polled_sub_address;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t30_set_tx_sender_ident(t30_state_t *s, const char *sender_ident)
+{
+    if (sender_ident == NULL)
+    {
+        s->tx_info.sender_ident[0] = '\0';
+        return 0;
+    }
+    if (strlen(sender_ident) > T30_MAX_IDENT_LEN)
+        return -1;
+    strcpy(s->tx_info.sender_ident, sender_ident);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(const char *) t30_get_tx_sender_ident(t30_state_t *s)
+{
+    if (s->tx_info.sender_ident[0] == '\0')
+        return NULL;
+    return s->tx_info.sender_ident;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(const char *) t30_get_rx_sender_ident(t30_state_t *s)
+{
+    if (s->rx_info.sender_ident[0] == '\0')
+        return NULL;
+    return s->rx_info.sender_ident;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t30_set_tx_password(t30_state_t *s, const char *password)
+{
+    if (password == NULL)
+    {
+        s->tx_info.password[0] = '\0';
+        return 0;
+    }
+    if (strlen(password) > T30_MAX_IDENT_LEN)
+        return -1;
+    strcpy(s->tx_info.password, password);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(const char *) t30_get_tx_password(t30_state_t *s)
+{
+    if (s->tx_info.password[0] == '\0')
+        return NULL;
+    return s->tx_info.password;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(const char *) t30_get_rx_password(t30_state_t *s)
+{
+    if (s->rx_info.password[0] == '\0')
+        return NULL;
+    return s->rx_info.password;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t30_set_tx_nsf(t30_state_t *s, const uint8_t *nsf, int len)
+{
+    if (s->tx_info.nsf)
+        free(s->tx_info.nsf);
+    if (nsf  &&  len > 0  &&  (s->tx_info.nsf = malloc(len + 3)))
+    {
+        memcpy(s->tx_info.nsf + 3, nsf, len);
+        s->tx_info.nsf_len = len;
+    }
+    else
+    {
+        s->tx_info.nsf = NULL;
+        s->tx_info.nsf_len = 0;
+    }
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(size_t) t30_get_tx_nsf(t30_state_t *s, const uint8_t *nsf[])
+{
+    if (nsf)
+        *nsf = s->tx_info.nsf;
+    return s->tx_info.nsf_len;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(size_t) t30_get_rx_nsf(t30_state_t *s, const uint8_t *nsf[])
+{
+    if (nsf)
+        *nsf = s->rx_info.nsf;
+    return s->rx_info.nsf_len;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t30_set_tx_nsc(t30_state_t *s, const uint8_t *nsc, int len)
+{
+    if (s->tx_info.nsc)
+        free(s->tx_info.nsc);
+    if (nsc  &&  len > 0  &&  (s->tx_info.nsc = malloc(len + 3)))
+    {
+        memcpy(s->tx_info.nsc + 3, nsc, len);
+        s->tx_info.nsc_len = len;
+    }
+    else
+    {
+        s->tx_info.nsc = NULL;
+        s->tx_info.nsc_len = 0;
+    }
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(size_t) t30_get_tx_nsc(t30_state_t *s, const uint8_t *nsc[])
+{
+    if (nsc)
+        *nsc = s->tx_info.nsc;
+    return s->tx_info.nsc_len;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(size_t) t30_get_rx_nsc(t30_state_t *s, const uint8_t *nsc[])
+{
+    if (nsc)
+        *nsc = s->rx_info.nsc;
+    return s->rx_info.nsc_len;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t30_set_tx_nss(t30_state_t *s, const uint8_t *nss, int len)
+{
+    if (s->tx_info.nss)
+        free(s->tx_info.nss);
+    if (nss  &&  len > 0  &&  (s->tx_info.nss = malloc(len + 3)))
+    {
+        memcpy(s->tx_info.nss + 3, nss, len);
+        s->tx_info.nss_len = len;
+    }
+    else
+    {
+        s->tx_info.nss = NULL;
+        s->tx_info.nss_len = 0;
+    }
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(size_t) t30_get_tx_nss(t30_state_t *s, const uint8_t *nss[])
+{
+    if (nss)
+        *nss = s->tx_info.nss;
+    return s->tx_info.nss_len;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(size_t) t30_get_rx_nss(t30_state_t *s, const uint8_t *nss[])
+{
+    if (nss)
+        *nss = s->rx_info.nss;
+    return s->rx_info.nss_len;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t30_set_tx_tsa(t30_state_t *s, int type, const char *address, int len)
+{
+    if (s->tx_info.tsa)
+        free(s->tx_info.tsa);
+    if (address == NULL  ||  len == 0)
+    {
+        s->tx_info.tsa = NULL;
+        s->tx_info.tsa_len = 0;
+        return 0;
+    }
+    s->tx_info.tsa_type = type;
+    if (len < 0)
+        len = strlen(address);
+    if ((s->tx_info.tsa = malloc(len)))
+    {
+        memcpy(s->tx_info.tsa, address, len);
+        s->tx_info.tsa_len = len;
+    }
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(size_t) t30_get_tx_tsa(t30_state_t *s, int *type, const char *address[])
+{
+    if (type)
+        *type = s->tx_info.tsa_type;
+    if (address)
+        *address = s->tx_info.tsa;
+    return s->tx_info.tsa_len;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(size_t) t30_get_rx_tsa(t30_state_t *s, int *type, const char *address[])
+{
+    if (type)
+        *type = s->rx_info.tsa_type;
+    if (address)
+        *address = s->rx_info.tsa;
+    return s->rx_info.tsa_len;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t30_set_tx_ira(t30_state_t *s, int type, const char *address, int len)
+{
+    if (s->tx_info.ira)
+        free(s->tx_info.ira);
+    if (address == NULL)
+    {
+        s->tx_info.ira = NULL;
+        return 0;
+    }
+    s->tx_info.ira = strdup(address);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(size_t) t30_get_tx_ira(t30_state_t *s, int *type, const char *address[])
+{
+    if (type)
+        *type = s->tx_info.ira_type;
+    if (address)
+        *address = s->tx_info.ira;
+    return s->tx_info.ira_len;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(size_t) t30_get_rx_ira(t30_state_t *s, int *type, const char *address[])
+{
+    if (type)
+        *type = s->rx_info.ira_type;
+    if (address)
+        *address = s->rx_info.ira;
+    return s->rx_info.ira_len;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t30_set_tx_cia(t30_state_t *s, int type, const char *address, int len)
+{
+    if (s->tx_info.cia)
+        free(s->tx_info.cia);
+    if (address == NULL)
+    {
+        s->tx_info.cia = NULL;
+        return 0;
+    }
+    s->tx_info.cia = strdup(address);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(size_t) t30_get_tx_cia(t30_state_t *s, int *type, const char *address[])
+{
+    if (type)
+        *type = s->tx_info.cia_type;
+    if (address)
+        *address = s->tx_info.cia;
+    return s->tx_info.cia_len;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(size_t) t30_get_rx_cia(t30_state_t *s, int *type, const char *address[])
+{
+    if (type)
+        *type = s->rx_info.cia_type;
+    if (address)
+        *address = s->rx_info.cia;
+    return s->rx_info.cia_len;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t30_set_tx_isp(t30_state_t *s, int type, const char *address, int len)
+{
+    if (s->tx_info.isp)
+        free(s->tx_info.isp);
+    if (address == NULL)
+    {
+        s->tx_info.isp = NULL;
+        return 0;
+    }
+    s->tx_info.isp = strdup(address);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(size_t) t30_get_tx_isp(t30_state_t *s, int *type, const char *address[])
+{
+    if (type)
+        *type = s->tx_info.isp_type;
+    if (address)
+        *address = s->tx_info.isp;
+    return s->tx_info.isp_len;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(size_t) t30_get_rx_isp(t30_state_t *s, int *type, const char *address[])
+{
+    if (type)
+        *type = s->rx_info.isp_type;
+    if (address)
+        *address = s->rx_info.isp;
+    return s->rx_info.isp_len;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t30_set_tx_csa(t30_state_t *s, int type, const char *address, int len)
+{
+    if (s->tx_info.csa)
+        free(s->tx_info.csa);
+    if (address == NULL)
+    {
+        s->tx_info.csa = NULL;
+        return 0;
+    }
+    s->tx_info.csa = strdup(address);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(size_t) t30_get_tx_csa(t30_state_t *s, int *type, const char *address[])
+{
+    if (type)
+        *type = s->tx_info.csa_type;
+    if (address)
+        *address = s->tx_info.csa;
+    return s->tx_info.csa_len;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(size_t) t30_get_rx_csa(t30_state_t *s, int *type, const char *address[])
+{
+    if (type)
+        *type = s->rx_info.csa_type;
+    if (address)
+        *address = s->rx_info.csa;
+    return s->rx_info.csa_len;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t30_set_tx_page_header_overlays_image(t30_state_t *s, int header_overlays_image)
+{
+#if 0
+    s->header_overlays_image = header_overlays_image;
+    t4_tx_set_header_overlays_image(&s->t4.tx, s->header_overlays_image);
+#endif
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t30_set_tx_page_header_info(t30_state_t *s, const char *info)
+{
+    if (info == NULL)
+    {
+        s->header_info[0] = '\0';
+        return 0;
+    }
+    if (strlen(info) > T30_MAX_PAGE_HEADER_INFO)
+        return -1;
+    strcpy(s->header_info, info);
+    t4_tx_set_header_info(&s->t4.tx, s->header_info);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t30_set_tx_page_header_tz(t30_state_t *s, const char *tzstring)
+{
+    t4_tx_set_header_tz(&s->t4.tx, tzstring);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(size_t) t30_get_tx_page_header_info(t30_state_t *s, char *info)
+{
+    if (info)
+        strcpy(info, s->header_info);
+    return strlen(s->header_info);
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(const char *) t30_get_rx_country(t30_state_t *s)
+{
+    return s->country;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(const char *) t30_get_rx_vendor(t30_state_t *s)
+{
+    return s->vendor;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(const char *) t30_get_rx_model(t30_state_t *s)
+{
+    return s->model;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t30_set_rx_file(t30_state_t *s, const char *file, int stop_page)
+{
+    strncpy(s->rx_file, file, sizeof(s->rx_file));
+    s->rx_file[sizeof(s->rx_file) - 1] = '\0';
+    s->rx_stop_page = stop_page;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t30_set_tx_file(t30_state_t *s, const char *file, int start_page, int stop_page)
+{
+    strncpy(s->tx_file, file, sizeof(s->tx_file));
+    s->tx_file[sizeof(s->tx_file) - 1] = '\0';
+    s->tx_start_page = start_page;
+    s->tx_stop_page = stop_page;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t30_set_iaf_mode(t30_state_t *s, int iaf)
+{
+    s->iaf = iaf;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t30_set_ecm_capability(t30_state_t *s, int enabled)
+{
+    s->ecm_allowed = enabled;
+    t30_build_dis_or_dtc(s);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t30_set_rx_encoding(t30_state_t *s, int encoding)
+{
+    switch (encoding)
+    {
+    case T4_COMPRESSION_ITU_T4_1D:
+    case T4_COMPRESSION_ITU_T4_2D:
+    case T4_COMPRESSION_ITU_T6:
+        s->output_encoding = encoding;
+        return 0;
+    }
+    return -1;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t30_set_minimum_scan_line_time(t30_state_t *s, int min_time)
+{
+    /* There are only certain possible times supported, so we need to select
+       the code which best matches the request. */
+    if (min_time == 0)
+        s->local_min_scan_time_code = 7;
+    else if (min_time <= 5)
+        s->local_min_scan_time_code = 1;
+    else if (min_time <= 10)
+        s->local_min_scan_time_code = 2;
+    else if (min_time <= 20)
+        s->local_min_scan_time_code = 0;
+    else if (min_time <= 40)
+        s->local_min_scan_time_code = 4;
+    else
+        return -1;
+    t30_build_dis_or_dtc(s);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t30_set_supported_modems(t30_state_t *s, int supported_modems)
+{
+    s->supported_modems = supported_modems;
+    t30_build_dis_or_dtc(s);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t30_set_supported_compressions(t30_state_t *s, int supported_compressions)
+{
+    int mask;
+
+    /* Mask out the ones we actually support today. */
+    mask = T30_SUPPORT_T4_1D_COMPRESSION
+         | T30_SUPPORT_T4_2D_COMPRESSION
+         | T30_SUPPORT_T6_COMPRESSION
+#if defined(SPANDSP_SUPPORT_T85)
+         | T30_SUPPORT_T85_COMPRESSION
+         | T30_SUPPORT_T85_L0_COMPRESSION;
+#else
+         | 0;
+#endif
+    s->supported_compressions = supported_compressions & mask;
+    t30_build_dis_or_dtc(s);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t30_set_supported_resolutions(t30_state_t *s, int supported_resolutions)
+{
+    s->supported_resolutions = supported_resolutions;
+    t30_build_dis_or_dtc(s);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t30_set_supported_image_sizes(t30_state_t *s, int supported_image_sizes)
+{
+    s->supported_image_sizes = supported_image_sizes;
+    t30_build_dis_or_dtc(s);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t30_set_supported_t30_features(t30_state_t *s, int supported_t30_features)
+{
+    s->supported_t30_features = supported_t30_features;
+    t30_build_dis_or_dtc(s);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t30_set_status(t30_state_t *s, int status)
+{
+    s->current_status = status;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t30_set_receiver_not_ready(t30_state_t *s, int count)
+{
+    s->receiver_not_ready_count = count;
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t30_set_phase_b_handler(t30_state_t *s, t30_phase_b_handler_t *handler, void *user_data)
+{
+    s->phase_b_handler = handler;
+    s->phase_b_user_data = user_data;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t30_set_phase_d_handler(t30_state_t *s, t30_phase_d_handler_t *handler, void *user_data)
+{
+    s->phase_d_handler = handler;
+    s->phase_d_user_data = user_data;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t30_set_phase_e_handler(t30_state_t *s, t30_phase_e_handler_t *handler, void *user_data)
+{
+    s->phase_e_handler = handler;
+    s->phase_e_user_data = user_data;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t30_set_document_handler(t30_state_t *s, t30_document_handler_t *handler, void *user_data)
+{
+    s->document_handler = handler;
+    s->document_user_data = user_data;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t30_set_real_time_frame_handler(t30_state_t *s, t30_real_time_frame_handler_t *handler, void *user_data)
+{
+    s->real_time_frame_handler = handler;
+    s->real_time_frame_user_data = user_data;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(logging_state_t *) t30_get_logging_state(t30_state_t *s)
+{
+    return &s->logging;
+}
+/*- End of function --------------------------------------------------------*/
+/*- End of file ------------------------------------------------------------*/
diff --git a/src/codec/spandsp/src/t30_logging.c b/src/codec/spandsp/src/t30_logging.c
new file mode 100644 (file)
index 0000000..463a8d4
--- /dev/null
@@ -0,0 +1,957 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * t30_logging.c - ITU T.30 FAX transfer processing
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2003, 2004, 2005, 2006, 2007 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.
+ */
+
+/*! \file */
+
+#if defined(HAVE_CONFIG_H)
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+//TODO// #include <stdio.h>
+#include <inttypes.h>
+#include <string.h>
+//TODO// #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"
+//TODO// #include <tiffio.h>
+
+#include "spandsp/0cpm.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_rx.h"
+#include "spandsp/t4_tx.h"
+#if defined(SPANDSP_SUPPORT_T85)
+#include "spandsp/t81_t82_arith_coding.h"
+#include "spandsp/t85.h"
+#endif
+#include "spandsp/t4_t6_decode.h"
+#include "spandsp/t4_t6_encode.h"
+#include "spandsp/t30_fcf.h"
+#include "spandsp/t35.h"
+#include "spandsp/t30.h"
+#include "spandsp/t30_logging.h"
+
+#include "spandsp/private/logging.h"
+#if defined(SPANDSP_SUPPORT_T85)
+#include "spandsp/private/t81_t82_arith_coding.h"
+#include "spandsp/private/t85.h"
+#endif
+#include "spandsp/private/t4_t6_decode.h"
+#include "spandsp/private/t4_t6_encode.h"
+#include "spandsp/private/t4_rx.h"
+#include "spandsp/private/t4_tx.h"
+#include "spandsp/private/t30.h"
+
+#include "t30_local.h"
+
+/*! Value string pair structure */
+typedef struct
+{
+    int val;
+    const char *str;
+} value_string_t;
+
+enum
+{
+    DISBIT1 = 0x01,
+    DISBIT2 = 0x02,
+    DISBIT3 = 0x04,
+    DISBIT4 = 0x08,
+    DISBIT5 = 0x10,
+    DISBIT6 = 0x20,
+    DISBIT7 = 0x40,
+    DISBIT8 = 0x80
+};
+
+SPAN_DECLARE(const char *) t30_completion_code_to_str(int result)
+{
+    switch (result)
+    {
+    case T30_ERR_OK:
+        return "OK";
+    case T30_ERR_CEDTONE:
+        return "The CED tone exceeded 5s";
+    case T30_ERR_T0_EXPIRED:
+        return "Timed out waiting for initial communication";
+    case T30_ERR_T1_EXPIRED:
+        return "Timed out waiting for the first message";
+    case T30_ERR_T3_EXPIRED:
+        return "Timed out waiting for procedural interrupt";
+    case T30_ERR_HDLC_CARRIER:
+        return "The HDLC carrier did not stop in a timely manner";
+    case T30_ERR_CANNOT_TRAIN:
+        return "Failed to train with any of the compatible modems";
+    case T30_ERR_OPER_INT_FAIL:
+        return "Operator intervention failed";
+    case T30_ERR_INCOMPATIBLE:
+        return "Far end is not compatible";
+    case T30_ERR_RX_INCAPABLE:
+        return "Far end is not able to receive";
+    case T30_ERR_TX_INCAPABLE:
+        return "Far end is not able to transmit";
+    case T30_ERR_NORESSUPPORT:
+        return "Far end cannot receive at the resolution of the image";
+    case T30_ERR_NOSIZESUPPORT:
+        return "Far end cannot receive at the size of image";
+    case T30_ERR_UNEXPECTED:
+        return "Unexpected message received";
+    case T30_ERR_TX_BADDCS:
+        return "Received bad response to DCS or training";
+    case T30_ERR_TX_BADPG:
+        return "Received a DCN from remote after sending a page";
+    case T30_ERR_TX_ECMPHD:
+        return "Invalid ECM response received from receiver";
+    case T30_ERR_TX_GOTDCN:
+        return "Received a DCN while waiting for a DIS";
+    case T30_ERR_TX_INVALRSP:
+        return "Invalid response after sending a page";
+    case T30_ERR_TX_NODIS:
+        return "Received other than DIS while waiting for DIS";
+    case T30_ERR_TX_PHBDEAD:
+        return "Received no response to DCS or TCF";
+    case T30_ERR_TX_PHDDEAD:
+        return "No response after sending a page";
+    case T30_ERR_TX_T5EXP:
+        return "Timed out waiting for receiver ready (ECM mode)";
+    case T30_ERR_RX_ECMPHD:
+        return "Invalid ECM response received from transmitter";
+    case T30_ERR_RX_GOTDCS:
+        return "DCS received while waiting for DTC";
+    case T30_ERR_RX_INVALCMD:
+        return "Unexpected command after page received";
+    case T30_ERR_RX_NOCARRIER:
+        return "Carrier lost during fax receive";
+    case T30_ERR_RX_NOEOL:
+        return "Timed out while waiting for EOL (end Of line)";
+    case T30_ERR_RX_NOFAX:
+        return "Timed out while waiting for first line";
+    case T30_ERR_RX_T2EXPDCN:
+        return "Timer T2 expired while waiting for DCN";
+    case T30_ERR_RX_T2EXPD:
+        return "Timer T2 expired while waiting for phase D";
+    case T30_ERR_RX_T2EXPFAX:
+        return "Timer T2 expired while waiting for fax page";
+    case T30_ERR_RX_T2EXPMPS:
+        return "Timer T2 expired while waiting for next fax page";
+    case T30_ERR_RX_T2EXPRR:
+        return "Timer T2 expired while waiting for RR command";
+    case T30_ERR_RX_T2EXP:
+        return "Timer T2 expired while waiting for NSS, DCS or MCF";
+    case T30_ERR_RX_DCNWHY:
+        return "Unexpected DCN while waiting for DCS or DIS";
+    case T30_ERR_RX_DCNDATA:
+        return "Unexpected DCN while waiting for image data";
+    case T30_ERR_RX_DCNFAX:
+        return "Unexpected DCN while waiting for EOM, EOP or MPS";
+    case T30_ERR_RX_DCNPHD:
+        return "Unexpected DCN after EOM or MPS sequence";
+    case T30_ERR_RX_DCNRRD:
+        return "Unexpected DCN after RR/RNR sequence";
+    case T30_ERR_RX_DCNNORTN:
+        return "Unexpected DCN after requested retransmission";
+    case T30_ERR_FILEERROR:
+        return "TIFF/F file cannot be opened";
+    case T30_ERR_NOPAGE:
+        return "TIFF/F page not found";
+    case T30_ERR_BADTIFF:
+        return "TIFF/F format is not compatible";
+    case T30_ERR_BADPAGE:
+        return "TIFF/F page number tag missing";
+    case T30_ERR_BADTAG:
+        return "Incorrect values for TIFF/F tags";
+    case T30_ERR_BADTIFFHDR:
+        return "Bad TIFF/F header - incorrect values in fields";
+    case T30_ERR_NOMEM:
+        return "Cannot allocate memory for more pages";
+    case T30_ERR_RETRYDCN:
+        return "Disconnected after permitted retries";
+    case T30_ERR_CALLDROPPED:
+        return "The call dropped prematurely";
+    case T30_ERR_NOPOLL:
+        return "Poll not accepted";
+    case T30_ERR_IDENT_UNACCEPTABLE:
+        return "Ident not accepted";
+    case T30_ERR_PSA_UNACCEPTABLE:
+        return "Polled sub-address not accepted";
+    case T30_ERR_SEP_UNACCEPTABLE:
+        return "Selective polling address not accepted";
+    case T30_ERR_SID_UNACCEPTABLE:
+        return "Sender identification not accepted";
+    case T30_ERR_PWD_UNACCEPTABLE:
+        return "Password not accepted";
+    case T30_ERR_SUB_UNACCEPTABLE:
+        return "Sub-address not accepted";
+    case T30_ERR_TSA_UNACCEPTABLE:
+        return "Transmitting subscriber internet address not accepted";
+    case T30_ERR_IRA_UNACCEPTABLE:
+        return "Internet routing address not accepted";
+    case T30_ERR_CIA_UNACCEPTABLE:
+        return "Calling subscriber internet address not accepted";
+    case T30_ERR_ISP_UNACCEPTABLE:
+        return "Internet selective polling address not accepted";
+    case T30_ERR_CSA_UNACCEPTABLE:
+        return "Called subscriber internet address not accepted";
+    }
+    return "???";
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(const char *) t30_frametype(uint8_t x)
+{
+    switch (x)
+    {
+    case T30_DIS:
+        return "DIS";
+    case T30_CSI:
+        return "CSI";
+    case T30_NSF:
+        return "NSF";
+    case T30_DTC:
+        return "DTC";
+    case T30_CIG:
+        return "CIG";
+    case T30_NSC:
+        return "NSC";
+    case T30_PWD:
+        return "PWD";
+    case T30_SEP:
+        return "SEP";
+    case T30_PSA:
+        return "PSA";
+    case T30_CIA:
+        return "CIA";
+    case T30_ISP:
+        return "ISP";
+    case T30_DCS:
+    case T30_DCS | 0x01:
+        return "DCS";
+    case T30_TSI:
+    case T30_TSI | 0x01:
+        return "TSI";
+    case T30_NSS:
+    case T30_NSS | 0x01:
+        return "NSS";
+    case T30_SUB:
+    case T30_SUB | 0x01:
+        return "SUB";
+    case T30_SID:
+    case T30_SID | 0x01:
+        return "SID";
+    case T30_CTC:
+    case T30_CTC | 0x01:
+        return "CTC";
+    case T30_TSA:
+    case T30_TSA | 0x01:
+        return "TSA";
+    case T30_IRA:
+    case T30_IRA | 0x01:
+        return "IRA";
+    case T30_CFR:
+    case T30_CFR | 0x01:
+        return "CFR";
+    case T30_FTT:
+    case T30_FTT | 0x01:
+        return "FTT";
+    case T30_CTR:
+    case T30_CTR | 0x01:
+        return "CTR";
+    case T30_CSA:
+    case T30_CSA | 0x01:
+        return "CSA";
+    case T30_EOM:
+    case T30_EOM | 0x01:
+        return "EOM";
+    case T30_MPS:
+    case T30_MPS | 0x01:
+        return "MPS";
+    case T30_EOP:
+    case T30_EOP | 0x01:
+        return "EOP";
+    case T30_PRI_EOM:
+    case T30_PRI_EOM | 0x01:
+        return "PRI-EOM";
+    case T30_PRI_MPS:
+    case T30_PRI_MPS | 0x01:
+        return "PRI-MPS";
+    case T30_PRI_EOP:
+    case T30_PRI_EOP | 0x01:
+        return "PRI-EOP";
+    case T30_EOS:
+    case T30_EOS | 0x01:
+        return "EOS";
+    case T30_PPS:
+    case T30_PPS | 0x01:
+        return "PPS";
+    case T30_EOR:
+    case T30_EOR | 0x01:
+        return "EOR";
+    case T30_RR:
+    case T30_RR | 0x01:
+        return "RR";
+    case T30_MCF:
+    case T30_MCF | 0x01:
+        return "MCF";
+    case T30_RTP:
+    case T30_RTP | 0x01:
+        return "RTP";
+    case T30_RTN:
+    case T30_RTN | 0x01:
+        return "RTN";
+    case T30_PIP:
+    case T30_PIP | 0x01:
+        return "PIP";
+    case T30_PIN:
+    case T30_PIN | 0x01:
+        return "PIN";
+    case T30_PPR:
+    case T30_PPR | 0x01:
+        return "PPR";
+    case T30_RNR:
+    case T30_RNR | 0x01:
+        return "RNR";
+    case T30_ERR:
+    case T30_ERR | 0x01:
+        return "ERR";
+    case T30_FDM:
+    case T30_FDM | 0x01:
+        return "FDM";
+    case T30_DCN:
+    case T30_DCN | 0x01:
+        return "DCN";
+    case T30_CRP:
+    case T30_CRP | 0x01:
+        return "CRP";
+    case T30_FNV:
+    case T30_FNV | 0x01:
+        return "FNV";
+    case T30_TNR:
+    case T30_TNR | 0x01:
+        return "TNR";
+    case T30_TR:
+    case T30_TR | 0x01:
+        return "TR";
+    case T30_TK:
+        return "TK";
+    case T30_RK:
+        return "RK";
+#if 0
+    case T30_PSS:
+        return "PSS";
+#endif
+    case T30_DES:
+        return "DES";
+    case T30_DEC:
+        return "DEC";
+    case T30_DER:
+        return "DER";
+#if 0
+    case T30_DTR:
+        return "DTR";
+#endif
+    case T30_DNK:
+    case T30_DNK | 0x01:
+        return "DNK";
+    case T30_PID:
+    case T30_PID | 0x01:
+        return "PID";
+    case T30_NULL:
+        return "NULL";
+    case T4_FCD:
+        return "FCD";
+    case T4_RCP:
+        return "RCP";
+    }
+    return "???";
+}
+/*- End of function --------------------------------------------------------*/
+
+static void octet_reserved_bit(logging_state_t *log,
+                               const uint8_t *msg,
+                               int bit_no,
+                               int expected)
+{
+    char s[10] = ".... ....";
+    int bit;
+    uint8_t octet;
+    
+    /* Break out the octet and the bit number within it. */
+    octet = msg[((bit_no - 1) >> 3) + 3];
+    bit_no = (bit_no - 1) & 7;
+    /* Now get the actual bit. */
+    bit = (octet >> bit_no) & 1;
+    /* Is it what it should be. */
+    if (bit ^ expected)
+    {
+        /* Only log unexpected values. */
+        s[7 - bit_no + ((bit_no < 4)  ?  1  :  0)] = (uint8_t) (bit + '0');
+        span_log(log, SPAN_LOG_FLOW, "  %s= Unexpected state for reserved bit: %d\n", s, bit);
+    }
+}
+/*- End of function --------------------------------------------------------*/
+
+static void octet_bit_field(logging_state_t *log,
+                            const uint8_t *msg,
+                            int bit_no,
+                            const char *desc,
+                            const char *yeah,
+                            const char *neigh)
+{
+    char s[10] = ".... ....";
+    int bit;
+    uint8_t octet;
+    const char *tag;
+
+    /* Break out the octet and the bit number within it. */
+    octet = msg[((bit_no - 1) >> 3) + 3];
+    bit_no = (bit_no - 1) & 7;
+    /* Now get the actual bit. */
+    bit = (octet >> bit_no) & 1;
+    /* Edit the bit string for display. */
+    s[7 - bit_no + ((bit_no < 4)  ?  1  :  0)] = (uint8_t) (bit + '0');
+    /* Find the right tag to display. */
+    if (bit)
+    {
+        if ((tag = yeah) == NULL)
+            tag = "Set";
+    }
+    else
+    {
+        if ((tag = neigh) == NULL)
+            tag = "Not set";
+    }
+    /* Eh, voila! */
+    span_log(log, SPAN_LOG_FLOW, "  %s= %s: %s\n", s, desc, tag);
+}
+/*- End of function --------------------------------------------------------*/
+
+static void octet_field(logging_state_t *log,
+                        const uint8_t *msg,
+                        int start,
+                        int end,
+                        const char *desc,
+                        const value_string_t tags[])
+{
+    char s[10] = ".... ....";
+    int i;
+    uint8_t octet;
+    const char *tag;
+    
+    /* Break out the octet and the bit number range within it. */
+    octet = msg[((start - 1) >> 3) + 3];
+    start = (start - 1) & 7;
+    end = ((end - 1) & 7) + 1;
+
+    /* Edit the bit string for display. */
+    for (i = start;  i < end;  i++)
+        s[7 - i + ((i < 4)  ?  1  :  0)] = (uint8_t) ((octet >> i) & 1) + '0';
+
+    /* Find the right tag to display. */
+    octet = (uint8_t) ((octet >> start) & ((0xFF + (1 << (end - start))) & 0xFF));
+    tag = "Invalid";
+    for (i = 0;  tags[i].str;  i++)
+    {
+        if (octet == tags[i].val)
+        {
+            tag = tags[i].str;
+            break;
+        }
+    }
+    /* Eh, voila! */
+    span_log(log, SPAN_LOG_FLOW, "  %s= %s: %s\n", s, desc, tag);
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t30_decode_dis_dtc_dcs(t30_state_t *s, const uint8_t *pkt, int len)
+{
+    logging_state_t *log;
+    uint8_t frame_type;
+    static const value_string_t available_signalling_rate_tags[] =
+    {
+        { 0x00, "V.27 ter fall-back mode" },
+        { 0x01, "V.29" },
+        { 0x02, "V.27 ter" },
+        { 0x03, "V.27 ter and V.29" },
+        { 0x0B, "V.27 ter, V.29, and V.17" },
+        { 0x06, "Reserved" },
+        { 0x0A, "Reserved" },
+        { 0x0E, "Reserved" },
+        { 0x0F, "Reserved" },
+        { 0x04, "Not used" },
+        { 0x05, "Not used" },
+        { 0x08, "Not used" },
+        { 0x09, "Not used" },
+        { 0x0C, "Not used" },
+        { 0x0D, "Not used" },
+        { 0x00, NULL }
+    };
+    static const value_string_t selected_signalling_rate_tags[] =
+    {
+        { 0x00, "V.27ter 2400bps" },
+        { 0x01, "V.29, 9600bps" },
+        { 0x02, "V.27ter 4800bps" },
+        { 0x03, "V.29 7200bps" },
+        { 0x08, "V.17 14400bps" },
+        { 0x09, "V.17 9600bps" },
+        { 0x0A, "V.17 12000bps" },
+        { 0x0B, "V.17 7200bps" },
+        { 0x05, "Reserved" },
+        { 0x07, "Reserved" },
+        { 0x0C, "Reserved" },
+        { 0x0D, "Reserved" },
+        { 0x0E, "Reserved" },
+        { 0x0F, "Reserved" },
+        { 0x00, NULL }
+    };
+    static const value_string_t available_scan_line_length_tags[] =
+    {
+        { 0x00, "215mm +- 1%" },
+        { 0x01, "215mm +- 1% and 255mm +- 1%" },
+        { 0x02, "215mm +- 1%, 255mm +- 1% and 303mm +- 1%" },
+        { 0x00, NULL }
+    };
+    static const value_string_t selected_scan_line_length_tags[] =
+    {
+        { 0x00, "215mm +- 1%" },
+        { 0x01, "255mm +- 1%" },
+        { 0x02, "303mm +- 1%" },
+        { 0x00, NULL }
+    };
+    static const value_string_t available_recording_length_tags[] =
+    {
+        { 0x00, "A4 (297mm)" },
+        { 0x01, "A4 (297mm) and B4 (364mm)" },
+        { 0x02, "Unlimited" },
+        { 0x00, NULL }
+    };
+    static const value_string_t selected_recording_length_tags[] =
+    {
+        { 0x00, "A4 (297mm)" },
+        { 0x01, "B4 (364mm)" },
+        { 0x02, "Unlimited" },
+        { 0x00, NULL }
+    };
+    static const value_string_t available_minimum_scan_line_time_tags[] =
+    {
+        { 0x00, "20ms at 3.85 l/mm; T7.7 = T3.85" },
+        { 0x01, "5ms at 3.85 l/mm; T7.7 = T3.85" },
+        { 0x02, "10ms at 3.85 l/mm; T7.7 = T3.85" },
+        { 0x03, "20ms at 3.85 l/mm; T7.7 = 1/2 T3.85" },
+        { 0x04, "40ms at 3.85 l/mm; T7.7 = T3.85" },
+        { 0x05, "40ms at 3.85 l/mm; T7.7 = 1/2 T3.85" },
+        { 0x06, "10ms at 3.85 l/mm; T7.7 = 1/2 T3.85" },
+        { 0x07, "0ms at 3.85 l/mm; T7.7 = T3.85" },
+        { 0x00, NULL }
+    };
+    static const value_string_t selected_minimum_scan_line_time_tags[] =
+    {
+        { 0x00, "20ms" },
+        { 0x01, "5ms" },
+        { 0x02, "10ms" },
+        { 0x04, "40ms" },
+        { 0x07, "0ms" },
+        { 0x00, NULL }
+    };
+    static const value_string_t shared_data_memory_capacity_tags[] =
+    {
+        { 0x00, "Not available" },
+        { 0x01, "Level 2 = 2.0 Mbytes" },
+        { 0x02, "Level 1 = 1.0 Mbytes" },
+        { 0x03, "Level 3 = unlimited (i.e. >= 32 Mbytes)" },
+        { 0x00, NULL }
+    };
+    static const value_string_t t89_profile_tags[] =
+    {
+        { 0x00, "Not used" },
+        { 0x01, "Profiles 2 and 3" },
+        { 0x02, "Profile 2" },
+        { 0x04, "Profile 1" },
+        { 0x06, "Profile 3" },
+        { 0x03, "Reserved" },
+        { 0x05, "Reserved" },
+        { 0x07, "Reserved" },
+        { 0x00, NULL }
+    };
+    static const value_string_t t44_mixed_raster_content_tags[] =
+    {
+        { 0x00, "0" },
+        { 0x01, "1" },
+        { 0x02, "2" },
+        { 0x32, "3" },
+        { 0x04, "4" },
+        { 0x05, "5" },
+        { 0x06, "6" },
+        { 0x07, "7" },
+        { 0x00, NULL }
+    };
+
+    if (!span_log_test(&s->logging, SPAN_LOG_FLOW))
+        return;
+    frame_type = pkt[2] & 0xFE;
+    log = &s->logging;
+    if (len <= 2)
+    {
+        span_log(log, SPAN_LOG_FLOW, "  Frame is short\n");
+        return;
+    }
+    
+    span_log(log, SPAN_LOG_FLOW, "%s:\n", t30_frametype(pkt[2]));
+    if (len <= 3)
+    {
+        span_log(log, SPAN_LOG_FLOW, "  Frame is short\n");
+        return;
+    }
+    octet_bit_field(log, pkt, 1, "Store and forward Internet fax (T.37)", NULL, NULL);
+    octet_reserved_bit(log, pkt, 2, 0);
+    octet_bit_field(log, pkt, 3, "Real-time Internet fax (T.38)", NULL, NULL);
+    octet_bit_field(log, pkt, 4, "3G mobile network", NULL, NULL);
+    octet_reserved_bit(log, pkt, 5, 0);
+    if (frame_type == T30_DCS)
+    {
+        octet_reserved_bit(log, pkt, 6, 0);
+        octet_reserved_bit(log, pkt, 7, 0);
+    }
+    else
+    {
+        octet_bit_field(log, pkt, 6, "V.8 capabilities", NULL, NULL);
+        octet_bit_field(log, pkt, 7, "Preferred octets", "64 octets", "256 octets");
+    }
+    octet_reserved_bit(log, pkt, 8, 0);
+    if (len <= 4)
+    {
+        span_log(log, SPAN_LOG_FLOW, "  Frame is short\n");
+        return;
+    }
+    
+    if (frame_type == T30_DCS)
+    {
+        octet_reserved_bit(log, pkt, 9, 0);
+        octet_bit_field(log, pkt, 10, "Receive fax", NULL, NULL);
+        octet_field(log, pkt, 11, 14, "Selected data signalling rate", selected_signalling_rate_tags);
+    }
+    else
+    {
+        octet_bit_field(log, pkt, 9, "Ready to transmit a fax document (polling)", NULL, NULL);
+        octet_bit_field(log, pkt, 10, "Can receive fax", NULL, NULL);
+        octet_field(log, pkt, 11, 14, "Supported data signalling rates", available_signalling_rate_tags);
+    }
+    octet_bit_field(log, pkt, 15, "R8x7.7lines/mm and/or 200x200pels/25.4mm", NULL, NULL);
+    octet_bit_field(log, pkt, 16, "2-D coding", NULL, NULL);
+    if (len <= 5)
+    {
+        span_log(log, SPAN_LOG_FLOW, "  Frame is short\n");
+        return;
+    }
+
+    if (frame_type == T30_DCS)
+    {
+        octet_field(log, pkt, 17, 18, "Recording width", selected_scan_line_length_tags);
+        octet_field(log, pkt, 19, 20, "Recording length", selected_recording_length_tags);
+        octet_field(log, pkt, 21, 23, "Minimum scan line time", selected_minimum_scan_line_time_tags);
+    }
+    else
+    {
+        octet_field(log, pkt, 17, 18, "Recording width", available_scan_line_length_tags);
+        octet_field(log, pkt, 19, 20, "Recording length", available_recording_length_tags);
+        octet_field(log, pkt, 21, 23, "Receiver's minimum scan line time", available_minimum_scan_line_time_tags);
+    }
+    octet_bit_field(log, pkt, 24, "Extension indicator", NULL, NULL);
+    if (!(pkt[5] & DISBIT8))
+        return;
+    if (len <= 6)
+    {
+        span_log(log, SPAN_LOG_FLOW, "  Frame is short\n");
+        return;
+    }
+
+    octet_reserved_bit(log, pkt, 25, 0);
+    octet_bit_field(log, pkt, 26, "Compressed/uncompressed mode", "Uncompressed", "Compressed");
+    octet_bit_field(log, pkt, 27, "Error correction mode (ECM)", "ECM", "Non-ECM");
+    if (frame_type == T30_DCS)
+        octet_bit_field(log, pkt, 28, "Frame size", "64 octets", "256 octets");
+    else
+        octet_reserved_bit(log, pkt, 28, 0);
+    octet_reserved_bit(log, pkt, 29, 0);
+    octet_reserved_bit(log, pkt, 30, 0);
+    octet_bit_field(log, pkt, 31, "T.6 coding", NULL, NULL);
+    octet_bit_field(log, pkt, 32, "Extension indicator", NULL, NULL);
+    if (!(pkt[6] & DISBIT8))
+        return;
+    if (len <= 7)
+    {
+        span_log(log, SPAN_LOG_FLOW, "  Frame is short\n");
+        return;
+    }
+
+    octet_bit_field(log, pkt, 33, "\"Field not valid\" supported", NULL, NULL);
+    if (frame_type == T30_DCS)
+    {
+        octet_reserved_bit(log, pkt, 34, 0);
+        octet_reserved_bit(log, pkt, 35, 0);
+    }
+    else
+    {
+        octet_bit_field(log, pkt, 34, "Multiple selective polling", NULL, NULL);
+        octet_bit_field(log, pkt, 35, "Polled sub-address", NULL, NULL);
+    }
+    octet_bit_field(log, pkt, 36, "T.43 coding", NULL, NULL);
+    octet_bit_field(log, pkt, 37, "Plane interleave", NULL, NULL);
+    octet_bit_field(log, pkt, 38, "Voice coding with 32kbit/s ADPCM (Rec. G.726)", NULL, NULL);
+    octet_bit_field(log, pkt, 39, "Reserved for the use of extended voice coding set", NULL, NULL);
+    octet_bit_field(log, pkt, 40, "Extension indicator", NULL, NULL);
+    if (!(pkt[7] & DISBIT8))
+        return;
+    if (len <= 8)
+    {
+        span_log(log, SPAN_LOG_FLOW, "  Frame is short\n");
+        return;
+    }
+    octet_bit_field(log, pkt, 41, "R8x15.4lines/mm", NULL, NULL);
+    octet_bit_field(log, pkt, 42, "300x300pels/25.4mm", NULL, NULL);
+    octet_bit_field(log, pkt, 43, "R16x15.4lines/mm and/or 400x400pels/25.4mm", NULL, NULL);
+    if (frame_type == T30_DCS)
+    {
+        octet_bit_field(log, pkt, 44, "Resolution type selection", "Inch", "Metric");
+        octet_reserved_bit(log, pkt, 45, 0);
+        octet_reserved_bit(log, pkt, 46, 0);
+        octet_reserved_bit(log, pkt, 47, 0);
+    }
+    else
+    {
+        octet_bit_field(log, pkt, 44, "Inch-based resolution preferred", NULL, NULL);
+        octet_bit_field(log, pkt, 45, "Metric-based resolution preferred", NULL, NULL);
+        octet_bit_field(log, pkt, 46, "Minimum scan line time for higher resolutions", "T15.4 = 1/2 T7.7", "T15.4 = T7.7");
+        octet_bit_field(log, pkt, 47, "Selective polling", NULL, NULL);
+    }
+    octet_bit_field(log, pkt, 48, "Extension indicator", NULL, NULL);
+    if (!(pkt[8] & DISBIT8))
+        return;
+    if (len <= 9)
+    {
+        span_log(log, SPAN_LOG_FLOW, "  Frame is short\n");
+        return;
+    }
+
+    octet_bit_field(log, pkt, 49, "Sub-addressing", NULL, NULL);
+    if (frame_type == T30_DCS)
+    {
+        octet_bit_field(log, pkt, 50, "Sender identification transmission", NULL, NULL);
+        octet_reserved_bit(log, pkt, 51, 0);
+    }
+    else
+    {
+        octet_bit_field(log, pkt, 50, "Password", NULL, NULL);
+        octet_bit_field(log, pkt, 51, "Ready to transmit a data file (polling)", NULL, NULL);
+    }
+    octet_reserved_bit(log, pkt, 52, 0);
+    octet_bit_field(log, pkt, 53, "Binary file transfer (BFT)", NULL, NULL);
+    octet_bit_field(log, pkt, 54, "Document transfer mode (DTM)", NULL, NULL);
+    octet_bit_field(log, pkt, 55, "Electronic data interchange (EDI)", NULL, NULL);
+    octet_bit_field(log, pkt, 56, "Extension indicator", NULL, NULL);
+    if (!(pkt[9] & DISBIT8))
+        return;
+    if (len <= 10)
+    {
+        span_log(log, SPAN_LOG_FLOW, "  Frame is short\n");
+        return;
+    }
+
+    octet_bit_field(log, pkt, 57, "Basic transfer mode (BTM)", NULL, NULL);
+    octet_reserved_bit(log, pkt, 58, 0);
+    if (frame_type == T30_DCS)
+        octet_reserved_bit(log, pkt, 59, 0);
+    else
+        octet_bit_field(log, pkt, 59, "Ready to transfer a character or mixed mode document (polling)", NULL, NULL);
+    octet_bit_field(log, pkt, 60, "Character mode", NULL, NULL);
+    octet_reserved_bit(log, pkt, 61, 0);
+    octet_bit_field(log, pkt, 62, "Mixed mode (Annex E/T.4)", NULL, NULL);
+    octet_reserved_bit(log, pkt, 63, 0);
+    octet_bit_field(log, pkt, 64, "Extension indicator", NULL, NULL);
+    if (!(pkt[10] & DISBIT8))
+        return;
+    if (len <= 11)
+    {
+        span_log(log, SPAN_LOG_FLOW, "  Frame is short\n");
+        return;
+    }
+
+    octet_bit_field(log, pkt, 65, "Processable mode 26 (Rec. T.505)", NULL, NULL);
+    octet_bit_field(log, pkt, 66, "Digital network capability", NULL, NULL);
+    octet_bit_field(log, pkt, 67, "Duplex capability", "Full", "Half only");
+    if (frame_type == T30_DCS)
+        octet_bit_field(log, pkt, 68, "Full colour mode", NULL, NULL);
+    else
+        octet_bit_field(log, pkt, 68, "JPEG coding", NULL, NULL);
+    octet_bit_field(log, pkt, 69, "Full colour mode", NULL, NULL);
+    if (frame_type == T30_DCS)
+        octet_bit_field(log, pkt, 70, "Preferred Huffman tables", NULL, NULL);
+    else
+        octet_reserved_bit(log, pkt, 70, 0);
+    octet_bit_field(log, pkt, 71, "12bits/pel component", NULL, NULL);
+    octet_bit_field(log, pkt, 72, "Extension indicator", NULL, NULL);
+    if (!(pkt[11] & DISBIT8))
+        return;
+    if (len <= 12)
+    {
+        span_log(log, SPAN_LOG_FLOW, "  Frame is short\n");
+        return;
+    }
+
+    octet_bit_field(log, pkt, 73, "No subsampling (1:1:1)", NULL, NULL);
+    octet_bit_field(log, pkt, 74, "Custom illuminant", NULL, NULL);
+    octet_bit_field(log, pkt, 75, "Custom gamut range", NULL, NULL);
+    octet_bit_field(log, pkt, 76, "North American Letter (215.9mm x 279.4mm)", NULL, NULL);
+    octet_bit_field(log, pkt, 77, "North American Legal (215.9mm x 355.6mm)", NULL, NULL);
+    octet_bit_field(log, pkt, 78, "Single-progression sequential coding (Rec. T.85) basic", NULL, NULL);
+    octet_bit_field(log, pkt, 79, "Single-progression sequential coding (Rec. T.85) optional L0", NULL, NULL);
+    octet_bit_field(log, pkt, 80, "Extension indicator", NULL, NULL);
+    if (!(pkt[12] & DISBIT8))
+        return;
+    if (len <= 13)
+    {
+        span_log(log, SPAN_LOG_FLOW, "  Frame is short\n");
+        return;
+    }
+
+    octet_bit_field(log, pkt, 81, "HKM key management", NULL, NULL);
+    octet_bit_field(log, pkt, 82, "RSA key management", NULL, NULL);
+    octet_bit_field(log, pkt, 83, "Override", NULL, NULL);
+    octet_bit_field(log, pkt, 84, "HFX40 cipher", NULL, NULL);
+    octet_bit_field(log, pkt, 85, "Alternative cipher number 2", NULL, NULL);
+    octet_bit_field(log, pkt, 86, "Alternative cipher number 3", NULL, NULL);
+    octet_bit_field(log, pkt, 87, "HFX40-I hashing", NULL, NULL);
+    octet_bit_field(log, pkt, 88, "Extension indicator", NULL, NULL);
+    if (!(pkt[13] & DISBIT8))
+        return;
+    if (len <= 14)
+    {
+        span_log(log, SPAN_LOG_FLOW, "  Frame is short\n");
+        return;
+    }
+
+    octet_bit_field(log, pkt, 89, "Alternative hashing system 2", NULL, NULL);
+    octet_bit_field(log, pkt, 90, "Alternative hashing system 3", NULL, NULL);
+    octet_bit_field(log, pkt, 91, "Reserved for future security features", NULL, NULL);
+    octet_field(log, pkt, 92, 94, "T.44 (Mixed Raster Content)", t44_mixed_raster_content_tags);
+    octet_bit_field(log, pkt, 95, "Page length maximum stripe size for T.44 (Mixed Raster Content)", NULL, NULL);
+    octet_bit_field(log, pkt, 96, "Extension indicator", NULL, NULL);
+    if (!(pkt[14] & DISBIT8))
+        return;
+    if (len <= 15)
+    {
+        span_log(log, SPAN_LOG_FLOW, "  Frame is short\n");
+        return;
+    }
+
+    octet_bit_field(log, pkt, 97, "Colour/gray-scale 300pels/25.4mm x 300lines/25.4mm or 400pels/25.4mm x 400lines/25.4mm resolution", NULL, NULL);
+    octet_bit_field(log, pkt, 98, "100pels/25.4mm x 100lines/25.4mm for colour/gray scale", NULL, NULL);
+    octet_bit_field(log, pkt, 99, "Simple phase C BFT negotiations", NULL, NULL);
+    if (frame_type == T30_DCS)
+    {
+        octet_reserved_bit(log, pkt, 100, 0);
+        octet_reserved_bit(log, pkt, 101, 0);
+    }
+    else
+    {
+        octet_bit_field(log, pkt, 100, "Extended BFT Negotiations capable", NULL, NULL);
+        octet_bit_field(log, pkt, 101, "Internet Selective Polling address (ISP)", NULL, NULL);
+    }
+    octet_bit_field(log, pkt, 102, "Internet Routing Address (IRA)", NULL, NULL);
+    octet_reserved_bit(log, pkt, 103, 0);
+    octet_bit_field(log, pkt, 104, "Extension indicator", NULL, NULL);
+    if (!(pkt[15] & DISBIT8))
+        return;
+    if (len <= 16)
+    {
+        span_log(log, SPAN_LOG_FLOW, "  Frame is short\n");
+        return;
+    }
+
+    octet_bit_field(log, pkt, 105, "600pels/25.4mm x 600lines/25.4mm", NULL, NULL);
+    octet_bit_field(log, pkt, 106, "1200pels/25.4mm x 1200lines/25.4mm", NULL, NULL);
+    octet_bit_field(log, pkt, 107, "300pels/25.4mm x 600lines/25.4mm", NULL, NULL);
+    octet_bit_field(log, pkt, 108, "400pels/25.4mm x 800lines/25.4mm", NULL, NULL);
+    octet_bit_field(log, pkt, 109, "600pels/25.4mm x 1200lines/25.4mm", NULL, NULL);
+    octet_bit_field(log, pkt, 110, "Colour/gray scale 600pels/25.4mm x 600lines/25.4mm", NULL, NULL);
+    octet_bit_field(log, pkt, 111, "Colour/gray scale 1200pels/25.4mm x 1200lines/25.4mm", NULL, NULL);
+    octet_bit_field(log, pkt, 112, "Extension indicator", NULL, NULL);
+    if (!(pkt[16] & DISBIT8))
+        return;
+    if (len <= 17)
+    {
+        span_log(log, SPAN_LOG_FLOW, "  Frame is short\n");
+        return;
+    }
+
+    octet_bit_field(log, pkt, 113, "Double sided printing capability (alternate mode)", NULL, NULL);
+    octet_bit_field(log, pkt, 114, "Double sided printing capability (continuous mode)", NULL, NULL);
+    if (frame_type == T30_DCS)
+        octet_bit_field(log, pkt, 115, "Black and white mixed raster content profile (MRCbw)", NULL, NULL);
+    else
+        octet_reserved_bit(log, pkt, 115, 0);
+    octet_bit_field(log, pkt, 116, "T.45 (run length colour encoded)", NULL, NULL);
+    octet_field(log, pkt, 117, 118, "Shared memory", shared_data_memory_capacity_tags);
+    octet_bit_field(log, pkt, 119, "T.44 colour space", NULL, NULL);
+    octet_bit_field(log, pkt, 120, "Extension indicator", NULL, NULL);
+    if (!(pkt[17] & DISBIT8))
+        return;
+    if (len <= 18)
+    {
+        span_log(log, SPAN_LOG_FLOW, "  Frame is short\n");
+        return;
+    }
+
+    octet_bit_field(log, pkt, 121, "Flow control capability for T.38 communication", NULL, NULL);
+    octet_bit_field(log, pkt, 122, "K>4", NULL, NULL);
+    octet_bit_field(log, pkt, 123, "Internet aware T.38 mode fax (not affected by data signal rate bits)", NULL, NULL);
+    octet_field(log, pkt, 124, 126, "T.89 (Application profiles for ITU-T Rec T.88)", t89_profile_tags);
+    octet_bit_field(log, pkt, 127, "sYCC-JPEG coding", NULL, NULL);
+    octet_bit_field(log, pkt, 128, "Extension indicator", NULL, NULL);
+    if (!(pkt[18] & DISBIT8))
+        return;
+
+    span_log(log, SPAN_LOG_FLOW, "  Extended beyond the current T.30 specification!\n");
+}
+/*- End of function --------------------------------------------------------*/
+/*- End of file ------------------------------------------------------------*/
diff --git a/src/codec/spandsp/src/t35.c b/src/codec/spandsp/src/t35.c
new file mode 100644 (file)
index 0000000..6ad2290
--- /dev/null
@@ -0,0 +1,910 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * t35.c - ITU T.35 FAX non-standard facility processing.
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2003 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.
+ */
+
+/*
+ * The NSF data tables are adapted from the NSF handling in HylaFAX, which
+ * carries the following copyright notice:
+ *
+ * Created by Dmitry Bely, April 2000
+ * Copyright (c) 1994-1996 Sam Leffler
+ * Copyright (c) 1994-1996 Silicon Graphics, Inc.
+ * HylaFAX is a trademark of Silicon Graphics
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and 
+ * its documentation for any purpose is hereby granted without fee, provided
+ * that (i) the above copyright notices and this permission notice appear in
+ * all copies of the software and related documentation, and (ii) the names of
+ * Sam Leffler and Silicon Graphics may not be used in any advertising or
+ * publicity relating to the software without the specific, prior written
+ * permission of Sam Leffler and Silicon Graphics.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY 
+ * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.  
+ * 
+ * IN NO EVENT SHALL SAM LEFFLER OR SILICON GRAPHICS BE LIABLE FOR
+ * ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
+ * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+ * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF 
+ * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE 
+ * OF THIS SOFTWARE.
+ */
+
+/*! \file */
+
+#if defined(HAVE_CONFIG_H)
+#include "config.h"
+#endif
+
+#include <inttypes.h>
+#include <ctype.h>
+#include <string.h>
+
+#include "spandsp/0cpm.h"
+
+#include "spandsp/telephony.h"
+#include "spandsp/bit_operations.h"
+#include "spandsp/t35.h"
+
+/*! NSF pattern for FAX machine identification */
+typedef struct
+{
+    /*! The number of bytes of the NSF byte string to match */
+    int model_id_size;
+    /*! The NSF byte string to expect */
+    const char *model_id;
+    /*! The model name of the FAX terminal */
+    const char *model_name;
+} model_data_t;
+
+/*! NSF pattern for identifying the manufacturer of a FAX machine */
+typedef struct
+{
+    /*! The vendor ID byte string */
+    const char *vendor_id;
+    /*! The length of the vendor ID byte string */
+    int vendor_id_len;
+    /*! The vendor's name */
+    const char *vendor_name;
+    /*! TRUE if the station ID for this vendor is reversed */
+    int inverse_station_id_order;
+    /*! A pointer to a list of known models from this vendor */
+    const model_data_t *known_models;
+} nsf_data_t;
+
+const char *t35_country_codes[256] =
+{
+    "Japan",                                    /* 0x00 */
+    "Albania",
+    "Algeria",
+    "American Samoa",
+    "Germany",
+    "Anguilla",
+    "Antigua and Barbuda",
+    "Argentina",
+    "Ascension (see S. Helena)",
+    "Australia",
+    "Austria",
+    "Bahamas",
+    "Bahrain",
+    "Bangladesh",
+    "Barbados",
+    "Belgium",
+    "Belize",
+    "Benin (Republic of)",
+    "Bermudas",
+    "Bhutan (Kingdom of)",
+    "Bolivia",
+    "Botswana",
+    "Brazil",
+    "British Antarctic Territory",
+    "British Indian Ocean Territory",
+    "British Virgin Islands",
+    "Brunei Darussalam",
+    "Bulgaria",
+    "Myanmar (Union of)",
+    "Burundi",
+    "Byelorussia",
+    "Cameroon",
+    "Canada",                                   /* 0x20 */
+    "Cape Verde",
+    "Cayman Islands",
+    "Central African Republic",
+    "Chad",
+    "Chile",
+    "China",
+    "Colombia",
+    "Comoros",
+    "Congo",
+    "Cook Islands",
+    "Costa Rica",
+    "Cuba",
+    "Cyprus",
+    "Czech and Slovak Federal Republic",
+    "Cambodia",
+    "Democratic People's Republic of Korea",
+    "Denmark",
+    "Djibouti",
+    "Dominican Republic",
+    "Dominica",
+    "Ecuador",
+    "Egypt",
+    "El Salvador",
+    "Equatorial Guinea",
+    "Ethiopia",
+    "Falkland Islands",
+    "Fiji",
+    "Finland",
+    "France",
+    "French Polynesia",
+    "French Southern and Antarctic Lands",
+    "Gabon",                                        /* 0x40 */
+    "Gambia",
+    "Germany (Federal Republic of)",
+    "Angola",
+    "Ghana",
+    "Gibraltar",
+    "Greece",
+    "Grenada",
+    "Guam",
+    "Guatemala",
+    "Guernsey",
+    "Guinea",
+    "Guinea-Bissau",
+    "Guayana",
+    "Haiti",
+    "Honduras",
+    "Hong Kong",
+    "Hungary (Republic of)",
+    "Iceland",
+    "India",
+    "Indonesia",
+    "Iran (Islamic Republic of)",
+    "Iraq",
+    "Ireland",
+    "Israel",
+    "Italy",
+    "Cote d'Ivoire",
+    "Jamaica",
+    "Afghanistan",
+    "Jersey",
+    "Jordan",
+    "Kenya",
+    "Kiribati",                                     /* 0x60 */
+    "Korea (Republic of)",
+    "Kuwait",
+    "Lao (People's Democratic Republic)",
+    "Lebanon",
+    "Lesotho",
+    "Liberia",
+    "Libya",
+    "Liechtenstein",
+    "Luxembourg",
+    "Macau",
+    "Madagascar",
+    "Malaysia",
+    "Malawi",
+    "Maldives",
+    "Mali",
+    "Malta",
+    "Mauritania",
+    "Mauritius",
+    "Mexico",
+    "Monaco",
+    "Mongolia",
+    "Montserrat",
+    "Morocco",
+    "Mozambique",
+    "Nauru",
+    "Nepal",
+    "Netherlands",
+    "Netherlands Antilles",
+    "New Caledonia",
+    "New Zealand",
+    "Nicaragua",
+    "Niger",                                        /* 0x80 */
+    "Nigeria",
+    "Norway",
+    "Oman",
+    "Pakistan",
+    "Panama",
+    "Papua New Guinea",
+    "Paraguay",
+    "Peru",
+    "Philippines",
+    "Poland (Republic of)",
+    "Portugal",
+    "Puerto Rico",
+    "Qatar",
+    "Romania",
+    "Rwanda",
+    "Saint Kitts and Nevis",
+    "Saint Croix",
+    "Saint Helena and Ascension",
+    "Saint Lucia",
+    "San Marino",
+    "Saint Thomas",
+    "Sao Tome and Principe",
+    "Saint Vincent and the Grenadines",
+    "Saudi Arabia",
+    "Senegal",
+    "Seychelles",
+    "Sierra Leone",
+    "Singapore",
+    "Solomon Islands",
+    "Somalia",
+    "South Africa",
+    "Spain",                                        /* 0xA0 */
+    "Sri Lanka",
+    "Sudan",
+    "Suriname",
+    "Swaziland",
+    "Sweden",
+    "Switzerland",
+    "Syria",
+    "Tanzania",
+    "Thailand",
+    "Togo",
+    "Tonga",
+    "Trinidad and Tobago",
+    "Tunisia",
+    "Turkey",
+    "Turks and Caicos Islands",
+    "Tuvalu",
+    "Uganda",
+    "Ukraine",
+    "United Arab Emirates",
+    "United Kingdom",
+    "United States",
+    "Burkina Faso",
+    "Uruguay",
+    "U.S.S.R.",
+    "Vanuatu",
+    "Vatican City State",
+    "Venezuela",
+    "Viet Nam",
+    "Wallis and Futuna",
+    "Western Samoa",
+    "Yemen (Republic of)",
+    "Yemen (Republic of)",                          /* 0xC0 */
+    "Yugoslavia",
+    "Zaire",
+    "Zambia",
+    "Zimbabwe"
+    "Slovakia",
+    "Slovenia",
+    "(available)",
+    "(available)",
+    "(available)",
+    "(available)",
+    "(available)",
+    "(available)",
+    "(available)",
+    "(available)",
+    "(available)",
+    "(available)",                                  /* 0xD0 */
+    "(available)",
+    "(available)",
+    "(available)",
+    "(available)",
+    "(available)",
+    "(available)",
+    "(available)",
+    "(available)",
+    "(available)",
+    "(available)",
+    "(available)",
+    "(available)",
+    "(available)",
+    "(available)",
+    "(available)",
+    "(available)",                                  /* 0xE0 */
+    "(available)",
+    "(available)",
+    "(available)",
+    "(available)",
+    "(available)",
+    "(available)",
+    "(available)",
+    "(available)",
+    "(available)",
+    "(available)",
+    "(available)",
+    "(available)",
+    "(available)",
+    "(available)",
+    "(available)",
+    "(available)",                                  /* 0xF0 */
+    "(available)",
+    "(available)",
+    "(available)",
+    "(available)",
+    "(available)",
+    "(available)",
+    "Lithuania",
+    "Latvia",
+    "Estonia",
+    "US Virgin Islands",
+    "(available)",
+    "(available)",
+    "(Universal)",
+    "Taiwan",
+    "(extension)"
+};
+
+static const model_data_t Canon[] =
+{
+    {5, "\x80\x00\x80\x48\x00", "Faxphone B640"},
+    {5, "\x80\x00\x80\x49\x10", "Fax B100"},
+    {5, "\x80\x00\x8A\x49\x10", "Laser Class 9000 Series"},
+    {0, NULL, NULL}
+};  
+
+static const model_data_t Brother[] =
+{
+    {9, "\x55\x55\x00\x88\x90\x80\x5F\x00\x15\x51", "Intellifax 770"},
+    {9, "\x55\x55\x00\x80\xB0\x80\x00\x00\x59\xD4", "Personal fax 190"},
+    {9, "\x55\x55\x00\x8C\x90\x80\xF0\x02\x20", "MFC-8600"},
+    {0, NULL, NULL}
+};
+
+static const model_data_t Panasonic0E[] =
+{
+    {10, "\x00\x00\x00\x96\x0F\x01\x02\x00\x10\x05\x02\x95\xC8\x08\x01\x49\x02\x41\x53\x54\x47", "KX-F90"},
+    {10, "\x00\x00\x00\x96\x0F\x01\x03\x00\x10\x05\x02\x95\xC8\x08\x01\x49\x02                \x03", "KX-F230 or KX-FT21 or ..."},
+    {10, "\x00\x00\x00\x16\x0F\x01\x03\x00\x10\x05\x02\x95\xC8\x08", "KX-F780"},
+    {10, "\x00\x00\x00\x16\x0F\x01\x03\x00\x10\x00\x02\x95\x80\x08\x75\xB5", "KX-M260"},
+    {10, "\x00\x00\x00\x16\x0F\x01\x02\x00\x10\x05\x02\x85\xC8\x08\xAD", "KX-F2050BS"},
+    {0, NULL, NULL}
+};
+
+static const model_data_t Panasonic79[] =
+{
+    {10, "\x00\x00\x00\x02\x0F\x09\x12\x00\x10\x05\x02\x95\xC8\x88\x80\x80\x01", "UF-S10"},
+    {10, "\x00\x00\x00\x16\x7F\x09\x13\x00\x10\x05\x16\x8D\xC0\xD0\xF8\x80\x01", "/Siemens Fax 940"},
+    {10, "\x00\x00\x00\x16\x0F\x09\x13\x00\x10\x05\x06\x8D\xC0\x50\xCB", "Panafax UF-321"},
+    {0, NULL, NULL}
+};
+
+static const model_data_t Ricoh[] =
+{
+    {10, "\x00\x00\x00\x12\x10\x0D\x02\x00\x50\x00\x2A\xB8\x2C", "/Nashuatec P394"},
+    {0, NULL, NULL}
+};
+
+static const model_data_t Samsung16[] =
+{
+    {4, "\x00\x00\xA4\x01", "M545 6800"},
+    {0, NULL, NULL}
+};
+
+static const model_data_t Samsung5A[] =
+{
+    {4, "\x00\x00\xC0\x00", "SF-5100"},
+    {0, NULL, NULL}
+};
+
+static const model_data_t Samsung8C[] =
+{
+    {4, "\x00\x00\x01\x00", "SF-2010"},
+    {0, NULL, NULL}
+};
+
+static const model_data_t SamsungA2[] =
+{
+    {4, "\x00\x00\x80\x00", "FX-4000"},
+    {0, NULL, NULL}
+};
+
+static const model_data_t Sanyo[] =
+{
+    {10, "\x00\x00\x10\xB1\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x41\x26\xFF\xFF\x00\x00\x85\xA1", "SFX-107"},
+    {10, "\x00\x00\x00\xB1\x12\xF2\x62\xB4\x82\x0A\xF2\x2A\x12\xD2\xA2\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x41\x4E\xFF\xFF\x00\x00", "MFP-510"},
+    {0, NULL, NULL}
+};
+
+static const model_data_t HP[] =
+{
+    {5, "\x20\x00\x45\x00\x0C\x04\x70\xCD\x4F\x00\x7F\x49", "LaserJet 3150"},
+    {5, "\x40\x80\x84\x01\xF0\x6A", "OfficeJet"},
+    {5, "\xC0\x00\x00\x00\x00", "OfficeJet 500"},
+    {5, "\xC0\x00\x00\x00\x00\x8B", "Fax-920"},
+    {0, NULL, NULL}
+};
+
+static const model_data_t Sharp[] =
+{
+    {32, "\x00\xCE\xB8\x80\x80\x11\x85\x0D\xDD\x00\x00\xDD\xDD\x00\x00\xDD\xDD\x00\x00\x00\x00\x00\x00\x00\x00\xED\x22\xB0\x00\x00\x90\x00", "Sharp F0-10"},
+    {33, "\x00\xCE\xB8\x80\x80\x11\x85\x0D\xDD\x00\x00\xDD\xDD\x00\x00\xDD\xDD\x00\x00\x00\x00\x00\x00\x00\x00\xED\x22\xB0\x00\x00\x90\x00\x8C", "Sharp UX-460"},
+    {33, "\x00\x4E\xB8\x80\x80\x11\x84\x0D\xDD\x00\x00\xDD\xDD\x00\x00\xDD\xDD\x00\x00\x00\x00\x00\x00\x00\x00\xED\x22\xB0\x00\x00\x90\x00\xAD", "Sharp UX-177"},
+    {33, "\x00\xCE\xB8\x00\x84\x0D\xDD\x00\x00\xDD\xDD\x00\x00\xDD\xDD\xDD\xDD\xDD\x02\x05\x28\x02\x22\x43\x29\xED\x23\x90\x00\x00\x90\x01\x00", "Sharp FO-4810"},
+    {0, NULL, NULL}
+};
+
+static const model_data_t Xerox[] =
+{
+    {10, "\x00\x08\x2D\x43\x57\x50\x61\x75\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x01\x1A\x02\x02\x10\x01\x82\x01\x30\x34", "635 Workcenter"},
+    {0, NULL, NULL}
+};
+
+static const model_data_t XeroxDA[] =
+{
+    {4, "\x00\x00\xC0\x00", "Workcentre Pro 580"},
+    {0, NULL, NULL}
+};
+
+static const model_data_t Lexmark[] =
+{
+    {4, "\x00\x80\xA0\x00", "X4270"},
+    {0, NULL, NULL}
+};
+
+static const model_data_t JetFax[] =
+{
+    {6, "\x01\x00\x45\x00\x0D\x7F", "M910e"},
+    {0, NULL, NULL}
+};
+
+static const model_data_t PitneyBowes[] = 
+{
+    {6, "\x79\x91\xB1\xB8\x7A\xD8", "9550"},
+    {0, NULL, NULL}
+};
+
+static const model_data_t Dialogic[] = 
+{
+    {8, "\x56\x8B\x06\x55\x00\x15\x00\x00", "VFX/40ESC"},
+    {0, NULL, NULL}
+};
+
+static const model_data_t Muratec45[] =
+{
+    {10, "\xF4\x91\xFF\xFF\xFF\x42\x2A\xBC\x01\x57", "M4700"},
+    {0, NULL, NULL}
+};
+
+/* Muratec uses unregistered Japan code "00 00 48" */
+static const model_data_t Muratec48[] =
+{
+    {3, "\x53\x53\x61", "M620"},
+    {0, NULL, NULL}
+};
+
+/*
+ * Country code first byte, then manufacturer is last two bytes. See T.35.
+ * Apparently Germany issued some manufacturer codes before the two-byte
+ * standard was accepted, and so some few German manufacturers are
+ * identified by a single manufacturer byte.
+ *
+ * T.30 5.3.6.2.7 (2003) states that the NSF FIF is transmitted
+ * in MSB2LSB order.  Revisions of T.30 prior to 2003 did not 
+ * contain explicit specification as to the transmit bit order.
+ * (Although it did otherwise state that all HDLC frame data should
+ * be in MSB order except as noted.)  Because CSI, TSI, and other
+ * prologue frames were in LSB order by way of an exception to the
+ * general rule (T.30 5.3.6.2.4-11) many manufacturers assumed that
+ * NSF should also be in LSB order.  Consequently there will be
+ * some country-code "masquerading" as a terminal may use the
+ * proper country-code, but with an inverted bit order.
+ *
+ * Thus, country code x61 (Korea) turns into x86 (Papua New Guinea),
+ * code xB5 (USA) turns into xAD (Tunisia), code x26 (China) turns
+ * into x64 (Lebanon), code x04 (Germany) turns into x20 (Canada), 
+ * and code x3D (France) turns into xBC (Vietnam).
+ *
+ * For the most part it should be safe to identify a manufacturer
+ * both with the MSB and LSB ordered bits, as the "masqueraded" country
+ * is likely to not be actively assigning T.38 manufacturer codes.
+ * However, some manufacturers (e.g. Microsoft) may use MSB for the
+ * country code and LSB for the rest of the NSF, and so basically this
+ * table must be verified and corrected against actual real-world
+ * results.
+ */
+static const nsf_data_t known_nsf[] =
+{
+    /* Japan */
+    {"\x00\x00\x00", 3, "Unknown - indeterminate", TRUE, NULL},
+    {"\x00\x00\x01", 3, "Anjitsu", FALSE, NULL},
+    {"\x00\x00\x02", 3, "Nippon Telephone", FALSE, NULL},
+    {"\x00\x00\x05", 3, "Mitsuba Electric", FALSE, NULL},
+    {"\x00\x00\x06", 3, "Master Net", FALSE, NULL},
+    {"\x00\x00\x09", 3, "Xerox/Toshiba", TRUE, Xerox},
+    {"\x00\x00\x0A", 3, "Kokusai", FALSE, NULL},
+    {"\x00\x00\x0D", 3, "Logic System International", FALSE, NULL},
+    {"\x00\x00\x0E", 3, "Panasonic", FALSE, Panasonic0E},
+    {"\x00\x00\x11", 3, "Canon", FALSE, Canon},
+    {"\x00\x00\x15", 3, "Toyotsushen Machinery", FALSE, NULL},
+    {"\x00\x00\x16", 3, "System House Mind", FALSE, NULL},
+    {"\x00\x00\x19", 3, "Xerox", TRUE, NULL},
+    {"\x00\x00\x1D", 3, "Hitachi Software", FALSE, NULL},
+    {"\x00\x00\x21", 3, "OKI Electric/Lanier", TRUE, NULL},
+    {"\x00\x00\x25", 3, "Ricoh", TRUE, Ricoh},
+    {"\x00\x00\x26", 3, "Konica", FALSE, NULL},
+    {"\x00\x00\x29", 3, "Japan Wireless", FALSE, NULL},
+    {"\x00\x00\x2D", 3, "Sony", FALSE, NULL},
+    {"\x00\x00\x31", 3, "Sharp/Olivetti", FALSE, Sharp},
+    {"\x00\x00\x35", 3, "Kogyu", FALSE, NULL},
+    {"\x00\x00\x36", 3, "Japan Telecom", FALSE, NULL},
+    {"\x00\x00\x3D", 3, "IBM Japan", FALSE, NULL},
+    {"\x00\x00\x39", 3, "Panasonic", FALSE, NULL},
+    {"\x00\x00\x41", 3, "Swasaki Communication", FALSE, NULL},
+    {"\x00\x00\x45", 3, "Muratec", FALSE, Muratec45},
+    {"\x00\x00\x46", 3, "Pheonix", FALSE, NULL},
+    {"\x00\x00\x48", 3, "Muratec", FALSE, Muratec48},  // not registered
+    {"\x00\x00\x49", 3, "Japan Electric", FALSE, NULL},
+    {"\x00\x00\x4D", 3, "Okura Electric", FALSE, NULL},
+    {"\x00\x00\x51", 3, "Sanyo", FALSE, Sanyo},
+    {"\x00\x00\x55", 3, "unknown - Japan 55", FALSE, NULL},
+    {"\x00\x00\x56", 3, "Brother", FALSE, Brother},
+    {"\x00\x00\x59", 3, "Fujitsu", FALSE, NULL},
+    {"\x00\x00\x5D", 3, "Kuoni", FALSE, NULL},
+    {"\x00\x00\x61", 3, "Casio", FALSE, NULL},
+    {"\x00\x00\x65", 3, "Tateishi Electric", FALSE, NULL},
+    {"\x00\x00\x66", 3, "Utax/Mita", TRUE, NULL},
+    {"\x00\x00\x69", 3, "Hitachi Production", FALSE, NULL},
+    {"\x00\x00\x6D", 3, "Hitachi Telecom", FALSE, NULL},
+    {"\x00\x00\x71", 3, "Tamura Electric Works", FALSE, NULL},
+    {"\x00\x00\x75", 3, "Tokyo Electric Corp.", FALSE, NULL},
+    {"\x00\x00\x76", 3, "Advance", FALSE, NULL},
+    {"\x00\x00\x79", 3, "Panasonic", FALSE, Panasonic79},
+    {"\x00\x00\x7D", 3, "Seiko", FALSE, NULL},
+    {"\x00\x08\x00", 3, "Daiko", FALSE, NULL},
+    {"\x00\x10\x00", 3, "Funai Electric", FALSE, NULL},
+    {"\x00\x20\x00", 3, "Eagle System", FALSE, NULL},
+    {"\x00\x30\x00", 3, "Nippon Business Systems", FALSE, NULL},
+    {"\x00\x40\x00", 3, "Comtron", FALSE, NULL},
+    {"\x00\x48\x00", 3, "Cosmo Consulting", FALSE, NULL},
+    {"\x00\x50\x00", 3, "Orion Electric", FALSE, NULL},
+    {"\x00\x60\x00", 3, "Nagano Nippon", FALSE, NULL},
+    {"\x00\x70\x00", 3, "Kyocera", FALSE, NULL},
+    {"\x00\x80\x00", 3, "Kanda Networks", FALSE, NULL},
+    {"\x00\x88\x00", 3, "Soft Front", FALSE, NULL},
+    {"\x00\x90\x00", 3, "Arctic", FALSE, NULL},
+    {"\x00\xA0\x00", 3, "Nakushima", FALSE, NULL},
+    {"\x00\xB0\x00", 3, "Minolta", FALSE, NULL},
+    {"\x00\xC0\x00", 3, "Tohoku Pioneer", FALSE, NULL},
+    {"\x00\xD0\x00", 3, "USC", FALSE, NULL},
+    {"\x00\xE0\x00", 3, "Hiboshi", FALSE, NULL},
+    {"\x00\xF0\x00", 3, "Sumitomo Electric", FALSE, NULL},
+
+    /* Germany */
+    {"\x20\x09",     2, "ITK Institut für Telekommunikation GmbH & Co KG", FALSE, NULL},
+    {"\x20\x11",     2, "Dr. Neuhaus Mikroelektronik", FALSE, NULL},
+    {"\x20\x21",     2, "ITO Communication", FALSE, NULL},
+    {"\x20\x31",     2, "mbp Kommunikationssysteme GmbH", FALSE, NULL},
+    {"\x20\x41",     2, "Siemens", FALSE, NULL},
+    {"\x20\x42",     2, "Deutsche Telekom AG", FALSE, NULL},
+    {"\x20\x51",     2, "mps Software", FALSE, NULL},
+    {"\x20\x61",     2, "Hauni Elektronik", FALSE, NULL},
+    {"\x20\x71",     2, "Digitronic computersysteme gmbh", FALSE, NULL},
+    {"\x20\x81\x00", 3, "Innovaphone GmbH", FALSE, NULL},
+    {"\x20\x81\x40", 3, "TEDAS Gesellschaft für Telekommunikations-, Daten- und Audiosysteme mbH", FALSE, NULL},
+    {"\x20\x81\x80", 3, "AVM Audiovisuelles Marketing und Computersysteme GmbH", FALSE, NULL},
+    {"\x20\x81\xC0", 3, "EICON Technology Research GmbH", FALSE, NULL},
+    {"\x20\xB1",     2, "Schneider Rundfunkwerke AG", FALSE, NULL},
+    {"\x20\xC2",     2, "Deutsche Telekom AG", FALSE, NULL},
+    {"\x20\xD1",     2, "Ferrari electronik GmbH", FALSE, NULL},
+    {"\x20\xF1",     2, "DeTeWe - Deutsche Telephonwerke AG & Co", FALSE, NULL},
+    {"\x20\xFF",     2, "Germany Regional Code", FALSE, NULL},
+
+    /* China */
+    {"\x64\x00\x00", 3, "unknown - China 00 00", FALSE, NULL},
+    {"\x64\x01\x00", 3, "unknown - China 01 00", FALSE, NULL},
+    {"\x64\x01\x01", 3, "unknown - China 01 01", FALSE, NULL},
+    {"\x64\x01\x02", 3, "unknown - China 01 02", FALSE, NULL},
+
+    /* France */
+    {"\xBC\x53\x01", 3, "Minolta", FALSE, NULL},
+
+    /* Korea */
+    {"\x86\x00\x02", 3, "unknown - Korea 02", FALSE, NULL},
+    {"\x86\x00\x06", 3, "unknown - Korea 06", FALSE, NULL},
+    {"\x86\x00\x08", 3, "unknown - Korea 08", FALSE, NULL},
+    {"\x86\x00\x0A", 3, "unknown - Korea 0A", FALSE, NULL},
+    {"\x86\x00\x0E", 3, "unknown - Korea 0E", FALSE, NULL},
+    {"\x86\x00\x10", 3, "Samsung", FALSE, NULL},
+    {"\x86\x00\x11", 3, "unknown - Korea 11", FALSE, NULL},
+    {"\x86\x00\x16", 3, "Samsung", FALSE, Samsung16},
+    {"\x86\x00\x1A", 3, "unknown - Korea 1A", FALSE, NULL},
+    {"\x86\x00\x40", 3, "unknown - Korea 40", FALSE, NULL},
+    {"\x86\x00\x48", 3, "unknown - Korea 48", FALSE, NULL},
+    {"\x86\x00\x52", 3, "unknown - Korea 52", FALSE, NULL},
+    {"\x86\x00\x5A", 3, "Samsung", FALSE, Samsung5A},
+    {"\x86\x00\x5E", 3, "unknown - Korea 5E", FALSE, NULL},
+    {"\x86\x00\x66", 3, "unknown - Korea 66", FALSE, NULL},
+    {"\x86\x00\x6E", 3, "unknown - Korea 6E", FALSE, NULL},
+    {"\x86\x00\x82", 3, "unknown - Korea 82", FALSE, NULL},
+    {"\x86\x00\x88", 3, "unknown - Korea 88", FALSE, NULL},
+    {"\x86\x00\x8A", 3, "unknown - Korea 8A", FALSE, NULL},
+    {"\x86\x00\x8C", 3, "Samsung", FALSE, Samsung8C},
+    {"\x86\x00\x92", 3, "unknown - Korea 92", FALSE, NULL},
+    {"\x86\x00\x98", 3, "Samsung", FALSE, NULL},
+    {"\x86\x00\xA2", 3, "Samsung", FALSE, SamsungA2},
+    {"\x86\x00\xA4", 3, "unknown - Korea A4", FALSE, NULL},
+    {"\x86\x00\xC2", 3, "Samsung", FALSE, NULL},
+    {"\x86\x00\xC9", 3, "unknown - Korea C9", FALSE, NULL},
+    {"\x86\x00\xCC", 3, "unknown - Korea CC", FALSE, NULL},
+    {"\x86\x00\xD2", 3, "unknown - Korea D2", FALSE, NULL},
+    {"\x86\x00\xDA", 3, "Xerox", FALSE, XeroxDA},
+    {"\x86\x00\xE2", 3, "unknown - Korea E2", FALSE, NULL},
+    {"\x86\x00\xEC", 3, "unknown - Korea EC", FALSE, NULL},
+    {"\x86\x00\xEE", 3, "unknown - Korea EE", FALSE, NULL},
+
+    /* United Kingdom */
+    {"\xB4\x00\xB0", 3, "DCE", FALSE, NULL},
+    {"\xB4\x00\xB1", 3, "Hasler", FALSE, NULL},
+    {"\xB4\x00\xB2", 3, "Interquad", FALSE, NULL},
+    {"\xB4\x00\xB3", 3, "Comwave", FALSE, NULL},
+    {"\xB4\x00\xB4", 3, "Iconographic", FALSE, NULL},
+    {"\xB4\x00\xB5", 3, "Wordcraft", FALSE, NULL},
+    {"\xB4\x00\xB6", 3, "Acorn", FALSE, NULL},
+
+    /* United States */
+    {"\xAD\x00\x00", 3, "Pitney Bowes", FALSE, PitneyBowes},
+    {"\xAD\x00\x0C", 3, "Dialogic", FALSE, NULL},
+    {"\xAD\x00\x15", 3, "Lexmark", FALSE, Lexmark},
+    {"\xAD\x00\x16", 3, "JetFax", FALSE, JetFax},
+    {"\xAD\x00\x24", 3, "Octel", FALSE, NULL},
+    {"\xAD\x00\x36", 3, "HP", FALSE, HP},
+    {"\xAD\x00\x42", 3, "FaxTalk", FALSE, NULL},
+    {"\xAD\x00\x44", 3, NULL, TRUE, NULL},
+    {"\xAD\x00\x46", 3, "BrookTrout", FALSE, NULL},
+    {"\xAD\x00\x51", 3, "Telogy Networks", FALSE, NULL},
+    {"\xAD\x00\x55", 3, "HylaFAX", FALSE, NULL},
+    {"\xAD\x00\x5C", 3, "IBM", FALSE, NULL},
+    {"\xAD\x00\x98", 3, "unknown - USA 98", TRUE, NULL},
+    {"\xB5\x00\x01", 3, "Picturetel", FALSE, NULL},
+    {"\xB5\x00\x20", 3, "Conexant", FALSE, NULL},
+    {"\xB5\x00\x22", 3, "Comsat", FALSE, NULL},
+    {"\xB5\x00\x24", 3, "Octel", FALSE, NULL},
+    {"\xB5\x00\x26", 3, "ROLM", FALSE, NULL},
+    {"\xB5\x00\x28", 3, "SOFNET", FALSE, NULL},
+    {"\xB5\x00\x29", 3, "TIA TR-29 Committee", FALSE, NULL},
+    {"\xB5\x00\x2A", 3, "STF Tech", FALSE, NULL},
+    {"\xB5\x00\x2C", 3, "HKB", FALSE, NULL},
+    {"\xB5\x00\x2E", 3, "Delrina", FALSE, NULL},
+    {"\xB5\x00\x30", 3, "Dialogic", FALSE, NULL},
+    {"\xB5\x00\x32", 3, "Applied Synergy", FALSE, NULL},
+    {"\xB5\x00\x34", 3, "Syncro Development", FALSE, NULL},
+    {"\xB5\x00\x36", 3, "Genoa", FALSE, NULL},
+    {"\xB5\x00\x38", 3, "Texas Instruments", FALSE, NULL},
+    {"\xB5\x00\x3A", 3, "IBM", FALSE, NULL},
+    {"\xB5\x00\x3C", 3, "ViaSat", FALSE, NULL},
+    {"\xB5\x00\x3E", 3, "Ericsson", FALSE, NULL},
+    {"\xB5\x00\x42", 3, "Bogosian", FALSE, NULL},
+    {"\xB5\x00\x44", 3, "Adobe", FALSE, NULL},
+    {"\xB5\x00\x46", 3, "Fremont Communications", FALSE, NULL},
+    {"\xB5\x00\x48", 3, "Hayes", FALSE, NULL},
+    {"\xB5\x00\x4A", 3, "Lucent", FALSE, NULL},
+    {"\xB5\x00\x4C", 3, "Data Race", FALSE, NULL},
+    {"\xB5\x00\x4E", 3, "TRW", FALSE, NULL},
+    {"\xB5\x00\x52", 3, "Audiofax", FALSE, NULL},
+    {"\xB5\x00\x54", 3, "Computer Automation", FALSE, NULL},
+    {"\xB5\x00\x56", 3, "Serca", FALSE, NULL},
+    {"\xB5\x00\x58", 3, "Octocom", FALSE, NULL},
+    {"\xB5\x00\x5C", 3, "Power Solutions", FALSE, NULL},
+    {"\xB5\x00\x5A", 3, "Digital Sound", FALSE, NULL},
+    {"\xB5\x00\x5E", 3, "Pacific Data", FALSE, NULL},
+    {"\xB5\x00\x60", 3, "Commetrex", FALSE, NULL},
+    {"\xB5\x00\x62", 3, "BrookTrout", FALSE, NULL},
+    {"\xB5\x00\x64", 3, "Gammalink", FALSE, NULL},
+    {"\xB5\x00\x66", 3, "Castelle", FALSE, NULL},
+    {"\xB5\x00\x68", 3, "Hybrid Fax", FALSE, NULL},
+    {"\xB5\x00\x6A", 3, "Omnifax", FALSE, NULL},
+    {"\xB5\x00\x6C", 3, "HP", FALSE, NULL},
+    {"\xB5\x00\x6E", 3, "Microsoft", FALSE, NULL},
+    {"\xB5\x00\x72", 3, "Speaking Devices", FALSE, NULL},
+    {"\xB5\x00\x74", 3, "Compaq", FALSE, NULL},
+/*
+    {"\xB5\x00\x76", 3, "Trust - Cryptek", FALSE, NULL},       // collision with Microsoft
+*/
+    {"\xB5\x00\x76", 3, "Microsoft", FALSE, NULL},             // uses LSB for country but MSB for manufacturer
+    {"\xB5\x00\x78", 3, "Cylink", FALSE, NULL},
+    {"\xB5\x00\x7A", 3, "Pitney Bowes", FALSE, NULL},
+    {"\xB5\x00\x7C", 3, "Digiboard", FALSE, NULL},
+    {"\xB5\x00\x7E", 3, "Codex", FALSE, NULL},
+    {"\xB5\x00\x82", 3, "Wang Labs", FALSE, NULL},
+    {"\xB5\x00\x84", 3, "Netexpress Communications", FALSE, NULL},
+    {"\xB5\x00\x86", 3, "Cable-Sat", FALSE, NULL},
+    {"\xB5\x00\x88", 3, "MFPA", FALSE, NULL},
+    {"\xB5\x00\x8A", 3, "Telogy Networks", FALSE, NULL},
+    {"\xB5\x00\x8E", 3, "Telecom Multimedia Systems", FALSE, NULL},
+    {"\xB5\x00\x8C", 3, "AT&T", FALSE, NULL},
+    {"\xB5\x00\x92", 3, "Nuera", FALSE, NULL},
+    {"\xB5\x00\x94", 3, "K56flex", FALSE, NULL},
+    {"\xB5\x00\x96", 3, "MiBridge", FALSE, NULL},
+    {"\xB5\x00\x98", 3, "Xerox", FALSE, NULL},
+    {"\xB5\x00\x9A", 3, "Fujitsu", FALSE, NULL},
+    {"\xB5\x00\x9B", 3, "Fujitsu", FALSE, NULL},
+    {"\xB5\x00\x9C", 3, "Natural Microsystems", FALSE, NULL},
+    {"\xB5\x00\x9E", 3, "CopyTele", FALSE, NULL},
+    {"\xB5\x00\xA2", 3, "Murata", FALSE, NULL},
+    {"\xB5\x00\xA4", 3, "Lanier", FALSE, NULL},
+    {"\xB5\x00\xA6", 3, "Qualcomm", FALSE, NULL},
+    {"\xB5\x00\xAA", 3, "HylaFAX", FALSE, NULL},               // we did it backwards for a while
+    {NULL, 0, NULL, FALSE, NULL}
+};
+
+#if 0
+SPAN_DECLARE(void) nsf_find_station_id(int reverse_order)
+{
+    const char *id = NULL;
+    int idSize = 0;
+    const char *maxId = NULL;
+    int maxIdSize = 0;
+    const char *p;
+
+    /* Trying to find the longest printable ASCII sequence */
+    for (p = (const char *) nsf + T35_VENDOR_ID_LEN, *end = p + nsf.length();
+         p < end;
+         p++)
+    {
+        if (isprint(*p))
+        {
+            if (!idSize++)
+                id = p;
+            if (idSize > maxIdSize)
+            {
+                max_id = id;
+                max_id_size = idSize;
+            }
+        }
+        else
+        {
+            id = NULL;
+            id_size = 0;
+        }
+    }
+    
+    /* Minimum acceptable id length */
+    const int MinIdSize = 4;
+
+    if (maxIdSize >= min_id_size)
+    {
+        stationId.resize(0);
+        const char *p;
+        int dir;
+
+        if (reverseOrder)
+        {
+            p = maxId + maxIdSize - 1;
+            dir = -1;
+        }
+        else
+        {
+            p = maxId;
+            dir = 1;
+        }
+        for (int i = 0;  i < maxIdSize;  i++)
+        {
+            stationId.append(*p);
+            p += dir;
+        }
+        station_id_decoded = TRUE;
+    }
+}
+/*- End of function --------------------------------------------------------*/
+#endif
+
+SPAN_DECLARE(int) t35_decode(const uint8_t *msg, int len, const char **country, const char **vendor, const char **model)
+{
+    int vendor_decoded;
+    const nsf_data_t *p;
+    const model_data_t *pp;
+
+    vendor_decoded = FALSE;
+    if (country)
+        *country = NULL;
+    if (vendor)
+        *vendor = NULL;
+    if (model)
+        *model = NULL;
+    if (country)
+    {
+        /* We need to apply realism over accuracy, though it blocks out some countries.
+           It is very rare to find a machine from any country but the following:
+        
+           Japan 0x00 (no confusion)
+           Germany 0x04 (0x20) (Canada/Germany confusion)
+           China 0x26 (0x64) (China/Lebanon confusion)
+           Korea 0x61 (0x86) (Korea/Papua New Guinea confusion)
+           UK 0xB4 (0x2D) (UK/Cyprus confusion)
+           USA 0xB5 (0xAD) (USA/Tunisia confusion)
+           France 0x3D (0xBC) (France/Vietnam confusion)
+
+           If we force the most likely of the two possible countries (forward or bit reversed),
+           the only mixup with any realistic probability is the Canada/Germany confusion. We
+           will just live with this, and force the more likely countries. */
+        switch (msg[0])
+        {
+        case 0x20:
+            /* Force Germany */
+            *country = t35_country_codes[0x04];
+            break;
+        case 0x64:
+            /* Force China */
+            *country = t35_country_codes[0x26];
+            break;
+        case 0x86:
+            /* Force Korea */
+            *country = t35_country_codes[0x61];
+            break;
+        case 0x2D:
+            /* Force UK */
+            *country = t35_country_codes[0xB4];
+            break;
+        case 0xAD:
+            /* Force USA */
+            *country = t35_country_codes[0xB5];
+            break;
+        case 0xBC:
+            /* Force France */
+            *country = t35_country_codes[0x3D];
+            break;
+        default:
+            /* Try the country code at face value, then bit reversed */
+            if (t35_country_codes[msg[0]])
+                *country = t35_country_codes[msg[0]];
+            else if (t35_country_codes[bit_reverse8(msg[0])])
+                *country = t35_country_codes[bit_reverse8(msg[0])];
+            break;
+        }
+    }
+    for (p = known_nsf;  p->vendor_id;  p++)
+    {
+        if (len >= p->vendor_id_len
+            &&
+            memcmp(p->vendor_id, msg, p->vendor_id_len) == 0)
+        {
+            if (p->vendor_name  &&  vendor)
+                *vendor = p->vendor_name;
+            if (p->known_models  &&  model)
+            {
+                for (pp = p->known_models;  pp->model_id;  pp++)
+                {
+                    if (len == p->vendor_id_len + pp->model_id_size
+                        &&
+                        memcmp(pp->model_id, &msg[p->vendor_id_len], pp->model_id_size) == 0)
+                    {
+                        *model = pp->model_name;
+                        break;
+                    }
+                }
+            }
+#if 0
+            findStationId(p->inverse_station_id_order);
+#endif
+            vendor_decoded = TRUE;
+            break;
+        }
+    }
+#if 0
+    if (!vendor_found())
+        find_station_id(0);
+#endif
+    return vendor_decoded;
+}
+/*- End of function --------------------------------------------------------*/
+/*- End of file ------------------------------------------------------------*/
diff --git a/src/codec/spandsp/src/t38_core.c b/src/codec/spandsp/src/t38_core.c
new file mode 100644 (file)
index 0000000..e1518ee
--- /dev/null
@@ -0,0 +1,1029 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * t38_core.c - Encode and decode the ASN.1 of a T.38 IFP message
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2005, 2006 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.
+ */
+
+/*! \file */
+
+#if defined(HAVE_CONFIG_H)
+#include "config.h"
+#endif
+
+#include <inttypes.h>
+#include <stdlib.h>
+//TODO// #include <stdio.h>
+//TODO// #include <fcntl.h>
+#include <time.h>
+#include <string.h>
+#if defined(HAVE_TGMATH_H)
+#include <tgmath.h>
+#endif
+#if defined(HAVE_MATH_H)
+#include <math.h>
+#endif
+#include "floating_fudge.h"
+#include <assert.h>
+#include <memory.h>
+// #include <tiffio.h>
+
+#include "spandsp/0cpm.h"
+
+#include "spandsp/telephony.h"
+#include "spandsp/logging.h"
+#include "spandsp/bit_operations.h"
+#include "spandsp/t38_core.h"
+
+#include "spandsp/private/logging.h"
+#include "spandsp/private/t38_core.h"
+
+#define ACCEPTABLE_SEQ_NO_OFFSET    2000
+
+/* The times for training, the optional TEP, and the HDLC preamble, for all the modem options, in ms.
+   Note that the preamble for V.21 is 1s+-15%, and for the other modems is 200ms+100ms. */
+static const struct
+{
+    int tep;
+    int training;
+    int flags;
+} modem_startup_time[] =
+{
+    {      0,   75000,       0},    /* T38_IND_NO_SIGNAL */
+    {      0,       0,       0},    /* T38_IND_CNG */
+    {      0, 3000000,       0},    /* T38_IND_CED */
+    {      0,       0, 1000000},    /* T38_IND_V21_PREAMBLE */ /* TODO: 850ms should be OK for this, but it causes trouble with some ATAs. Why? */
+    { 215000,  943000,  200000},    /* T38_IND_V27TER_2400_TRAINING */
+    { 215000,  708000,  200000},    /* T38_IND_V27TER_4800_TRAINING */
+    { 215000,  234000,  200000},    /* T38_IND_V29_7200_TRAINING */
+    { 215000,  234000,  200000},    /* T38_IND_V29_9600_TRAINING */
+    { 215000,  142000,  200000},    /* T38_IND_V17_7200_SHORT_TRAINING */
+    { 215000, 1393000,  200000},    /* T38_IND_V17_7200_LONG_TRAINING */
+    { 215000,  142000,  200000},    /* T38_IND_V17_9600_SHORT_TRAINING */
+    { 215000, 1393000,  200000},    /* T38_IND_V17_9600_LONG_TRAINING */
+    { 215000,  142000,  200000},    /* T38_IND_V17_12000_SHORT_TRAINING */
+    { 215000, 1393000,  200000},    /* T38_IND_V17_12000_LONG_TRAINING */
+    { 215000,  142000,  200000},    /* T38_IND_V17_14400_SHORT_TRAINING */
+    { 215000, 1393000,  200000},    /* T38_IND_V17_14400_LONG_TRAINING */
+    { 215000,       0,       0},    /* T38_IND_V8_ANSAM */
+    { 215000,       0,       0},    /* T38_IND_V8_SIGNAL */
+    { 215000,       0,       0},    /* T38_IND_V34_CNTL_CHANNEL_1200 */
+    { 215000,       0,       0},    /* T38_IND_V34_PRI_CHANNEL */
+    { 215000,       0,       0},    /* T38_IND_V34_CC_RETRAIN */
+    { 215000,       0,       0},    /* T38_IND_V33_12000_TRAINING */
+    { 215000,       0,       0}     /* T38_IND_V33_14400_TRAINING */
+};
+
+SPAN_DECLARE(const char *) t38_indicator_to_str(int indicator)
+{
+    switch (indicator)
+    {
+    case T38_IND_NO_SIGNAL:
+        return "no-signal";
+    case T38_IND_CNG:
+        return "cng";
+    case T38_IND_CED:
+        return "ced";
+    case T38_IND_V21_PREAMBLE:
+        return "v21-preamble";
+    case T38_IND_V27TER_2400_TRAINING:
+        return "v27-2400-training";
+    case T38_IND_V27TER_4800_TRAINING:
+        return "v27-4800-training";
+    case T38_IND_V29_7200_TRAINING:
+        return "v29-7200-training";
+    case T38_IND_V29_9600_TRAINING:
+        return "v29-9600-training";
+    case T38_IND_V17_7200_SHORT_TRAINING:
+        return "v17-7200-short-training";
+    case T38_IND_V17_7200_LONG_TRAINING:
+        return "v17-7200-long-training";
+    case T38_IND_V17_9600_SHORT_TRAINING:
+        return "v17-9600-short-training";
+    case T38_IND_V17_9600_LONG_TRAINING:
+        return "v17-9600-long-training";
+    case T38_IND_V17_12000_SHORT_TRAINING:
+        return "v17-12000-short-training";
+    case T38_IND_V17_12000_LONG_TRAINING:
+        return "v17-12000-long-training";
+    case T38_IND_V17_14400_SHORT_TRAINING:
+        return "v17-14400-short-training";
+    case T38_IND_V17_14400_LONG_TRAINING:
+        return "v17-14400-long-training";
+    case T38_IND_V8_ANSAM:
+        return "v8-ansam";
+    case T38_IND_V8_SIGNAL:
+        return "v8-signal";
+    case T38_IND_V34_CNTL_CHANNEL_1200:
+        return "v34-cntl-channel-1200";
+    case T38_IND_V34_PRI_CHANNEL:
+        return "v34-pri-channel";
+    case T38_IND_V34_CC_RETRAIN:
+        return "v34-CC-retrain";
+    case T38_IND_V33_12000_TRAINING:
+        return "v33-12000-training";
+    case T38_IND_V33_14400_TRAINING:
+        return "v33-14400-training";
+    }
+    return "???";
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(const char *) t38_data_type_to_str(int data_type)
+{
+    switch (data_type)
+    {
+    case T38_DATA_V21:
+        return "v21";
+    case T38_DATA_V27TER_2400:
+        return "v27-2400";
+    case T38_DATA_V27TER_4800:
+        return "v27-4800";
+    case T38_DATA_V29_7200:
+        return "v29-7200";
+    case T38_DATA_V29_9600:
+        return "v29-9600";
+    case T38_DATA_V17_7200:
+        return "v17-7200";
+    case T38_DATA_V17_9600:
+        return "v17-9600";
+    case T38_DATA_V17_12000:
+        return "v17-12000";
+    case T38_DATA_V17_14400:
+        return "v17-14400";
+    case T38_DATA_V8:
+        return "v8";
+    case T38_DATA_V34_PRI_RATE:
+        return "v34-pri-rate";
+    case T38_DATA_V34_CC_1200:
+        return "v34-CC-1200";
+    case T38_DATA_V34_PRI_CH:
+        return "v34-pri-ch";
+    case T38_DATA_V33_12000:
+        return "v33-12000";
+    case T38_DATA_V33_14400:
+        return "v33-14400";
+    }
+    return "???";
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(const char *) t38_field_type_to_str(int field_type)
+{
+    switch (field_type)
+    {
+    case T38_FIELD_HDLC_DATA:
+        return "hdlc-data";
+    case T38_FIELD_HDLC_SIG_END:
+        return "hdlc-sig-end";
+    case T38_FIELD_HDLC_FCS_OK:
+        return "hdlc-fcs-OK";
+    case T38_FIELD_HDLC_FCS_BAD:
+        return "hdlc-fcs-BAD";
+    case T38_FIELD_HDLC_FCS_OK_SIG_END:
+        return "hdlc-fcs-OK-sig-end";
+    case T38_FIELD_HDLC_FCS_BAD_SIG_END:
+        return "hdlc-fcs-BAD-sig-end";
+    case T38_FIELD_T4_NON_ECM_DATA:
+        return "t4-non-ecm-data";
+    case T38_FIELD_T4_NON_ECM_SIG_END:
+        return "t4-non-ecm-sig-end";
+    case T38_FIELD_CM_MESSAGE:
+        return "cm-message";
+    case T38_FIELD_JM_MESSAGE:
+        return "jm-message";
+    case T38_FIELD_CI_MESSAGE:
+        return "ci-message";
+    case T38_FIELD_V34RATE:
+        return "v34rate";
+    }
+    return "???";
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(const char *) t38_cm_profile_to_str(int profile)
+{
+    switch (profile)
+    {
+    case '1':
+        return "G3 FAX sending terminal";
+    case '2':
+        return "G3 FAX receiving terminal";
+    case '3':
+        return "V.34 HDX and G3 FAX sending terminal";
+    case '4':
+        return "V.34 HDX and G3 FAX receiving terminal";
+    case '5':
+        return "V.34 HDX-only FAX sending terminal";
+    case '6':
+        return "V.34 HDX-only FAX receiving terminal";
+    }
+    return "???";
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(const char *) t38_jm_to_str(const uint8_t *data, int len)
+{
+    if (len < 2)
+        return "???";
+    switch (data[0])
+    {
+    case 'A':
+        switch (data[1])
+        {
+        case '0':
+            return "ACK";
+        }
+        break;
+    case 'N':
+        switch (data[1])
+        {
+        case '0':
+            return "NACK: No compatible mode available";
+        case '1':
+            /* Response for profiles 1 and 2 */
+            return "NACK: No V.34 FAX, use G3 FAX";
+        case '2':
+            /* Response for profiles 5 and 6 */
+            return "NACK: V.34 only FAX.";
+        }
+        break;
+    }
+    return "???";
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t38_v34rate_to_bps(const uint8_t *data, int len)
+{
+    int i;
+    int rate;
+
+    if (len < 3)
+        return -1;
+    for (i = 0, rate = 0;  i < 3;  i++)
+    {
+        if (data[i] < '0'  ||  data[i] > '9')
+            return -1;
+        rate = rate*10 + data[i] - '0';
+    }
+    return rate*100;
+}
+/*- End of function --------------------------------------------------------*/
+
+static __inline__ int classify_seq_no_offset(int expected, int actual)
+{
+    /* Classify the mismatch between expected and actual sequence numbers
+       according to whether the actual is a little in the past (late), a
+       little in the future (some packets have been lost), or a large jump
+       that represents the sequence being lost (possibly when some RTP
+       gets dumped to a UDPTL port). */
+    /* This assumes they are not equal */
+    if (expected > actual)
+    {
+        if (expected > actual + 0x10000 - ACCEPTABLE_SEQ_NO_OFFSET)
+        {
+            /* In the near future */
+            return 1;
+        }
+        if (expected < actual + ACCEPTABLE_SEQ_NO_OFFSET)
+        {
+            /* In the recent past */
+            return -1;
+        }
+    }
+    else
+    {
+        if (expected + ACCEPTABLE_SEQ_NO_OFFSET > actual)
+        {
+            /* In the near future */
+            return 1;
+        }
+        if (expected + 0x10000 - ACCEPTABLE_SEQ_NO_OFFSET < actual)
+        {
+            /* In the recent past */
+            return -1;
+        }
+    }
+    /* There has been a huge step in the sequence */
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t38_core_rx_ifp_packet(t38_core_state_t *s, const uint8_t *buf, int len, uint16_t seq_no)
+{
+    int i;
+    int t30_indicator;
+    int t30_data;
+    int ptr;
+    int other_half;
+    int numocts;
+    int log_seq_no;
+    const uint8_t *msg;
+    unsigned int count;
+    unsigned int t30_field_type;
+    uint8_t type;
+    uint8_t data_field_present;
+    uint8_t field_data_present;
+    char tag[20];
+
+    log_seq_no = (s->check_sequence_numbers)  ?  seq_no  :  s->rx_expected_seq_no;
+
+    if (span_log_test(&s->logging, SPAN_LOG_FLOW))
+    {
+        sprintf(tag, "Rx %5d: IFP", log_seq_no);
+        span_log_buf(&s->logging, SPAN_LOG_FLOW, tag, buf, len);
+    }
+    if (len < 1)
+    {
+        span_log(&s->logging, SPAN_LOG_PROTOCOL_WARNING, "Rx %5d: Bad packet length - %d\n", log_seq_no, len);
+        return -1;
+    }
+    if (s->check_sequence_numbers)
+    {
+        seq_no &= 0xFFFF;
+        if (seq_no != s->rx_expected_seq_no)
+        {
+            /* An expected value of -1 indicates this is the first received packet, and will accept
+               anything for that. We can't assume they will start from zero, even though they should. */
+            if (s->rx_expected_seq_no != -1)
+            {
+                /* We have a packet with a serial number that is not in sequence. The cause could be:
+                    - 1. a repeat copy of a recent packet. Many T.38 implementations can preduce quite a lot of these.
+                    - 2. a late packet, whose point in the sequence we have already passed.
+                    - 3. the result of a hop in the sequence numbers cause by something weird from the other
+                         end. Stream switching might cause this
+                    - 4. missing packets.
+    
+                    In cases 1 and 2 we need to drop this packet. In case 2 it might make sense to try to do
+                    something with it in the terminal case. Currently we don't. For gateway operation it will be
+                    too late to do anything useful.
+                 */
+                if (((seq_no + 1) & 0xFFFF) == s->rx_expected_seq_no)
+                {
+                    /* Assume this is truly a repeat packet, and don't bother checking its contents. */
+                    span_log(&s->logging, SPAN_LOG_FLOW, "Rx %5d: Repeat packet number\n", log_seq_no);
+                    return 0;
+                }
+                /* Distinguish between a little bit out of sequence, and a huge hop. */
+                switch (classify_seq_no_offset(s->rx_expected_seq_no, seq_no))
+                {
+                case -1:
+                    /* This packet is in the near past, so its late. */
+                    span_log(&s->logging, SPAN_LOG_FLOW, "Rx %5d: Late packet - expected %d\n", log_seq_no, s->rx_expected_seq_no);
+                    return 0;
+                case 1:
+                    /* This packet is in the near future, so some packets have been lost */
+                    span_log(&s->logging, SPAN_LOG_FLOW, "Rx %5d: Missing from %d\n", log_seq_no, s->rx_expected_seq_no);
+                    s->rx_missing_handler(s, s->rx_user_data, s->rx_expected_seq_no, seq_no);
+                    s->missing_packets += (seq_no - s->rx_expected_seq_no);
+                    break;
+                default:
+                    /* The sequence has jumped wildly */
+                    span_log(&s->logging, SPAN_LOG_FLOW, "Rx %5d: Sequence restart\n", log_seq_no);
+                    s->rx_missing_handler(s, s->rx_user_data, -1, -1);
+                    s->missing_packets++;
+                    break;
+                }
+            }
+            s->rx_expected_seq_no = seq_no;
+        }
+    }
+    /* The sequence numbering is defined as rolling from 0xFFFF to 0x0000. Some implementations
+       of T.38 roll from 0xFFFF to 0x0001. Isn't standardisation a wonderful thing? The T.38
+       document specifies only a small fraction of what it should, yet then they actually nail
+       something properly, people ignore it. Developers in this industry truly deserves the ****
+       **** **** **** **** **** documents they have to live with. Anyway, when the far end has a
+       broken rollover behaviour we will get a hiccup at the rollover point. Don't worry too
+       much. We will just treat the message in progress as one with some missing data. With any
+       luck a retry will ride over the problem. Rollovers don't occur that often. It takes quite
+       a few FAX pages to reach rollover. */
+    s->rx_expected_seq_no = (s->rx_expected_seq_no + 1) & 0xFFFF;
+    data_field_present = (buf[0] >> 7) & 1;
+    type = (buf[0] >> 6) & 1;
+    ptr = 0;
+    switch (type)
+    {
+    case T38_TYPE_OF_MSG_T30_INDICATOR:
+        /* Indicators should never have a data field */
+        if (data_field_present)
+        {
+            span_log(&s->logging, SPAN_LOG_PROTOCOL_WARNING, "Rx %5d: Data field with indicator\n", log_seq_no);
+            return -1;
+        }
+        /* Any received indicator should mean we no longer have a valid concept of "last received data/field type". */
+        s->current_rx_data_type = -1;
+        s->current_rx_field_type = -1;
+        if ((buf[0] & 0x20))
+        {
+            /* Extension */
+            if (len != 2)
+            {
+                span_log(&s->logging, SPAN_LOG_PROTOCOL_WARNING, "Rx %5d: Invalid length for indicator (A)\n", log_seq_no);
+                return -1;
+            }
+            t30_indicator = T38_IND_V8_ANSAM + (((buf[0] << 2) & 0x3C) | ((buf[1] >> 6) & 0x3));
+            if (t30_indicator > T38_IND_V33_14400_TRAINING)
+            {
+                span_log(&s->logging, SPAN_LOG_PROTOCOL_WARNING, "Rx %5d: Unknown indicator - %d\n", log_seq_no, t30_indicator);
+                return -1;
+            }
+        }
+        else
+        {
+            if (len != 1)
+            {
+                span_log(&s->logging, SPAN_LOG_PROTOCOL_WARNING, "Rx %5d: Invalid length for indicator (B)\n", log_seq_no);
+                return -1;
+            }
+            t30_indicator = (buf[0] >> 1) & 0xF;
+        }
+        span_log(&s->logging, SPAN_LOG_FLOW, "Rx %5d: indicator %s\n", log_seq_no, t38_indicator_to_str(t30_indicator));
+        s->rx_indicator_handler(s, s->rx_user_data, t30_indicator);
+        /* This must come after the indicator handler, so the handler routine sees the existing state of the
+           indicator. */
+        s->current_rx_indicator = t30_indicator;
+        break;
+    case T38_TYPE_OF_MSG_T30_DATA:
+        if ((buf[0] & 0x20))
+        {
+            /* Extension */
+            if (len < 2)
+            {
+                span_log(&s->logging, SPAN_LOG_PROTOCOL_WARNING, "Rx %5d: Invalid length for data (A)\n", log_seq_no);
+                return -1;
+            }
+            t30_data = T38_DATA_V8 + (((buf[0] << 2) & 0x3C) | ((buf[1] >> 6) & 0x3));
+            if (t30_data > T38_DATA_V33_14400)
+            {
+                span_log(&s->logging, SPAN_LOG_PROTOCOL_WARNING, "Rx %5d: Unknown data type - %d\n", log_seq_no, t30_data);
+                return -1;
+            }
+            ptr = 2;
+        }
+        else
+        {
+            t30_data = (buf[0] >> 1) & 0xF;
+            if (t30_data > T38_DATA_V17_14400)
+            {
+                span_log(&s->logging, SPAN_LOG_PROTOCOL_WARNING, "Rx %5d: Unknown data type - %d\n", log_seq_no, t30_data);
+                return -1;
+            }
+            ptr = 1;
+        }
+        if (!data_field_present)
+        {
+            /* This is kinda weird, but I guess if the length checks out we accept it. */
+            span_log(&s->logging, SPAN_LOG_PROTOCOL_WARNING, "Rx %5d: Data type with no data field\n", log_seq_no);
+            if (ptr != len)
+            {
+                span_log(&s->logging, SPAN_LOG_PROTOCOL_WARNING, "Rx %5d: Invalid length for data (B)\n", log_seq_no);
+                return -1;
+            }
+            break;
+        }
+        if (ptr >= len)
+        {
+            span_log(&s->logging, SPAN_LOG_PROTOCOL_WARNING, "Rx %5d: Invalid length for data (C)\n", log_seq_no);
+            return -1;
+        }
+        count = buf[ptr++];
+        //printf("Count is %d\n", count);
+        other_half = FALSE;
+        t30_field_type = 0;
+        for (i = 0;  i < (int) count;  i++)
+        {
+            if (ptr >= len)
+            {
+                span_log(&s->logging, SPAN_LOG_PROTOCOL_WARNING, "Rx %5d: Invalid length for data (D)\n", log_seq_no);
+                return -1;
+            }
+            if (s->t38_version == 0)
+            {
+                /* The original version of T.38 with a typo in the ASN.1 spec. */
+                if (other_half)
+                {
+                    /* The lack of a data field in the previous message means
+                       we are currently in the middle of an octet. */
+                    field_data_present = (buf[ptr] >> 3) & 1;
+                    /* Decode field_type */
+                    t30_field_type = buf[ptr] & 0x7;
+                    ptr++;
+                    other_half = FALSE;
+                }
+                else
+                {
+                    field_data_present = (buf[ptr] >> 7) & 1;
+                    /* Decode field_type */
+                    t30_field_type = (buf[ptr] >> 4) & 0x7;
+                    if (field_data_present)
+                        ptr++;
+                    else
+                        other_half = TRUE;
+                }
+                if (t30_field_type > T38_FIELD_T4_NON_ECM_SIG_END)
+                {
+                    span_log(&s->logging, SPAN_LOG_PROTOCOL_WARNING, "Rx %5d: Unknown field type - %d\n", log_seq_no, t30_field_type);
+                    return -1;
+                }
+            }
+            else
+            {
+                field_data_present = (buf[ptr] >> 7) & 1;
+                /* Decode field_type */
+                if ((buf[ptr] & 0x40))
+                {
+                    if (ptr > len - 2)
+                    {
+                        span_log(&s->logging, SPAN_LOG_PROTOCOL_WARNING, "Rx %5d: Invalid length for data (E)\n", log_seq_no);
+                        return -1;
+                    }
+                    t30_field_type = T38_FIELD_CM_MESSAGE + (((buf[ptr] << 2) & 0x3C) | ((buf[ptr + 1] >> 6) & 0x3));
+                    if (t30_field_type > T38_FIELD_V34RATE)
+                    {
+                        span_log(&s->logging, SPAN_LOG_PROTOCOL_WARNING, "Rx %5d: Unknown field type - %d\n", log_seq_no, t30_field_type);
+                        return -1;
+                    }
+                    ptr += 2;
+                }
+                else
+                {
+                    t30_field_type = (buf[ptr++] >> 3) & 0x7;
+                }
+            }
+            /* Decode field_data */
+            if (field_data_present)
+            {
+                if (ptr > len - 2)
+                {
+                    span_log(&s->logging, SPAN_LOG_PROTOCOL_WARNING, "Rx %5d: Invalid length for data (F)\n", log_seq_no);
+                    return -1;
+                }
+                numocts = ((buf[ptr] << 8) | buf[ptr + 1]) + 1;
+                msg = buf + ptr + 2;
+                ptr += numocts + 2;
+            }
+            else
+            {
+                numocts = 0;
+                msg = NULL;
+            }
+            if (ptr > len)
+            {
+                span_log(&s->logging, SPAN_LOG_PROTOCOL_WARNING, "Rx %5d: Invalid length for data (G)\n", log_seq_no);
+                return -1;
+            }
+            span_log(&s->logging,
+                     SPAN_LOG_FLOW,
+                     "Rx %5d: (%d) data %s/%s + %d byte(s)\n",
+                     log_seq_no,
+                     i,
+                     t38_data_type_to_str(t30_data),
+                     t38_field_type_to_str(t30_field_type),
+                     numocts);
+            s->rx_data_handler(s, s->rx_user_data, t30_data, t30_field_type, msg, numocts);
+            s->current_rx_data_type = t30_data;
+            s->current_rx_field_type = t30_field_type;
+        }
+        if (ptr != len)
+        {
+            if (s->t38_version != 0  ||  ptr != (len - 1)  ||  !other_half)
+            {
+                span_log(&s->logging, SPAN_LOG_PROTOCOL_WARNING, "Rx %5d: Invalid length for data (H) - %d %d\n", log_seq_no, ptr, len);
+                return -1;
+            }
+        }
+        break;
+    }
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int t38_encode_indicator(t38_core_state_t *s, uint8_t buf[], int indicator)
+{
+    int len;
+
+    /* Build the IFP packet */
+    /* Data field not present */
+    /* Indicator packet */
+    /* Type of indicator */
+    if (indicator <= T38_IND_V17_14400_LONG_TRAINING)
+    {
+        buf[0] = (uint8_t) (indicator << 1);
+        len = 1;
+    }
+    else if (s->t38_version != 0  &&  indicator <= T38_IND_V33_14400_TRAINING)
+    {
+        buf[0] = (uint8_t) (0x20 | (((indicator - T38_IND_V8_ANSAM) & 0xF) >> 2));
+        buf[1] = (uint8_t) (((indicator - T38_IND_V8_ANSAM) << 6) & 0xFF);
+        len = 2;
+    }
+    else
+    {
+        len = -1;
+    }
+    return len;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int t38_encode_data(t38_core_state_t *s, uint8_t buf[], int data_type, const t38_data_field_t field[], int fields)
+{
+    int len;
+    int i;
+    int enclen;
+    int multiplier;
+    int data_field_no;
+    const t38_data_field_t *q;
+    unsigned int encoded_len;
+    unsigned int fragment_len;
+    unsigned int value;
+    uint8_t data_field_present;
+    uint8_t field_data_present;
+    char tag[20];
+
+    /* Build the IFP packet */
+
+    /* There seems no valid reason why a packet would ever be generated without a data field present */
+    data_field_present = TRUE;
+
+    for (data_field_no = 0;  data_field_no < fields;  data_field_no++)
+    {
+        span_log(&s->logging,
+                 SPAN_LOG_FLOW,
+                 "Tx %5d: (%d) data %s/%s + %d byte(s)\n",
+                 s->tx_seq_no,
+                 data_field_no,
+                 t38_data_type_to_str(data_type),
+                 t38_field_type_to_str(field[data_field_no].field_type),
+                 field[data_field_no].field_len);
+    }
+    
+    data_field_no = 0;
+    len = 0;
+    /* Data field present */
+    /* Data packet */
+    /* Type of data */
+    if (data_type <= T38_DATA_V17_14400)
+    {
+        buf[len++] = (uint8_t) ((data_field_present << 7) | 0x40 | (data_type << 1));
+    }
+    else if (s->t38_version != 0  &&  data_type <= T38_DATA_V33_14400)
+    {
+        buf[len++] = (uint8_t) ((data_field_present << 7) | 0x60 | (((data_type - T38_DATA_V8) & 0xF) >> 2));
+        buf[len++] = (uint8_t) (((data_type - T38_DATA_V8) << 6) & 0xFF);
+    }
+    else
+    {
+        return -1;
+    }
+    if (data_field_present)
+    {
+        encoded_len = 0;
+        data_field_no = 0;
+        do
+        {
+            value = fields - encoded_len;
+            if (value < 0x80)
+            {
+                /* 1 octet case */
+                buf[len++] = (uint8_t) value;
+                enclen = value;
+            }
+            else if (value < 0x4000)
+            {
+                /* 2 octet case */
+                buf[len++] = (uint8_t) (0x80 | ((value >> 8) & 0xFF));
+                buf[len++] = (uint8_t) (value & 0xFF);
+                enclen = value;
+            }
+            else
+            {
+                /* Fragmentation case */
+                multiplier = (value/0x4000 < 4)  ?  value/0x4000  :  4;
+                buf[len++] = (uint8_t) (0xC0 | multiplier);
+                enclen = 0x4000*multiplier;
+            }
+
+            fragment_len = enclen;
+            encoded_len += fragment_len;
+            /* Encode the elements */
+            for (i = 0;  i < (int) encoded_len;  i++)
+            {
+                q = &field[data_field_no];
+                field_data_present = (uint8_t) (q->field_len > 0);
+                /* Encode field_type */
+                if (s->t38_version == 0)
+                {
+                    /* Original version of T.38 with a typo */
+                    if (q->field_type > T38_FIELD_T4_NON_ECM_SIG_END)
+                        return -1;
+                    buf[len++] = (uint8_t) ((field_data_present << 7) | (q->field_type << 4));
+                }
+                else
+                {
+                    if (q->field_type <= T38_FIELD_T4_NON_ECM_SIG_END)
+                    {
+                        buf[len++] = (uint8_t) ((field_data_present << 7) | (q->field_type << 3));
+                    }
+                    else if (q->field_type <= T38_FIELD_V34RATE)
+                    {
+                        buf[len++] = (uint8_t) ((field_data_present << 7) | 0x40 | ((q->field_type - T38_FIELD_CM_MESSAGE) >> 2));
+                        buf[len++] = (uint8_t) (((q->field_type - T38_FIELD_CM_MESSAGE) << 6) & 0xC0);
+                    }
+                    else
+                    {
+                        return -1;
+                    }
+                }
+                /* Encode field_data */
+                if (field_data_present)
+                {
+                    if (q->field_len < 1  ||  q->field_len > 65535)
+                        return -1;
+                    buf[len++] = (uint8_t) (((q->field_len - 1) >> 8) & 0xFF);
+                    buf[len++] = (uint8_t) ((q->field_len - 1) & 0xFF);
+                    memcpy(buf + len, q->field, q->field_len);
+                    len += q->field_len;
+                }
+                data_field_no++;
+            }
+        }
+        while (fields != (int) encoded_len  ||  fragment_len >= 16384);
+    }
+
+    if (span_log_test(&s->logging, SPAN_LOG_FLOW))
+    {
+        sprintf(tag, "Tx %5d: IFP", s->tx_seq_no);
+        span_log_buf(&s->logging, SPAN_LOG_FLOW, tag, buf, len);
+    }
+    return len;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t38_core_send_indicator(t38_core_state_t *s, int indicator)
+{
+    uint8_t buf[100];
+    int len;
+    int delay;
+    int transmissions;
+
+    delay = 0;
+    /* Only send an indicator if it represents a change of state. */
+    /* If the 0x100 bit is set in indicator it will bypass this test, and force transmission */
+    if (s->current_tx_indicator != indicator)
+    {
+        /* Zero is a valid count, to suppress the transmission of indicators when the
+           transport means they are not needed - e.g. TPKT/TCP. */
+        transmissions = (indicator & 0x100)  ?  1  :  s->category_control[T38_PACKET_CATEGORY_INDICATOR];
+        indicator &= 0xFF;
+        if (s->category_control[T38_PACKET_CATEGORY_INDICATOR])
+        {
+            if ((len = t38_encode_indicator(s, buf, indicator)) < 0)
+            {
+                span_log(&s->logging, SPAN_LOG_FLOW, "T.38 indicator len is %d\n", len);
+                return len;
+            }
+            span_log(&s->logging, SPAN_LOG_FLOW, "Tx %5d: indicator %s\n", s->tx_seq_no, t38_indicator_to_str(indicator));
+            s->tx_packet_handler(s, s->tx_packet_user_data, buf, len, transmissions);
+            s->tx_seq_no = (s->tx_seq_no + 1) & 0xFFFF;
+            delay = modem_startup_time[indicator].training;
+            if (s->allow_for_tep)
+                delay += modem_startup_time[indicator].tep;
+        }
+        s->current_tx_indicator = indicator;
+    }
+    return delay;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t38_core_send_flags_delay(t38_core_state_t *s, int indicator)
+{
+    return modem_startup_time[indicator].flags;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t38_core_send_training_delay(t38_core_state_t *s, int indicator)
+{
+    return modem_startup_time[indicator].training;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t38_core_send_data(t38_core_state_t *s, int data_type, int field_type, const uint8_t field[], int field_len, int category)
+{
+    t38_data_field_t field0;
+    uint8_t buf[1000];
+    int len;
+
+    field0.field_type = field_type;
+    field0.field = field;
+    field0.field_len = field_len;
+    if ((len = t38_encode_data(s, buf, data_type, &field0, 1)) < 0)
+    {
+        span_log(&s->logging, SPAN_LOG_FLOW, "T.38 data len is %d\n", len);
+        return len;
+    }
+    s->tx_packet_handler(s, s->tx_packet_user_data, buf, len, s->category_control[category]);
+    s->tx_seq_no = (s->tx_seq_no + 1) & 0xFFFF;
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t38_core_send_data_multi_field(t38_core_state_t *s, int data_type, const t38_data_field_t field[], int fields, int category)
+{
+    uint8_t buf[1000];
+    int len;
+
+    if ((len = t38_encode_data(s, buf, data_type, field, fields)) < 0)
+    {
+        span_log(&s->logging, SPAN_LOG_FLOW, "T.38 data len is %d\n", len);
+        return len;
+    }
+    s->tx_packet_handler(s, s->tx_packet_user_data, buf, len, s->category_control[category]);
+    s->tx_seq_no = (s->tx_seq_no + 1) & 0xFFFF;
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t38_set_data_rate_management_method(t38_core_state_t *s, int method)
+{
+    s->data_rate_management_method = method;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t38_set_data_transport_protocol(t38_core_state_t *s, int data_transport_protocol)
+{
+    s->data_transport_protocol = data_transport_protocol;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t38_set_fill_bit_removal(t38_core_state_t *s, int fill_bit_removal)
+{
+    s->fill_bit_removal = fill_bit_removal;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t38_set_mmr_transcoding(t38_core_state_t *s, int mmr_transcoding)
+{
+    s->mmr_transcoding = mmr_transcoding;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t38_set_jbig_transcoding(t38_core_state_t *s, int jbig_transcoding)
+{
+    s->jbig_transcoding = jbig_transcoding;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t38_set_max_buffer_size(t38_core_state_t *s, int max_buffer_size)
+{
+    s->max_buffer_size = max_buffer_size;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t38_set_max_datagram_size(t38_core_state_t *s, int max_datagram_size)
+{
+    s->max_datagram_size = max_datagram_size;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t38_set_t38_version(t38_core_state_t *s, int t38_version)
+{
+    s->t38_version = t38_version;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t38_set_sequence_number_handling(t38_core_state_t *s, int check)
+{
+    s->check_sequence_numbers = check;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t38_set_tep_handling(t38_core_state_t *s, int allow_for_tep)
+{
+    s->allow_for_tep = allow_for_tep;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t38_set_redundancy_control(t38_core_state_t *s, int category, int setting)
+{
+    s->category_control[category] = setting;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t38_set_fastest_image_data_rate(t38_core_state_t *s, int max_rate)
+{
+    s->fastest_image_data_rate = max_rate;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t38_get_fastest_image_data_rate(t38_core_state_t *s)
+{
+    return s->fastest_image_data_rate;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(logging_state_t *) t38_core_get_logging_state(t38_core_state_t *s)
+{
+    return &s->logging;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t38_core_restart(t38_core_state_t *s)
+{
+    /* Set the initial current receive states to something invalid, so the
+       first data received is seen as a change of state. */
+    s->current_rx_indicator = -1;
+    s->current_rx_data_type = -1;
+    s->current_rx_field_type = -1;
+
+    /* Set the initial current indicator state to something invalid, so the
+       first attempt to send an indicator will work. */
+    s->current_tx_indicator = -1;
+
+    /* We have no initial expectation of the received packet sequence number.
+       They most often start at 0 or 1 for a UDPTL transport, but random
+       starting numbers are possible. */
+    s->rx_expected_seq_no = -1;
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(t38_core_state_t *) t38_core_init(t38_core_state_t *s,
+                                               t38_rx_indicator_handler_t *rx_indicator_handler,
+                                               t38_rx_data_handler_t *rx_data_handler,
+                                               t38_rx_missing_handler_t *rx_missing_handler,
+                                               void *rx_user_data,
+                                               t38_tx_packet_handler_t *tx_packet_handler,
+                                               void *tx_packet_user_data)
+{
+    if (s == NULL)
+    {
+        if ((s = (t38_core_state_t *) malloc(sizeof(*s))) == NULL)
+            return NULL;
+    }
+    memset(s, 0, sizeof(*s));
+    span_log_init(&s->logging, SPAN_LOG_NONE, NULL);
+    span_log_set_protocol(&s->logging, "T.38");
+
+    /* Set some defaults for the parameters configurable from outside the
+       T.38 domain - e.g. from SDP data. */
+    s->data_rate_management_method = T38_DATA_RATE_MANAGEMENT_TRANSFERRED_TCF;
+    s->data_transport_protocol = T38_TRANSPORT_UDPTL;
+    s->fill_bit_removal = FALSE;
+    s->mmr_transcoding = FALSE;
+    s->jbig_transcoding = FALSE;
+    s->max_buffer_size = 400;
+    s->max_datagram_size = 100;
+    s->t38_version = 0;
+    s->check_sequence_numbers = TRUE;
+
+    /* Set some defaults */
+    s->category_control[T38_PACKET_CATEGORY_INDICATOR] = 1;
+    s->category_control[T38_PACKET_CATEGORY_CONTROL_DATA] = 1;
+    s->category_control[T38_PACKET_CATEGORY_CONTROL_DATA_END] = 1;
+    s->category_control[T38_PACKET_CATEGORY_IMAGE_DATA] = 1;
+    s->category_control[T38_PACKET_CATEGORY_IMAGE_DATA_END] = 1;
+
+    s->rx_indicator_handler = rx_indicator_handler;
+    s->rx_data_handler = rx_data_handler;
+    s->rx_missing_handler = rx_missing_handler;
+    s->rx_user_data = rx_user_data;
+    s->tx_packet_handler = tx_packet_handler;
+    s->tx_packet_user_data = tx_packet_user_data;
+
+    t38_core_restart(s);
+    return s;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t38_core_release(t38_core_state_t *s)
+{
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t38_core_free(t38_core_state_t *s)
+{
+    if (s)
+        free(s);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+/*- End of file ------------------------------------------------------------*/
diff --git a/src/codec/spandsp/src/t38_terminal.c b/src/codec/spandsp/src/t38_terminal.c
new file mode 100644 (file)
index 0000000..cf3450c
--- /dev/null
@@ -0,0 +1,1408 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * t38_terminal.c - T.38 termination, less the packet exchange part
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2005, 2006, 2008 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.
+ */
+
+/*! \file */
+
+#if defined(HAVE_CONFIG_H)
+#include "config.h"
+#endif
+
+#include <inttypes.h>
+#include <stdlib.h>
+//TODO// #include <stdio.h>
+//TODO// #include <fcntl.h>
+#include <time.h>
+#include <string.h>
+#if defined(HAVE_TGMATH_H)
+#include <tgmath.h>
+#endif
+#if defined(HAVE_MATH_H)
+#include <math.h>
+#endif
+#include "floating_fudge.h"
+#include <assert.h>
+//TODO// #include <tiffio.h>
+
+#include "spandsp/0cpm.h"
+
+#include "spandsp/telephony.h"
+#include "spandsp/logging.h"
+#include "spandsp/bit_operations.h"
+//TODO// #include "spandsp/queue.h"
+//TODO// #include "spandsp/power_meter.h"
+//TODO// #include "spandsp/complex.h"
+//TODO// #include "spandsp/tone_generate.h"
+#include "spandsp/async.h"
+//TODO// #include "spandsp/hdlc.h"
+//TODO// #include "spandsp/fsk.h"
+//TODO// #include "spandsp/v29tx.h"
+//TODO// #include "spandsp/v29rx.h"
+//TODO// #include "spandsp/v27ter_tx.h"
+//TODO// #include "spandsp/v27ter_rx.h"
+//TODO// #include "spandsp/v17tx.h"
+//TODO// #include "spandsp/v17rx.h"
+#include "spandsp/t4_rx.h"
+#include "spandsp/t4_tx.h"
+#if defined(SPANDSP_SUPPORT_T85)
+#include "spandsp/t81_t82_arith_coding.h"
+#include "spandsp/t85.h"
+#endif
+#include "spandsp/t4_t6_decode.h"
+#include "spandsp/t4_t6_encode.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/t38_core.h"
+#include "spandsp/t38_terminal.h"
+
+#include "spandsp/private/logging.h"
+#if defined(SPANDSP_SUPPORT_T85)
+#include "spandsp/private/t81_t82_arith_coding.h"
+#include "spandsp/private/t85.h"
+#endif
+#include "spandsp/private/t4_t6_decode.h"
+#include "spandsp/private/t4_t6_encode.h"
+#include "spandsp/private/t4_rx.h"
+#include "spandsp/private/t4_tx.h"
+#include "spandsp/private/t30.h"
+#include "spandsp/private/t38_core.h"
+#include "spandsp/private/t38_terminal.h"
+
+/* Settings suitable for paced transmission over a UDP transport */
+#define DEFAULT_MS_PER_TX_CHUNK                 30
+
+#define INDICATOR_TX_COUNT                      3
+#define DATA_TX_COUNT                           1
+#define DATA_END_TX_COUNT                       3
+
+/* Settings suitable for unpaced transmission over a TCP transport */
+#define MAX_OCTETS_PER_UNPACED_CHUNK            300
+
+/* Backstop timeout if reception of packets stops in the middle of a burst */
+#define MID_RX_TIMEOUT                          15000
+
+enum
+{
+    T38_CHUNKING_MERGE_FCS_WITH_DATA = 0x0001,
+    T38_CHUNKING_WHOLE_FRAMES = 0x0002,
+    T38_CHUNKING_ALLOW_TEP_TIME = 0x0004,
+    T38_CHUNKING_SEND_REGULAR_INDICATORS = 0x0008,
+    T38_CHUNKING_SEND_2S_REGULAR_INDICATORS = 0x0010
+};
+
+enum
+{
+    T38_TIMED_STEP_NONE = 0,
+    T38_TIMED_STEP_NON_ECM_MODEM = 0x10,
+    T38_TIMED_STEP_NON_ECM_MODEM_2 = 0x11,
+    T38_TIMED_STEP_NON_ECM_MODEM_3 = 0x12,
+    T38_TIMED_STEP_NON_ECM_MODEM_4 = 0x13,
+    T38_TIMED_STEP_NON_ECM_MODEM_5 = 0x14,
+    T38_TIMED_STEP_HDLC_MODEM = 0x20,
+    T38_TIMED_STEP_HDLC_MODEM_2 = 0x21,
+    T38_TIMED_STEP_HDLC_MODEM_3 = 0x22,
+    T38_TIMED_STEP_HDLC_MODEM_4 = 0x23,
+    T38_TIMED_STEP_HDLC_MODEM_5 = 0x24,
+    T38_TIMED_STEP_CED = 0x30,
+    T38_TIMED_STEP_CED_2 = 0x31,
+    T38_TIMED_STEP_CED_3 = 0x32,
+    T38_TIMED_STEP_CNG = 0x40,
+    T38_TIMED_STEP_CNG_2 = 0x41,
+    T38_TIMED_STEP_PAUSE = 0x50,
+    T38_TIMED_STEP_NO_SIGNAL = 0x60
+};
+
+static __inline__ void front_end_status(t38_terminal_state_t *s, int status)
+{
+    t30_front_end_status(&s->t30, status);
+}
+/*- End of function --------------------------------------------------------*/
+
+static __inline__ void hdlc_accept_frame(t38_terminal_state_t *s, const uint8_t *msg, int len, int ok)
+{
+    t30_hdlc_accept(&s->t30, msg, len, ok);
+}
+/*- End of function --------------------------------------------------------*/
+
+static int extra_bits_in_stuffed_frame(const uint8_t buf[], int len)
+{
+    int bitstream;
+    int ones;
+    int stuffed;
+    int i;
+    int j;
+    
+    bitstream = 0;
+    ones = 0;
+    stuffed = 0;
+    /* We should really append the CRC, and include the stuffed bits for that, to get
+       the exact number of bits in the frame. */
+    //len = crc_itu16_append(buf, len);
+    for (i = 0;  i < len;  i++)
+    {
+        bitstream = buf[i];
+        for (j = 0;  j < 8;  j++)
+        {
+            if ((bitstream & 1))
+            {
+                if (++ones >= 5)
+                {
+                    ones = 0;
+                    stuffed++;
+                }
+                /*endif*/
+            }
+            else
+            {
+                ones = 0;
+            }
+            /*endif*/
+            bitstream >>= 1;
+        }
+        /*endfor*/
+    }
+    /*endfor*/
+    /* The total length of the frame is:
+          the number of bits in the body
+        + the number of additional bits in the body due to stuffing
+        + the number of bits in the CRC
+        + the number of additional bits in the CRC due to stuffing
+        + 16 bits for the two terminating flag octets.
+       Lets just allow 3 bits for the CRC, which is the worst case. It
+       avoids calculating the real CRC, and the worst it can do is cause
+       a flag octet's worth of additional output.
+     */
+    return stuffed + 16 + 3 + 16;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int process_rx_missing(t38_core_state_t *t, void *user_data, int rx_seq_no, int expected_seq_no)
+{
+    t38_terminal_state_t *s;
+    
+    s = (t38_terminal_state_t *) user_data;
+    s->t38_fe.rx_data_missing = TRUE;
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int process_rx_indicator(t38_core_state_t *t, void *user_data, int indicator)
+{
+    t38_terminal_state_t *s;
+    t38_terminal_front_end_state_t *fe;
+    
+    s = (t38_terminal_state_t *) user_data;
+    fe = &s->t38_fe;
+
+    if (t->current_rx_indicator == indicator)
+    {
+        /* This is probably due to the far end repeating itself, or slipping
+           preamble messages in between HDLC frames. T.38/V.1.3 tells us to
+           ignore it. Its harmless. */
+        return 0;
+    }
+    /*endif*/
+    /* In termination mode we don't care very much about indicators telling us training
+       is starting. We only care about V.21 preamble starting, for timeout control, and
+       the actual data. */
+    switch (indicator)
+    {
+    case T38_IND_NO_SIGNAL:
+        if (t->current_rx_indicator == T38_IND_V21_PREAMBLE
+            &&
+            (fe->current_rx_type == T30_MODEM_V21  ||  fe->current_rx_type == T30_MODEM_CNG))
+        {
+            hdlc_accept_frame(s, NULL, SIG_STATUS_CARRIER_DOWN, TRUE);
+        }
+        /*endif*/
+        fe->timeout_rx_samples = 0;
+        front_end_status(s, T30_FRONT_END_SIGNAL_ABSENT);
+        break;
+    case T38_IND_CNG:
+        /* We are completely indifferent to the startup tones. They serve no purpose for us.
+           We can't even assume that the existance of a tone means the far end is achieving
+           proper communication. Some T.38 gateways will just send out a CED or CNG indicator
+           without having seen anything from the far end FAX terminal.
+           Just report them for completeness. */
+        front_end_status(s, T30_FRONT_END_CNG_PRESENT);
+        break;
+    case T38_IND_CED:
+        front_end_status(s, T30_FRONT_END_CED_PRESENT);
+        break;
+    case T38_IND_V21_PREAMBLE:
+        fe->timeout_rx_samples = fe->samples + ms_to_samples(MID_RX_TIMEOUT);
+        front_end_status(s, T30_FRONT_END_SIGNAL_PRESENT);
+        break;
+    case T38_IND_V27TER_2400_TRAINING:
+    case T38_IND_V27TER_4800_TRAINING:
+    case T38_IND_V29_7200_TRAINING:
+    case T38_IND_V29_9600_TRAINING:
+    case T38_IND_V17_7200_SHORT_TRAINING:
+    case T38_IND_V17_7200_LONG_TRAINING:
+    case T38_IND_V17_9600_SHORT_TRAINING:
+    case T38_IND_V17_9600_LONG_TRAINING:
+    case T38_IND_V17_12000_SHORT_TRAINING:
+    case T38_IND_V17_12000_LONG_TRAINING:
+    case T38_IND_V17_14400_SHORT_TRAINING:
+    case T38_IND_V17_14400_LONG_TRAINING:
+    case T38_IND_V34_CNTL_CHANNEL_1200:
+    case T38_IND_V34_PRI_CHANNEL:
+    case T38_IND_V33_12000_TRAINING:
+    case T38_IND_V33_14400_TRAINING:
+        /* We really don't care what kind of modem is delivering the following image data.
+           We only care that some kind of fast modem signal is coming next. */
+        fe->timeout_rx_samples = fe->samples + ms_to_samples(MID_RX_TIMEOUT);
+        front_end_status(s, T30_FRONT_END_SIGNAL_PRESENT);
+        break;
+    case T38_IND_V8_ANSAM:
+    case T38_IND_V8_SIGNAL:
+    case T38_IND_V34_CC_RETRAIN:
+        /* V.34 support is a work in progress. */
+        break;
+    default:
+        front_end_status(s, T30_FRONT_END_SIGNAL_ABSENT);
+        break;
+    }
+    /*endswitch*/
+    fe->hdlc_rx.len = 0;
+    fe->rx_data_missing = FALSE;
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int fake_rx_indicator(t38_core_state_t *t, t38_terminal_state_t *s, int indicator)
+{
+    int ret;
+
+    ret = process_rx_indicator(t, s, indicator);
+    t->current_rx_indicator = indicator;
+    return ret;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int process_rx_data(t38_core_state_t *t, void *user_data, int data_type, int field_type, const uint8_t *buf, int len)
+{
+    t38_terminal_state_t *s;
+    t38_terminal_front_end_state_t *fe;
+#if defined(_MSC_VER)
+    uint8_t *buf2 = (uint8_t *) _alloca(len);
+#else
+    uint8_t buf2[len];
+#endif
+
+    s = (t38_terminal_state_t *) user_data;
+    fe = &s->t38_fe;
+    /* In termination mode we don't care very much what the data type is apart from a couple of
+       special cases. */
+    switch (data_type)
+    {
+    case T38_DATA_V8:
+        switch (field_type)
+        {
+        case T38_FIELD_CM_MESSAGE:
+            if (len >= 1)
+                span_log(&s->logging, SPAN_LOG_FLOW, "CM profile %d - %s\n", buf[0] - '0', t38_cm_profile_to_str(buf[0]));
+            else
+                span_log(&s->logging, SPAN_LOG_FLOW, "Bad length for CM message - %d\n", len);
+            /*endif*/
+            //front_end_status(s, T30_FRONT_END_RECEIVE_COMPLETE);
+            break;
+        case T38_FIELD_JM_MESSAGE:
+            if (len >= 2)
+                span_log(&s->logging, SPAN_LOG_FLOW, "JM - %s\n", t38_jm_to_str(buf, len));
+            else
+                span_log(&s->logging, SPAN_LOG_FLOW, "Bad length for JM message - %d\n", len);
+            /*endif*/
+            //front_end_status(s, T30_FRONT_END_RECEIVE_COMPLETE);
+            break;
+        case T38_FIELD_CI_MESSAGE:
+            if (len >= 1)
+                span_log(&s->logging, SPAN_LOG_FLOW, "CI 0x%X\n", buf[0]);
+            else
+                span_log(&s->logging, SPAN_LOG_FLOW, "Bad length for CI message - %d\n", len);
+            /*endif*/
+            //front_end_status(s, T30_FRONT_END_RECEIVE_COMPLETE);
+            break;
+        default:
+            break;
+        }
+        /*endswitch*/
+        return 0;
+    case T38_DATA_V34_PRI_RATE:
+        switch (field_type)
+        {
+        case T38_FIELD_V34RATE:
+            if (len >= 3)
+            {
+                /* Just get and store the rate. The front end has no real interest in the
+                   actual bit rate. */
+                fe->t38.v34_rate = t38_v34rate_to_bps(buf, len);
+                span_log(&s->logging, SPAN_LOG_FLOW, "V.34 rate %d bps\n", fe->t38.v34_rate);
+            }
+            else
+            {
+                span_log(&s->logging, SPAN_LOG_FLOW, "Bad length for V34rate message - %d\n", len);
+            }
+            /*endif*/
+            break;
+        default:
+            break;
+        }
+        /*endswitch*/
+        return 0;
+    default:
+        break;
+    }
+    /*endswitch*/
+    switch (field_type)
+    {
+    case T38_FIELD_HDLC_DATA:
+        if (fe->timeout_rx_samples == 0)
+        {
+            /* HDLC can just start without any signal indicator on some platforms, even when
+               there is zero packet loss. Nasty, but true. Its a good idea to be tolerant of
+               loss, though, so accepting a sudden start of HDLC data is the right thing to do. */
+            fake_rx_indicator(t, s, T38_IND_V21_PREAMBLE);
+            /* All real HDLC messages in the FAX world start with 0xFF. If this one is not starting
+               with 0xFF it would appear some octets must have been missed before this one. */
+            if (len <= 0  ||  buf[0] != 0xFF)
+                fe->rx_data_missing = TRUE;
+            /*endif*/
+        }
+        /*endif*/
+        if (len > 0)
+        {
+            if (fe->hdlc_rx.len + len <= T38_MAX_HDLC_LEN)
+            {
+                bit_reverse(fe->hdlc_rx.buf + fe->hdlc_rx.len, buf, len);
+                fe->hdlc_rx.len += len;
+            }
+            else
+            {
+                fe->rx_data_missing = TRUE;
+            }
+            /*endif*/
+        }
+        /*endif*/
+        fe->timeout_rx_samples = fe->samples + ms_to_samples(MID_RX_TIMEOUT);
+        break;
+    case T38_FIELD_HDLC_FCS_OK:
+        if (len > 0)
+        {
+            span_log(&s->logging, SPAN_LOG_WARNING, "There is data in a T38_FIELD_HDLC_FCS_OK!\n");
+            /* The sender has incorrectly included data in this message. It is unclear what we should do
+               with it, to maximise tolerance of buggy implementations. */
+        }
+        /*endif*/
+        /* Some T.38 implementations send multiple T38_FIELD_HDLC_FCS_OK messages, in IFP packets with
+           incrementing sequence numbers, which are actually repeats. They get through to this point because
+           of the incrementing sequence numbers. We need to filter them here in a context sensitive manner. */
+        if (t->current_rx_data_type != data_type  ||  t->current_rx_field_type != field_type)
+        {
+            span_log(&s->logging, SPAN_LOG_FLOW, "Type %s - CRC OK (%s)\n", (fe->hdlc_rx.len >= 3)  ?  t30_frametype(fe->hdlc_rx.buf[2])  :  "???", (fe->rx_data_missing)  ?  "missing octets"  :  "clean");
+            hdlc_accept_frame(s, fe->hdlc_rx.buf, fe->hdlc_rx.len, !fe->rx_data_missing);
+        }
+        /*endif*/
+        fe->hdlc_rx.len = 0;
+        fe->rx_data_missing = FALSE;
+        fe->timeout_rx_samples = fe->samples + ms_to_samples(MID_RX_TIMEOUT);
+        break;
+    case T38_FIELD_HDLC_FCS_BAD:
+        if (len > 0)
+        {
+            span_log(&s->logging, SPAN_LOG_WARNING, "There is data in a T38_FIELD_HDLC_FCS_BAD!\n");
+            /* The sender has incorrectly included data in this message. We can safely ignore it, as the
+               bad FCS means we will throw away the whole message, anyway. */
+        }
+        /*endif*/
+        /* Some T.38 implementations send multiple T38_FIELD_HDLC_FCS_BAD messages, in IFP packets with
+           incrementing sequence numbers, which are actually repeats. They get through to this point because
+           of the incrementing sequence numbers. We need to filter them here in a context sensitive manner. */
+        if (t->current_rx_data_type != data_type  ||  t->current_rx_field_type != field_type)
+        {
+            span_log(&s->logging, SPAN_LOG_FLOW, "Type %s - CRC bad (%s)\n", (fe->hdlc_rx.len >= 3)  ?  t30_frametype(fe->hdlc_rx.buf[2])  :  "???", (fe->rx_data_missing)  ?  "missing octets"  :  "clean");
+            hdlc_accept_frame(s, fe->hdlc_rx.buf, fe->hdlc_rx.len, FALSE);
+        }
+        /*endif*/
+        fe->hdlc_rx.len = 0;
+        fe->rx_data_missing = FALSE;
+        fe->timeout_rx_samples = fe->samples + ms_to_samples(MID_RX_TIMEOUT);
+        break;
+    case T38_FIELD_HDLC_FCS_OK_SIG_END:
+        if (len > 0)
+        {
+            span_log(&s->logging, SPAN_LOG_WARNING, "There is data in a T38_FIELD_HDLC_FCS_OK_SIG_END!\n");
+            /* The sender has incorrectly included data in this message. It is unclear what we should do
+               with it, to maximise tolerance of buggy implementations. */
+        }
+        /*endif*/
+        /* Some T.38 implementations send multiple T38_FIELD_HDLC_FCS_OK_SIG_END messages, in IFP packets with
+           incrementing sequence numbers, which are actually repeats. They get through to this point because
+           of the incrementing sequence numbers. We need to filter them here in a context sensitive manner. */
+        if (t->current_rx_data_type != data_type  ||  t->current_rx_field_type != field_type)
+        {
+            span_log(&s->logging, SPAN_LOG_FLOW, "Type %s - CRC OK, sig end (%s)\n", (fe->hdlc_rx.len >= 3)  ?  t30_frametype(fe->hdlc_rx.buf[2])  :  "???", (fe->rx_data_missing)  ?  "missing octets"  :  "clean");
+            hdlc_accept_frame(s, fe->hdlc_rx.buf, fe->hdlc_rx.len, !fe->rx_data_missing);
+            hdlc_accept_frame(s, NULL, SIG_STATUS_CARRIER_DOWN, TRUE);
+        }
+        /*endif*/
+        fe->hdlc_rx.len = 0;
+        fe->rx_data_missing = FALSE;
+        /* Treat this like a no signal indicator has occurred, so if the no signal indicator is missing, we are still OK */
+        fake_rx_indicator(t, s, T38_IND_NO_SIGNAL);
+        break;
+    case T38_FIELD_HDLC_FCS_BAD_SIG_END:
+        if (len > 0)
+        {
+            span_log(&s->logging, SPAN_LOG_WARNING, "There is data in a T38_FIELD_HDLC_FCS_BAD_SIG_END!\n");
+            /* The sender has incorrectly included data in this message. We can safely ignore it, as the
+               bad FCS means we will throw away the whole message, anyway. */
+        }
+        /*endif*/
+        /* Some T.38 implementations send multiple T38_FIELD_HDLC_FCS_BAD_SIG_END messages, in IFP packets with
+           incrementing sequence numbers, which are actually repeats. They get through to this point because
+           of the incrementing sequence numbers. We need to filter them here in a context sensitive manner. */
+        if (t->current_rx_data_type != data_type  ||  t->current_rx_field_type != field_type)
+        {
+            span_log(&s->logging, SPAN_LOG_FLOW, "Type %s - CRC bad, sig end (%s)\n", (fe->hdlc_rx.len >= 3)  ?  t30_frametype(fe->hdlc_rx.buf[2])  :  "???", (fe->rx_data_missing)  ?  "missing octets"  :  "clean");
+            hdlc_accept_frame(s, fe->hdlc_rx.buf, fe->hdlc_rx.len, FALSE);
+            hdlc_accept_frame(s, NULL, SIG_STATUS_CARRIER_DOWN, TRUE);
+        }
+        /*endif*/
+        fe->hdlc_rx.len = 0;
+        fe->rx_data_missing = FALSE;
+        /* Treat this like a no signal indicator has occurred, so if the no signal indicator is missing, we are still OK */
+        fake_rx_indicator(t, s, T38_IND_NO_SIGNAL);
+        break;
+    case T38_FIELD_HDLC_SIG_END:
+        if (len > 0)
+        {
+            span_log(&s->logging, SPAN_LOG_WARNING, "There is data in a T38_FIELD_HDLC_SIG_END!\n");
+            /* The sender has incorrectly included data in this message, but there seems nothing meaningful
+               it could be. There could not be an FCS good/bad report beyond this. */
+        }
+        /*endif*/
+        /* Some T.38 implementations send multiple T38_FIELD_HDLC_SIG_END messages, in IFP packets with
+           incrementing sequence numbers, which are actually repeats. They get through to this point because
+           of the incrementing sequence numbers. We need to filter them here in a context sensitive manner. */
+        if (t->current_rx_data_type != data_type  ||  t->current_rx_field_type != field_type)
+        {
+            /* WORKAROUND: At least some Mediatrix boxes have a bug, where they can send this message at the
+                           end of non-ECM data. We need to tolerate this. We use the generic receive complete
+                           indication, rather than the specific HDLC carrier down. */
+            /* This message is expected under 2 circumstances. One is as an alternative to T38_FIELD_HDLC_FCS_OK_SIG_END - 
+               i.e. they send T38_FIELD_HDLC_FCS_OK, and then T38_FIELD_HDLC_SIG_END when the carrier actually drops.
+               The other is because the HDLC signal drops unexpectedly - i.e. not just after a final frame. */
+            fe->hdlc_rx.len = 0;
+            fe->rx_data_missing = FALSE;
+            front_end_status(s, T30_FRONT_END_RECEIVE_COMPLETE);
+        }
+        /*endif*/
+        /* Treat this like a no signal indicator has occurred, so if the no signal indicator is missing, we are still OK */
+        fake_rx_indicator(t, s, T38_IND_NO_SIGNAL);
+        break;
+    case T38_FIELD_T4_NON_ECM_DATA:
+        if (!fe->rx_signal_present)
+        {
+            t30_non_ecm_put_bit(&s->t30, SIG_STATUS_TRAINING_SUCCEEDED);
+            fe->rx_signal_present = TRUE;
+        }
+        /*endif*/
+        if (len > 0)
+        {
+            bit_reverse(buf2, buf, len);
+            t30_non_ecm_put_chunk(&s->t30, buf2, len);
+        }
+        /*endif*/
+        fe->timeout_rx_samples = fe->samples + ms_to_samples(MID_RX_TIMEOUT);
+        break;
+    case T38_FIELD_T4_NON_ECM_SIG_END:
+        /* Some T.38 implementations send multiple T38_FIELD_T4_NON_ECM_SIG_END messages, in IFP packets with
+           incrementing sequence numbers, which are actually repeats. They get through to this point because
+           of the incrementing sequence numbers. We need to filter them here in a context sensitive manner. */
+        if (t->current_rx_data_type != data_type  ||  t->current_rx_field_type != field_type)
+        {
+            if (len > 0)
+            {
+                if (!fe->rx_signal_present)
+                {
+                    t30_non_ecm_put_bit(&s->t30, SIG_STATUS_TRAINING_SUCCEEDED);
+                    fe->rx_signal_present = TRUE;
+                }
+                /*endif*/
+                bit_reverse(buf2, buf, len);
+                t30_non_ecm_put_chunk(&s->t30, buf2, len);
+            }
+            /*endif*/
+            /* WORKAROUND: At least some Mediatrix boxes have a bug, where they can send HDLC signal end where
+                           they should send non-ECM signal end. It is possible they also do the opposite.
+                           We need to tolerate this, so we use the generic receive complete
+                           indication, rather than the specific non-ECM carrier down. */
+            front_end_status(s, T30_FRONT_END_RECEIVE_COMPLETE);
+        }
+        /*endif*/
+        fe->rx_signal_present = FALSE;
+        /* Treat this like a no signal indicator has occurred, so if the no signal indicator is missing, we are still OK */
+        fake_rx_indicator(t, s, T38_IND_NO_SIGNAL);
+        break;
+    default:
+        break;
+    }
+    /*endswitch*/
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static void send_hdlc(void *user_data, const uint8_t *msg, int len)
+{
+    t38_terminal_state_t *s;
+
+    s = (t38_terminal_state_t *) user_data;
+    if (len <= 0)
+    {
+        s->t38_fe.hdlc_tx.len = -1;
+    }
+    else
+    {
+        s->t38_fe.hdlc_tx.extra_bits = extra_bits_in_stuffed_frame(msg, len);
+        bit_reverse(s->t38_fe.hdlc_tx.buf, msg, len);
+        s->t38_fe.hdlc_tx.len = len;
+        s->t38_fe.hdlc_tx.ptr = 0;
+    }
+    /*endif*/
+}
+/*- End of function --------------------------------------------------------*/
+
+static __inline__ int bits_to_us(t38_terminal_state_t *s, int bits)
+{
+    if (s->t38_fe.ms_per_tx_chunk == 0  ||  s->t38_fe.tx_bit_rate == 0)
+        return 0;
+    /*endif*/
+    return bits*1000000/s->t38_fe.tx_bit_rate;
+}
+/*- End of function --------------------------------------------------------*/
+
+static void set_octets_per_data_packet(t38_terminal_state_t *s, int bit_rate)
+{
+    s->t38_fe.tx_bit_rate = bit_rate;
+    if (s->t38_fe.ms_per_tx_chunk)
+    {
+        s->t38_fe.octets_per_data_packet = s->t38_fe.ms_per_tx_chunk*bit_rate/(8*1000);
+        /* Make sure we have a positive number (i.e. we didn't truncate to zero). */
+        if (s->t38_fe.octets_per_data_packet < 1)
+            s->t38_fe.octets_per_data_packet = 1;
+        /*endif*/
+    }
+    else
+    {
+        s->t38_fe.octets_per_data_packet = MAX_OCTETS_PER_UNPACED_CHUNK;
+    }
+    /*endif*/
+}
+/*- End of function --------------------------------------------------------*/
+
+static int set_no_signal(t38_terminal_state_t *s)
+{
+    int delay;
+
+    if ((s->t38_fe.chunking_modes & T38_CHUNKING_SEND_REGULAR_INDICATORS))
+    {
+        t38_core_send_indicator(&s->t38_fe.t38, 0x100 | T38_IND_NO_SIGNAL);
+        s->t38_fe.timed_step = T38_TIMED_STEP_NO_SIGNAL;
+#if 0
+        if ((s->t38_fe.chunking_modes & T38_CHUNKING_SEND_2S_REGULAR_INDICATORS))
+            s->t38_fe.timeout_tx_samples = s->t38_fe.next_tx_samples + us_to_samples(2000000);
+        else
+            s->t38_fe.timeout_tx_samples = 0;
+        /*endif*/
+#endif
+        return s->t38_fe.ms_per_tx_chunk*1000;
+    }
+    /*endif*/
+    delay = t38_core_send_indicator(&s->t38_fe.t38, T38_IND_NO_SIGNAL);
+    s->t38_fe.timed_step = T38_TIMED_STEP_NONE;
+    return delay;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int stream_no_signal(t38_terminal_state_t *s)
+{
+    t38_core_send_indicator(&s->t38_fe.t38, 0x100 | T38_IND_NO_SIGNAL);
+#if 0
+    if (s->t38_fe.timeout_tx_samples  &&  s->t38_fe.next_tx_samples >= s->t38_fe.timeout_tx_samples)
+        s->t38_fe.timed_step = T38_TIMED_STEP_NONE;
+    /*endif*/
+#endif
+    return s->t38_fe.ms_per_tx_chunk*1000;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int stream_non_ecm(t38_terminal_state_t *s)
+{
+    t38_terminal_front_end_state_t *fe;
+    uint8_t buf[MAX_OCTETS_PER_UNPACED_CHUNK + 50];
+    int delay;
+    int len;
+
+    fe = &s->t38_fe;
+    for (delay = 0;  delay == 0;  )
+    {
+        switch (fe->timed_step)
+        {
+        case T38_TIMED_STEP_NON_ECM_MODEM:
+            /* Create a 75ms silence */
+            if (fe->t38.current_tx_indicator != T38_IND_NO_SIGNAL)
+                delay = t38_core_send_indicator(&fe->t38, T38_IND_NO_SIGNAL);
+            else
+                delay = 75000;
+            /*endif*/
+            fe->timed_step = T38_TIMED_STEP_NON_ECM_MODEM_2;
+#if 0
+            fe->timeout_tx_samples = fe->next_tx_samples
+                                   + us_to_samples(t38_core_send_training_delay(&fe->t38, fe->next_tx_indicator));
+#endif
+            fe->next_tx_samples = fe->samples;
+            break;
+        case T38_TIMED_STEP_NON_ECM_MODEM_2:
+            /* Switch on a fast modem, and give the training time to complete */
+#if 0
+            if ((s->t38_fe.chunking_modes & T38_CHUNKING_SEND_REGULAR_INDICATORS))
+            {
+                delay = t38_core_send_indicator(&fe->t38, 0x100 | fe->next_tx_indicator);
+                if (fe->next_tx_samples >= fe->timeout_tx_samples)
+                    fe->timed_step = T38_TIMED_STEP_NON_ECM_MODEM_3;
+                /*endif*/
+                return fe->ms_per_tx_chunk*1000;
+            }
+            /*endif*/
+#endif
+            delay = t38_core_send_indicator(&fe->t38, fe->next_tx_indicator);
+            fe->timed_step = T38_TIMED_STEP_NON_ECM_MODEM_3;
+            break;
+        case T38_TIMED_STEP_NON_ECM_MODEM_3:
+            /* Send a chunk of non-ECM image data */
+            /* T.38 says it is OK to send the last of the non-ECM data in the signal end message.
+               However, I think the early versions of T.38 said the signal end message should not
+               contain data. Hopefully, following the current spec will not cause compatibility
+               issues. */
+            len = t30_non_ecm_get_chunk(&s->t30, buf, fe->octets_per_data_packet);
+            if (len > 0)
+                bit_reverse(buf, buf, len);
+            /*endif*/
+            if (len < fe->octets_per_data_packet)
+            {
+                /* That's the end of the image data. */
+                if (s->t38_fe.ms_per_tx_chunk)
+                {
+                    /* Pad the end of the data with some zeros. If we just stop abruptly
+                       at the end of the EOLs, some ATAs fail to clean up properly before
+                       shutting down their transmit modem, and the last few rows of the image
+                       are lost or corrupted. Simply delaying the no-signal message does not
+                       help for all implentations. It is usually ignored, which is probably
+                       the right thing to do after receiving a message saying the signal has
+                       ended. */
+                    memset(buf + len, 0, fe->octets_per_data_packet - len);
+                    fe->non_ecm_trailer_bytes = 3*fe->octets_per_data_packet + len;
+                    len = fe->octets_per_data_packet;
+                    fe->timed_step = T38_TIMED_STEP_NON_ECM_MODEM_4;
+                }
+                else
+                {
+                    /* If we are sending quickly there seems no point in doing any padding */
+                    t38_core_send_data(&fe->t38, fe->current_tx_data_type, T38_FIELD_T4_NON_ECM_SIG_END, buf, len, T38_PACKET_CATEGORY_IMAGE_DATA_END);
+                    fe->timed_step = T38_TIMED_STEP_NON_ECM_MODEM_5;
+                    delay = 0;
+                    break;
+                }
+                /*endif*/
+            }
+            /*endif*/
+            t38_core_send_data(&fe->t38, fe->current_tx_data_type, T38_FIELD_T4_NON_ECM_DATA, buf, len, T38_PACKET_CATEGORY_IMAGE_DATA);
+            delay = bits_to_us(s, 8*len);
+            break;
+        case T38_TIMED_STEP_NON_ECM_MODEM_4:
+            /* Send padding */
+            len = fe->octets_per_data_packet;
+            fe->non_ecm_trailer_bytes -= fe->octets_per_data_packet;
+            if (fe->non_ecm_trailer_bytes <= 0)
+            {
+                len += fe->non_ecm_trailer_bytes;
+                memset(buf, 0, len);
+                t38_core_send_data(&fe->t38, fe->current_tx_data_type, T38_FIELD_T4_NON_ECM_SIG_END, buf, len, T38_PACKET_CATEGORY_IMAGE_DATA_END);
+                fe->timed_step = T38_TIMED_STEP_NON_ECM_MODEM_5;
+                /* Allow a bit more time than the data will take to play out, to ensure the far ATA does not
+                   cut things short. */
+                delay = bits_to_us(s, 8*len);
+                if (s->t38_fe.ms_per_tx_chunk)
+                    delay += 60000;
+                /*endif*/
+                front_end_status(s, T30_FRONT_END_SEND_STEP_COMPLETE);
+                break;
+            }
+            /*endif*/
+            memset(buf, 0, len);
+            t38_core_send_data(&fe->t38, fe->current_tx_data_type, T38_FIELD_T4_NON_ECM_DATA, buf, len, T38_PACKET_CATEGORY_IMAGE_DATA);
+            delay = bits_to_us(s, 8*len);
+            break;
+        case T38_TIMED_STEP_NON_ECM_MODEM_5:
+            /* This should not be needed, since the message above indicates the end of the signal, but it
+               seems like it can improve compatibility with quirky implementations. */
+            return set_no_signal(s);
+        }
+        /*endswitch*/
+    }
+    /*endfor*/
+    return delay;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int stream_hdlc(t38_terminal_state_t *s)
+{
+    t38_terminal_front_end_state_t *fe;
+    uint8_t buf[MAX_OCTETS_PER_UNPACED_CHUNK + 50];
+    t38_data_field_t data_fields[2];
+    int category;
+    int previous;
+    int delay;
+    int i;
+
+    fe = &s->t38_fe;
+    for (delay = 0;  delay == 0;  )
+    {
+        switch (fe->timed_step)
+        {
+        case T38_TIMED_STEP_HDLC_MODEM:
+            /* Create a 75ms silence */
+            if (fe->t38.current_tx_indicator != T38_IND_NO_SIGNAL)
+                delay = t38_core_send_indicator(&fe->t38, T38_IND_NO_SIGNAL);
+            else
+                delay = 75000;
+            /*endif*/
+            fe->timed_step = T38_TIMED_STEP_HDLC_MODEM_2;
+#if 0
+            fe->timeout_tx_samples = fe->next_tx_samples
+                                   + us_to_samples(t38_core_send_training_delay(&fe->t38, fe->next_tx_indicator))
+                                   + us_to_samples(t38_core_send_flags_delay(&fe->t38, fe->next_tx_indicator))
+                                   + us_to_samples(delay);
+#endif
+            fe->next_tx_samples = fe->samples;
+            break;
+        case T38_TIMED_STEP_HDLC_MODEM_2:
+            /* Send HDLC preambling */
+#if 0
+            if ((s->t38_fe.chunking_modes & T38_CHUNKING_SEND_REGULAR_INDICATORS))
+            {
+                delay = t38_core_send_indicator(&fe->t38, 0x100 | fe->next_tx_indicator);
+                if (fe->next_tx_samples >= fe->timeout_tx_samples)
+                    fe->timed_step = T38_TIMED_STEP_HDLC_MODEM_3;
+                /*endif*/
+                return fe->ms_per_tx_chunk*1000;
+            }
+            /*endif*/
+#endif
+            delay = t38_core_send_indicator(&fe->t38, fe->next_tx_indicator)
+                  + t38_core_send_flags_delay(&fe->t38, fe->next_tx_indicator);
+            fe->timed_step = T38_TIMED_STEP_HDLC_MODEM_3;
+            break;
+        case T38_TIMED_STEP_HDLC_MODEM_3:
+            /* Send a chunk of HDLC data */
+            i = fe->hdlc_tx.len - fe->hdlc_tx.ptr;
+            if (fe->octets_per_data_packet >= i)
+            {
+                /* The last part of an HDLC frame */
+                if (fe->chunking_modes & T38_CHUNKING_MERGE_FCS_WITH_DATA)
+                {
+                    /* Copy the data, as we might be about to refill the buffer it is in */
+                    memcpy(buf, &fe->hdlc_tx.buf[fe->hdlc_tx.ptr], i);
+                    data_fields[0].field_type = T38_FIELD_HDLC_DATA;
+                    data_fields[0].field = buf;
+                    data_fields[0].field_len = i;
+
+                    /* Now see about the next HDLC frame. This will tell us whether to send FCS_OK or FCS_OK_SIG_END */
+                    previous = fe->current_tx_data_type;
+                    fe->hdlc_tx.ptr = 0;
+                    fe->hdlc_tx.len = 0;
+                    front_end_status(s, T30_FRONT_END_SEND_STEP_COMPLETE);
+                    /* The above step should have got the next HDLC step ready - either another frame, or an instruction to stop transmission. */
+                    if (fe->hdlc_tx.len < 0)
+                    {
+                        data_fields[1].field_type = T38_FIELD_HDLC_FCS_OK_SIG_END;
+                        data_fields[1].field = NULL;
+                        data_fields[1].field_len = 0;
+                        category = (s->t38_fe.current_tx_data_type == T38_DATA_V21)  ?  T38_PACKET_CATEGORY_CONTROL_DATA_END  :  T38_PACKET_CATEGORY_IMAGE_DATA_END;
+                        t38_core_send_data_multi_field(&fe->t38, fe->current_tx_data_type, data_fields, 2, category);
+                        fe->timed_step = T38_TIMED_STEP_HDLC_MODEM_5;
+                        /* We add a bit of extra time here, as with some implementations
+                           the carrier falling too abruptly causes data loss. */
+                        delay = bits_to_us(s, i*8 + fe->hdlc_tx.extra_bits);
+                        if (s->t38_fe.ms_per_tx_chunk)
+                            delay += 100000;
+                        /*endif*/
+                        front_end_status(s, T30_FRONT_END_SEND_STEP_COMPLETE);
+                    }
+                    else
+                    {
+                        data_fields[1].field_type = T38_FIELD_HDLC_FCS_OK;
+                        data_fields[1].field = NULL;
+                        data_fields[1].field_len = 0;
+                        category = (s->t38_fe.current_tx_data_type == T38_DATA_V21)  ?  T38_PACKET_CATEGORY_CONTROL_DATA  :  T38_PACKET_CATEGORY_IMAGE_DATA;
+                        t38_core_send_data_multi_field(&fe->t38, fe->current_tx_data_type, data_fields, 2, category);
+                        fe->timed_step = T38_TIMED_STEP_HDLC_MODEM_3;
+                        delay = bits_to_us(s, i*8 + fe->hdlc_tx.extra_bits);
+                    }
+                    /*endif*/
+                    break;
+                }
+                /*endif*/
+                category = (s->t38_fe.current_tx_data_type == T38_DATA_V21)  ?  T38_PACKET_CATEGORY_CONTROL_DATA  :  T38_PACKET_CATEGORY_IMAGE_DATA;
+                t38_core_send_data(&fe->t38, fe->current_tx_data_type, T38_FIELD_HDLC_DATA, &fe->hdlc_tx.buf[fe->hdlc_tx.ptr], i, category);
+                fe->timed_step = T38_TIMED_STEP_HDLC_MODEM_4;
+            }
+            else
+            {
+                i = fe->octets_per_data_packet;
+                category = (s->t38_fe.current_tx_data_type == T38_DATA_V21)  ?  T38_PACKET_CATEGORY_CONTROL_DATA  :  T38_PACKET_CATEGORY_IMAGE_DATA;
+                t38_core_send_data(&fe->t38, fe->current_tx_data_type, T38_FIELD_HDLC_DATA, &fe->hdlc_tx.buf[fe->hdlc_tx.ptr], i, category);
+                fe->hdlc_tx.ptr += i;
+            }
+            /*endif*/
+            delay = bits_to_us(s, i*8);
+            break;
+        case T38_TIMED_STEP_HDLC_MODEM_4:
+            /* End of HDLC frame */
+            previous = fe->current_tx_data_type;
+            fe->hdlc_tx.ptr = 0;
+            fe->hdlc_tx.len = 0;
+            front_end_status(s, T30_FRONT_END_SEND_STEP_COMPLETE);
+            /* The above step should have got the next HDLC step ready - either another frame, or an instruction to stop transmission. */
+            if (fe->hdlc_tx.len < 0)
+            {
+                /* End of transmission */
+                category = (s->t38_fe.current_tx_data_type == T38_DATA_V21)  ?  T38_PACKET_CATEGORY_CONTROL_DATA_END  :  T38_PACKET_CATEGORY_IMAGE_DATA_END;
+                t38_core_send_data(&fe->t38, previous, T38_FIELD_HDLC_FCS_OK_SIG_END, NULL, 0, category);
+                fe->timed_step = T38_TIMED_STEP_HDLC_MODEM_5;
+                /* We add a bit of extra time here, as with some implementations
+                   the carrier falling too abruptly causes data loss. */
+                delay = bits_to_us(s, fe->hdlc_tx.extra_bits);
+                if (s->t38_fe.ms_per_tx_chunk)
+                    delay += 100000;
+                /*endif*/
+                front_end_status(s, T30_FRONT_END_SEND_STEP_COMPLETE);
+                break;
+            }
+            /*endif*/
+            if (fe->hdlc_tx.len == 0)
+            {
+                /* Now, how did we get here? We have finished a frame, but have no new frame to
+                   send, and no end of transmission condition. */
+                span_log(&s->logging, SPAN_LOG_FLOW, "No new frame or end transmission condition.\n");
+            }
+            /*endif*/
+            /* Finish the current frame off, and prepare for the next one. */
+            category = (s->t38_fe.current_tx_data_type == T38_DATA_V21)  ?  T38_PACKET_CATEGORY_CONTROL_DATA  :  T38_PACKET_CATEGORY_IMAGE_DATA;
+            t38_core_send_data(&fe->t38, previous, T38_FIELD_HDLC_FCS_OK, NULL, 0, category);
+            fe->timed_step = T38_TIMED_STEP_HDLC_MODEM_3;
+            /* We should now wait enough time for everything to clear through an analogue modem at the far end. */
+            delay = bits_to_us(s, fe->hdlc_tx.extra_bits);
+            break;
+        case T38_TIMED_STEP_HDLC_MODEM_5:
+            /* Note that some boxes do not like us sending a T38_FIELD_HDLC_SIG_END at this point.
+               A T38_IND_NO_SIGNAL should always be OK. */
+            return set_no_signal(s);
+        }
+        /*endswitch*/
+    }
+    /*endfor*/
+    return delay;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int stream_ced(t38_terminal_state_t *s)
+{
+    t38_terminal_front_end_state_t *fe;
+    int delay;
+
+    fe = &s->t38_fe;
+    for (delay = 0;  delay == 0;  )
+    {
+        switch (fe->timed_step)
+        {
+        case T38_TIMED_STEP_CED:
+            /* It seems common practice to start with a no signal indicator, though
+               this is not a specified requirement. Since we should be sending 200ms
+               of silence, starting the delay with a no signal indication makes sense.
+               We do need a 200ms delay, as that is a specification requirement. */
+            fe->timed_step = T38_TIMED_STEP_CED_2;
+            delay = t38_core_send_indicator(&fe->t38, T38_IND_NO_SIGNAL);
+            delay = 200000;
+            fe->next_tx_samples = fe->samples;
+            break;
+        case T38_TIMED_STEP_CED_2:
+            /* Initial 200ms delay over. Send the CED indicator */
+            fe->timed_step = T38_TIMED_STEP_CED_3;
+            delay = t38_core_send_indicator(&fe->t38, T38_IND_CED);
+            fe->current_tx_data_type = T38_DATA_NONE;
+            break;
+        case T38_TIMED_STEP_CED_3:
+            /* End of CED */
+            fe->timed_step = T38_TIMED_STEP_NONE;
+            front_end_status(s, T30_FRONT_END_SEND_STEP_COMPLETE);
+            return 0;
+        }
+        /*endswitch*/
+    }
+    /*endfor*/
+    return delay;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int stream_cng(t38_terminal_state_t *s)
+{
+    t38_terminal_front_end_state_t *fe;
+    int delay;
+
+    fe = &s->t38_fe;
+    for (delay = 0;  delay == 0;  )
+    {
+        switch (fe->timed_step)
+        {
+        case T38_TIMED_STEP_CNG:
+            /* It seems common practice to start with a no signal indicator, though
+               this is not a specified requirement of the T.38 spec. Since we should
+               be sending 200ms of silence, according to T.30, starting that delay with
+               a no signal indication makes sense. */
+            fe->timed_step = T38_TIMED_STEP_CNG_2;
+            delay = t38_core_send_indicator(&fe->t38, T38_IND_NO_SIGNAL);
+            delay = 200000;
+            fe->next_tx_samples = fe->samples;
+            break;
+        case T38_TIMED_STEP_CNG_2:
+            /* Initial short delay over. Send the CNG indicator. CNG persists until something
+               coming the other way interrupts it, or a long timeout controlled by the T.30 engine
+               expires. */
+            fe->timed_step = T38_TIMED_STEP_NONE;
+            delay = t38_core_send_indicator(&fe->t38, T38_IND_CNG);
+            fe->current_tx_data_type = T38_DATA_NONE;
+            return delay;
+        }
+        /*endswitch*/
+    }
+    /*endfor*/
+    return delay;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t38_terminal_send_timeout(t38_terminal_state_t *s, int samples)
+{
+    t38_terminal_front_end_state_t *fe;
+    int delay;
+
+    fe = &s->t38_fe;
+    if (fe->current_rx_type == T30_MODEM_DONE  ||  fe->current_tx_type == T30_MODEM_DONE)
+        return TRUE;
+    /*endif*/
+
+    fe->samples += samples;
+    t30_timer_update(&s->t30, samples);
+    if (fe->timeout_rx_samples  &&  fe->samples > fe->timeout_rx_samples)
+    {
+        span_log(&s->logging, SPAN_LOG_FLOW, "Timeout mid-receive\n");
+        fe->timeout_rx_samples = 0;
+        front_end_status(s, T30_FRONT_END_RECEIVE_COMPLETE);
+    }
+    /*endif*/
+    if (fe->timed_step == T38_TIMED_STEP_NONE)
+        return FALSE;
+    /*endif*/
+    /* Wait until the right time comes along, unless we are working in "no delays" mode, while talking to an
+       IAF terminal. */
+    if (fe->ms_per_tx_chunk  &&  fe->samples < fe->next_tx_samples)
+        return FALSE;
+    /*endif*/
+    /* Its time to send something */
+    delay = 0;
+    switch ((fe->timed_step & 0xFFF0))
+    {
+    case T38_TIMED_STEP_NON_ECM_MODEM:
+        delay = stream_non_ecm(s);
+        break;
+    case T38_TIMED_STEP_HDLC_MODEM:
+        delay = stream_hdlc(s);
+        break;
+    case T38_TIMED_STEP_CED:
+        delay = stream_ced(s);
+        break;
+    case T38_TIMED_STEP_CNG:
+        delay = stream_cng(s);
+        break;
+    case T38_TIMED_STEP_PAUSE:
+        /* End of timed pause */
+        fe->timed_step = T38_TIMED_STEP_NONE;
+        front_end_status(s, T30_FRONT_END_SEND_STEP_COMPLETE);
+        break;
+    case T38_TIMED_STEP_NO_SIGNAL:
+        delay = stream_no_signal(s);
+        break;
+    }
+    /*endswitch*/
+    fe->next_tx_samples += us_to_samples(delay);
+    return FALSE;
+}
+/*- End of function --------------------------------------------------------*/
+
+static void set_rx_type(void *user_data, int type, int bit_rate, int short_train, int use_hdlc)
+{
+    t38_terminal_state_t *s;
+
+    s = (t38_terminal_state_t *) user_data;
+    span_log(&s->logging, SPAN_LOG_FLOW, "Set rx type %d\n", type);
+    s->t38_fe.current_rx_type = type;
+}
+/*- End of function --------------------------------------------------------*/
+
+static void start_tx(t38_terminal_front_end_state_t *fe, int use_hdlc)
+{
+    /* The actual transmission process depends on whether we are sending at a paced manner,
+       for interaction with a traditional FAX machine, or streaming as fast as we can, normally
+       over a TCP connection to a machine directly connected to the internet. */
+    if (fe->ms_per_tx_chunk)
+    {
+        /* Start the paced packet transmission process. */
+        fe->timed_step = (use_hdlc)  ?  T38_TIMED_STEP_HDLC_MODEM  :  T38_TIMED_STEP_NON_ECM_MODEM;
+        if (fe->next_tx_samples < fe->samples)
+            fe->next_tx_samples = fe->samples;
+        /*endif*/
+    }
+    else
+    {
+        /* Start the fast streaming transmission process. */
+    }
+    /*endif*/
+}
+/*- End of function --------------------------------------------------------*/
+
+static void set_tx_type(void *user_data, int type, int bit_rate, int short_train, int use_hdlc)
+{
+    t38_terminal_state_t *s;
+    t38_terminal_front_end_state_t *fe;
+
+    s = (t38_terminal_state_t *) user_data;
+    fe = &s->t38_fe;
+    span_log(&s->logging, SPAN_LOG_FLOW, "Set tx type %d\n", type);
+    if (fe->current_tx_type == type)
+        return;
+    /*endif*/
+
+    set_octets_per_data_packet(s, bit_rate);
+    switch (type)
+    {
+    case T30_MODEM_NONE:
+        /* If a "no signal" indicator is waiting to be played out, don't disturb it. */
+        if (fe->timed_step != T38_TIMED_STEP_NON_ECM_MODEM_5  &&  fe->timed_step != T38_TIMED_STEP_HDLC_MODEM_5)
+            fe->timed_step = T38_TIMED_STEP_NONE;
+        /*endif*/
+        fe->current_tx_data_type = T38_DATA_NONE;
+        break;
+    case T30_MODEM_PAUSE:
+        fe->next_tx_samples = fe->samples + ms_to_samples(short_train);
+        fe->timed_step = T38_TIMED_STEP_PAUSE;
+        fe->current_tx_data_type = T38_DATA_NONE;
+        break;
+    case T30_MODEM_CED:
+        fe->next_tx_samples = fe->samples;
+        fe->timed_step = T38_TIMED_STEP_CED;
+        fe->current_tx_data_type = T38_DATA_NONE;
+        break;
+    case T30_MODEM_CNG:
+        fe->next_tx_samples = fe->samples;
+        fe->timed_step = T38_TIMED_STEP_CNG;
+        fe->current_tx_data_type = T38_DATA_NONE;
+        break;
+    case T30_MODEM_V21:
+        fe->next_tx_indicator = T38_IND_V21_PREAMBLE;
+        fe->current_tx_data_type = T38_DATA_V21;
+        start_tx(fe, use_hdlc);
+        break;
+    case T30_MODEM_V27TER:
+        switch (bit_rate)
+        {
+        case 2400:
+            fe->next_tx_indicator = T38_IND_V27TER_2400_TRAINING;
+            fe->current_tx_data_type = T38_DATA_V27TER_2400;
+            break;
+        case 4800:
+            fe->next_tx_indicator = T38_IND_V27TER_4800_TRAINING;
+            fe->current_tx_data_type = T38_DATA_V27TER_4800;
+            break;
+        }
+        /*endswitch*/
+        start_tx(fe, use_hdlc);
+        break;
+    case T30_MODEM_V29:
+        switch (bit_rate)
+        {
+        case 7200:
+            fe->next_tx_indicator = T38_IND_V29_7200_TRAINING;
+            fe->current_tx_data_type = T38_DATA_V29_7200;
+            break;
+        case 9600:
+            fe->next_tx_indicator = T38_IND_V29_9600_TRAINING;
+            fe->current_tx_data_type = T38_DATA_V29_9600;
+            break;
+        }
+        /*endswitch*/
+        start_tx(fe, use_hdlc);
+        break;
+    case T30_MODEM_V17:
+        switch (bit_rate)
+        {
+        case 7200:
+            fe->next_tx_indicator = (short_train)  ?  T38_IND_V17_7200_SHORT_TRAINING  :  T38_IND_V17_7200_LONG_TRAINING;
+            fe->current_tx_data_type = T38_DATA_V17_7200;
+            break;
+        case 9600:
+            fe->next_tx_indicator = (short_train)  ?  T38_IND_V17_9600_SHORT_TRAINING  :  T38_IND_V17_9600_LONG_TRAINING;
+            fe->current_tx_data_type = T38_DATA_V17_9600;
+            break;
+        case 12000:
+            fe->next_tx_indicator = (short_train)  ?  T38_IND_V17_12000_SHORT_TRAINING  :  T38_IND_V17_12000_LONG_TRAINING;
+            fe->current_tx_data_type = T38_DATA_V17_12000;
+            break;
+        case 14400:
+            fe->next_tx_indicator = (short_train)  ?  T38_IND_V17_14400_SHORT_TRAINING  :  T38_IND_V17_14400_LONG_TRAINING;
+            fe->current_tx_data_type = T38_DATA_V17_14400;
+            break;
+        }
+        /*endswitch*/
+        start_tx(fe, use_hdlc);
+        break;
+    case T30_MODEM_DONE:
+        span_log(&s->logging, SPAN_LOG_FLOW, "FAX exchange complete\n");
+        fe->timed_step = T38_TIMED_STEP_NONE;
+        fe->current_tx_data_type = T38_DATA_NONE;
+        break;
+    }
+    /*endswitch*/
+    fe->current_tx_type = type;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t38_terminal_set_config(t38_terminal_state_t *s, int config)
+{
+    if ((config & T38_TERMINAL_OPTION_NO_PACING))
+    {
+        /* Continuous streaming mode, as used for TPKT over TCP transport */
+        /* Inhibit indicator packets */
+        t38_set_redundancy_control(&s->t38_fe.t38, T38_PACKET_CATEGORY_INDICATOR, 0);
+        t38_set_redundancy_control(&s->t38_fe.t38, T38_PACKET_CATEGORY_CONTROL_DATA, 1);
+        t38_set_redundancy_control(&s->t38_fe.t38, T38_PACKET_CATEGORY_CONTROL_DATA_END, 1);
+        t38_set_redundancy_control(&s->t38_fe.t38, T38_PACKET_CATEGORY_IMAGE_DATA, 1);
+        t38_set_redundancy_control(&s->t38_fe.t38, T38_PACKET_CATEGORY_IMAGE_DATA_END, 1);
+        s->t38_fe.ms_per_tx_chunk = 0;
+        s->t38_fe.chunking_modes &= ~T38_CHUNKING_SEND_REGULAR_INDICATORS;
+    }
+    else
+    {
+        /* Paced streaming mode, as used for UDP transports */
+        t38_set_redundancy_control(&s->t38_fe.t38, T38_PACKET_CATEGORY_INDICATOR, INDICATOR_TX_COUNT);
+        t38_set_redundancy_control(&s->t38_fe.t38, T38_PACKET_CATEGORY_CONTROL_DATA, DATA_TX_COUNT);
+        t38_set_redundancy_control(&s->t38_fe.t38, T38_PACKET_CATEGORY_CONTROL_DATA_END, DATA_END_TX_COUNT);
+        t38_set_redundancy_control(&s->t38_fe.t38, T38_PACKET_CATEGORY_IMAGE_DATA, DATA_TX_COUNT);
+        t38_set_redundancy_control(&s->t38_fe.t38, T38_PACKET_CATEGORY_IMAGE_DATA_END, DATA_END_TX_COUNT);
+        s->t38_fe.ms_per_tx_chunk = DEFAULT_MS_PER_TX_CHUNK;
+        if ((config & (T38_TERMINAL_OPTION_REGULAR_INDICATORS | T38_TERMINAL_OPTION_2S_REPEATING_INDICATORS)))
+            s->t38_fe.chunking_modes |= T38_CHUNKING_SEND_REGULAR_INDICATORS;
+        else
+            s->t38_fe.chunking_modes &= ~T38_CHUNKING_SEND_REGULAR_INDICATORS;
+        /*endif*/
+        if ((config & T38_TERMINAL_OPTION_2S_REPEATING_INDICATORS))
+            s->t38_fe.chunking_modes |= T38_CHUNKING_SEND_2S_REGULAR_INDICATORS;
+        else
+            s->t38_fe.chunking_modes &= ~T38_CHUNKING_SEND_2S_REGULAR_INDICATORS;
+        /*endif*/
+    }
+    /*endif*/
+    set_octets_per_data_packet(s, 300);
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t38_terminal_set_tep_mode(t38_terminal_state_t *s, int use_tep)
+{
+    if (use_tep)
+        s->t38_fe.chunking_modes |= T38_CHUNKING_ALLOW_TEP_TIME;
+    else
+        s->t38_fe.chunking_modes &= ~T38_CHUNKING_ALLOW_TEP_TIME;
+    /*endif*/
+    t38_set_tep_handling(&s->t38_fe.t38, use_tep);
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t38_terminal_set_fill_bit_removal(t38_terminal_state_t *s, int remove)
+{
+    if (remove)
+        s->t38_fe.iaf |= T30_IAF_MODE_NO_FILL_BITS;
+    else
+        s->t38_fe.iaf &= ~T30_IAF_MODE_NO_FILL_BITS;
+    /*endif*/
+    t30_set_iaf_mode(&s->t30, s->t38_fe.iaf);
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(t30_state_t *) t38_terminal_get_t30_state(t38_terminal_state_t *s)
+{
+    return &s->t30;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(t38_core_state_t *) t38_terminal_get_t38_core_state(t38_terminal_state_t *s)
+{
+    return &s->t38_fe.t38;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int t38_terminal_t38_fe_restart(t38_terminal_state_t *t)
+{
+    t38_terminal_front_end_state_t *s;
+    
+    s = &t->t38_fe;
+    t38_core_restart(&s->t38);
+
+    s->rx_signal_present = FALSE;
+    s->timed_step = T38_TIMED_STEP_NONE;
+    //s->iaf = T30_IAF_MODE_T37 | T30_IAF_MODE_T38;
+    s->iaf = T30_IAF_MODE_T38;
+
+    s->current_tx_data_type = T38_DATA_NONE;
+    s->next_tx_samples = 0;
+
+    s->hdlc_tx.ptr = 0;
+
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int t38_terminal_t38_fe_init(t38_terminal_state_t *t,
+                                    t38_tx_packet_handler_t *tx_packet_handler,
+                                    void *tx_packet_user_data)
+{
+    t38_terminal_front_end_state_t *s;
+    
+    s = &t->t38_fe;
+    t38_core_init(&s->t38,
+                  process_rx_indicator,
+                  process_rx_data,
+                  process_rx_missing,
+                  (void *) t,
+                  tx_packet_handler,
+                  tx_packet_user_data);
+    t38_set_fastest_image_data_rate(&s->t38, 14400);
+
+    s->rx_signal_present = FALSE;
+    s->timed_step = T38_TIMED_STEP_NONE;
+    //s->iaf = T30_IAF_MODE_T37 | T30_IAF_MODE_T38;
+    s->iaf = T30_IAF_MODE_T38;
+
+    s->current_tx_data_type = T38_DATA_NONE;
+    s->next_tx_samples = 0;
+    s->chunking_modes = T38_CHUNKING_ALLOW_TEP_TIME;
+
+    s->hdlc_tx.ptr = 0;
+
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(logging_state_t *) t38_terminal_get_logging_state(t38_terminal_state_t *s)
+{
+    return &s->logging;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t38_terminal_restart(t38_terminal_state_t *s,
+                                       int calling_party)
+{
+    t38_terminal_t38_fe_restart(s);
+    t30_restart(&s->t30);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(t38_terminal_state_t *) t38_terminal_init(t38_terminal_state_t *s,
+                                                       int calling_party,
+                                                       t38_tx_packet_handler_t *tx_packet_handler,
+                                                       void *tx_packet_user_data)
+{
+    if (tx_packet_handler == NULL)
+        return NULL;
+    /*endif*/
+
+    if (s == NULL)
+    {
+        if ((s = (t38_terminal_state_t *) malloc(sizeof(*s))) == NULL)
+            return NULL;
+        /*endif*/
+    }
+    /*endif*/
+    memset(s, 0, sizeof(*s));
+    span_log_init(&s->logging, SPAN_LOG_NONE, NULL);
+    span_log_set_protocol(&s->logging, "T.38T");
+
+    t38_terminal_t38_fe_init(s, tx_packet_handler, tx_packet_user_data);
+
+    t38_terminal_set_config(s, 0);
+
+    t30_init(&s->t30,
+             calling_party,
+             set_rx_type,
+             (void *) s,
+             set_tx_type,
+             (void *) s,
+             send_hdlc,
+             (void *) s);
+    t30_set_iaf_mode(&s->t30, s->t38_fe.iaf);
+    t30_set_supported_modems(&s->t30,
+                             T30_SUPPORT_V27TER | T30_SUPPORT_V29 | T30_SUPPORT_V17 | T30_SUPPORT_IAF);
+    t30_restart(&s->t30);
+    return s;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t38_terminal_release(t38_terminal_state_t *s)
+{
+    t30_release(&s->t30);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t38_terminal_free(t38_terminal_state_t *s)
+{
+    t38_terminal_release(s);
+    free(s);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+/*- End of file ------------------------------------------------------------*/
diff --git a/src/codec/spandsp/src/t4_rx.c b/src/codec/spandsp/src/t4_rx.c
new file mode 100644 (file)
index 0000000..67abcca
--- /dev/null
@@ -0,0 +1,1242 @@
+//#define T4_STATE_DEBUGGING
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * t4_rx.c - ITU T.4 FAX image receive processing
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2003, 2007 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.
+ */
+
+/*
+ * Much of this file is based on the T.4 and T.6 support in libtiff, which requires
+ * the following notice in any derived source code:
+ *
+ * Copyright (c) 1990-1997 Sam Leffler
+ * Copyright (c) 1991-1997 Silicon Graphics, Inc.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and 
+ * its documentation for any purpose is hereby granted without fee, provided
+ * that (i) the above copyright notices and this permission notice appear in
+ * all copies of the software and related documentation, and (ii) the names of
+ * Sam Leffler and Silicon Graphics may not be used in any advertising or
+ * publicity relating to the software without the specific, prior written
+ * permission of Sam Leffler and Silicon Graphics.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY 
+ * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.  
+ * 
+ * IN NO EVENT SHALL SAM LEFFLER OR SILICON GRAPHICS BE LIABLE FOR
+ * ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
+ * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+ * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF 
+ * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE 
+ * OF THIS SOFTWARE.
+ *
+ * Decoder support is derived from code in Frank Cringle's viewfax program;
+ *      Copyright (C) 1990, 1995  Frank D. Cringle.
+ */
+
+/*! \file */
+
+#if defined(HAVE_CONFIG_H)
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <inttypes.h>
+#include <limits.h>
+//TODO// #include <stdio.h>
+//TODO// #include <fcntl.h>
+//TODO// #include <unistd.h>
+#include <time.h>
+#include <memory.h>
+#include <string.h>
+#if defined(HAVE_TGMATH_H)
+#include <tgmath.h>
+#endif
+#if defined(HAVE_MATH_H)
+#include <math.h>
+#endif
+#include "floating_fudge.h"
+//TODO// #include <tiffio.h>
+
+#include "spandsp/0cpm.h"
+
+#include "spandsp/telephony.h"
+#include "spandsp/logging.h"
+#include "spandsp/bit_operations.h"
+#include "spandsp/async.h"
+#include "spandsp/t4_rx.h"
+#include "spandsp/t4_tx.h"
+#if defined(SPANDSP_SUPPORT_T85)
+#include "spandsp/t81_t82_arith_coding.h"
+#include "spandsp/t85.h"
+#endif
+#include "spandsp/t4_t6_decode.h"
+#include "spandsp/t4_t6_encode.h"
+#include "spandsp/version.h"
+
+#include "spandsp/private/logging.h"
+#if defined(SPANDSP_SUPPORT_T85)
+#include "spandsp/private/t81_t82_arith_coding.h"
+#include "spandsp/private/t85.h"
+#endif
+#include "spandsp/private/t4_t6_decode.h"
+#include "spandsp/private/t4_t6_encode.h"
+#include "spandsp/private/t4_rx.h"
+#include "spandsp/private/t4_tx.h"
+
+/*! The number of centimetres in one inch */
+#define CM_PER_INCH                 2.54f
+
+/*! The number of EOLs to expect at the end of a T.4 page */
+#define EOLS_TO_END_ANY_RX_PAGE     6
+/*! The number of EOLs to check at the end of a T.4 page */
+#define EOLS_TO_END_T4_RX_PAGE      5
+/*! The number of EOLs to check at the end of a T.6 page */
+#define EOLS_TO_END_T6_RX_PAGE      2
+
+#include "t4_t6_decode_states.h"
+
+#if defined(T4_STATE_DEBUGGING)
+static void STATE_TRACE(const char *format, ...)
+{
+    va_list arg_ptr;
+
+    va_start(arg_ptr, format);
+    vprintf(format, arg_ptr);
+    va_end(arg_ptr);
+}
+/*- End of function --------------------------------------------------------*/
+#else
+#define STATE_TRACE(...) /**/
+#endif
+
+#if defined(HAVE_LIBTIFF)
+static int set_tiff_directory_info(t4_state_t *s)
+{
+    time_t now;
+    struct tm *tm;
+    char buf[256 + 1];
+    uint16_t resunit;
+    float x_resolution;
+    float y_resolution;
+    t4_tiff_state_t *t;
+
+    t = &s->tiff;
+    /* Prepare the directory entry fully before writing the image, or libtiff complains */
+    TIFFSetField(t->tiff_file, TIFFTAG_COMPRESSION, t->output_compression);
+    if (t->output_compression == COMPRESSION_CCITT_T4)
+    {
+        TIFFSetField(t->tiff_file, TIFFTAG_T4OPTIONS, t->output_t4_options);
+        TIFFSetField(t->tiff_file, TIFFTAG_FAXMODE, FAXMODE_CLASSF);
+    }
+    TIFFSetField(t->tiff_file, TIFFTAG_IMAGEWIDTH, s->image_width);
+    TIFFSetField(t->tiff_file, TIFFTAG_BITSPERSAMPLE, 1);
+    TIFFSetField(t->tiff_file, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
+    TIFFSetField(t->tiff_file, TIFFTAG_SAMPLESPERPIXEL, 1);
+    if (t->output_compression == COMPRESSION_CCITT_T4
+        ||
+        t->output_compression == COMPRESSION_CCITT_T6)
+    {
+        TIFFSetField(t->tiff_file, TIFFTAG_ROWSPERSTRIP, -1L);
+    }
+    else
+    {
+        TIFFSetField(t->tiff_file,
+                     TIFFTAG_ROWSPERSTRIP,
+                     TIFFDefaultStripSize(t->tiff_file, 0));
+    }
+    TIFFSetField(t->tiff_file, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
+    TIFFSetField(t->tiff_file, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISWHITE);
+    TIFFSetField(t->tiff_file, TIFFTAG_FILLORDER, FILLORDER_LSB2MSB);
+
+    x_resolution = s->x_resolution/100.0f;
+    y_resolution = s->y_resolution/100.0f;
+    /* Metric seems the sane thing to use in the 21st century, but a lot of lousy software
+       gets FAX resolutions wrong, and more get it wrong using metric than using inches. */
+#if 0
+    TIFFSetField(t->tiff_file, TIFFTAG_XRESOLUTION, x_resolution);
+    TIFFSetField(t->tiff_file, TIFFTAG_YRESOLUTION, y_resolution);
+    resunit = RESUNIT_CENTIMETER;
+    TIFFSetField(t->tiff_file, TIFFTAG_RESOLUTIONUNIT, resunit);
+#else
+    TIFFSetField(t->tiff_file, TIFFTAG_XRESOLUTION, floorf(x_resolution*CM_PER_INCH + 0.5f));
+    TIFFSetField(t->tiff_file, TIFFTAG_YRESOLUTION, floorf(y_resolution*CM_PER_INCH + 0.5f));
+    resunit = RESUNIT_INCH;
+    TIFFSetField(t->tiff_file, TIFFTAG_RESOLUTIONUNIT, resunit);
+#endif
+    /* TODO: add the version of spandsp */
+    TIFFSetField(t->tiff_file, TIFFTAG_SOFTWARE, "Spandsp " SPANDSP_RELEASE_DATETIME_STRING);
+    if (gethostname(buf, sizeof(buf)) == 0)
+        TIFFSetField(t->tiff_file, TIFFTAG_HOSTCOMPUTER, buf);
+
+#if defined(TIFFTAG_FAXDCS)
+    if (t->dcs)
+        TIFFSetField(t->tiff_file, TIFFTAG_FAXDCS, t->dcs);
+#endif
+    if (t->sub_address)
+        TIFFSetField(t->tiff_file, TIFFTAG_FAXSUBADDRESS, t->sub_address);
+    if (t->far_ident)
+        TIFFSetField(t->tiff_file, TIFFTAG_IMAGEDESCRIPTION, t->far_ident);
+    if (t->vendor)
+        TIFFSetField(t->tiff_file, TIFFTAG_MAKE, t->vendor);
+    if (t->model)
+        TIFFSetField(t->tiff_file, TIFFTAG_MODEL, t->model);
+
+    time(&now);
+    tm = localtime(&now);
+    sprintf(buf,
+            "%4d/%02d/%02d %02d:%02d:%02d",
+            tm->tm_year + 1900,
+            tm->tm_mon + 1,
+            tm->tm_mday,
+            tm->tm_hour,
+            tm->tm_min,
+            tm->tm_sec);
+    TIFFSetField(t->tiff_file, TIFFTAG_DATETIME, buf);
+    TIFFSetField(t->tiff_file, TIFFTAG_FAXRECVTIME, now - s->page_start_time);
+
+    TIFFSetField(t->tiff_file, TIFFTAG_IMAGELENGTH, s->image_length);
+    /* Set the total pages to 1. For any one page document we will get this
+       right. For multi-page documents we will need to come back and fill in
+       the right answer when we know it. */
+    TIFFSetField(t->tiff_file, TIFFTAG_PAGENUMBER, s->current_page++, 1);
+    s->tiff.pages_in_file = s->current_page;
+    if (t->output_compression == COMPRESSION_CCITT_T4)
+    {
+        if (s->t4_t6_rx.bad_rows)
+        {
+            TIFFSetField(t->tiff_file, TIFFTAG_BADFAXLINES, s->t4_t6_rx.bad_rows);
+            TIFFSetField(t->tiff_file, TIFFTAG_CLEANFAXDATA, CLEANFAXDATA_REGENERATED);
+            TIFFSetField(t->tiff_file, TIFFTAG_CONSECUTIVEBADFAXLINES, s->t4_t6_rx.longest_bad_row_run);
+        }
+        else
+        {
+            TIFFSetField(t->tiff_file, TIFFTAG_CLEANFAXDATA, CLEANFAXDATA_CLEAN);
+        }
+    }
+    TIFFSetField(t->tiff_file, TIFFTAG_IMAGEWIDTH, s->image_width);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int open_tiff_output_file(t4_state_t *s, const char *file)
+{
+    if ((s->tiff.tiff_file = TIFFOpen(file, "w")) == NULL)
+        return -1;
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static void write_tiff_image(t4_state_t *s)
+{
+    /* Set up the TIFF directory info... */
+    set_tiff_directory_info(s);
+    /* ..and then write the image... */
+    if (TIFFWriteEncodedStrip(s->tiff.tiff_file, 0, s->image_buffer, s->image_length*s->bytes_per_row) < 0)
+        span_log(&s->logging, SPAN_LOG_WARNING, "%s: Error writing TIFF strip.\n", s->tiff.file);
+    /* ...then the directory entry, and libtiff is happy. */
+    TIFFWriteDirectory(s->tiff.tiff_file);
+}
+/*- End of function --------------------------------------------------------*/
+
+static int close_tiff_output_file(t4_state_t *s)
+{
+    int i;
+    t4_tiff_state_t *t;
+
+    t = &s->tiff;
+    /* Perform any operations needed to tidy up a written TIFF file before
+       closure. */
+    if (s->current_page > 1)
+    {
+        /* We need to edit the TIFF directories. Until now we did not know
+           the total page count, so the TIFF file currently says one. Now we
+           need to set the correct total page count associated with each page. */
+        for (i = 0;  i < s->current_page;  i++)
+        {
+            TIFFSetDirectory(t->tiff_file, (tdir_t) i);
+            TIFFSetField(t->tiff_file, TIFFTAG_PAGENUMBER, i, s->current_page);
+            TIFFWriteDirectory(t->tiff_file);
+        }
+    }
+    TIFFClose(t->tiff_file);
+    t->tiff_file = NULL;
+    if (t->file)
+    {
+        /* Try not to leave a file behind, if we didn't receive any pages to
+           put in it. */
+        if (s->current_page == 0)
+            remove(t->file);
+        free((char *) t->file);
+        t->file = NULL;
+    }
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+#else
+
+static int set_tiff_directory_info(t4_state_t *s)
+{
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int get_tiff_directory_info(t4_state_t *s)
+{
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int test_tiff_directory_info(t4_state_t *s)
+{
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int open_tiff_input_file(t4_state_t *s, const char *file)
+{
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int read_tiff_image(t4_state_t *s)
+{
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int close_tiff_input_file(t4_state_t *s)
+{
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int open_tiff_output_file(t4_state_t *s, const char *file)
+{
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static void write_tiff_image(t4_state_t *s)
+{
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int close_tiff_output_file(t4_state_t *s)
+{
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+#endif
+
+static void update_row_bit_info(t4_state_t *s)
+{
+    if (s->row_bits > s->max_row_bits)
+        s->max_row_bits = s->row_bits;
+    if (s->row_bits < s->min_row_bits)
+        s->min_row_bits = s->row_bits;
+    s->row_bits = 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+#if defined(__i386__)  ||  defined(__x86_64__)  ||  defined(__ppc__)  ||   defined(__powerpc__)
+static __inline__ int run_length(unsigned int bits)
+{
+    return 7 - top_bit(bits);
+}
+/*- End of function --------------------------------------------------------*/
+#else
+static __inline__ int run_length(unsigned int bits)
+{
+    static const uint8_t run_len[256] =
+    {
+        8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, /* 0x00 - 0x0F */
+        3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0x10 - 0x1F */
+        2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0x20 - 0x2F */
+        2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0x30 - 0x3F */
+        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x40 - 0x4F */
+        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x50 - 0x5F */
+        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x60 - 0x6F */
+        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x70 - 0x7F */
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x80 - 0x8F */
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x90 - 0x9F */
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xA0 - 0xAF */
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xB0 - 0xBF */
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xC0 - 0xCF */
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xD0 - 0xDF */
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xE0 - 0xEF */
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xF0 - 0xFF */
+    };
+
+    return run_len[bits];
+}
+/*- End of function --------------------------------------------------------*/
+#endif
+
+static int free_buffers(t4_state_t *s)
+{
+    if (s->image_buffer)
+    {
+        free(s->image_buffer);
+        s->image_buffer = NULL;
+        s->image_buffer_size = 0;
+    }
+    if (s->cur_runs)
+    {
+        free(s->cur_runs);
+        s->cur_runs = NULL;
+    }
+    if (s->ref_runs)
+    {
+        free(s->ref_runs);
+        s->ref_runs = NULL;
+    }
+    if (s->row_buf)
+    {
+        free(s->row_buf);
+        s->row_buf = NULL;
+    }
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static __inline__ void add_run_to_row(t4_state_t *s)
+{
+    if (s->t4_t6_rx.run_length >= 0)
+    {
+        s->row_len += s->t4_t6_rx.run_length;
+        /* Don't allow rows to grow too long, and overflow the buffers */
+        if (s->row_len <= s->image_width)
+            s->cur_runs[s->t4_t6_rx.a_cursor++] = s->t4_t6_rx.run_length;
+    }
+    s->t4_t6_rx.run_length = 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int put_decoded_row(t4_state_t *s)
+{
+    static const int msbmask[9] =
+    {
+        0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff
+    };
+    uint8_t *t;
+    uint32_t i;
+    uint32_t *p;
+    int fudge;
+    int row_starts_at;
+    int x;
+    int j;
+
+    if (s->t4_t6_rx.run_length)
+        add_run_to_row(s);
+#if defined(T4_STATE_DEBUGGING)
+    /* Dump the runs of black and white for analysis */
+    {
+        int total;
+
+        total = 0;
+        for (x = 0;  x < s->t4_t6_rx.b_cursor;  x++)
+            total += s->ref_runs[x];
+        printf("Ref (%d)", total);
+        for (x = 0;  x < s->t4_t6_rx.b_cursor;  x++)
+            printf(" %" PRIu32, s->ref_runs[x]);
+        printf("\n");
+        total = 0;
+        for (x = 0;  x < s->t4_t6_rx.a_cursor;  x++)
+            total += s->cur_runs[x];
+        printf("Cur (%d)", total);
+        for (x = 0;  x < s->t4_t6_rx.a_cursor;  x++)
+            printf(" %" PRIu32, s->cur_runs[x]);
+        printf("\n");
+    }
+#endif
+    row_starts_at = s->image_size;
+    /* Make sure there is enough room for another row */
+    if (s->image_size + s->bytes_per_row >= s->image_buffer_size)
+    {
+        if ((t = realloc(s->image_buffer, s->image_buffer_size + 100*s->bytes_per_row)) == NULL)
+            return -1;
+        s->image_buffer_size += 100*s->bytes_per_row;
+        s->image_buffer = t;
+    }
+    if (s->row_len == s->image_width)
+    {
+        STATE_TRACE("%d Good row - %d %s\n", s->image_length, s->row_len, (s->row_is_2d)  ?  "2D"  :  "1D");
+        if (s->t4_t6_rx.curr_bad_row_run)
+        {
+            if (s->t4_t6_rx.curr_bad_row_run > s->t4_t6_rx.longest_bad_row_run)
+                s->t4_t6_rx.longest_bad_row_run = s->t4_t6_rx.curr_bad_row_run;
+            s->t4_t6_rx.curr_bad_row_run = 0;
+        }
+        /* Convert the runs to a bit image of the row */
+        /* White/black/white... runs, always starting with white. That means the first run could be
+           zero length. */
+        for (x = 0, fudge = 0;  x < s->t4_t6_rx.a_cursor;  x++, fudge ^= 0xFF)
+        {
+            i = s->cur_runs[x];
+            if ((int) i >= s->tx_bits)
+            {
+                s->tx_bitstream = (s->tx_bitstream << s->tx_bits) | (msbmask[s->tx_bits] & fudge);
+                for (i += (8 - s->tx_bits);  i >= 8;  i -= 8)
+                {
+                    s->tx_bits = 8;
+                    s->image_buffer[s->image_size++] = (uint8_t) s->tx_bitstream;
+                    s->tx_bitstream = fudge;
+                }
+            }
+            s->tx_bitstream = (s->tx_bitstream << i) | (msbmask[i] & fudge);
+            s->tx_bits -= i;
+        }
+        s->image_length++;
+    }
+    else
+    {
+        STATE_TRACE("%d Bad row - %d %s\n", s->image_length, s->row_len, (s->row_is_2d)  ?  "2D"  :  "1D");
+        /* Try to clean up the bad runs, and produce something reasonable as the reference
+           row for the next row. Use a copy of the previous good row as the actual current
+           row. If the row only fell apart near the end, reusing it might be the best
+           solution. */
+        for (j = 0, fudge = 0;  j < s->t4_t6_rx.a_cursor  &&  fudge < s->image_width;  j++)
+            fudge += s->cur_runs[j];
+        if (fudge < s->image_width)
+        {
+            /* Try to pad with white, and avoid black, to minimise mess on the image. */
+            if ((s->t4_t6_rx.a_cursor & 1))
+            {
+                /* We currently finish in white. We could extend that, but it is probably of
+                   the right length. Changing it would only further mess up what happens in the
+                   next row. It seems better to add a black spot, and an extra white run. */
+                s->cur_runs[s->t4_t6_rx.a_cursor++] = 1;
+                fudge++;
+                if (fudge < s->image_width)
+                    s->cur_runs[s->t4_t6_rx.a_cursor++] = s->image_width - fudge;
+            }
+            else
+            {
+                /* We currently finish on black, so we add an extra white run to fill out the line. */
+                s->cur_runs[s->t4_t6_rx.a_cursor++] = s->image_width - fudge;
+            }
+        }
+        else
+        {
+            /* Trim the last element to align with the proper image width */
+            s->cur_runs[s->t4_t6_rx.a_cursor] += (s->image_width - fudge);
+        }
+        /* Ensure there is a previous line to copy from. */
+        if (s->image_size != s->t4_t6_rx.last_row_starts_at)
+        {
+            /* Copy the previous row over this one */
+            memcpy(s->image_buffer + s->image_size, s->image_buffer + s->t4_t6_rx.last_row_starts_at, s->bytes_per_row);
+            s->image_size += s->bytes_per_row;
+            s->image_length++;
+        }
+        s->t4_t6_rx.bad_rows++;
+        s->t4_t6_rx.curr_bad_row_run++;
+    }
+
+    /* Pad the row as it becomes the reference row, so there are no odd runs to pick up if we
+       step off the end of the list. */
+    s->cur_runs[s->t4_t6_rx.a_cursor] = 0;
+    s->cur_runs[s->t4_t6_rx.a_cursor + 1] = 0;
+
+    /* Prepare the buffers for the next row. */
+    s->t4_t6_rx.last_row_starts_at = row_starts_at;
+    /* Swap the buffers */
+    p = s->cur_runs;
+    s->cur_runs = s->ref_runs;
+    s->ref_runs = p;
+
+    s->t4_t6_rx.b_cursor = 1;
+    s->t4_t6_rx.a_cursor = 0;
+    s->t4_t6_rx.b1 = s->ref_runs[0];
+    s->t4_t6_rx.a0 = 0;
+    
+    s->t4_t6_rx.run_length = 0;
+
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t4_rx_end_page(t4_state_t *s)
+{
+    int row;
+    int i;
+
+    if (s->line_encoding == T4_COMPRESSION_ITU_T6)
+    {
+        /* Push enough zeros through the decoder to flush out any remaining codes */
+        for (i = 0;  i < 13;  i++)
+            t4_rx_put_bit(s, 0);
+    }
+    if (s->t4_t6_rx.curr_bad_row_run)
+    {
+        if (s->t4_t6_rx.curr_bad_row_run > s->t4_t6_rx.longest_bad_row_run)
+            s->t4_t6_rx.longest_bad_row_run = s->t4_t6_rx.curr_bad_row_run;
+        s->t4_t6_rx.curr_bad_row_run = 0;
+    }
+
+    if (s->image_size == 0)
+        return -1;
+
+    if (s->t4_t6_rx.row_write_handler)
+    {
+        for (row = 0;  row < s->image_length;  row++)
+        {
+            if (s->t4_t6_rx.row_write_handler(s->t4_t6_rx.row_write_user_data, s->image_buffer + row*s->bytes_per_row, s->bytes_per_row) < 0)
+            {
+                span_log(&s->logging, SPAN_LOG_WARNING, "Write error at row %d.\n", row);
+                break;
+            }
+        }
+        /* Write a blank row to indicate the end of the image. */
+        if (s->t4_t6_rx.row_write_handler(s->t4_t6_rx.row_write_user_data, NULL, 0) < 0)
+            span_log(&s->logging, SPAN_LOG_WARNING, "Write error at row %d.\n", row);
+    }
+    else
+    {
+        write_tiff_image(s);
+    }
+    s->t4_t6_rx.rx_bits = 0;
+    s->t4_t6_rx.rx_skip_bits = 0;
+    s->t4_t6_rx.rx_bitstream = 0;
+    s->t4_t6_rx.consecutive_eols = EOLS_TO_END_ANY_RX_PAGE;
+
+    s->image_size = 0;
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static __inline__ void drop_rx_bits(t4_state_t *s, int bits)
+{
+    /* Only remove one bit right now. The rest need to be removed step by step,
+       checking for a misaligned EOL along the way. This is time consuming, but
+       if we don't do it a single bit error can severely damage an image. */
+    s->row_bits += bits;
+    s->t4_t6_rx.rx_skip_bits += (bits - 1);
+    s->t4_t6_rx.rx_bits--;
+    s->t4_t6_rx.rx_bitstream >>= 1;
+}
+/*- End of function --------------------------------------------------------*/
+
+static __inline__ void force_drop_rx_bits(t4_state_t *s, int bits)
+{
+    /* This should only be called to drop the bits of an EOL, as that is the
+       only place where it is safe to drop them all at once. */
+    s->row_bits += bits;
+    s->t4_t6_rx.rx_skip_bits = 0;
+    s->t4_t6_rx.rx_bits -= bits;
+    s->t4_t6_rx.rx_bitstream >>= bits;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int rx_put_bits(t4_state_t *s, uint32_t bit_string, int quantity)
+{
+    int bits;
+
+    /* We decompress bit by bit, as the data stream is received. We need to
+       scan continuously for EOLs, so we might as well work this way. */
+    s->line_image_size += quantity;
+    s->t4_t6_rx.rx_bitstream |= (bit_string << s->t4_t6_rx.rx_bits);
+    /* The longest item we need to scan for is 13 bits long (a 2D EOL), so we
+       need a minimum of 13 bits in the buffer to proceed with any bit stream
+       analysis. */
+    if ((s->t4_t6_rx.rx_bits += quantity) < 13)
+        return FALSE;
+    if (s->t4_t6_rx.consecutive_eols)
+    {
+        /* Check if the image has already terminated. */
+        if (s->t4_t6_rx.consecutive_eols >= EOLS_TO_END_ANY_RX_PAGE)
+            return TRUE;
+        /* Check if the image hasn't even started. */
+        if (s->t4_t6_rx.consecutive_eols < 0)
+        {
+            /* We are waiting for the very first EOL (1D or 2D only). */
+            /* We need to take this bit by bit, as the EOL could be anywhere,
+               and any junk could preceed it. */
+            while ((s->t4_t6_rx.rx_bitstream & 0xFFF) != 0x800)
+            {
+                s->t4_t6_rx.rx_bitstream >>= 1;
+                if (--s->t4_t6_rx.rx_bits < 13)
+                    return FALSE;
+            }
+            /* We have an EOL, so now the page begins and we can proceed to
+               process the bit stream as image data. */
+            s->t4_t6_rx.consecutive_eols = 0;
+            if (s->line_encoding == T4_COMPRESSION_ITU_T4_1D)
+            {
+                s->row_is_2d = FALSE;
+                force_drop_rx_bits(s, 12);
+            }
+            else
+            {
+                s->row_is_2d = !(s->t4_t6_rx.rx_bitstream & 0x1000);
+                force_drop_rx_bits(s, 13);
+            }
+        }
+    }
+
+    while (s->t4_t6_rx.rx_bits >= 13)
+    {
+        /* We need to check for EOLs bit by bit through the whole stream. If
+           we just try looking between code words, we will miss an EOL when a bit
+           error has throw the code words completely out of step. The can mean
+           recovery takes many lines, and the image gets really messed up. */
+        /* Although EOLs are not inserted at the end of each row of a T.6 image,
+           they are still perfectly valid, and can terminate an image. */
+        if ((s->t4_t6_rx.rx_bitstream & 0x0FFF) == 0x0800)
+        {
+            STATE_TRACE("EOL\n");
+            if (s->row_len == 0)
+            {
+                /* A zero length row - i.e. 2 consecutive EOLs - is distinctly
+                   the end of page condition. That's all we actually get on a
+                   T.6 page. However, there are a minimum of 6 EOLs at the end of
+                   any T.4 page. We can look for more than 2 EOLs in case bit
+                   errors simulate the end of page condition at the wrong point.
+                   Such robust checking is irrelevant for a T.6 page, as it should
+                   be error free. */
+                /* Note that for a T.6 page we should get here on the very first
+                   EOL, as the row length should be zero at that point. Therefore
+                   we should count up both EOLs, unless there is some bogus partial
+                   row ahead of them. */
+                s->t4_t6_rx.consecutive_eols++;
+                if (s->line_encoding == T4_COMPRESSION_ITU_T6)
+                {
+                    if (s->t4_t6_rx.consecutive_eols >= EOLS_TO_END_T6_RX_PAGE)
+                    {
+                        s->t4_t6_rx.consecutive_eols = EOLS_TO_END_ANY_RX_PAGE;
+                        return TRUE;
+                    }
+                }
+                else
+                {
+                    if (s->t4_t6_rx.consecutive_eols >= EOLS_TO_END_T4_RX_PAGE)
+                    {
+                        s->t4_t6_rx.consecutive_eols = EOLS_TO_END_ANY_RX_PAGE;
+                        return TRUE;
+                    }
+                }
+            }
+            else
+            {
+                /* The EOLs are not back-to-back, so they are not part of the
+                   end of page condition. */
+                if (s->t4_t6_rx.run_length > 0)
+                    add_run_to_row(s);
+                s->t4_t6_rx.consecutive_eols = 0;
+                if (put_decoded_row(s))
+                    return TRUE;
+                update_row_bit_info(s);
+            }
+            if (s->line_encoding == T4_COMPRESSION_ITU_T4_2D)
+            {
+                s->row_is_2d = !(s->t4_t6_rx.rx_bitstream & 0x1000);
+                force_drop_rx_bits(s, 13);
+            }
+            else
+            {
+                force_drop_rx_bits(s, 12);
+            }
+            s->t4_t6_rx.its_black = FALSE;
+            s->t4_t6_rx.black_white = 0;
+            s->t4_t6_rx.run_length = 0;
+            s->row_len = 0;
+            continue;
+        }
+        if (s->t4_t6_rx.rx_skip_bits)
+        {
+            /* We are clearing out the remaining bits of the last code word we
+               absorbed. */
+            s->t4_t6_rx.rx_skip_bits--;
+            s->t4_t6_rx.rx_bits--;
+            s->t4_t6_rx.rx_bitstream >>= 1;
+            continue;
+        }
+        if (s->row_is_2d  &&  s->t4_t6_rx.black_white == 0)
+        {
+            bits = s->t4_t6_rx.rx_bitstream & 0x7F;
+            STATE_TRACE("State %d, %d - ",
+                        t4_2d_table[bits].state,
+                        t4_2d_table[bits].width);
+            if (s->row_len >= s->image_width)
+            {
+                drop_rx_bits(s, t4_2d_table[bits].width);
+                continue;
+            }
+            if (s->t4_t6_rx.a_cursor)
+            {
+                /* Move past a0, always staying on the current colour */
+                for (  ;  s->t4_t6_rx.b1 <= s->t4_t6_rx.a0;  s->t4_t6_rx.b_cursor += 2)
+                    s->t4_t6_rx.b1 += (s->ref_runs[s->t4_t6_rx.b_cursor] + s->ref_runs[s->t4_t6_rx.b_cursor + 1]);
+            }
+            switch (t4_2d_table[bits].state)
+            {
+            case S_Horiz:
+                STATE_TRACE("Horiz %d %d %d\n",
+                            s->image_width,
+                            s->t4_t6_rx.a0,
+                            s->t4_t6_rx.a_cursor);
+                /* We now need to extract a white/black or black/white pair of runs, using the 1D
+                   method. If the first of the pair takes us exactly to the end of the row, there
+                   should still be a zero length element for the second of the pair. */
+                s->t4_t6_rx.its_black = s->t4_t6_rx.a_cursor & 1;
+                s->t4_t6_rx.black_white = 2;
+                break;
+            case S_Vert:
+                STATE_TRACE("Vert[%d] %d %d %d %d\n",
+                            t4_2d_table[bits].param,
+                            s->image_width,
+                            s->t4_t6_rx.a0,
+                            s->t4_t6_rx.b1,
+                            s->t4_t6_rx.run_length);
+                s->t4_t6_rx.run_length += (s->t4_t6_rx.b1 - s->t4_t6_rx.a0 + t4_2d_table[bits].param);
+                s->t4_t6_rx.a0 = s->t4_t6_rx.b1 + t4_2d_table[bits].param;
+                add_run_to_row(s);
+                /* We need to move one step in one direction or the other, to change to the
+                   opposite colour */
+                if (t4_2d_table[bits].param >= 0)
+                {
+                    s->t4_t6_rx.b1 += s->ref_runs[s->t4_t6_rx.b_cursor++];
+                }
+                else
+                {
+                    if (s->t4_t6_rx.b_cursor)
+                        s->t4_t6_rx.b1 -= s->ref_runs[--s->t4_t6_rx.b_cursor];
+                }
+                break;
+            case S_Pass:
+                STATE_TRACE("Pass %d %d %d %d %d\n",
+                            s->image_width,
+                            s->t4_t6_rx.a0,
+                            s->t4_t6_rx.b1,
+                            s->ref_runs[s->t4_t6_rx.b_cursor],
+                            s->ref_runs[s->t4_t6_rx.b_cursor + 1]);
+                s->t4_t6_rx.b1 += s->ref_runs[s->t4_t6_rx.b_cursor++];
+                s->t4_t6_rx.run_length += (s->t4_t6_rx.b1 - s->t4_t6_rx.a0);
+                s->t4_t6_rx.a0 = s->t4_t6_rx.b1;
+                s->t4_t6_rx.b1 += s->ref_runs[s->t4_t6_rx.b_cursor++];
+                break;
+            case S_Ext:
+                /* We do not currently handle any kind of extension */
+                STATE_TRACE("Ext %d %d %d 0x%x\n",
+                            s->image_width,
+                            s->t4_t6_rx.a0,
+                            ((s->t4_t6_rx.rx_bitstream >> t4_2d_table[bits].width) & 0x7),
+                            s->t4_t6_rx.rx_bitstream);
+                /* TODO: The uncompressed option should be implemented. */
+                break;
+            case S_Null:
+                STATE_TRACE("Null\n");
+                break;
+            default:
+                STATE_TRACE("Unexpected T.4 state\n");
+                span_log(&s->logging, SPAN_LOG_WARNING, "Unexpected T.4 state %d\n", t4_2d_table[bits].state);
+                break;
+            }
+            drop_rx_bits(s, t4_2d_table[bits].width);
+        }
+        else
+        {
+            if (s->t4_t6_rx.its_black)
+            {
+                bits = s->t4_t6_rx.rx_bitstream & 0x1FFF;
+                STATE_TRACE("State %d, %d - Black %d %d %d\n",
+                            t4_1d_black_table[bits].state,
+                            t4_1d_black_table[bits].width,
+                            s->image_width,
+                            s->t4_t6_rx.a0,
+                            t4_1d_black_table[bits].param);
+                switch (t4_1d_black_table[bits].state)
+                {
+                case S_MakeUpB:
+                case S_MakeUp:
+                    s->t4_t6_rx.run_length += t4_1d_black_table[bits].param;
+                    s->t4_t6_rx.a0 += t4_1d_black_table[bits].param;
+                    break;
+                case S_TermB:
+                    s->t4_t6_rx.its_black = FALSE;
+                    if (s->row_len < s->image_width)
+                    {
+                        s->t4_t6_rx.run_length += t4_1d_black_table[bits].param;
+                        s->t4_t6_rx.a0 += t4_1d_black_table[bits].param;
+                        add_run_to_row(s);
+                    }
+                    if (s->t4_t6_rx.black_white)
+                        s->t4_t6_rx.black_white--;
+                    break;
+                default:
+                    /* Bad black */
+                    s->t4_t6_rx.black_white = 0;
+                    break;
+                }
+                drop_rx_bits(s, t4_1d_black_table[bits].width);
+            }
+            else
+            {
+                bits = s->t4_t6_rx.rx_bitstream & 0xFFF;
+                STATE_TRACE("State %d, %d - White %d %d %d\n",
+                            t4_1d_white_table[bits].state,
+                            t4_1d_white_table[bits].width,
+                            s->image_width,
+                            s->t4_t6_rx.a0,
+                            t4_1d_white_table[bits].param);
+                switch (t4_1d_white_table[bits].state)
+                {
+                case S_MakeUpW:
+                case S_MakeUp:
+                    s->t4_t6_rx.run_length += t4_1d_white_table[bits].param;
+                    s->t4_t6_rx.a0 += t4_1d_white_table[bits].param;
+                    break;
+                case S_TermW:
+                    s->t4_t6_rx.its_black = TRUE;
+                    if (s->row_len < s->image_width)
+                    {
+                        s->t4_t6_rx.run_length += t4_1d_white_table[bits].param;
+                        s->t4_t6_rx.a0 += t4_1d_white_table[bits].param;
+                        add_run_to_row(s);
+                    }
+                    if (s->t4_t6_rx.black_white)
+                        s->t4_t6_rx.black_white--;
+                    break;
+                default:
+                    /* Bad white */
+                    s->t4_t6_rx.black_white = 0;
+                    break;
+                }
+                drop_rx_bits(s, t4_1d_white_table[bits].width);
+            }
+        }
+        if (s->t4_t6_rx.a0 >= s->image_width)
+            s->t4_t6_rx.a0 = s->image_width - 1;
+
+        if (s->line_encoding == T4_COMPRESSION_ITU_T6)
+        {
+            /* T.6 has no EOL markers. We sense the end of a line by its length alone. */
+            /* The last test here is a backstop protection, so a corrupt image cannot
+               cause us to do bad things. Bad encoders have actually been seen, which
+               demand such protection. */
+            if (s->t4_t6_rx.black_white == 0  &&  s->row_len >= s->image_width)
+            {
+                STATE_TRACE("EOL T.6\n");
+                if (s->t4_t6_rx.run_length > 0)
+                    add_run_to_row(s);
+                update_row_bit_info(s);
+                if (put_decoded_row(s))
+                    return TRUE;
+                s->t4_t6_rx.its_black = FALSE;
+                s->t4_t6_rx.black_white = 0;
+                s->t4_t6_rx.run_length = 0;
+                s->row_len = 0;
+            }
+        }
+    }
+    return FALSE;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t4_rx_put_bit(t4_state_t *s, int bit)
+{
+    return rx_put_bits(s, bit & 1, 1);
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t4_rx_put_byte(t4_state_t *s, uint8_t byte)
+{
+    return rx_put_bits(s, byte & 0xFF, 8);
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t4_rx_put_chunk(t4_state_t *s, const uint8_t buf[], int len)
+{
+    int i;
+    uint8_t byte;
+
+    for (i = 0;  i < len;  i++)
+    {
+        byte = buf[i];
+        if (rx_put_bits(s, byte & 0xFF, 8))
+            return TRUE;
+    }
+    return FALSE;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t4_rx_set_row_write_handler(t4_state_t *s, t4_row_write_handler_t handler, void *user_data)
+{
+    s->t4_t6_rx.row_write_handler = handler;
+    s->t4_t6_rx.row_write_user_data = user_data;
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(t4_state_t *) t4_rx_init(t4_state_t *s, const char *file, int output_encoding)
+{
+    if (s == NULL)
+    {
+        if ((s = (t4_state_t *) malloc(sizeof(*s))) == NULL)
+            return NULL;
+    }
+    memset(s, 0, sizeof(*s));
+    span_log_init(&s->logging, SPAN_LOG_NONE, NULL);
+    span_log_set_protocol(&s->logging, "T.4");
+    s->rx = TRUE;
+    
+    span_log(&s->logging, SPAN_LOG_FLOW, "Start rx document\n");
+
+    if (open_tiff_output_file(s, file) < 0)
+        return NULL;
+
+    /* Save the file name for logging reports. */
+    s->tiff.file = strdup(file);
+    /* Only provide for one form of coding throughout the file, even though the
+       coding on the wire could change between pages. */
+    switch (output_encoding)
+    {
+    case T4_COMPRESSION_ITU_T4_1D:
+        s->tiff.output_compression = COMPRESSION_CCITT_T4;
+        s->tiff.output_t4_options = GROUP3OPT_FILLBITS;
+        break;
+    case T4_COMPRESSION_ITU_T4_2D:
+        s->tiff.output_compression = COMPRESSION_CCITT_T4;
+        s->tiff.output_t4_options = GROUP3OPT_FILLBITS | GROUP3OPT_2DENCODING;
+        break;
+    case T4_COMPRESSION_ITU_T6:
+        s->tiff.output_compression = COMPRESSION_CCITT_T6;
+        s->tiff.output_t4_options = 0;
+        break;
+    }
+
+    /* Until we have a valid figure for the bytes per row, we need it to be set to a suitable
+       value to ensure it will be seen as changing when the real value is used. */
+    s->bytes_per_row = 0;
+
+    s->current_page = 0;
+    s->tiff.pages_in_file = 0;
+    s->tiff.start_page = 0;
+    s->tiff.stop_page = INT_MAX;
+
+    s->image_buffer = NULL;
+    s->image_buffer_size = 0;
+
+    /* Set some default values */
+    s->x_resolution = T4_X_RESOLUTION_R8;
+    s->y_resolution = T4_Y_RESOLUTION_FINE;
+    s->image_width = T4_WIDTH_R8_A4;
+
+    return s;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t4_rx_start_page(t4_state_t *s)
+{
+    int bytes_per_row;
+    int run_space;
+    uint32_t *bufptr;
+
+    span_log(&s->logging, SPAN_LOG_FLOW, "Start rx page - compression %d\n", s->line_encoding);
+    if (s->tiff.tiff_file == NULL)
+        return -1;
+
+    /* Calculate the scanline/tile width. */
+    bytes_per_row = (s->image_width + 7)/8;
+    run_space = (s->image_width + 4)*sizeof(uint32_t);
+    if (bytes_per_row != s->bytes_per_row)
+    {
+        /* Allocate the space required for decoding the new row length. */
+        s->bytes_per_row = bytes_per_row;
+        if ((bufptr = (uint32_t *) realloc(s->cur_runs, run_space)) == NULL)
+            return -1;
+        s->cur_runs = bufptr;
+        if ((bufptr = (uint32_t *) realloc(s->ref_runs, run_space)) == NULL)
+            return -1;
+        s->ref_runs = bufptr;
+    }
+    memset(s->cur_runs, 0, run_space);
+    memset(s->ref_runs, 0, run_space);
+
+    s->t4_t6_rx.rx_bits = 0;
+    s->t4_t6_rx.rx_skip_bits = 0;
+    s->t4_t6_rx.rx_bitstream = 0;
+    s->row_bits = 0;
+    s->min_row_bits = INT_MAX;
+    s->max_row_bits = 0;
+
+    s->row_is_2d = (s->line_encoding == T4_COMPRESSION_ITU_T6);
+    /* We start at -1 EOLs for 1D and 2D decoding, as an indication we are waiting for the
+       first EOL. T.6 coding starts without any preamble. */
+    s->t4_t6_rx.consecutive_eols = (s->line_encoding == T4_COMPRESSION_ITU_T6)  ?  0  :  -1;
+
+    s->t4_t6_rx.bad_rows = 0;
+    s->t4_t6_rx.longest_bad_row_run = 0;
+    s->t4_t6_rx.curr_bad_row_run = 0;
+    s->image_length = 0;
+    s->tx_bitstream = 0;
+    s->tx_bits = 8;
+    s->image_size = 0;
+    s->line_image_size = 0;
+    s->t4_t6_rx.last_row_starts_at = 0;
+
+    s->row_len = 0;
+    s->t4_t6_rx.its_black = FALSE;
+    s->t4_t6_rx.black_white = 0;
+
+    /* Initialise the reference line to all white */
+    s->ref_runs[0] =
+    s->ref_runs[1] =
+    s->ref_runs[2] =
+    s->ref_runs[3] = s->image_width;
+
+    s->t4_t6_rx.b_cursor = 1;
+    s->t4_t6_rx.a_cursor = 0;
+    s->t4_t6_rx.b1 = s->ref_runs[0];
+    s->t4_t6_rx.a0 = 0;
+
+    s->t4_t6_rx.run_length = 0;
+
+    time (&s->page_start_time);
+
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t4_rx_release(t4_state_t *s)
+{
+    if (!s->rx)
+        return -1;
+    if (s->tiff.tiff_file)
+        close_tiff_output_file(s);
+    free_buffers(s);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t4_rx_free(t4_state_t *s)
+{
+    int ret;
+
+    ret = t4_rx_release(s);
+    free(s);
+    return ret;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t4_rx_set_rx_encoding(t4_state_t *s, int encoding)
+{
+    s->line_encoding = encoding;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t4_rx_set_image_width(t4_state_t *s, int width)
+{
+    s->image_width = width;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t4_rx_set_y_resolution(t4_state_t *s, int resolution)
+{
+    s->y_resolution = resolution;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t4_rx_set_x_resolution(t4_state_t *s, int resolution)
+{
+    s->x_resolution = resolution;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t4_rx_set_dcs(t4_state_t *s, const char *dcs)
+{
+    s->tiff.dcs = (dcs  &&  dcs[0])  ?  dcs  :  NULL;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t4_rx_set_sub_address(t4_state_t *s, const char *sub_address)
+{
+    s->tiff.sub_address = (sub_address  &&  sub_address[0])  ?  sub_address  :  NULL;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t4_rx_set_far_ident(t4_state_t *s, const char *ident)
+{
+    s->tiff.far_ident = (ident  &&  ident[0])  ?  ident  :  NULL;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t4_rx_set_vendor(t4_state_t *s, const char *vendor)
+{
+    s->tiff.vendor = vendor;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t4_rx_set_model(t4_state_t *s, const char *model)
+{
+    s->tiff.model = model;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t4_rx_get_transfer_statistics(t4_state_t *s, t4_stats_t *t)
+{
+    t->pages_transferred = s->current_page - s->tiff.start_page;
+    t->pages_in_file = s->tiff.pages_in_file;
+    t->width = s->image_width;
+    t->length = s->image_length;
+    t->bad_rows = s->t4_t6_rx.bad_rows;
+    t->longest_bad_row_run = s->t4_t6_rx.longest_bad_row_run;
+    t->x_resolution = s->x_resolution;
+    t->y_resolution = s->y_resolution;
+    t->encoding = s->line_encoding;
+    t->line_image_size = s->line_image_size/8;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(const char *) t4_encoding_to_str(int encoding)
+{
+    switch (encoding)
+    {
+    case T4_COMPRESSION_NONE:
+        return "None";
+    case T4_COMPRESSION_ITU_T4_1D:
+        return "T.4 1-D";
+    case T4_COMPRESSION_ITU_T4_2D:
+        return "T.4 2-D";
+    case T4_COMPRESSION_ITU_T6:
+        return "T.6";
+    case T4_COMPRESSION_ITU_T85:
+        return "T.85";
+    case T4_COMPRESSION_ITU_T85_L0:
+        return "T.85(L0)";
+    case T4_COMPRESSION_ITU_T43:
+        return "T.43";
+    case T4_COMPRESSION_ITU_T45:
+        return "T.45";
+    case T4_COMPRESSION_ITU_T81:
+        return "T.81";
+    case T4_COMPRESSION_ITU_SYCC_T81:
+        return "sYCC T.81";
+    }
+    return "???";
+}
+/*- End of function --------------------------------------------------------*/
+/*- End of file ------------------------------------------------------------*/
diff --git a/src/codec/spandsp/src/t4_tx.c b/src/codec/spandsp/src/t4_tx.c
new file mode 100644 (file)
index 0000000..c4216af
--- /dev/null
@@ -0,0 +1,1575 @@
+//#define T4_STATE_DEBUGGING
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * t4_tx.c - ITU T.4 FAX image transmit processing
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2003, 2007 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.
+ */
+
+/*
+ * Much of this file is based on the T.4 and T.6 support in libtiff, which requires
+ * the following notice in any derived source code:
+ *
+ * Copyright (c) 1990-1997 Sam Leffler
+ * Copyright (c) 1991-1997 Silicon Graphics, Inc.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and 
+ * its documentation for any purpose is hereby granted without fee, provided
+ * that (i) the above copyright notices and this permission notice appear in
+ * all copies of the software and related documentation, and (ii) the names of
+ * Sam Leffler and Silicon Graphics may not be used in any advertising or
+ * publicity relating to the software without the specific, prior written
+ * permission of Sam Leffler and Silicon Graphics.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY 
+ * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.  
+ * 
+ * IN NO EVENT SHALL SAM LEFFLER OR SILICON GRAPHICS BE LIABLE FOR
+ * ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
+ * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+ * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF 
+ * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE 
+ * OF THIS SOFTWARE.
+ */
+
+/*! \file */
+
+#if defined(HAVE_CONFIG_H)
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <inttypes.h>
+#include <limits.h>
+//TODO// #include <stdio.h>
+//TODO// #include <fcntl.h>
+//TODO// #include <unistd.h>
+#include <time.h>
+#include <memory.h>
+#include <string.h>
+#if defined(HAVE_TGMATH_H)
+#include <tgmath.h>
+#endif
+#if defined(HAVE_MATH_H)
+#include <math.h>
+#endif
+#include "floating_fudge.h"
+//TODO// #include <tiffio.h>
+
+#include "spandsp/0cpm.h"
+
+#include "spandsp/telephony.h"
+#include "spandsp/logging.h"
+#include "spandsp/bit_operations.h"
+#include "spandsp/async.h"
+#include "spandsp/timezone.h"
+#include "spandsp/t4_rx.h"
+#include "spandsp/t4_tx.h"
+#if defined(SPANDSP_SUPPORT_T85)
+#include "spandsp/t81_t82_arith_coding.h"
+#include "spandsp/t85.h"
+#endif
+#include "spandsp/t4_t6_decode.h"
+#include "spandsp/t4_t6_encode.h"
+
+#include "spandsp/private/logging.h"
+#if defined(SPANDSP_SUPPORT_T85)
+#include "spandsp/private/t81_t82_arith_coding.h"
+#include "spandsp/private/t85.h"
+#endif
+#include "spandsp/private/t4_t6_decode.h"
+#include "spandsp/private/t4_t6_encode.h"
+#include "spandsp/private/t4_rx.h"
+#include "spandsp/private/t4_tx.h"
+
+/*! The number of centimetres in one inch */
+#define CM_PER_INCH                 2.54f
+
+/*! The number of EOLs to be sent at the end of a T.4 page */
+#define EOLS_TO_END_T4_TX_PAGE      6
+/*! The number of EOLs to be sent at the end of a T.6 page */
+#define EOLS_TO_END_T6_TX_PAGE      2
+
+#if defined(T4_STATE_DEBUGGING)
+static void STATE_TRACE(const char *format, ...)
+{
+    va_list arg_ptr;
+
+    va_start(arg_ptr, format);
+    vprintf(format, arg_ptr);
+    va_end(arg_ptr);
+}
+/*- End of function --------------------------------------------------------*/
+#else
+#define STATE_TRACE(...) /**/
+#endif
+
+/*! T.4 run length table entry */
+typedef struct
+{
+    /*! Length of T.4 code, in bits */
+    uint16_t length;
+    /*! T.4 code */
+    uint16_t code;
+    /*! Run length, in bits */
+    int16_t run_length;
+} t4_run_table_entry_t;
+
+#include "faxfont.h"
+
+/* Legitimate runs of zero bits which are the tail end of one code
+   plus the start of the next code do not exceed 10 bits. */
+
+/*
+ * Note that these tables are ordered such that the index into the table
+ * is known to be either the run length, or (run length / 64) + a fixed
+ * offset.
+ */
+static const t4_run_table_entry_t t4_white_codes[] =
+{
+    { 8, 0x00AC,    0},         /* 0011 0101 */
+    { 6, 0x0038,    1},         /* 0001 11 */
+    { 4, 0x000E,    2},         /* 0111 */
+    { 4, 0x0001,    3},         /* 1000 */
+    { 4, 0x000D,    4},         /* 1011 */
+    { 4, 0x0003,    5},         /* 1100 */
+    { 4, 0x0007,    6},         /* 1110 */
+    { 4, 0x000F,    7},         /* 1111 */
+    { 5, 0x0019,    8},         /* 1001 1 */
+    { 5, 0x0005,    9},         /* 1010 0 */
+    { 5, 0x001C,   10},         /* 0011 1 */
+    { 5, 0x0002,   11},         /* 0100 0 */
+    { 6, 0x0004,   12},         /* 0010 00 */
+    { 6, 0x0030,   13},         /* 0000 11 */
+    { 6, 0x000B,   14},         /* 1101 00 */
+    { 6, 0x002B,   15},         /* 1101 01 */
+    { 6, 0x0015,   16},         /* 1010 10 */
+    { 6, 0x0035,   17},         /* 1010 11 */
+    { 7, 0x0072,   18},         /* 0100 111 */
+    { 7, 0x0018,   19},         /* 0001 100 */
+    { 7, 0x0008,   20},         /* 0001 000 */
+    { 7, 0x0074,   21},         /* 0010 111 */
+    { 7, 0x0060,   22},         /* 0000 011 */
+    { 7, 0x0010,   23},         /* 0000 100 */
+    { 7, 0x000A,   24},         /* 0101 000 */
+    { 7, 0x006A,   25},         /* 0101 011 */
+    { 7, 0x0064,   26},         /* 0010 011 */
+    { 7, 0x0012,   27},         /* 0100 100 */
+    { 7, 0x000C,   28},         /* 0011 000 */
+    { 8, 0x0040,   29},         /* 0000 0010 */
+    { 8, 0x00C0,   30},         /* 0000 0011 */
+    { 8, 0x0058,   31},         /* 0001 1010 */
+    { 8, 0x00D8,   32},         /* 0001 1011 */
+    { 8, 0x0048,   33},         /* 0001 0010 */
+    { 8, 0x00C8,   34},         /* 0001 0011 */
+    { 8, 0x0028,   35},         /* 0001 0100 */
+    { 8, 0x00A8,   36},         /* 0001 0101 */
+    { 8, 0x0068,   37},         /* 0001 0110 */
+    { 8, 0x00E8,   38},         /* 0001 0111 */
+    { 8, 0x0014,   39},         /* 0010 1000 */
+    { 8, 0x0094,   40},         /* 0010 1001 */
+    { 8, 0x0054,   41},         /* 0010 1010 */
+    { 8, 0x00D4,   42},         /* 0010 1011 */
+    { 8, 0x0034,   43},         /* 0010 1100 */
+    { 8, 0x00B4,   44},         /* 0010 1101 */
+    { 8, 0x0020,   45},         /* 0000 0100 */
+    { 8, 0x00A0,   46},         /* 0000 0101 */
+    { 8, 0x0050,   47},         /* 0000 1010 */
+    { 8, 0x00D0,   48},         /* 0000 1011 */
+    { 8, 0x004A,   49},         /* 0101 0010 */
+    { 8, 0x00CA,   50},         /* 0101 0011 */
+    { 8, 0x002A,   51},         /* 0101 0100 */
+    { 8, 0x00AA,   52},         /* 0101 0101 */
+    { 8, 0x0024,   53},         /* 0010 0100 */
+    { 8, 0x00A4,   54},         /* 0010 0101 */
+    { 8, 0x001A,   55},         /* 0101 1000 */
+    { 8, 0x009A,   56},         /* 0101 1001 */
+    { 8, 0x005A,   57},         /* 0101 1010 */
+    { 8, 0x00DA,   58},         /* 0101 1011 */
+    { 8, 0x0052,   59},         /* 0100 1010 */
+    { 8, 0x00D2,   60},         /* 0100 1011 */
+    { 8, 0x004C,   61},         /* 0011 0010 */
+    { 8, 0x00CC,   62},         /* 0011 0011 */
+    { 8, 0x002C,   63},         /* 0011 0100 */
+    { 5, 0x001B,   64},         /* 1101 1 */
+    { 5, 0x0009,  128},         /* 1001 0 */
+    { 6, 0x003A,  192},         /* 0101 11 */
+    { 7, 0x0076,  256},         /* 0110 111 */
+    { 8, 0x006C,  320},         /* 0011 0110 */
+    { 8, 0x00EC,  384},         /* 0011 0111 */
+    { 8, 0x0026,  448},         /* 0110 0100 */
+    { 8, 0x00A6,  512},         /* 0110 0101 */
+    { 8, 0x0016,  576},         /* 0110 1000 */
+    { 8, 0x00E6,  640},         /* 0110 0111 */
+    { 9, 0x0066,  704},         /* 0110 0110 0 */
+    { 9, 0x0166,  768},         /* 0110 0110 1 */
+    { 9, 0x0096,  832},         /* 0110 1001 0 */
+    { 9, 0x0196,  896},         /* 0110 1001 1 */
+    { 9, 0x0056,  960},         /* 0110 1010 0 */
+    { 9, 0x0156, 1024},         /* 0110 1010 1 */
+    { 9, 0x00D6, 1088},         /* 0110 1011 0 */
+    { 9, 0x01D6, 1152},         /* 0110 1011 1 */
+    { 9, 0x0036, 1216},         /* 0110 1100 0 */
+    { 9, 0x0136, 1280},         /* 0110 1100 1 */
+    { 9, 0x00B6, 1344},         /* 0110 1101 0 */
+    { 9, 0x01B6, 1408},         /* 0110 1101 1 */
+    { 9, 0x0032, 1472},         /* 0100 1100 0 */
+    { 9, 0x0132, 1536},         /* 0100 1100 1 */
+    { 9, 0x00B2, 1600},         /* 0100 1101 0 */
+    { 6, 0x0006, 1664},         /* 0110 00 */
+    { 9, 0x01B2, 1728},         /* 0100 1101 1 */
+    {11, 0x0080, 1792},         /* 0000 0001 000 */
+    {11, 0x0180, 1856},         /* 0000 0001 100 */
+    {11, 0x0580, 1920},         /* 0000 0001 101 */
+    {12, 0x0480, 1984},         /* 0000 0001 0010 */
+    {12, 0x0C80, 2048},         /* 0000 0001 0011 */
+    {12, 0x0280, 2112},         /* 0000 0001 0100 */
+    {12, 0x0A80, 2176},         /* 0000 0001 0101 */
+    {12, 0x0680, 2240},         /* 0000 0001 0110 */
+    {12, 0x0E80, 2304},         /* 0000 0001 0111 */
+    {12, 0x0380, 2368},         /* 0000 0001 1100 */
+    {12, 0x0B80, 2432},         /* 0000 0001 1101 */
+    {12, 0x0780, 2496},         /* 0000 0001 1110 */
+    {12, 0x0F80, 2560},         /* 0000 0001 1111 */
+};
+
+static const t4_run_table_entry_t t4_black_codes[] =
+{
+    {10, 0x03B0,    0},         /* 0000 1101 11 */
+    { 3, 0x0002,    1},         /* 010 */
+    { 2, 0x0003,    2},         /* 11 */
+    { 2, 0x0001,    3},         /* 10 */
+    { 3, 0x0006,    4},         /* 011 */
+    { 4, 0x000C,    5},         /* 0011 */
+    { 4, 0x0004,    6},         /* 0010 */
+    { 5, 0x0018,    7},         /* 0001 1 */
+    { 6, 0x0028,    8},         /* 0001 01 */
+    { 6, 0x0008,    9},         /* 0001 00 */
+    { 7, 0x0010,   10},         /* 0000 100 */
+    { 7, 0x0050,   11},         /* 0000 101 */
+    { 7, 0x0070,   12},         /* 0000 111 */
+    { 8, 0x0020,   13},         /* 0000 0100 */
+    { 8, 0x00E0,   14},         /* 0000 0111 */
+    { 9, 0x0030,   15},         /* 0000 1100 0 */
+    {10, 0x03A0,   16},         /* 0000 0101 11 */
+    {10, 0x0060,   17},         /* 0000 0110 00 */
+    {10, 0x0040,   18},         /* 0000 0010 00 */
+    {11, 0x0730,   19},         /* 0000 1100 111 */
+    {11, 0x00B0,   20},         /* 0000 1101 000 */
+    {11, 0x01B0,   21},         /* 0000 1101 100 */
+    {11, 0x0760,   22},         /* 0000 0110 111 */
+    {11, 0x00A0,   23},         /* 0000 0101 000 */
+    {11, 0x0740,   24},         /* 0000 0010 111 */
+    {11, 0x00C0,   25},         /* 0000 0011 000 */
+    {12, 0x0530,   26},         /* 0000 1100 1010 */
+    {12, 0x0D30,   27},         /* 0000 1100 1011 */
+    {12, 0x0330,   28},         /* 0000 1100 1100 */
+    {12, 0x0B30,   29},         /* 0000 1100 1101 */
+    {12, 0x0160,   30},         /* 0000 0110 1000 */
+    {12, 0x0960,   31},         /* 0000 0110 1001 */
+    {12, 0x0560,   32},         /* 0000 0110 1010 */
+    {12, 0x0D60,   33},         /* 0000 0110 1011 */
+    {12, 0x04B0,   34},         /* 0000 1101 0010 */
+    {12, 0x0CB0,   35},         /* 0000 1101 0011 */
+    {12, 0x02B0,   36},         /* 0000 1101 0100 */
+    {12, 0x0AB0,   37},         /* 0000 1101 0101 */
+    {12, 0x06B0,   38},         /* 0000 1101 0110 */
+    {12, 0x0EB0,   39},         /* 0000 1101 0111 */
+    {12, 0x0360,   40},         /* 0000 0110 1100 */
+    {12, 0x0B60,   41},         /* 0000 0110 1101 */
+    {12, 0x05B0,   42},         /* 0000 1101 1010 */
+    {12, 0x0DB0,   43},         /* 0000 1101 1011 */
+    {12, 0x02A0,   44},         /* 0000 0101 0100 */
+    {12, 0x0AA0,   45},         /* 0000 0101 0101 */
+    {12, 0x06A0,   46},         /* 0000 0101 0110 */
+    {12, 0x0EA0,   47},         /* 0000 0101 0111 */
+    {12, 0x0260,   48},         /* 0000 0110 0100 */
+    {12, 0x0A60,   49},         /* 0000 0110 0101 */
+    {12, 0x04A0,   50},         /* 0000 0101 0010 */
+    {12, 0x0CA0,   51},         /* 0000 0101 0011 */
+    {12, 0x0240,   52},         /* 0000 0010 0100 */
+    {12, 0x0EC0,   53},         /* 0000 0011 0111 */
+    {12, 0x01C0,   54},         /* 0000 0011 1000 */
+    {12, 0x0E40,   55},         /* 0000 0010 0111 */
+    {12, 0x0140,   56},         /* 0000 0010 1000 */
+    {12, 0x01A0,   57},         /* 0000 0101 1000 */
+    {12, 0x09A0,   58},         /* 0000 0101 1001 */
+    {12, 0x0D40,   59},         /* 0000 0010 1011 */
+    {12, 0x0340,   60},         /* 0000 0010 1100 */
+    {12, 0x05A0,   61},         /* 0000 0101 1010 */
+    {12, 0x0660,   62},         /* 0000 0110 0110 */
+    {12, 0x0E60,   63},         /* 0000 0110 0111 */
+    {10, 0x03C0,   64},         /* 0000 0011 11 */
+    {12, 0x0130,  128},         /* 0000 1100 1000 */
+    {12, 0x0930,  192},         /* 0000 1100 1001 */
+    {12, 0x0DA0,  256},         /* 0000 0101 1011 */
+    {12, 0x0CC0,  320},         /* 0000 0011 0011 */
+    {12, 0x02C0,  384},         /* 0000 0011 0100 */
+    {12, 0x0AC0,  448},         /* 0000 0011 0101 */
+    {13, 0x06C0,  512},         /* 0000 0011 0110 0 */
+    {13, 0x16C0,  576},         /* 0000 0011 0110 1 */
+    {13, 0x0A40,  640},         /* 0000 0010 0101 0 */
+    {13, 0x1A40,  704},         /* 0000 0010 0101 1 */
+    {13, 0x0640,  768},         /* 0000 0010 0110 0 */
+    {13, 0x1640,  832},         /* 0000 0010 0110 1 */
+    {13, 0x09C0,  896},         /* 0000 0011 1001 0 */
+    {13, 0x19C0,  960},         /* 0000 0011 1001 1 */
+    {13, 0x05C0, 1024},         /* 0000 0011 1010 0 */
+    {13, 0x15C0, 1088},         /* 0000 0011 1010 1 */
+    {13, 0x0DC0, 1152},         /* 0000 0011 1011 0 */
+    {13, 0x1DC0, 1216},         /* 0000 0011 1011 1 */
+    {13, 0x0940, 1280},         /* 0000 0010 1001 0 */
+    {13, 0x1940, 1344},         /* 0000 0010 1001 1 */
+    {13, 0x0540, 1408},         /* 0000 0010 1010 0 */
+    {13, 0x1540, 1472},         /* 0000 0010 1010 1 */
+    {13, 0x0B40, 1536},         /* 0000 0010 1101 0 */
+    {13, 0x1B40, 1600},         /* 0000 0010 1101 1 */
+    {13, 0x04C0, 1664},         /* 0000 0011 0010 0 */
+    {13, 0x14C0, 1728},         /* 0000 0011 0010 1 */
+    {11, 0x0080, 1792},         /* 0000 0001 000 */
+    {11, 0x0180, 1856},         /* 0000 0001 100 */
+    {11, 0x0580, 1920},         /* 0000 0001 101 */
+    {12, 0x0480, 1984},         /* 0000 0001 0010 */
+    {12, 0x0C80, 2048},         /* 0000 0001 0011 */
+    {12, 0x0280, 2112},         /* 0000 0001 0100 */
+    {12, 0x0A80, 2176},         /* 0000 0001 0101 */
+    {12, 0x0680, 2240},         /* 0000 0001 0110 */
+    {12, 0x0E80, 2304},         /* 0000 0001 0111 */
+    {12, 0x0380, 2368},         /* 0000 0001 1100 */
+    {12, 0x0B80, 2432},         /* 0000 0001 1101 */
+    {12, 0x0780, 2496},         /* 0000 0001 1110 */
+    {12, 0x0F80, 2560},         /* 0000 0001 1111 */
+};
+
+static int encode_row(t4_state_t *s);
+
+static void make_header(t4_state_t *s, char *header)
+{
+    time_t now;
+    struct tm tm;
+    static const char *months[] =
+    {
+        "Jan",
+        "Feb",
+        "Mar",
+        "Apr",
+        "May",
+        "Jun",
+        "Jul",
+        "Aug",
+        "Sep",
+        "Oct",
+        "Nov",
+        "Dec"
+    };
+
+    time(&now);
+    if (s->tz)
+        tz_localtime(s->tz, &tm, now);
+    else
+        tm = *localtime(&now);
+    snprintf(header,
+             132,
+             "  %2d-%s-%d  %02d:%02d    %-50s %-21s   p.%d",
+             tm.tm_mday,
+             months[tm.tm_mon],
+             tm.tm_year + 1900,
+             tm.tm_hour,
+             tm.tm_min,
+             s->header_info,
+             (s->tiff.local_ident)  ?  s->tiff.local_ident  :  "",
+             s->current_page + 1);
+}
+/*- End of function --------------------------------------------------------*/
+
+static int t4_tx_put_fax_header(t4_state_t *s)
+{
+    int row;
+    int i;
+    int repeats;
+    int pattern;
+    int row_bufptr;
+    char *t;
+    char header[132 + 1];
+
+    /* Modify the resulting image to include a header line, typical of hardware FAX machines */
+    make_header(s, header);
+    switch (s->y_resolution)
+    {
+    case T4_Y_RESOLUTION_1200:
+        repeats = 12;
+        break;
+    case T4_Y_RESOLUTION_800:
+        repeats = 8;
+        break;
+    case T4_Y_RESOLUTION_600:
+        repeats = 6;
+        break;
+    case T4_Y_RESOLUTION_SUPERFINE:
+        repeats = 4;
+        break;
+    case T4_Y_RESOLUTION_300:
+        repeats = 3;
+        break;
+    case T4_Y_RESOLUTION_FINE:
+        repeats = 2;
+        break;
+    default:
+        repeats = 1;
+        break;
+    }
+    for (row = 0;  row < 16;  row++)
+    {
+        t = header;
+        row_bufptr = 0;
+        for (t = header;  *t  &&  row_bufptr <= s->bytes_per_row - 2;  t++)
+        {
+            pattern = header_font[(uint8_t) *t][row];
+            s->row_buf[row_bufptr++] = (uint8_t) (pattern >> 8);
+            s->row_buf[row_bufptr++] = (uint8_t) (pattern & 0xFF);
+        }
+        for (  ;  row_bufptr < s->bytes_per_row;  )
+            s->row_buf[row_bufptr++] = 0;
+        for (i = 0;  i < repeats;  i++)
+        {
+            if (encode_row(s))
+                return -1;
+        }
+    }
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int test_resolution(int res_unit, float actual, float expected)
+{
+    if (res_unit == RESUNIT_INCH)
+        actual *= 1.0f/CM_PER_INCH;
+    return (expected*0.95f <= actual  &&  actual <= expected*1.05f);
+}
+/*- End of function --------------------------------------------------------*/
+
+static int get_tiff_directory_info(t4_state_t *s)
+{
+    static const struct
+    {
+        float resolution;
+        int code;
+    } x_res_table[] =
+    {
+        { 102.0f/CM_PER_INCH, T4_X_RESOLUTION_R4},
+        { 204.0f/CM_PER_INCH, T4_X_RESOLUTION_R8},
+        { 300.0f/CM_PER_INCH, T4_X_RESOLUTION_300},
+        { 408.0f/CM_PER_INCH, T4_X_RESOLUTION_R16},
+        { 600.0f/CM_PER_INCH, T4_X_RESOLUTION_600},
+        { 800.0f/CM_PER_INCH, T4_X_RESOLUTION_800},
+        {1200.0f/CM_PER_INCH, T4_X_RESOLUTION_1200},
+        {             -1.00f, -1}
+    };
+    static const struct
+    {
+        float resolution;
+        int code;
+        int max_rows_to_next_1d_row;
+    } y_res_table[] =
+    {
+        {             38.50f, T4_Y_RESOLUTION_STANDARD, 2},
+        {             77.00f, T4_Y_RESOLUTION_FINE, 4},
+        { 300.0f/CM_PER_INCH, T4_Y_RESOLUTION_300, 6},
+        {            154.00f, T4_Y_RESOLUTION_SUPERFINE, 8},
+        { 600.0f/CM_PER_INCH, T4_Y_RESOLUTION_600, 12},
+        { 800.0f/CM_PER_INCH, T4_Y_RESOLUTION_800, 16},
+        {1200.0f/CM_PER_INCH, T4_Y_RESOLUTION_1200, 24},
+        {             -1.00f, -1, -1}
+    };
+    uint16_t res_unit;
+    uint16_t parm16;
+    uint32_t parm32;
+    float x_resolution;
+    float y_resolution;
+    int i;
+    t4_tiff_state_t *t;
+
+    t = &s->tiff;
+    parm16 = 0;
+    TIFFGetField(t->tiff_file, TIFFTAG_BITSPERSAMPLE, &parm16);
+    if (parm16 != 1)
+        return -1;
+    TIFFGetField(t->tiff_file, TIFFTAG_SAMPLESPERPIXEL, &parm16);
+    if (parm16 != 1)
+        return -1;
+    parm32 = 0;
+    TIFFGetField(t->tiff_file, TIFFTAG_IMAGEWIDTH, &parm32);
+    s->image_width = parm32;
+    s->bytes_per_row = (s->image_width + 7)/8;
+    parm32 = 0;
+    TIFFGetField(t->tiff_file, TIFFTAG_IMAGELENGTH, &parm32);
+    s->image_length = parm32;
+    x_resolution = 0.0f;
+    TIFFGetField(t->tiff_file, TIFFTAG_XRESOLUTION, &x_resolution);
+    y_resolution = 0.0f;
+    TIFFGetField(t->tiff_file, TIFFTAG_YRESOLUTION, &y_resolution);
+    res_unit = RESUNIT_INCH;
+    TIFFGetField(t->tiff_file, TIFFTAG_RESOLUTIONUNIT, &res_unit);
+    t->photo_metric = PHOTOMETRIC_MINISWHITE;
+    TIFFGetField(t->tiff_file, TIFFTAG_PHOTOMETRIC, &t->photo_metric);
+    if (t->photo_metric != PHOTOMETRIC_MINISWHITE)
+        span_log(&s->logging, SPAN_LOG_FLOW, "%s: Photometric needs swapping.\n", t->file);
+    t->fill_order = FILLORDER_LSB2MSB;
+#if 0
+    TIFFGetField(t->tiff_file, TIFFTAG_FILLORDER, &t->fill_order);
+    if (t->fill_order != FILLORDER_LSB2MSB)
+        span_log(&s->logging, SPAN_LOG_FLOW, "%s: Fill order needs swapping.\n", t->file);
+#endif
+
+    /* Allow a little range for the X resolution in centimeters. The spec doesn't pin down the
+       precise value. The other value should be exact. */
+    /* Treat everything we can't match as R8. Most FAXes are this resolution anyway. */
+    s->x_resolution = T4_X_RESOLUTION_R8;
+    for (i = 0;  x_res_table[i].code > 0;  i++)
+    {
+        if (test_resolution(res_unit, x_resolution, x_res_table[i].resolution))
+        {
+            s->x_resolution = x_res_table[i].code;
+            break;
+        }
+    }
+
+    s->y_resolution = T4_Y_RESOLUTION_STANDARD;
+    s->t4_t6_tx.max_rows_to_next_1d_row = 2;
+    for (i = 0;  y_res_table[i].code > 0;  i++)
+    {
+        if (test_resolution(res_unit, y_resolution, y_res_table[i].resolution))
+        {
+            s->y_resolution = y_res_table[i].code;
+            s->t4_t6_tx.max_rows_to_next_1d_row = y_res_table[i].max_rows_to_next_1d_row;
+            break;
+        }
+    }
+
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int test_tiff_directory_info(t4_state_t *s)
+{
+    static const struct
+    {
+        float resolution;
+        int code;
+    } x_res_table[] =
+    {
+        { 102.0f/CM_PER_INCH, T4_X_RESOLUTION_R4},
+        { 204.0f/CM_PER_INCH, T4_X_RESOLUTION_R8},
+        { 300.0f/CM_PER_INCH, T4_X_RESOLUTION_300},
+        { 408.0f/CM_PER_INCH, T4_X_RESOLUTION_R16},
+        { 600.0f/CM_PER_INCH, T4_X_RESOLUTION_600},
+        { 800.0f/CM_PER_INCH, T4_X_RESOLUTION_800},
+        {1200.0f/CM_PER_INCH, T4_X_RESOLUTION_1200},
+        {             -1.00f, -1}
+    };
+    static const struct
+    {
+        float resolution;
+        int code;
+        int max_rows_to_next_1d_row;
+    } y_res_table[] =
+    {
+        {             38.50f, T4_Y_RESOLUTION_STANDARD, 2},
+        {             77.00f, T4_Y_RESOLUTION_FINE, 4},
+        { 300.0f/CM_PER_INCH, T4_Y_RESOLUTION_300, 6},
+        {            154.00f, T4_Y_RESOLUTION_SUPERFINE, 8},
+        { 600.0f/CM_PER_INCH, T4_Y_RESOLUTION_600, 12},
+        { 800.0f/CM_PER_INCH, T4_Y_RESOLUTION_800, 16},
+        {1200.0f/CM_PER_INCH, T4_Y_RESOLUTION_1200, 24},
+        {             -1.00f, -1, -1}
+    };
+    uint16_t res_unit;
+    uint16_t parm16;
+    uint32_t parm32;
+    float x_resolution;
+    float y_resolution;
+    int i;
+    t4_tiff_state_t *t;
+
+    t = &s->tiff;
+    parm16 = 0;
+    TIFFGetField(t->tiff_file, TIFFTAG_BITSPERSAMPLE, &parm16);
+    if (parm16 != 1)
+        return -1;
+    parm32 = 0;
+    TIFFGetField(t->tiff_file, TIFFTAG_IMAGEWIDTH, &parm32);
+    if (s->image_width != (int) parm32)
+        return 1;
+    x_resolution = 0.0f;
+    TIFFGetField(t->tiff_file, TIFFTAG_XRESOLUTION, &x_resolution);
+    y_resolution = 0.0f;
+    TIFFGetField(t->tiff_file, TIFFTAG_YRESOLUTION, &y_resolution);
+    res_unit = RESUNIT_INCH;
+    TIFFGetField(t->tiff_file, TIFFTAG_RESOLUTIONUNIT, &res_unit);
+
+    /* Allow a little range for the X resolution in centimeters. The spec doesn't pin down the
+       precise value. The other value should be exact. */
+    /* Treat everything we can't match as R8. Most FAXes are this resolution anyway. */
+    for (i = 0;  x_res_table[i].code > 0;  i++)
+    {
+        if (test_resolution(res_unit, x_resolution, x_res_table[i].resolution))
+            break;
+    }
+    if (s->x_resolution != x_res_table[i].code)
+        return 1;
+    for (i = 0;  y_res_table[i].code > 0;  i++)
+    {
+        if (test_resolution(res_unit, y_resolution, y_res_table[i].resolution))
+            break;
+    }
+    if (s->y_resolution != y_res_table[i].code)
+        return 1;
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int get_tiff_total_pages(t4_state_t *s)
+{
+    int max;
+
+    /* Each page *should* contain the total number of pages, but can this be
+       trusted? Some files say 0. Actually searching for the last page is
+       more reliable. */
+    max = 0;
+    while (TIFFSetDirectory(s->tiff.tiff_file, (tdir_t) max))
+        max++;
+    /* Back to the previous page */
+    if (!TIFFSetDirectory(s->tiff.tiff_file, (tdir_t) s->current_page))
+        return -1;
+    return max;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int open_tiff_input_file(t4_state_t *s, const char *file)
+{
+    if ((s->tiff.tiff_file = TIFFOpen(file, "r")) == NULL)
+        return -1;
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int read_tiff_image(t4_state_t *s)
+{
+    int row;
+    int image_length;
+    int i;
+
+    image_length = 0;
+    TIFFGetField(s->tiff.tiff_file, TIFFTAG_IMAGELENGTH, &image_length);
+    for (row = 0;  row < image_length;  row++)
+    {
+        if (TIFFReadScanline(s->tiff.tiff_file, s->row_buf, row, 0) <= 0)
+        {
+            span_log(&s->logging, SPAN_LOG_WARNING, "%s: Read error at row %d.\n", s->tiff.file, row);
+            break;
+        }
+        if (s->tiff.photo_metric != PHOTOMETRIC_MINISWHITE)
+        {
+            for (i = 0;  i < s->bytes_per_row;  i++)
+                s->row_buf[i] = ~s->row_buf[i];
+        }
+        if (s->tiff.fill_order != FILLORDER_LSB2MSB)
+            bit_reverse(s->row_buf, s->row_buf, s->bytes_per_row);
+        if (encode_row(s))
+            return -1;
+    }
+    return image_length;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int close_tiff_input_file(t4_state_t *s)
+{
+    TIFFClose(s->tiff.tiff_file);
+    s->tiff.tiff_file = NULL;
+    if (s->tiff.file)
+        free((char *) s->tiff.file);
+    s->tiff.file = NULL;
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static void update_row_bit_info(t4_state_t *s)
+{
+    if (s->row_bits > s->max_row_bits)
+        s->max_row_bits = s->row_bits;
+    if (s->row_bits < s->min_row_bits)
+        s->min_row_bits = s->row_bits;
+    s->row_bits = 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int free_buffers(t4_state_t *s)
+{
+    if (s->image_buffer)
+    {
+        free(s->image_buffer);
+        s->image_buffer = NULL;
+        s->image_buffer_size = 0;
+    }
+    if (s->cur_runs)
+    {
+        free(s->cur_runs);
+        s->cur_runs = NULL;
+    }
+    if (s->ref_runs)
+    {
+        free(s->ref_runs);
+        s->ref_runs = NULL;
+    }
+    if (s->row_buf)
+    {
+        free(s->row_buf);
+        s->row_buf = NULL;
+    }
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int row_to_run_lengths(uint32_t list[], const uint8_t row[], int width)
+{
+    uint32_t flip;
+    uint32_t x;
+    int span;
+    int entry;
+    int frag;
+    int rem;
+    int limit;
+    int i;
+    int pos;
+
+    /* Deal with whole words first. We know we are starting on a word boundary. */
+    entry = 0;
+    flip = 0;
+    limit = (width >> 3) & ~3;
+    span = 0;
+    pos = 0;
+    for (i = 0;  i < limit;  i += sizeof(uint32_t))
+    {
+        x = *((uint32_t *) &row[i]);
+        if (x != flip)
+        {
+            x = ((uint32_t) row[i] << 24) | ((uint32_t) row[i + 1] << 16) | ((uint32_t) row[i + 2] << 8) | ((uint32_t) row[i + 3]);
+            /* We know we are going to find at least one transition. */
+            frag = 31 - top_bit(x ^ flip);
+            pos += ((i << 3) - span + frag);
+            list[entry++] = pos;
+            x <<= frag;
+            flip ^= 0xFFFFFFFF;
+            rem = 32 - frag;
+            /* Now see if there are any more */
+            while ((frag = 31 - top_bit(x ^ flip)) < rem)
+            {
+                pos += frag;
+                list[entry++] = pos;
+                x <<= frag;
+                flip ^= 0xFFFFFFFF;
+                rem -= frag;
+            }
+            /* Save the remainder of the word */
+            span = (i << 3) + 32 - rem;
+        }
+    }
+    /* Now deal with some whole bytes, if there are any left. */
+    limit = width >> 3;
+    flip &= 0xFF000000;
+    if (i < limit)
+    {
+        for (  ;  i < limit;  i++)
+        {
+            x = (uint32_t) row[i] << 24;
+            if (x != flip)
+            {
+                /* We know we are going to find at least one transition. */
+                frag = 31 - top_bit(x ^ flip);
+                pos += ((i << 3) - span + frag);
+                list[entry++] = pos;
+                x <<= frag;
+                flip ^= 0xFF000000;
+                rem = 8 - frag;
+                /* Now see if there are any more */
+                while ((frag = 31 - top_bit(x ^ flip)) < rem)
+                {
+                    pos += frag;
+                    list[entry++] = pos;
+                    x <<= frag;
+                    flip ^= 0xFF000000;
+                    rem -= frag;
+                }   
+                /* Save the remainder of the word */
+                span = (i << 3) + 8 - rem;
+            }
+        }
+    }
+    /* Deal with any left over fractional byte. */
+    span = (i << 3) - span;
+    if ((rem = width & 7))
+    {
+        x = row[i];
+        x <<= 24;
+        do
+        {
+            frag = 31 - top_bit(x ^ flip);
+            if (frag > rem)
+                frag = rem;
+            pos += (span + frag);
+            list[entry++] = pos;
+            x <<= frag;
+            span = 0;
+            flip ^= 0xFF000000;
+            rem -= frag;
+        }
+        while (rem > 0);
+    }
+    else
+    {
+        if (span)
+        {
+            pos += span;
+            list[entry++] = pos;
+        }
+    }
+    return entry;
+}
+/*- End of function --------------------------------------------------------*/
+
+static __inline__ int put_encoded_bits(t4_state_t *s, uint32_t bits, int length)
+{
+    uint8_t *t;
+
+    /* We might be called with a large length value, to spew out a mass of zero bits for
+       minimum row length padding. */
+    s->tx_bitstream |= (bits << s->tx_bits);
+    s->tx_bits += length;
+    s->row_bits += length;
+    if ((s->image_size + (s->tx_bits + 7)/8) >= s->image_buffer_size)
+    {
+        if ((t = realloc(s->image_buffer, s->image_buffer_size + 100*s->bytes_per_row)) == NULL)
+            return -1;
+        s->image_buffer = t;
+        s->image_buffer_size += 100*s->bytes_per_row;
+    }
+    while (s->tx_bits >= 8)
+    {
+        s->image_buffer[s->image_size++] = (uint8_t) (s->tx_bitstream & 0xFF);
+        s->tx_bitstream >>= 8;
+        s->tx_bits -= 8;
+    }
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+/*
+ * Write the sequence of codes that describes
+ * the specified span of zero's or one's.  The
+ * appropriate table that holds the make-up and
+ * terminating codes is supplied.
+ */
+static __inline__ int put_1d_span(t4_state_t *s, int32_t span, const t4_run_table_entry_t *tab)
+{
+    const t4_run_table_entry_t *te;
+
+    te = &tab[63 + (2560 >> 6)];
+    while (span >= 2560 + 64)
+    {
+        if (put_encoded_bits(s, te->code, te->length))
+            return -1;
+        span -= te->run_length;
+    }
+    te = &tab[63 + (span >> 6)];
+    if (span >= 64)
+    {
+        if (put_encoded_bits(s, te->code, te->length))
+            return -1;
+        span -= te->run_length;
+    }
+    if (put_encoded_bits(s, tab[span].code, tab[span].length))
+        return -1;
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+#define pixel_is_black(x,bit) (((x)[(bit) >> 3] << ((bit) & 7)) & 0x80)
+
+/*
+ * Write an EOL code to the output stream.  We also handle writing the tag
+ * bit for the next scanline when doing 2D encoding.
+ */
+static void encode_eol(t4_state_t *s)
+{
+    uint32_t code;
+    int length;
+
+    if (s->line_encoding == T4_COMPRESSION_ITU_T4_2D)
+    {
+        code = 0x0800 | ((!s->row_is_2d) << 12);
+        length = 13;
+    }
+    else
+    {
+        /* T.4 1D EOL, or T.6 EOFB */
+        code = 0x800;
+        length = 12;
+    }
+    if (s->row_bits)
+    {
+        /* We may need to pad the row to a minimum length, unless we are in T.6 mode.
+           In T.6 we only come here at the end of the page to add the EOFB marker, which
+           is like two 1D EOLs. */
+        if (s->line_encoding != T4_COMPRESSION_ITU_T6)
+        {
+            if (s->row_bits + length < s->t4_t6_tx.min_bits_per_row)
+                put_encoded_bits(s, 0, s->t4_t6_tx.min_bits_per_row - (s->row_bits + length));
+        }
+        put_encoded_bits(s, code, length);
+        update_row_bit_info(s);
+    }
+    else
+    {
+        /* We don't pad zero length rows. They are the consecutive EOLs which end a page. */
+        put_encoded_bits(s, code, length);
+        /* Don't do the full update row bit info, or the minimum suddenly drops to the
+           length of an EOL. Just clear the row bits, so we treat the next EOL as an
+           end of page EOL, with no padding. */
+        s->row_bits = 0;
+    }
+}
+/*- End of function --------------------------------------------------------*/
+
+/*
+ * 2D-encode a row of pixels.  Consult ITU specification T.4 for the algorithm.
+ */
+static void encode_2d_row(t4_state_t *s)
+{
+    static const t4_run_table_entry_t codes[] =
+    {
+        { 7, 0x60, 0 },         /* VR3          0000 011 */
+        { 6, 0x30, 0 },         /* VR2          0000 11 */
+        { 3, 0x06, 0 },         /* VR1          011 */
+        { 1, 0x01, 0 },         /* V0           1 */
+        { 3, 0x02, 0 },         /* VL1          010 */
+        { 6, 0x10, 0 },         /* VL2          0000 10 */
+        { 7, 0x20, 0 },         /* VL3          0000 010 */
+        { 3, 0x04, 0 },         /* horizontal   001 */
+        { 4, 0x08, 0 }          /* pass         0001 */
+    };
+
+    /* The reference or starting changing element on the coding line. At the start of the coding
+       line, a0 is set on an imaginary white changing element situated just before the first element
+       on the line. During the coding of the coding line, the position of a0 is defined by the
+       previous coding mode. (See T.4/4.2.1.3.2.) */
+    int a0;
+    /* The next changing element to the right of a0 on the coding line. */
+    int a1;
+    /* The next changing element to the right of a1 on the coding line. */
+    int a2;
+    /* The first changing element on the reference line to the right of a0 and of opposite colour to a0. */
+    int b1;
+    /* The next changing element to the right of b1 on the reference line. */
+    int b2;
+    int diff;
+    int a_cursor;
+    int b_cursor;
+    int cur_steps;
+    uint32_t *p;
+
+    /*
+                                                    b1          b2 
+            XX  XX  XX  XX  XX  --  --  --  --  --  XX  XX  XX  --  --  --  --  --
+            XX  XX  XX  --  --  --  --  --  XX  XX  XX  XX  XX  XX  --  --  --  --
+                        a0                  a1                      a2
+
+
+        a)  Pass mode
+            This mode is identified when the position of b2 lies to the left of a1. When this mode
+            has been coded, a0 is set on the element of the coding line below b2 in preparation for
+            the next coding (i.e. on a0').
+            
+                                    b1          b2 
+            XX  XX  XX  XX  --  --  XX  XX  XX  --  --  --  --  --
+            XX  XX  --  --  --  --  --  --  --  --  --  --  XX  XX 
+                    a0                          a0'         a1
+                                Pass mode
+                                
+
+            However, the state where b2 occurs just above a1, as shown in the figure below, is not
+            considered as a pass mode.
+
+                                    b1          b2 
+            XX  XX  XX  XX  --  --  XX  XX  XX  --  --  --  --  --
+            XX  XX  --  --  --  --  --  --  --  XX  XX  XX  XX  XX
+                    a0                          a1
+                                Not pass mode
+
+
+        b)  Vertical mode
+            When this mode is identified, the position of a1 is coded relative to the position of b1.
+            The relative distance a1b1 can take on one of seven values V(0), VR(1), VR(2), VR(3),
+            VL(1), VL(2) and VL(3), each of which is represented by a separate code word. The
+            subscripts R and L indicate that a1 is to the right or left respectively of b1, and the
+            number in brackets indicates the value of the distance a1b1. After vertical mode coding
+            has occurred, the position of a0 is set on a1 (see figure below).
+
+        c)  Horizontal mode
+            When this mode is identified, both the run-lengths a0a1 and a1a2 are coded using the code
+            words H + M(a0a1) + M(a1a2). H is the flag code word 001 taken from the two-dimensional
+            code table. M(a0a1) and M(a1a2) are code words which represent the length and "colour"
+            of the runs a0a1 and a1a2 respectively and are taken from the appropriate white or black
+            one-dimensional code tables. After a horizontal mode coding, the position of a0 is set on
+            a2 (see figure below).
+
+                                                            Vertical
+                                                            <a1 b1>
+                                                                    b1              b2 
+            --  XX  XX  XX  XX  XX  --  --  --  --  --  --  --  --  XX  XX  XX  XX  --  --  --
+            --  --  --  --  --  --  --  --  --  --  --  --  XX  XX  XX  XX  XX  XX  XX  --  --
+                                    a0                      a1                          a2
+                                   <-------- a0a1 --------><-------- a1a2 ------------>
+                                                    Horizontal mode
+                          Vertical and horizontal modes
+     */
+    /* The following implements the 2-D encoding section of the flow chart in Figure7/T.4 */
+    cur_steps = row_to_run_lengths(s->cur_runs, s->row_buf, s->image_width);
+    /* Stretch the row a little, so when we step by 2 we are guaranteed to
+       hit an entry showing the row length */
+    s->cur_runs[cur_steps] =
+    s->cur_runs[cur_steps + 1] =
+    s->cur_runs[cur_steps + 2] = s->cur_runs[cur_steps - 1];
+
+    a0 = 0;
+    a1 = s->cur_runs[0];
+    b1 = s->ref_runs[0];
+    a_cursor = 0;
+    b_cursor = 0;
+    for (;;)
+    {
+        b2 = s->ref_runs[b_cursor + 1];
+        if (b2 >= a1)
+        {
+            diff = b1 - a1;
+            if (abs(diff) <= 3)
+            {
+                /* Vertical mode coding */
+                put_encoded_bits(s, codes[diff + 3].code, codes[diff + 3].length);
+                a0 = a1;
+                a_cursor++;
+            }
+            else
+            {
+                /* Horizontal mode coding */
+                a2 = s->cur_runs[a_cursor + 1];
+                put_encoded_bits(s, codes[7].code, codes[7].length);
+                if (a0 + a1 == 0  ||  pixel_is_black(s->row_buf, a0) == 0)
+                {
+                    put_1d_span(s, a1 - a0, t4_white_codes);
+                    put_1d_span(s, a2 - a1, t4_black_codes);
+                }
+                else
+                {
+                    put_1d_span(s, a1 - a0, t4_black_codes);
+                    put_1d_span(s, a2 - a1, t4_white_codes);
+                }
+                a0 = a2;
+                a_cursor += 2;
+            }
+            if (a0 >= s->image_width)
+                break;
+            if (a_cursor >= cur_steps)
+                a_cursor = cur_steps - 1;
+            a1 = s->cur_runs[a_cursor];
+        }
+        else
+        {
+            /* Pass mode coding */
+            put_encoded_bits(s, codes[8].code, codes[8].length);
+            /* We now set a0 to somewhere in the middle of its current run,
+               but we know are aren't moving beyond that run. */
+            a0 = b2;
+            if (a0 >= s->image_width)
+                break;
+        }
+        /* We need to hunt for the correct position in the reference row, as the
+           runs there have no particular alignment with the runs in the current
+           row. */
+        if (pixel_is_black(s->row_buf, a0))
+            b_cursor |= 1;
+        else
+            b_cursor &= ~1;
+        if (a0 < (int) s->ref_runs[b_cursor])
+        {
+            for (  ;  b_cursor >= 0;  b_cursor -= 2)
+            {
+                if (a0 >= (int) s->ref_runs[b_cursor])
+                    break;
+            }
+            b_cursor += 2;
+        }
+        else
+        {
+            for (  ;  b_cursor < s->t4_t6_tx.ref_steps;  b_cursor += 2)
+            {
+                if (a0 < (int) s->ref_runs[b_cursor])
+                    break;
+            }
+            if (b_cursor >= s->t4_t6_tx.ref_steps)
+                b_cursor = s->t4_t6_tx.ref_steps - 1;
+        }
+        b1 = s->ref_runs[b_cursor];
+    }
+    /* Swap the buffers */
+    s->t4_t6_tx.ref_steps = cur_steps;
+    p = s->cur_runs;
+    s->cur_runs = s->ref_runs;
+    s->ref_runs = p;
+}
+/*- End of function --------------------------------------------------------*/
+
+/*
+ * 1D-encode a row of pixels. The encoding is a sequence of all-white or
+ * all-black spans of pixels encoded with Huffman codes.
+ */
+static void encode_1d_row(t4_state_t *s)
+{
+    int i;
+
+    /* Do our work in the reference row buffer, and it is already in place if
+       we need a reference row for a following 2D encoded row. */
+    s->t4_t6_tx.ref_steps = row_to_run_lengths(s->ref_runs, s->row_buf, s->image_width);
+    put_1d_span(s, s->ref_runs[0], t4_white_codes);
+    for (i = 1;  i < s->t4_t6_tx.ref_steps;  i++)
+        put_1d_span(s, s->ref_runs[i] - s->ref_runs[i - 1], (i & 1)  ?  t4_black_codes  :  t4_white_codes);
+    /* Stretch the row a little, so when we step by 2 we are guaranteed to
+       hit an entry showing the row length */
+    s->ref_runs[s->t4_t6_tx.ref_steps] =
+    s->ref_runs[s->t4_t6_tx.ref_steps + 1] =
+    s->ref_runs[s->t4_t6_tx.ref_steps + 2] = s->ref_runs[s->t4_t6_tx.ref_steps - 1];
+}
+/*- End of function --------------------------------------------------------*/
+
+static int encode_row(t4_state_t *s)
+{
+    switch (s->line_encoding)
+    {
+    case T4_COMPRESSION_ITU_T6:
+        /* T.6 compression is a trivial step up from T.4 2D, so we just
+           throw it in here. T.6 is only used with error correction,
+           so it does not need independantly compressed (i.e. 1D) lines
+           to recover from data errors. It doesn't need EOLs, either. */
+        if (s->row_bits)
+            update_row_bit_info(s);
+        encode_2d_row(s);
+        break;
+    case T4_COMPRESSION_ITU_T4_2D:
+        encode_eol(s);
+        if (s->row_is_2d)
+        {
+            encode_2d_row(s);
+            s->t4_t6_tx.rows_to_next_1d_row--;
+        }
+        else
+        {
+            encode_1d_row(s);
+            s->row_is_2d = TRUE;
+        }
+        if (s->t4_t6_tx.rows_to_next_1d_row <= 0)
+        {
+            /* Insert a row of 1D encoding */
+            s->row_is_2d = FALSE;
+            s->t4_t6_tx.rows_to_next_1d_row = s->t4_t6_tx.max_rows_to_next_1d_row - 1;
+        }
+        break;
+    default:
+    case T4_COMPRESSION_ITU_T4_1D:
+        encode_eol(s);
+        encode_1d_row(s);
+        break;
+    }
+    s->row++;
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t4_tx_set_row_read_handler(t4_state_t *s, t4_row_read_handler_t handler, void *user_data)
+{
+    s->t4_t6_tx.row_read_handler = handler;
+    s->t4_t6_tx.row_read_user_data = user_data;
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(t4_state_t *) t4_tx_init(t4_state_t *s, const char *file, int start_page, int stop_page)
+{
+    int run_space;
+
+    if (s == NULL)
+    {
+        if ((s = (t4_state_t *) malloc(sizeof(*s))) == NULL)
+            return NULL;
+    }
+    memset(s, 0, sizeof(*s));
+    span_log_init(&s->logging, SPAN_LOG_NONE, NULL);
+    span_log_set_protocol(&s->logging, "T.4");
+    s->rx = FALSE;
+
+    span_log(&s->logging, SPAN_LOG_FLOW, "Start tx document\n");
+
+    if (open_tiff_input_file(s, file) < 0)
+        return NULL;
+    s->tiff.file = strdup(file);
+    s->current_page =
+    s->tiff.start_page = (start_page >= 0)  ?  start_page  :  0;
+    s->tiff.stop_page = (stop_page >= 0)  ?  stop_page : INT_MAX;
+
+    if (!TIFFSetDirectory(s->tiff.tiff_file, (tdir_t) s->current_page))
+        return NULL;
+    if (get_tiff_directory_info(s))
+    {
+        close_tiff_input_file(s);
+        return NULL;
+    }
+
+    s->t4_t6_tx.rows_to_next_1d_row = s->t4_t6_tx.max_rows_to_next_1d_row - 1;
+
+    s->tiff.pages_in_file = -1;
+
+    run_space = (s->image_width + 4)*sizeof(uint32_t);
+    if ((s->cur_runs = (uint32_t *) malloc(run_space)) == NULL)
+        return NULL;
+    if ((s->ref_runs = (uint32_t *) malloc(run_space)) == NULL)
+    {
+        free_buffers(s);
+        close_tiff_input_file(s);
+        return NULL;
+    }
+    if ((s->row_buf = malloc(s->bytes_per_row)) == NULL)
+    {
+        free_buffers(s);
+        close_tiff_input_file(s);
+        return NULL;
+    }
+    s->ref_runs[0] =
+    s->ref_runs[1] =
+    s->ref_runs[2] =
+    s->ref_runs[3] = s->image_width;
+    s->t4_t6_tx.ref_steps = 1;
+    s->image_buffer_size = 0;
+    return s;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t4_tx_start_page(t4_state_t *s)
+{
+    int row;
+    int i;
+    int run_space;
+    int len;
+    int old_image_width;
+    uint8_t *bufptr8;
+    uint32_t *bufptr;
+
+    span_log(&s->logging, SPAN_LOG_FLOW, "Start tx page %d\n", s->current_page);
+    if (s->current_page > s->tiff.stop_page)
+        return -1;
+    if (s->tiff.tiff_file == NULL)
+        return -1;
+    old_image_width = s->image_width;
+    if (s->t4_t6_tx.row_read_handler == NULL)
+    {
+#if defined(HAVE_LIBTIFF)
+        if (!TIFFSetDirectory(s->tiff.tiff_file, (tdir_t) s->current_page))
+            return -1;
+        get_tiff_directory_info(s);
+#endif
+    }
+    s->image_size = 0;
+    s->tx_bitstream = 0;
+    s->tx_bits = 0;
+    s->row_is_2d = (s->line_encoding == T4_COMPRESSION_ITU_T6);
+    s->t4_t6_tx.rows_to_next_1d_row = s->t4_t6_tx.max_rows_to_next_1d_row - 1;
+
+    /* Allow for pages being of different width. */
+    run_space = (s->image_width + 4)*sizeof(uint32_t);
+    if (old_image_width != s->image_width)
+    {
+        s->bytes_per_row = (s->image_width + 7)/8;
+
+        if ((bufptr = (uint32_t *) realloc(s->cur_runs, run_space)) == NULL)
+            return -1;
+        s->cur_runs = bufptr;
+        if ((bufptr = (uint32_t *) realloc(s->ref_runs, run_space)) == NULL)
+            return -1;
+        s->ref_runs = bufptr;
+        if ((bufptr8 = realloc(s->row_buf, s->bytes_per_row)) == NULL)
+            return -1;
+        s->row_buf = bufptr8;
+    }
+    s->ref_runs[0] =
+    s->ref_runs[1] =
+    s->ref_runs[2] =
+    s->ref_runs[3] = s->image_width;
+    s->t4_t6_tx.ref_steps = 1;
+
+    s->row_bits = 0;
+    s->min_row_bits = INT_MAX;
+    s->max_row_bits = 0;
+
+    if (s->header_info  &&  s->header_info[0])
+    {
+        if (t4_tx_put_fax_header(s))
+            return -1;
+    }
+    if (s->t4_t6_tx.row_read_handler)
+    {
+        for (row = 0;  ;  row++)
+        {
+            if ((len = s->t4_t6_tx.row_read_handler(s->t4_t6_tx.row_read_user_data, s->row_buf, s->bytes_per_row)) < 0)
+            {
+                span_log(&s->logging, SPAN_LOG_WARNING, "%s: Read error at row %d.\n", s->tiff.file, row);
+                break;
+            }
+            if (len == 0)
+                break;
+            if (encode_row(s))
+                return -1;
+        }
+        s->image_length = row;
+    }
+    else
+    {
+        if ((s->image_length = read_tiff_image(s)) < 0)
+            return -1;
+    }
+    if (s->line_encoding == T4_COMPRESSION_ITU_T6)
+    {
+        /* Attach an EOFB (end of facsimile block == 2 x EOLs) to the end of the page */
+        for (i = 0;  i < EOLS_TO_END_T6_TX_PAGE;  i++)
+            encode_eol(s);
+    }
+    else
+    {
+        /* Attach an RTC (return to control == 6 x EOLs) to the end of the page */
+        s->row_is_2d = FALSE;
+        for (i = 0;  i < EOLS_TO_END_T4_TX_PAGE;  i++)
+            encode_eol(s);
+    }
+
+    /* Force any partial byte in progress to flush using ones. Any post EOL padding when
+       sending is normally ones, so this is consistent. */
+    put_encoded_bits(s, 0xFF, 7);
+    s->t4_t6_tx.bit_pos = 7;
+    s->t4_t6_tx.bit_ptr = 0;
+    s->line_image_size = s->image_size*8;
+
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t4_tx_next_page_has_different_format(t4_state_t *s)
+{
+    span_log(&s->logging, SPAN_LOG_FLOW, "Checking for the existance of page %d\n", s->current_page + 1);
+    if (s->current_page >= s->tiff.stop_page)
+        return -1;
+    if (s->t4_t6_tx.row_read_handler == NULL)
+    {
+#if defined(HAVE_LIBTIFF)
+        if (s->tiff.tiff_file == NULL)
+            return -1;
+        if (!TIFFSetDirectory(s->tiff.tiff_file, (tdir_t) s->current_page + 1))
+            return -1;
+        return test_tiff_directory_info(s);
+#endif
+    }
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t4_tx_restart_page(t4_state_t *s)
+{
+    s->t4_t6_tx.bit_pos = 7;
+    s->t4_t6_tx.bit_ptr = 0;
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t4_tx_end_page(t4_state_t *s)
+{
+    s->current_page++;
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t4_tx_get_bit(t4_state_t *s)
+{
+    int bit;
+
+    if (s->t4_t6_tx.bit_ptr >= s->image_size)
+        return SIG_STATUS_END_OF_DATA;
+    bit = (s->image_buffer[s->t4_t6_tx.bit_ptr] >> (7 - s->t4_t6_tx.bit_pos)) & 1;
+    if (--s->t4_t6_tx.bit_pos < 0)
+    {
+        s->t4_t6_tx.bit_pos = 7;
+        s->t4_t6_tx.bit_ptr++;
+    }
+    return bit;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t4_tx_get_byte(t4_state_t *s)
+{
+    if (s->t4_t6_tx.bit_ptr >= s->image_size)
+        return 0x100;
+    return s->image_buffer[s->t4_t6_tx.bit_ptr++];
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t4_tx_get_chunk(t4_state_t *s, uint8_t buf[], int max_len)
+{
+    if (s->t4_t6_tx.bit_ptr >= s->image_size)
+        return 0;
+    if (s->t4_t6_tx.bit_ptr + max_len > s->image_size)
+        max_len = s->image_size - s->t4_t6_tx.bit_ptr;
+    memcpy(buf, &s->image_buffer[s->t4_t6_tx.bit_ptr], max_len);
+    s->t4_t6_tx.bit_ptr += max_len;
+    return max_len;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t4_tx_check_bit(t4_state_t *s)
+{
+    int bit;
+
+    if (s->t4_t6_tx.bit_ptr >= s->image_size)
+        return SIG_STATUS_END_OF_DATA;
+    bit = (s->image_buffer[s->t4_t6_tx.bit_ptr] >> s->t4_t6_tx.bit_pos) & 1;
+    return bit;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t4_tx_release(t4_state_t *s)
+{
+    if (s->rx)
+        return -1;
+    if (s->tiff.tiff_file)
+        close_tiff_input_file(s);
+    free_buffers(s);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t4_tx_free(t4_state_t *s)
+{
+    int ret;
+
+    ret = t4_tx_release(s);
+    free(s);
+    return ret;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t4_tx_set_tx_encoding(t4_state_t *s, int encoding)
+{
+    s->line_encoding = encoding;
+    s->t4_t6_tx.rows_to_next_1d_row = s->t4_t6_tx.max_rows_to_next_1d_row - 1;
+    s->row_is_2d = FALSE;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t4_tx_set_min_bits_per_row(t4_state_t *s, int bits)
+{
+    s->t4_t6_tx.min_bits_per_row = bits;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t4_tx_set_local_ident(t4_state_t *s, const char *ident)
+{
+    s->tiff.local_ident = (ident  &&  ident[0])  ?  ident  :  NULL;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t4_tx_set_header_info(t4_state_t *s, const char *info)
+{
+    s->header_info = (info  &&  info[0])  ?  info  :  NULL;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t4_tx_set_header_tz(t4_state_t *s, const char *tzstring)
+{
+    s->tz = tz_init(s->tz, tzstring);
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t4_tx_get_y_resolution(t4_state_t *s)
+{
+    return s->y_resolution;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t4_tx_get_x_resolution(t4_state_t *s)
+{
+    return s->x_resolution;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t4_tx_get_image_width(t4_state_t *s)
+{
+    return s->image_width;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t4_tx_get_pages_in_file(t4_state_t *s)
+{
+    int max;
+
+    max = 0;
+    if (s->t4_t6_tx.row_read_handler == NULL)
+        max = get_tiff_total_pages(s);
+    if (max >= 0)
+        s->tiff.pages_in_file = max;
+    return max;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) t4_tx_get_current_page_in_file(t4_state_t *s)
+{
+    return s->current_page;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) t4_tx_get_transfer_statistics(t4_state_t *s, t4_stats_t *t)
+{
+    t->pages_transferred = s->current_page - s->tiff.start_page;
+    t->pages_in_file = s->tiff.pages_in_file;
+    t->width = s->image_width;
+    t->length = s->image_length;
+    t->bad_rows = s->t4_t6_rx.bad_rows;
+    t->longest_bad_row_run = s->t4_t6_rx.longest_bad_row_run;
+    t->x_resolution = s->x_resolution;
+    t->y_resolution = s->y_resolution;
+    t->encoding = s->line_encoding;
+    t->line_image_size = s->line_image_size/8;
+}
+/*- End of function --------------------------------------------------------*/
+/*- End of file ------------------------------------------------------------*/
diff --git a/src/codec/spandsp/src/timezone.c b/src/codec/spandsp/src/timezone.c
new file mode 100644 (file)
index 0000000..b991531
--- /dev/null
@@ -0,0 +1,822 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * timezone.c - Timezone handling for time interpretation
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2010 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.
+ */
+
+/*! \file */
+
+/* Timezone processing might not seem like a DSP activity, but getting the headers
+   right on FAXes demands it. We need to handle multiple time zones within a process,
+   for FAXes related to different parts of the globe, so the system timezone handling
+   is not adequate. */
+
+/* This timezone handling is derived from public domain software by Arthur David Olson
+   <arthur_david_olson@nih.gov> which you may download from ftp://elsie.nci.nih.gov/pub
+   at the time of writing. */
+
+#if defined(HAVE_CONFIG_H)
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "spandsp/telephony.h"
+#include "spandsp/timezone.h"
+
+#include "spandsp/private/timezone.h"
+
+#if !defined(FALSE)
+#define FALSE    0
+#endif
+
+#if !defined(TRUE)
+#define TRUE    (!FALSE)
+#endif
+
+#define SECS_PER_MIN            60
+#define MINS_PER_HOUR           60
+#define HOURS_PER_DAY           24
+#define DAYS_PER_WEEK           7
+#define DAYS_PER_NON_LEAP_YEAR  365
+#define DAYS_PER_LEAP_YEAR      366
+#define SECS_PER_HOUR           (SECS_PER_MIN*MINS_PER_HOUR)
+#define SECS_PER_DAY            ((long int) SECS_PER_HOUR*HOURS_PER_DAY)
+#define MONTHS_PER_YEAR         12
+
+#define TM_YEAR_BASE            1900
+
+#define EPOCH_YEAR              1970
+#define EPOCH_WDAY              TM_THURSDAY
+
+#define isleap(y)               (((y)%4) == 0  &&  (((y)%100) != 0  ||  ((y)%400) == 0))
+
+#define isleap_sum(a, b)        isleap((a)%400 + (b)%400)
+
+/* Unlike <ctype.h>'s isdigit, this also works if c < 0 | c > UCHAR_MAX. */
+#define is_digit(c)             ((unsigned int) (c) - '0' <= 9)
+
+#define TZ_DEF_RULE_STRING      ",M4.1.0,M10.5.0"
+
+#define JULIAN_DAY              0       /* Jn - Julian day */
+#define DAY_OF_YEAR             1       /* n - day of year */
+#define MONTH_NTH_DAY_OF_WEEK   2       /* Mm.n.d - month, week, day of week */
+
+static const char wildabbr[] = "   ";
+
+static const char gmt[] = "GMT";
+
+struct tz_rule_s
+{
+    int r_type;                         /* Type of rule--see below */
+    int r_day;                          /* Day number of rule */
+    int r_week;                         /* Week number of rule */
+    int r_mon;                          /* Month number of rule */
+    long int r_time;                    /* Transition time of rule */
+};
+
+static const int mon_lengths[2][MONTHS_PER_YEAR] =
+{
+    {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
+    {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
+};
+
+static const int year_lengths[2] =
+{
+    DAYS_PER_NON_LEAP_YEAR,
+    DAYS_PER_LEAP_YEAR
+};
+
+static int increment_overflow(int *number, int delta)
+{
+    int number0;
+
+    number0 = *number;
+    *number += delta;
+    return (*number < number0) != (delta < 0);
+}
+/*- End of function --------------------------------------------------------*/
+
+static void set_tzname(tz_t *tz)
+{
+    struct tz_state_s *sp;
+    const struct tz_ttinfo_s *ttisp;
+    int i;
+
+    sp = &tz->state;
+    tz->tzname[0] = wildabbr;
+    tz->tzname[1] = wildabbr;
+    for (i = 0;  i < sp->typecnt;  i++)
+    {
+        ttisp = &sp->ttis[i];
+        tz->tzname[ttisp->isdst] = &sp->chars[ttisp->abbrind];
+    }
+    for (i = 0;  i < sp->timecnt;  i++)
+    {
+        ttisp = &sp->ttis[sp->types[i]];
+        tz->tzname[ttisp->isdst] = &sp->chars[ttisp->abbrind];
+    }
+}
+/*- End of function --------------------------------------------------------*/
+
+/* Return the number of leap years through the end of the given year
+   where, to make the math easy, the answer for year zero is defined as zero. */
+static int leaps_thru_end_of(const int y)
+{
+    return (y >= 0)  ?  (y/4 - y/100 + y/400)  :  -(leaps_thru_end_of(-(y + 1)) + 1);
+}
+/*- End of function --------------------------------------------------------*/
+
+static struct tm *time_sub(const time_t * const timep, const long int offset, const struct tz_state_s * const sp, struct tm * const tmp)
+{
+    const struct tz_lsinfo_s *lp;
+    time_t tdays;
+    const int *ip;
+    int32_t corr;
+    int32_t seconds;
+    int32_t rem;
+    int idays;
+    int y;
+    int hit;
+    int i;
+
+    corr = 0;
+    hit = 0;
+    i = sp->leapcnt;
+    while (--i >= 0)
+    {
+        lp = &sp->lsis[i];
+        if (*timep >= lp->trans)
+        {
+            if (*timep == lp->trans)
+            {
+                hit = ((i == 0  &&  lp->corr > 0)  ||  lp->corr > sp->lsis[i - 1].corr);
+                if (hit)
+                {
+                    while (i > 0
+                           &&
+                           sp->lsis[i].trans == sp->lsis[i - 1].trans + 1
+                           &&
+                           sp->lsis[i].corr == sp->lsis[i - 1].corr + 1)
+                    {
+                        hit++;
+                        --i;
+                    }
+                }
+            }
+            corr = lp->corr;
+            break;
+        }
+    }
+    y = EPOCH_YEAR;
+    tdays = *timep/SECS_PER_DAY;
+    rem = *timep - tdays*SECS_PER_DAY;
+    while (tdays < 0  ||  tdays >= year_lengths[isleap(y)])
+    {
+        int newy;
+        time_t tdelta;
+        int idelta;
+        int leapdays;
+
+        tdelta = tdays / DAYS_PER_LEAP_YEAR;
+        idelta = tdelta;
+        if (tdelta - idelta >= 1  ||  idelta - tdelta >= 1)
+            return NULL;
+        if (idelta == 0)
+            idelta = (tdays < 0)  ?  -1  :  1;
+        newy = y;
+        if (increment_overflow(&newy, idelta))
+            return NULL;
+        leapdays = leaps_thru_end_of(newy - 1) - leaps_thru_end_of(y - 1);
+        tdays -= ((time_t) newy - y)*DAYS_PER_NON_LEAP_YEAR;
+        tdays -= leapdays;
+        y = newy;
+    }
+    seconds = tdays*SECS_PER_DAY;
+    tdays = seconds/SECS_PER_DAY;
+    rem += seconds - tdays*SECS_PER_DAY;
+    /* Given the range, we can now fearlessly cast... */
+    idays = tdays;
+    rem += (offset - corr);
+    while (rem < 0)
+    {
+        rem += SECS_PER_DAY;
+        idays--;
+    }
+    while (rem >= SECS_PER_DAY)
+    {
+        rem -= SECS_PER_DAY;
+        idays++;
+    }
+    while (idays < 0)
+    {
+        if (increment_overflow(&y, -1))
+            return NULL;
+        idays += year_lengths[isleap(y)];
+    }
+    while (idays >= year_lengths[isleap(y)])
+    {
+        idays -= year_lengths[isleap(y)];
+        if (increment_overflow(&y, 1))
+            return NULL;
+    }
+    tmp->tm_year = y;
+    if (increment_overflow(&tmp->tm_year, -TM_YEAR_BASE))
+        return NULL;
+    tmp->tm_yday = idays;
+    /* The "extra" mods below avoid overflow problems. */
+    tmp->tm_wday = EPOCH_WDAY
+                 + ((y - EPOCH_YEAR) % DAYS_PER_WEEK)*(DAYS_PER_NON_LEAP_YEAR % DAYS_PER_WEEK)
+                 + leaps_thru_end_of(y - 1)
+                 - leaps_thru_end_of(EPOCH_YEAR - 1)
+                 + idays;
+    tmp->tm_wday %= DAYS_PER_WEEK;
+    if (tmp->tm_wday < 0)
+        tmp->tm_wday += DAYS_PER_WEEK;
+    tmp->tm_hour = (int) (rem/SECS_PER_HOUR);
+    rem %= SECS_PER_HOUR;
+    tmp->tm_min = (int) (rem/SECS_PER_MIN);
+    /* A positive leap second requires a special
+     * representation. This uses "... ??:59:60" et seq. */
+    tmp->tm_sec = (int) (rem%SECS_PER_MIN) + hit;
+    ip = mon_lengths[isleap(y)];
+    for (tmp->tm_mon = 0;  idays >= ip[tmp->tm_mon];  (tmp->tm_mon)++)
+        idays -= ip[tmp->tm_mon];
+    tmp->tm_mday = (int) (idays + 1);
+    tmp->tm_isdst = 0;
+    return tmp;
+}
+/*- End of function --------------------------------------------------------*/
+
+/* Given a pointer into a time zone string, scan until a character that is not
+ * a valid character in a zone name is found. Return a pointer to that
+ * character. */
+static const char *get_tzname(const char *strp)
+{
+    char c;
+
+    while ((c = *strp) != '\0'  &&  !is_digit(c)  &&  c != ','  &&  c != '-'  &&  c != '+')
+        strp++;
+    return strp;
+}
+/*- End of function --------------------------------------------------------*/
+
+/* Given a pointer into a time zone string, extract a number from that string.
+ * Check that the number is within a specified range; if it is not, return
+ * NULL.
+ * Otherwise, return a pointer to the first character not part of the number. */
+static const char *get_num(const char *strp, int * const nump, const int min, const int max)
+{
+    char c;
+    int num;
+
+    if (strp == NULL  ||  !is_digit(c = *strp))
+        return NULL;
+    num = 0;
+    do
+    {
+        num = num*10 + (c - '0');
+        if (num > max)
+            return NULL;    /* Illegal value */
+        c = *++strp;
+    }
+    while (is_digit(c));
+    if (num < min)
+        return NULL;        /* Illegal value */
+    *nump = num;
+    return strp;
+}
+/*- End of function --------------------------------------------------------*/
+
+/* Given a pointer into a time zone string, extract a number of seconds,
+ * in hh[:mm[:ss]] form, from the string.
+ * If any error occurs, return NULL.
+ * Otherwise, return a pointer to the first character not part of the number
+ * of seconds. */
+static const char *get_secs(const char *strp, long int * const secsp)
+{
+    int num;
+
+    /* HOURS_PER_DAY*DAYS_PER_WEEK - 1 allows quasi-Posix rules like
+     * "M10.4.6/26", which does not conform to Posix,
+     * but which specifies the equivalent of
+     * "02:00 on the first Sunday on or after 23 Oct". */
+    strp = get_num(strp, &num, 0, HOURS_PER_DAY*DAYS_PER_WEEK - 1);
+    if (strp == NULL)
+        return NULL;
+    *secsp = num*(long int) SECS_PER_HOUR;
+    if (*strp == ':')
+    {
+        strp = get_num(strp + 1, &num, 0, MINS_PER_HOUR - 1);
+        if (strp == NULL)
+            return NULL;
+        *secsp += num*SECS_PER_MIN;
+        if (*strp == ':')
+        {
+            /* SECS_PER_MIN allows for leap seconds. */
+            strp = get_num(strp + 1, &num, 0, SECS_PER_MIN);
+            if (strp == NULL)
+                return NULL;
+            *secsp += num;
+        }
+    }
+    return strp;
+}
+/*- End of function --------------------------------------------------------*/
+
+/* Given a pointer into a time zone string, extract an offset, in
+ * [+-]hh[:mm[:ss]] form, from the string.
+ * If any error occurs, return NULL.
+ * Otherwise, return a pointer to the first character not part of the time. */
+static const char *get_offset(const char *strp, long int * const offsetp)
+{
+    int neg = 0;
+
+    if (*strp == '-')
+    {
+        neg = 1;
+        strp++;
+    }
+    else if (*strp == '+')
+    {
+        strp++;
+    }
+    strp = get_secs(strp, offsetp);
+    if (strp == NULL)
+        return NULL;        /* Illegal time */
+    if (neg)
+        *offsetp = -*offsetp;
+    return strp;
+}
+/*- End of function --------------------------------------------------------*/
+
+/* Given a pointer into a time zone string, extract a rule in the form
+ * date[/time]. See POSIX section 8 for the format of "date" and "time".
+ * If a valid rule is not found, return NULL.
+ * Otherwise, return a pointer to the first character not part of the rule. */
+static const char *get_rule(const char *strp, struct tz_rule_s * const rulep)
+{
+    if (*strp == 'J')
+    {
+        /* Julian day. */
+        rulep->r_type = JULIAN_DAY;
+        strp = get_num(strp + 1, &rulep->r_day, 1, DAYS_PER_NON_LEAP_YEAR);
+    }
+    else if (*strp == 'M')
+    {
+        /* Month, week, day. */
+        rulep->r_type = MONTH_NTH_DAY_OF_WEEK;
+        strp = get_num(strp + 1, &rulep->r_mon, 1, MONTHS_PER_YEAR);
+        if (strp == NULL  ||  *strp++ != '.')
+            return NULL;
+        strp = get_num(strp, &rulep->r_week, 1, 5);
+        if (strp == NULL  ||  *strp++ != '.')
+            return NULL;
+        strp = get_num(strp, &rulep->r_day, 0, DAYS_PER_WEEK - 1);
+    }
+    else if (is_digit(*strp))
+    {
+        /* Day of the year. */
+        rulep->r_type = DAY_OF_YEAR;
+        strp = get_num(strp, &rulep->r_day, 0, DAYS_PER_LEAP_YEAR - 1);
+    }
+    else
+    {
+        /* Invalid format */
+        return NULL;
+    }
+    if (strp == NULL)
+        return NULL;
+    if (*strp == '/')
+    {
+        /* Time specified. */
+        strp = get_secs(strp + 1, &rulep->r_time);
+    }
+    else
+    {
+        /* Default = 2:00:00 */
+        rulep->r_time = 2*SECS_PER_HOUR;
+    }
+    return strp;
+}
+/*- End of function --------------------------------------------------------*/
+
+/* Given the Epoch-relative time of January 1, 00:00:00 UTC, in a year, the
+ * year, a rule, and the offset from UTC at the time that rule takes effect,
+ * calculate the Epoch-relative time that rule takes effect. */
+static time_t trans_time(const time_t janfirst, const int year, const struct tz_rule_s * const rulep, const long int offset)
+{
+    int leapyear;
+    time_t value;
+    int i;
+    int d;
+    int m1;
+    int yy0;
+    int yy1;
+    int yy2;
+    int dow;
+
+    value = 0;
+    leapyear = isleap(year);
+    switch (rulep->r_type)
+    {
+    case JULIAN_DAY:
+        /* Jn - Julian day, 1 == January 1, 60 == March 1 even in leap
+         * years.
+         * In non-leap years, or if the day number is 59 or less, just
+         * add SECS_PER_DAY times the day number-1 to the time of
+         * January 1, midnight, to get the day. */
+        value = janfirst + (rulep->r_day - 1)*SECS_PER_DAY;
+        if (leapyear  &&  rulep->r_day >= 60)
+            value += SECS_PER_DAY;
+        break;
+    case DAY_OF_YEAR:
+        /* n - day of year.
+         * Just add SECS_PER_DAY times the day number to the time of
+         * January 1, midnight, to get the day. */
+        value = janfirst + rulep->r_day * SECS_PER_DAY;
+        break;
+    case MONTH_NTH_DAY_OF_WEEK:
+        /* Mm.n.d - nth "dth day" of month m. */
+        value = janfirst;
+        for (i = 0;  i < rulep->r_mon - 1;  i++)
+            value += mon_lengths[leapyear][i]*SECS_PER_DAY;
+
+        /* Use Zeller's Congruence to get day-of-week of first day of month. */
+        m1 = (rulep->r_mon + 9)%12 + 1;
+        yy0 = (rulep->r_mon <= 2)  ?  (year - 1)  :  year;
+        yy1 = yy0/100;
+        yy2 = yy0%100;
+        dow = ((26*m1 - 2)/10 + 1 + yy2 + yy2/4 + yy1/4 - 2*yy1)%7;
+        if (dow < 0)
+            dow += DAYS_PER_WEEK;
+
+        /* "dow" is the day-of-week of the first day of the month. Get
+         * the day-of-month (zero-origin) of the first "dow" day of the
+         * month. */
+        d = rulep->r_day - dow;
+        if (d < 0)
+            d += DAYS_PER_WEEK;
+        for (i = 1;  i < rulep->r_week;  i++)
+        {
+            if (d + DAYS_PER_WEEK >= mon_lengths[leapyear][rulep->r_mon - 1])
+                break;
+            d += DAYS_PER_WEEK;
+        }
+
+        /* "d" is the day-of-month (zero-origin) of the day we want. */
+        value += d*SECS_PER_DAY;
+        break;
+    }
+
+    /* "value" is the Epoch-relative time of 00:00:00 UTC on the day in
+     * question. To get the Epoch-relative time of the specified local
+     * time on that day, add the transition time and the current offset
+     * from UTC. */
+    return value + rulep->r_time + offset;
+}
+/*- End of function --------------------------------------------------------*/
+
+/* Given a POSIX section 8-style TZ string, fill in the rule tables as
+   appropriate. */
+static int tzparse(const char *name, struct tz_state_s * const sp, const int lastditch)
+{
+    const char *stdname;
+    const char *dstname;
+    size_t stdlen;
+    size_t dstlen;
+    long int stdoffset;
+    long int dstoffset;
+    long int theirstdoffset;
+    long int theirdstoffset;
+    long int theiroffset;
+    unsigned char *typep;
+    char *cp;
+    int load_result;
+    int isdst;
+    int i;
+    int j;
+    int year;
+    struct tz_rule_s start;
+    struct tz_rule_s end;
+    time_t *atp;
+    time_t janfirst;
+    time_t starttime;
+    time_t endtime;
+
+    dstname = NULL;
+    stdname = name;
+    if (lastditch)
+    {
+        stdlen = strlen(name);      /* Length of standard zone name */
+        name += stdlen;
+        if (stdlen >= sizeof(sp->chars))
+            stdlen = sizeof(sp->chars) - 1;
+        stdoffset = 0;
+    }
+    else
+    {
+        name = get_tzname(name);
+        stdlen = name - stdname;
+        if (stdlen < 3)
+            return -1;
+        if (*name == '\0')
+            return -1;
+        name = get_offset(name, &stdoffset);
+        if (name == NULL)
+            return -1;
+    }
+    load_result = -1;
+    if (load_result != 0)
+        sp->leapcnt = 0;            /* So, we're off a little */
+    if (*name != '\0')
+    {
+        dstname = name;
+        name = get_tzname(name);
+        dstlen = name - dstname;    /* Length of DST zone name */
+        if (dstlen < 3)
+            return -1;
+        if (*name != '\0'  &&  *name != ','  &&  *name != ';')
+        {
+            if ((name = get_offset(name, &dstoffset)) == NULL)
+                return -1;
+        }
+        else
+        {
+            dstoffset = stdoffset - SECS_PER_HOUR;
+        }
+        if (*name == '\0'  &&  load_result != 0)
+            name = TZ_DEF_RULE_STRING;
+        if (*name == ','  ||  *name == ';')
+        {
+            if ((name = get_rule(name + 1, &start)) == NULL)
+                return -1;
+            if (*name++ != ',')
+                return -1;
+            if ((name = get_rule(name, &end)) == NULL)
+                return -1;
+            if (*name != '\0')
+                return -1;
+            sp->typecnt = 2;        /* Standard time and DST */
+            /* Two transitions per year, from EPOCH_YEAR to 2037. */
+            sp->timecnt = 2*(2037 - EPOCH_YEAR + 1);
+            if (sp->timecnt > TZ_MAX_TIMES)
+                return -1;
+            sp->ttis[0].gmtoff = -dstoffset;
+            sp->ttis[0].isdst = 1;
+            sp->ttis[0].abbrind = stdlen + 1;
+            sp->ttis[1].gmtoff = -stdoffset;
+            sp->ttis[1].isdst = 0;
+            sp->ttis[1].abbrind = 0;
+            atp = sp->ats;
+            typep = sp->types;
+            janfirst = 0;
+            for (year = EPOCH_YEAR;  year <= 2037;  year++)
+            {
+                starttime = trans_time(janfirst, year, &start, stdoffset);
+                endtime = trans_time(janfirst, year, &end, dstoffset);
+                if (starttime > endtime)
+                {
+                    *atp++ = endtime;
+                    *typep++ = 1;    /* DST ends */
+                    *atp++ = starttime;
+                    *typep++ = 0;    /* DST begins */
+                }
+                else
+                {
+                    *atp++ = starttime;
+                    *typep++ = 0;    /* DST begins */
+                    *atp++ = endtime;
+                    *typep++ = 1;    /* DST ends */
+                }
+                janfirst += year_lengths[isleap(year)]*SECS_PER_DAY;
+            }
+        }
+        else
+        {
+            if (*name != '\0')
+                return -1;
+            /* Initial values of theirstdoffset and theirdstoffset. */
+            theirstdoffset = 0;
+            for (i = 0;  i < sp->timecnt;  i++)
+            {
+                j = sp->types[i];
+                if (!sp->ttis[j].isdst)
+                {
+                    theirstdoffset = -sp->ttis[j].gmtoff;
+                    break;
+                }
+            }
+            theirdstoffset = 0;
+            for (i = 0;  i < sp->timecnt;  i++)
+            {
+                j = sp->types[i];
+                if (sp->ttis[j].isdst)
+                {
+                    theirdstoffset = -sp->ttis[j].gmtoff;
+                    break;
+                }
+            }
+            /* Initially we're assumed to be in standard time. */
+            isdst = FALSE;
+            theiroffset = theirstdoffset;
+            /* Now juggle transition times and types tracking offsets as you do. */
+            for (i = 0;  i < sp->timecnt;  i++)
+            {
+                j = sp->types[i];
+                sp->types[i] = sp->ttis[j].isdst;
+                if (sp->ttis[j].ttisgmt)
+                {
+                    /* No adjustment to transition time */
+                }
+                else
+                {
+                    /* If summer time is in effect, and the
+                     * transition time was not specified as
+                     * standard time, add the summer time
+                     * offset to the transition time;
+                     * otherwise, add the standard time
+                     * offset to the transition time. */
+                    /* Transitions from DST to DDST
+                     * will effectively disappear since
+                     * POSIX provides for only one DST
+                     * offset. */
+                    if (isdst  &&  !sp->ttis[j].ttisstd)
+                        sp->ats[i] += (dstoffset - theirdstoffset);
+                    else
+                        sp->ats[i] += (stdoffset - theirstdoffset);
+                }
+                theiroffset = -sp->ttis[j].gmtoff;
+                if (sp->ttis[j].isdst)
+                    theirdstoffset = theiroffset;
+                else
+                    theirstdoffset = theiroffset;
+            }
+            /* Finally, fill in ttis. ttisstd and ttisgmt need not be handled. */
+            sp->ttis[0].gmtoff = -stdoffset;
+            sp->ttis[0].isdst = FALSE;
+            sp->ttis[0].abbrind = 0;
+            sp->ttis[1].gmtoff = -dstoffset;
+            sp->ttis[1].isdst = TRUE;
+            sp->ttis[1].abbrind = stdlen + 1;
+            sp->typecnt = 2;
+        }
+    }
+    else
+    {
+        dstlen = 0;
+        sp->typecnt = 1;        /* Only standard time */
+        sp->timecnt = 0;
+        sp->ttis[0].gmtoff = -stdoffset;
+        sp->ttis[0].isdst = 0;
+        sp->ttis[0].abbrind = 0;
+    }
+    sp->charcnt = stdlen + 1;
+    if (dstlen != 0)
+        sp->charcnt += dstlen + 1;
+    if ((size_t) sp->charcnt > sizeof(sp->chars))
+        return -1;
+    cp = sp->chars;
+    strncpy(cp, stdname, stdlen);
+    cp += stdlen;
+    *cp++ = '\0';
+    if (dstlen != 0)
+    {
+        strncpy(cp, dstname, dstlen);
+        cp[dstlen] = '\0';
+    }
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static void tz_set(tz_t *tz, const char *tzstring)
+{
+    const char *name = "";
+    struct tz_state_s *lclptr = &tz->state;
+
+    if (tzstring)
+        name = tzstring;
+
+    /* See if we are already set OK */
+    if (tz->lcl_is_set > 0  &&  strcmp(tz->lcl_tzname, name) == 0)
+        return;
+    tz->lcl_is_set = strlen(name) < sizeof(tz->lcl_tzname);
+    if (tz->lcl_is_set)
+        strcpy(tz->lcl_tzname, name);
+
+    if (name[0] == '\0')
+    {
+        /* User wants it fast rather than right, so, we're off a little. */
+        lclptr->leapcnt = 0;
+        lclptr->timecnt = 0;
+        lclptr->typecnt = 0;
+        lclptr->ttis[0].isdst = 0;
+        lclptr->ttis[0].gmtoff = 0;
+        lclptr->ttis[0].abbrind = 0;
+        strcpy(lclptr->chars, gmt);
+    }
+    else if (name[0] == ':'  ||  tzparse(name, lclptr, FALSE) != 0)
+    {
+        tzparse(gmt, lclptr, TRUE);
+    }
+    set_tzname(tz);
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) tz_localtime(tz_t *tz, struct tm *tmp, time_t t)
+{
+    struct tz_state_s *sp;
+    const struct tz_ttinfo_s *ttisp;
+    int i;
+
+    sp = &tz->state;
+
+    if (sp->timecnt == 0  ||  t < sp->ats[0])
+    {
+        i = 0;
+        while (sp->ttis[i].isdst)
+        {
+            if (++i >= sp->typecnt)
+            {
+                i = 0;
+                break;
+            }
+        }
+    }
+    else
+    {
+        for (i = 1;  i < sp->timecnt;  i++)
+        {
+            if (t < sp->ats[i])
+                break;
+        }
+        i = (int) sp->types[i - 1];
+    }
+    ttisp = &sp->ttis[i];
+    time_sub(&t, ttisp->gmtoff, sp, tmp);
+    tmp->tm_isdst = ttisp->isdst;
+    tz->tzname[tmp->tm_isdst] = &sp->chars[ttisp->abbrind];
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(const char *) tz_tzname(tz_t *tz, int isdst)
+{
+    return tz->tzname[(!isdst)  ?  0  :  1];
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(tz_t *) tz_init(tz_t *tz, const char *tzstring)
+{
+    if (tz == NULL)
+    {
+        if ((tz = (tz_t *) malloc(sizeof(*tz))) == NULL)
+            return NULL;
+    }
+    memset(tz, 0, sizeof(*tz));
+    tz->tzname[0] =
+    tz->tzname[1] = wildabbr;
+    tz_set(tz, tzstring);
+    return tz;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) tz_release(tz_t *tz)
+{
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) tz_free(tz_t *tz)
+{
+    if (tz)
+        free(tz);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+/*- End of file ------------------------------------------------------------*/
index 10ec7f8..81efde6 100644 (file)
@@ -53,6 +53,8 @@
 // 101 audio/telephone-event   8000    LOCAL (but commonly used)
 // 110 audio/G726-32           8000    LOCAL
 // 111 audio/AAL2-G726-32      8000    LOCAL
+// 115 audio/L8                var.    LOCAL
+// 116  audio/L16              var.    LOCAL
 // 125  audio/x-codec2         2550    LOCAL
 // 124  audio/x-codec2         2400    LOCAL
 // 123  audio/x-codec2         2000    LOCAL
 
 
 
+static void null_init (struct codec *hdl, uint32_t samplerate) {
+       ;
+}
+
+static void null_finish (struct codec *hdl) {
+       ;
+}
+
+
+/********** L8 AND L16 DEFINITIONS **********/
+
+
+#ifdef CONFIG_CODEC_L8_L16
+
+struct codec_fun decoder_l8 = {
+       "audio", "L8", NULL,
+       "L8",
+       null_init, null_finish, l8_decode,
+       8000, 115
+};
+
+struct codec_fun encoder_l8 = {
+       "audio", "L8", NULL,
+       "L8",
+       null_init, null_finish, l8_encode,
+       8000, 115
+};
+
+struct codec_fun decoder_l16 = {
+       "audio", "L16", NULL,
+       "L16",
+       null_init, null_finish, l16_decode,
+       8000, 116
+};
+
+struct codec_fun encoder_l16 = {
+       "audio", "L16", NULL,
+       "L16",
+       null_init, null_finish, l16_encode,
+       8000, 116
+};
+
+#endif
+
+
 /********** G.711 DEFINITIONS **********/
 
 #ifdef CONFIG_CODEC_G711
index c01e4a6..55814cd 100644 (file)
@@ -250,118 +250,113 @@ bottom_led_set (LED_IDX_MESSAGE, 0);
 /******** TLV320AIC20K DATA ACCESS OVER MCBSP1 ********/
 
 
-#define BUFSZ (64*25)
-
-extern volatile uint16_t samplebuf_play   [BUFSZ];
-extern volatile uint16_t samplebuf_record [BUFSZ];
-
-extern volatile uint16_t available_play;
-extern volatile uint16_t available_record;
-
-extern volatile uint16_t threshold_play;
-extern volatile uint16_t threshold_record;
-
-
-/* Copy encoded samples to plain samples */
-//TODO// Better to switch once and then loop in a separate routine which may even be .asm -- but that could require a codec-specific state storage structure
-int16_t codec_decode (codec_t codec, uint8_t *in, uint16_t inlen, int16_t *out, uint16_t outlen) {
-       while ((inlen > 0) && (outlen > 0)) {
-               register uint16_t outval;
-               register uint8_t inval = *in++;
-               switch (codec) {
-               case CODEC_L8:
-                       *out++ = (inval ^ 0x80) << 8;
-//DOH!// *out++ = 16384 + 1 + (outlen & 0x01)? 0: 32768;
-                       break;
-               case CODEC_L16:
-                       *out++ = inval;
-                       break;
-               case CODEC_G711A:
-                       outval = (inval & 0x0f);
-                       if (inval & 0x70) {
-                               outval |= 0x10;
-                       }
-                       outval <<= ((inval >> 4) & 0x07);
-                       if (inval & 0x80) {
-                               outval = -outval;
-                       }
-                       *out++ = outval;
-                       break;
-               case CODEC_G711MU:
-                       outval = (inval & 0x0f);
-                       outval |= 0x10;
-                       outval <<= ((inval >> 4) & 0x07);
-                       outval -= 32;
-                       if (inval & 0x80) {
-                               outval = -outval;
-                       }
-                       *out++ = outval;
-                       break;
-               default:
-                       *out++ = 0;
-                       break;
-               }
-               inlen--;
-               outlen--;
+#define BUFSZ (80*24)
+
+static volatile int16_t samplebuf_play   [BUFSZ];
+static volatile int16_t samplebuf_record [BUFSZ];
+
+static uint16_t samplebuf_blocksize = 80;
+static uint16_t samplebuf_wrapindex = BUFSZ;
+
+/* The buffer is a contiguous array of samples, made accessible
+ * to the top layer per block.  Upon setting sample rate and
+ * block size, all pointers start at the beginning of the buffer.
+ * While processing, the pointers advance with one blocksize at
+ * a time; they wrap around if there is not enough space left to
+ * cover a complete block within the bounds of the sample buffer.
+ *
+ * The buffer has three types of areas, each with their own
+ * consumer/producer relationships.  The areas are the same for
+ * the playback and recording buffer.  They are:
+ *  - playable: Can be filled be the codec for playback
+ *  - dmaready: Can be played/recorded through lockstep DMA
+ *  - recordable: Can be processed by the codec in recording
+ * Each of these areas is marked with an index pointer; their
+ * ends +1 are marked by another index pointer, as follows:
+ *
+ *     AREA            START   END+1
+ *     playable        play0   rec0
+ *     dmaready        dma0    play0
+ *     recordable      rec0    dma0
+ *
+ * DMA is possible if the dmaready area is non-empty, so if
+ * dma0 != play0.  When DMA has finished transferring a block,
+ * the dma0 pointer is incremented, and this is checked.
+ *
+ * bottom_record_claim() succeeds if rec0 != dma0, and when
+ * bottom_record_release() is called, rec0 increments.  Note
+ * that bottom_echo_claim() applies the same condition, but
+ * it returns the rec0 offset of the playbuf, instead of the
+ * rec0 offset in the recbuf as is returned by record_claim.
+ *
+ * bottom_play_claim() succeeds if play0 != rec0 *or* if there
+ * is no actual recordable area, that is, rec0 == dma0.  This
+ * means that playback is the first thing to start when a new
+ * set of index pointers is setup with zero values.  When the
+ * bottom_play_release() is called, play0 increments, and the
+ * DMA conditions are evaluated.
+ */
+
+static volatile uint16_t bufofs_play0 = 0;
+static volatile uint16_t bufofs_dma0  = 0;
+static volatile uint16_t bufofs_rec0  = 0;
+
+
+inline void bottom_bufferdma_progress (uint8_t chan) {
+       bool recordinghint = (bufofs_dma0 == bufofs_rec0);
+       bufofs_dma0 = (bufofs_dma0 + samplebuf_blocksize) % samplebuf_wrapindex;
+       if (bufofs_dma0 == bufofs_play0) {
+               SPCR2_1 &= ~REGVAL_SPCR2_FRST_NOTRESET;         // Stop DMA
+       }
+       if (recordinghint) {
+               top_codec_can_record (chan);
        }
-       return inlen - outlen;
 }
 
-/* Copy plain samples to encoded samples */
-//TODO// Better to switch once and then loop in a separate routine which may even be .asm -- but that could require a codec-specific state storage structure
-int16_t codec_encode (codec_t codec, int16_t *in, uint16_t inlen, uint8_t *out, uint16_t outlen) {
-bottom_printf ("Have %d, %d, %d at %04x\n", (intptr_t) in [0] & 0x0000ffff, (intptr_t) in [1] & 0x0000ffff, (intptr_t) in [2] & 0x0000ffff, (intptr_t) in);
-       while ((inlen > 0) && (outlen > 0)) {
-               register uint16_t inval = *in++;
-               bool signbit;
-               uint8_t exp;
-               switch (codec) {
-               case CODEC_L8:
-                       *out++ = (inval >> 8) ^ 0x80;
-                       break;
-               case CODEC_L16:
-                       *out++ = inval;
-                       break;
-               case CODEC_G711A:
-                       // Handle sign bit
-                       signbit = inval >> 15;
-                       if (signbit) {
-                               inval = -inval;
-                       }
-                       // Find exponent part of sample
-                       for (exp = 7; exp >= 0; exp--) {
-                               if (inval >= (1 << (8 + exp))) {
-                                       break;
-                               }
-                       }
-                       // Encode is sign/exp/mant
-                       *out++ = 0x55 ^ ( (signbit << 7) | (exp << 4) | ((inval >> (exp + 3)) & 0x0f) );
-                       break;
-               case CODEC_G711MU:
-                       // Handle sign bit
-                       signbit = inval >> 15;
-                       if (signbit) {
-                               inval = -inval;
-                       }
-                       // Shift range for uLaw
-                       inval += 32;
-                       // Find exponent part of sample
-                       for (exp = 7; exp >= 0; exp--) {
-                               if (inval >= (1 << (8 + exp))) {
-                                       break;
-                               }
-                       }
-                       // Encode in sign/exp/mant
-                       *out++ = 0xff ^ ( (signbit << 7) | (exp << 4) | ((inval >> (exp + 3)) & 0x0f) );
-                       break;
-               default:
-                       *out++ = 0x00;
-                       break;
-               }
-               inlen--;
-               outlen--;
+int16_t *bottom_play_claim (uint8_t chan) {
+       if ((bufofs_play0 != bufofs_rec0) || (bufofs_rec0 == bufofs_dma0)) {
+               return (int16_t *) &samplebuf_play [bufofs_play0];
+       } else {
+               return NULL;
+       }
+}
+
+void bottom_play_release (uint8_t chan) {
+       bool dmahint = (bufofs_play0 == bufofs_dma0);
+       bufofs_play0 = (bufofs_play0 + samplebuf_blocksize) % samplebuf_wrapindex;
+       if (dmahint) {
+               //TODO// Following 5 lines can be done much earlier
+               (void) DRR1_1;  // Flag down RFULL
+               (void) DRR1_1;
+               DMACCR_0 |= REGVAL_DMACCR_EN;
+               DXR1_1 = DXR1_1;        // Flag down XEMPTY
+               DMACCR_1 |= REGVAL_DMACCR_EN;
+               SPCR2_1 |= REGVAL_SPCR2_FRST_NOTRESET;          // Start DMA
+       }
+}
+
+int16_t *bottom_record_claim (uint8_t chan) {
+       if (bufofs_rec0 != bufofs_dma0) {
+               return (int16_t *) &samplebuf_record [bufofs_rec0];
+       } else {
+               return NULL;
+       }
+}
+
+int16_t *bottom_echo_claim (uint8_t chan) {
+       bool playbackhint = (bufofs_rec0 == bufofs_play0);
+       if (bufofs_rec0 != bufofs_dma0) {
+               return (int16_t *) &samplebuf_play [bufofs_rec0];
+       } else {
+               return NULL;
+       }
+       if (playbackhint) {
+               top_codec_can_play (chan);
        }
-       return outlen - inlen;
+}
+
+void bottom_record_release (uint8_t chan) {
+       bufofs_rec0 = (bufofs_rec0 + samplebuf_blocksize) % samplebuf_wrapindex;
 }
 
 static int TODO_setratectr = 0;
@@ -370,11 +365,13 @@ static int TODO_setratectr = 0;
 //TODO// Not all this code is properly split between generic TLV and specific BT200
 void tlv320aic2x_set_samplerate (uint8_t chan, uint32_t samplerate) {
        uint16_t m, n, p;
+#if 0
        SPCR2_1 |= REGVAL_SPCR2_GRST_NOTRESET | REGVAL_SPCR2_FRST_NOTRESET;
 { uint32_t ctr = 100; while (ctr-- > 0) ; }
        SPCR1_1 |= REGVAL_SPCR1_RRST_NOTRESET;
        SPCR2_1 |= REGVAL_SPCR2_XRST_NOTRESET;
 { uint32_t ctr = 10000; while (ctr-- > 0) ; }
+#endif
        DXR1_1 = DXR1_1;        // Flag down XEMPTY
 tlv320aic2x_setreg (chan, 3, 0x31);    // Channel offline
 { uint32_t ctr = 1000; while (ctr-- > 0) ; }
@@ -384,7 +381,7 @@ tlv320aic2x_setreg (chan, 3, 0x31); // Channel offline
        n = 1;
        p = 2;
        m = ( 30720000 / 16 ) / ( n * p * samplerate );
-       if (m % 8 == 0) {
+       if (m & 0x03 == 0x00) {
                // Save PLL energy without compromising accuracy
                p = 8;          // Factor 2 -> 8 so multiplied by 4
                m >>= 2;        // Divide by 4
@@ -432,7 +429,7 @@ tlv320aic2x_setreg (chan, 3, 0x01); // Channel online
        SPCR1_1 |= REGVAL_SPCR1_RRST_NOTRESET;
        SPCR2_1 |= REGVAL_SPCR2_XRST_NOTRESET;
 { uint32_t ctr = 1000; while (ctr-- > 0) ; }
-       SPCR2_1 |= REGVAL_SPCR2_FRST_NOTRESET;
+       //NOT_FOR_NEW_STYLE_BUFFERS// SPCR2_1 |= REGVAL_SPCR2_FRST_NOTRESET;
 { uint32_t ctr = 10000; while (ctr-- > 0) ; }
        DXR1_1 = DXR1_1;        // Flag down XEMPTY
        (void) DRR1_1;  // Flag down RFULL
@@ -441,10 +438,28 @@ tlv320aic2x_setreg (chan, 3, 0x01);       // Channel online
 // #endif
 }
 
+void bottom_soundchannel_set_samplerate (uint8_t chan, uint32_t samplerate,
+                       uint8_t blocksize, uint8_t upsample_play, uint8_t downsample_record) {
+       //
+       // Setup the buffersize of the sample buffers
+       samplebuf_blocksize = blocksize;
+       samplebuf_wrapindex = (BUFSZ / blocksize) * blocksize;
+       //
+       // Setup hardware with the requested sample rate (but no FRST generated)
+       tlv320aic2x_set_samplerate (chan, samplerate);
+       //
+       // Setup buffer index pointers at the start; effectively clearing all
+       bufofs_play0 = 0;
+       bufofs_dma0  = 0;
+       bufofs_rec0  = 0;
+}
+
 /* A full frame of 64 samples has been recorded.  See if space exists for
  * another, otherwise disable DMA until a dmahint_play() restarts it.
+ * TODO: No processing needed for RX_DMA_IRQ, as TX_DMA_IRQ comes later.
  */
 interrupt void tic55x_dmac0_isr (void) {
+#ifdef PREFER_OLD_STUFF
        uint16_t irq = DMACSR_0;        // Note causes and clear
        tic55x_top_has_been_interrupted = true;
        if ((available_record += 64) > (BUFSZ - 64)) {
@@ -459,14 +474,16 @@ interrupt void tic55x_dmac0_isr (void) {
 // #endif
        }
        if (available_record >= threshold_record) {
-               top_can_record (available_record);
+               top_codec_can_record (available_record);
        }
+#endif
 }
 
 /* A full frame of 64 samples has been played.  See if another is availabe,
  * otherwise disable DMA until a dmahint_record() restarts it.
  */
 interrupt void tic55x_dmac1_isr (void) {
+#ifdef PREFER_OLD_STUFF
        uint16_t irq = DMACSR_1;        // Note causes and clear
        uint16_t toplay;
        tic55x_top_has_been_interrupted = true;
@@ -481,10 +498,13 @@ interrupt void tic55x_dmac1_isr (void) {
        }
        toplay = BUFSZ - available_play;
        if (BUFSZ - available_play >= threshold_play) {
-               top_can_play (available_play);
+               top_codec_can_play (available_play);
        }
+#endif
+       bottom_bufferdma_progress (0);
 }
 
+#ifdef PREFER_OLD_STUFF
 /* Data has been removed from what was recorded.  As a result,
  * it may be possible to restart DMA channel 1 if it was disabled.
  */
@@ -504,7 +524,9 @@ void dmahint_record (void) {
                }
        }
 }
+#endif
 
+#ifdef PREFER_OLD_STUFF
 /* New data has been written for playback.  As a result, it may
  * be possible to restart DMA channel 0 if it was disabled.
  */
@@ -519,6 +541,7 @@ bottom_printf ("dmahint_play() started playing DMA\n");
        }
 //TODO:DEBUG// else bottom_printf ("dmahint_play() did not start playing -- available_play = %d\n", (intptr_t) available_play);
 }
+#endif
 
 
 
index de40f48..f0fad62 100644 (file)
@@ -85,7 +85,9 @@ static uint8_t volume [2];
 
 /* Read and write buffers, with counters of read-ahead */
 
-#define BUFSZ (64*25)
+#ifdef PREFER_OLD_STUFF
+
+#define BUFSZ (80*24)
 
 volatile int16_t samplebuf_play   [BUFSZ];
 volatile int16_t samplebuf_record [BUFSZ];
@@ -102,6 +104,7 @@ static uint16_t nextwrite_record = 0;
 static uint16_t nextread_play    = 0;
 static uint16_t nextread_record  = 0;
 
+#endif
 
 /******** Calls to change sound channels ********/
 
@@ -180,11 +183,14 @@ void bottom_soundchannel_setvolume (uint8_t chan, uint8_t vol) {
 /******** Calls to play or record through a codec ********/
 
 
+#ifdef PREFER_OLD_STUFF
 int16_t codec_decode (codec_t codec, uint8_t *in, uint16_t inlen, int16_t *out, uint16_t outlen);
 int16_t codec_encode (codec_t codec, int16_t *in, uint16_t inlen, uint8_t *out, uint16_t outlen);
 void tlv320aic2x_set_samplerate (uint8_t chan, uint32_t samplerate);
+#endif
 
 
+#ifdef PREFER_OLD_STUFF
 void bottom_codec_play_samplerate   (uint8_t chan, uint32_t samplerate) {
        /* TODO: Filters only work up to rate 26000 (bandwidth 11700);
         * TODO: Consider 2x oversampling and no filter for higher rates?
@@ -195,13 +201,17 @@ void bottom_codec_play_samplerate   (uint8_t chan, uint32_t samplerate) {
         */
        tlv320aic2x_set_samplerate (chan, samplerate);
 }
+#endif
 
+#ifdef PREFER_OLD_STUFF
 void bottom_codec_record_samplerate (uint8_t chan, uint32_t samplerate) {
        // No setting to make: documentation says to call both with the same values
        // bottom_codec_play_samplerate (chan, samplerate);
        dmahint_record ();
 }
+#endif
 
+#ifdef PREFER_OLD_STUFF
 int16_t bottom_codec_play   (uint8_t chan, codec_t codec, uint8_t *coded_samples, uint16_t coded_bytes, uint16_t samples) {
        int16_t retval;
        //TODO// Guard against buffer wraparound
@@ -220,31 +230,37 @@ int16_t bottom_codec_play   (uint8_t chan, codec_t codec, uint8_t *coded_samples
        dmahint_play ();
        return retval;
 }
+#endif
 
+#ifdef PREFER_OLD_STUFF
 int16_t bottom_codec_record (uint8_t chan, codec_t codec, uint8_t *coded_samples, uint16_t coded_bytes, uint16_t samples) {
-       int16_t retval;
+       int16_t codecreduce = 0;
+       int16_t samplereduce = 0;
        //TODO// Guard against buffer wraparound
        uint16_t ar = available_record;
        if (samples > ar) {
-               samples = ar;
+               samplereduce = ar - samples;   /* Negative value */
+               samples += samplereduce;
        }
-       retval = codec_encode (codec,
+       codecreduce = codec_encode (codec,
                                (int16_t *) (samplebuf_record + nextread_record), samples,
                                coded_samples, coded_bytes);
        nextread_record += samples;
        available_record -= samples;
-       if (retval < 0) {
-               nextread_record += retval;
-               available_record -= retval;
+       if (codecreduce < 0) {
+               nextread_record += codecreduce;
+               available_record -= codecreduce;
        }
        if (nextread_record >= BUFSZ) {
                nextread_record -= BUFSZ;
        }
        dmahint_record ();
-       return retval;
+       return (codecreduce + samplereduce);
        //TODO:ALT-API-TEST// return available_record;
 }
+#endif
 
+#ifdef PREFER_OLD_STUFF
 void bottom_codec_play_skip (codec_t codec, uint16_t samples) {
        uint16_t ctr;
        //TODO: Better fill up with previous sample
@@ -261,6 +277,7 @@ void bottom_codec_play_skip (codec_t codec, uint16_t samples) {
        available_play += samples;
        dmahint_play ();
 }
+#endif
 
 
 /******** Initialisation of this module ********/
@@ -270,7 +287,7 @@ void bottom_codec_play_skip (codec_t codec, uint16_t samples) {
  */
 void tlv320aic2x_setup_sound (void) {
        uint8_t chan;
-       tlv320aic2x_set_samplerate (0, 8000);                   /* Only once per codec */
+       // tlv320aic2x_set_samplerate (0, 8000);                        /* Only once per codec */
        // Setup the various registers in the TLV320AIC2x
        for (chan = 0; chan < 2; chan++) {
                tlv320aic2x_setreg (chan, 1,        0x49);      /* Continuous 16-bit, 2.35V bias */
@@ -285,7 +302,7 @@ void tlv320aic2x_setup_sound (void) {
 // tlv320aic2x_setreg (chan, 4, 0x80 | (16 & 0x7f));                   // M:=16
                tlv320aic2x_setreg (chan, 5, 0x00 | 0x12);      /* ADC gain 27 dB -- ok? */
                bottom_soundchannel_setvolume (chan, 15);       /* DAC gain -24 dB initially */
-               tlv320aic2x_setreg (chan, 5, 0x80 | 0x00);      /* No sidetones */
+               tlv320aic2x_setreg (chan, 5, 0x80 | 0x3e);      /* Minimal, "lively" sidetone */
                /* Register 5D resets ok to 0xc0: No speaker gain */
                tlv320aic2x_setreg (chan, 6, 0x00 | 0x20);      /* Handset feedback, no input */
                /* Register 6B resets ok to 0x80: No output selected */
index 5294331..7bf9d54 100644 (file)
@@ -58,6 +58,7 @@ config MAINFUNCTION_DEVEL_SOUND
        bool "Test sound"
        depends on DEVEL
        select FUNCTION_DEVEL_SOUNDIO
+       select CODEC_G711
        help
          This test turns your phone into an echo well.  While off-hook, it will
          echo handset voice to the handset with a one-second delay.  If it is
index fa4437d..5115dfd 100644 (file)
@@ -22,7 +22,7 @@
 objs-top-$(CONFIG_FUNCTION_NETCONSOLE) += src/function/netconsole.o
 objs-top-$(CONFIG_MAINFUNCTION_NETCONSOLE) += src/net/llconly.o
 objs-top-$(CONFIG_MAINFUNCTION_DEVEL_NETWORK) += src/net/llconly.o
-objs-top-$(CONFIG_MAINFUNCTION_DEVEL_SOUND) += src/net/llconly.o
+objs-top-$(CONFIG_MAINFUNCTION_DEVEL_SOUND) += src/net/llconly.o src/codec/l8l16.o
 
 #
 # Actual targets and their constituent object files
index 7b878fe..eed2368 100644 (file)
@@ -212,8 +212,8 @@ void top_network_can_send (void) { ; }
 void top_network_offline (void) { ; }
 void top_network_online (void) { ; }
 void top_timer_expiration (void) { ; }
-void top_can_play (uint16_t samples) { ; }
-void top_can_record (uint16_t samples) { ; }
+void top_codec_can_play (uint8_t chan) { ; }
+void top_codec_can_record (uint8_t chan) { ; }
 
 #endif /* CONFIG_MAINFUNCTION_BOOTLOADER */
 
index 5aa199f..3f177fa 100644 (file)
 #include <stdarg.h>
 #include <stdbool.h>
 
-//TODO// test inclusion of bottom definitions
+//TODO// test inclusion of bottom and text definitions
 #define BOTTOM
 #include <config.h>
+#include <0cpm/text.h>
 
 #include <0cpm/kbd.h>
 #include <0cpm/app.h>
@@ -104,20 +105,18 @@ void top_button_release (void) {
 }
 
 uint16_t plyirqs = 0;
-void top_can_play (uint16_t samples) {
-       tobeplayed = samples;
+void top_codec_can_play (uint8_t chan) {
        plyirqs++;
 }
 
 uint16_t recirqs = 0;
-void top_can_record (uint16_t samples) {
-       toberecorded = samples;
+void top_codec_can_record (uint8_t chan) {
        recirqs++;
 }
 
 
-// #define top_main_sine_1khz  top_main
-#define top_main_delay_1sec top_main
+#define top_main_sine_1khz  top_main
+// #define top_main_delay_1sec top_main
 
 
 #ifdef CONFIG_FUNCTION_NETCONSOLE
@@ -129,16 +128,28 @@ void nethandler_llconly (uint8_t *pkt, uint16_t pktlen);
 
 /******** TOP_MAIN FOR A 1 KHZ SINE WAVE OUTPUT ********/
 
-uint8_t sinewaveL8 [8] = {
-       0x00, 0x5a, 0x7f, 0x5a, 0x00, 0xa5, 0x80, 0xa5
+uint8_t sinewaveL8 [64] = {
+       // 8-bit samples with 0x80 offset
+       0x80, 0xda, 0xff, 0xda, 0x80, 0x25, 0x00, 0x25,
+       0x80, 0xda, 0xff, 0xda, 0x80, 0x25, 0x00, 0x25,
+       0x80, 0xda, 0xff, 0xda, 0x80, 0x25, 0x00, 0x25,
+       0x80, 0xda, 0xff, 0xda, 0x80, 0x25, 0x00, 0x25,
+       0x80, 0xda, 0xff, 0xda, 0x80, 0x25, 0x00, 0x25,
+       0x80, 0xda, 0xff, 0xda, 0x80, 0x25, 0x00, 0x25,
+       0x80, 0xda, 0xff, 0xda, 0x80, 0x25, 0x00, 0x25,
+       0x80, 0xda, 0xff, 0xda, 0x80, 0x25, 0x00, 0x25
 };
 
-uint16_t sinewaveL16 [8] = {
-       // 0x0000, 0x5a82, 0x7fff, 0x5a82, 0x0000, 0xa57e, 0x8001, 0xa57e
-       // 0x0000 + 32768, 0x5a82 + 32768, 0x7fff + 32768, 0x5a82 + 32768, 0x0000 + 32768, 0xa57e - 32768, 0x8001 - 32768, 0xa57e - 32768
-       0x0000, 0x2d41, 0x3fff, 0x2d41, 0x0000, 0xdabf, 0xc000, 0xdabf
-       // 0x0000, 0x002d, 0x0040, 0x002d, 0x0000, 0xffda, 0xffc0, 0xffda
-       // 4096+0x0000, 4096+0x05a8, 4096+0x07ff, 4096+0x05a8, 4096+0x0000, 4096+0xfa57, 4096+0xf801, 4096+0xfa57
+int8_t sinewaveL16 [128] = {
+       // 16-bit signed values, encoded as byte pairs H,L
+       0,0, 45,65, 63,255, 45,65, 0,0, 210,191, 192,1, 210,191,
+       0,0, 45,65, 63,255, 45,65, 0,0, 210,191, 192,1, 210,191,
+       0,0, 45,65, 63,255, 45,65, 0,0, 210,191, 192,1, 210,191,
+       0,0, 45,65, 63,255, 45,65, 0,0, 210,191, 192,1, 210,191,
+       0,0, 45,65, 63,255, 45,65, 0,0, 210,191, 192,1, 210,191,
+       0,0, 45,65, 63,255, 45,65, 0,0, 210,191, 192,1, 210,191,
+       0,0, 45,65, 63,255, 45,65, 0,0, 210,191, 192,1, 210,191,
+       0,0, 45,65, 63,255, 45,65, 0,0, 210,191, 192,1, 210,191
 };
 
 void top_main_sine_1khz (void) {
@@ -146,35 +157,41 @@ void top_main_sine_1khz (void) {
 extern volatile uint16_t available_play;
 extern volatile uint16_t available_record;
 uint8_t l16ctr = 1;
-       top_hook_update (bottom_phone_is_offhook ());
        bottom_critical_region_end ();
-       bottom_codec_play_samplerate (0, 8000);
-       bottom_codec_record_samplerate (0, 8000); // Both MUST be called for now
+       if (!bottom_soundchannel_acceptable_samplerate (PHONE_CHANNEL_TELEPHONY, 8000)) {
+               bottom_printf ("Failed to set sample rate");
+               bottom_show_fixed_msg (APP_LEVEL_ZERO, FIXMSG_CALL_ENDED);
+               exit (1);
+       }
+       bottom_soundchannel_set_samplerate (PHONE_CHANNEL_TELEPHONY, 8000, 64, 1, 1);
        bottom_soundchannel_setvolume (PHONE_CHANNEL_TELEPHONY, 127);
-       bottom_show_fixed_msg (APP_LEVEL_ZERO, FIXMSG_RINGING); // TODO: Not really necessary
+       bottom_show_fixed_msg (APP_LEVEL_ZERO, FIXMSG_RINGING);
        bottom_printf ("Playing 1 kHz tone to speaker or handset\n");
-       tobeplayed = 64;
+       top_hook_update (bottom_phone_is_offhook ());
        while (true) {
-               uint16_t newplayed = tobeplayed;
-               uint16_t oldplayed = newplayed;
-               if (oldirqs != plyirqs) {
-                       bottom_printf ("New playing IRQs detected; available_play=%d, available_record=%d\n", (intptr_t) available_play, (intptr_t) available_record);
-                       oldirqs = plyirqs;
-               }
 #if 0
 if (SPCR2_1 & REGVAL_SPCR2_XRDY) { DXR1_1 = sinewaveL16 [l16ctr++]; if (l16ctr == 8) { l16ctr = 0; } }
 { uint16_t _ = DRR1_1; }
 #else
-               while (newplayed >= 8) {
-                       bottom_codec_play (0, CODEC_L8, sinewaveL8, 8, 8);
-                       newplayed -= 8;
-               }
-#endif
+               do {
+                       int16_t *outbuf = bottom_play_claim (PHONE_CHANNEL_TELEPHONY);
+                       uint16_t pcmlen, pktlen;
+                       if (!outbuf) {
+                               break;
+                       }
 #if 1
-               if (oldplayed != newplayed) {
-                       bottom_printf ("available_play := %d\n", (intptr_t) available_play);
-                       // bottom_printf ("Playbuffer reduced from %d to %d\n", (intptr_t) oldplayed, (intptr_t) newplayed);
-               }
+                       pcmlen = 64;
+                       pktlen = 64;
+                       // Note: No handle needed for stateless L8
+                       l8_decode (NULL, outbuf, &pcmlen, sinewaveL8, &pktlen);
+#else
+                       pcmlen = 64;
+                       pktlen = 128;
+                       // Note: No handle needed for stateless L16
+                       l16_decode (NULL, outbuf, &pcmlen, sinewaveL16, &pktlen);
+#endif
+                       bottom_play_release (PHONE_CHANNEL_TELEPHONY);
+               } while (true);
 #endif
 
 #ifdef CONFIG_FUNCTION_NETCONSOLE
@@ -216,80 +233,49 @@ uint16_t sampled = 0;
 uint8_t samples [10000];
 
 void top_main_delay_1sec (void) {
-       uint16_t prevsampled = 0;
-       uint16_t prevrecirqs = 0;
-       uint16_t prevplyirqs = 0;
-uint16_t oldspcr1 = 0xffff;
-uint16_t loop = 0;
+       uint16_t prepblocks = 0;
+       uint16_t playptr = 2000, recptr = 0;
+memset (samples, 0x33, sizeof (samples));
        bottom_critical_region_end ();
-       top_hook_update (bottom_phone_is_offhook ());
-       bottom_codec_play_samplerate (PHONE_CHANNEL_TELEPHONY, 8000);
-       bottom_codec_record_samplerate (PHONE_CHANNEL_TELEPHONY, 8000);
+       if (!bottom_soundchannel_acceptable_samplerate (PHONE_CHANNEL_TELEPHONY, 8000)) {
+               bottom_printf ("Failed to set sample rate");
+               bottom_show_fixed_msg (APP_LEVEL_ZERO, FIXMSG_CALL_ENDED);
+               exit (1);
+       }
+       bottom_soundchannel_set_samplerate (PHONE_CHANNEL_TELEPHONY, 8000, 100, 1, 1);
        bottom_soundchannel_setvolume (PHONE_CHANNEL_TELEPHONY, 127);
        bottom_show_fixed_msg (APP_LEVEL_ZERO, FIXMSG_READY);
        bottom_printf ("Running the development function \"echo\" (Test sound)\n");
+       top_hook_update (bottom_phone_is_offhook ());
        while (true) {
-#if 0
-               if (recirqs != prevrecirqs) {
-                       bottom_printf ("Record IRQs #%d, ", (intptr_t) recirqs);
-                       bottom_printf ("available %d\n", (intptr_t) toberecorded);
-                       prevrecirqs = recirqs;
-               }
-               if (plyirqs != prevplyirqs) {
-                       bottom_printf ("Playbk IRQs #%d, ", (intptr_t) plyirqs);
-                       bottom_printf ("available %d\n", (intptr_t) tobeplayed);
-                       prevplyirqs = plyirqs;
-               }
-#endif
-{ uint16_t xor = oldspcr1 ^ SPCR1_1; if (xor) { oldspcr1 ^= xor; bottom_printf ("SPCR1_1 := 0x%04x\n", (intptr_t) oldspcr1); } }
-               if (sampled != prevsampled) {
-                       bottom_printf ("Buffered %d samples\n", (intptr_t) sampled);
-                       prevsampled = sampled;
-               }
-               if (toberecorded > 0) {
-                       int16_t rec = toberecorded;
-bottom_led_set (LED_IDX_HANDSET, 1);
-                       if (sampled + rec > 10000) {
-                               rec = 10000 - sampled;
-                       }
-                       if (recpos + rec > 10000) {
-                               rec = 10000 - recpos;
-                       }
-                       if (rec > 0) {
-                               // bottom_printf ("Recording %d at %d extends buffer from %d to %d\n", (intptr_t) rec, (intptr_t) recpos, (intptr_t) sampled, (intptr_t) (rec+sampled));
-                               // bottom_printf ("Recording %d at %d\n", (intptr_t) rec, (intptr_t) recpos);
-                               // Codec implies that #samples and #bytes are the same
-                               rec -= abs (bottom_codec_record (0, CODEC_L8, samples + recpos, rec, rec));
-                               bottom_printf ("Got 0x%02x, 0x%02x, 0x%02x, ...\n", (intptr_t) samples [recpos], (intptr_t) samples [recpos+1], (intptr_t) samples [recpos+2]);
-                               sampled += rec;
-                               recpos += rec;
-                               if (recpos >= 10000) {
-                                       recpos -= 10000;
-                               }
-                               bottom_critical_region_begin ();
-                               //TODO:SPYING-ON-NEXT-LINE// toberecorded -= rec;
-                               { extern volatile uint16_t available_record; toberecorded = available_record; }
-                               bottom_critical_region_end ();
-                       }
-bottom_led_set (LED_IDX_SPEAKERPHONE, 0);
+               int16_t *buf;
+               uint16_t pcmlen, pktlen;
+
+               do {
+                       buf = bottom_play_claim (PHONE_CHANNEL_TELEPHONY);
+               } while (buf == NULL);
+               pcmlen = 100;
+               pktlen = 100;
+               // Note: No handle needed for stateless L8
+               l8_encode (NULL, buf, &pcmlen, samples + playptr, &pktlen);
+               bottom_play_release (PHONE_CHANNEL_TELEPHONY);
+
+               playptr += 100;
+               if (playptr >= 10000) {
+                       playptr -= 10000;
                }
-               if (sampled > 8000) {
-                       uint16_t ply = sampled - 8000;
-bottom_led_set (LED_IDX_SPEAKERPHONE, 1);
-                       if (playpos + ply > 10000) {
-                               ply = 10000 - playpos;
-                       }
-                       if (ply > 0) {
-                               // bottom_printf ("Playback of %d samples at %d reduces buffer from %d to %d\n", (intptr_t) ply, (intptr_t) playpos, (intptr_t) sampled, (intptr_t) (sampled - ply));
-                               // bottom_printf ("Playback of %d samples at %d\n", (intptr_t) ply, (intptr_t) playpos);
-                               ply -= abs (bottom_codec_play (0, CODEC_L8, samples + playpos, ply, ply));
-                               sampled -= ply;
-                               playpos += ply;
-                               if (playpos >= 10000) {
-                                       playpos -= 10000;
-                               }
-bottom_led_set (LED_IDX_HANDSET, 0);
-                       }
+
+               do {
+                       buf = bottom_record_claim (PHONE_CHANNEL_TELEPHONY);
+               } while (buf == NULL);
+               pcmlen = 100;
+               pktlen = 100;
+               // Note: No handle needed for stateless L8
+               l8_decode (NULL, buf, &pcmlen, samples + recptr, &pktlen);
+
+               recptr += 100;
+               if (recptr >= 10000) {
+                       recptr -= 10000;
                }
 
 #ifdef CONFIG_FUNCTION_NETCONSOLE
@@ -314,6 +300,7 @@ bottom_led_set (LED_IDX_BACKLIGHT, 0);
 #if defined NEED_HOOK_SCANNER_WHEN_ONHOOK || defined NEED_HOOK_SCANNER_WHEN_OFFHOOK
                bottom_hook_scan ();
 #endif
+
        }
 }
 
index 9920f42..346acb8 100644 (file)
@@ -55,11 +55,11 @@ void top_network_can_recv (void) {
        /* Keep the linker happy */ ;
 }
 
-void top_can_play (uint16_t samples) {
+void top_codec_can_play (uint8_t chan) {
        /* Keep the linker happy */ ;
 }
 
-void top_can_record (uint16_t samples) {
+void top_codec_can_record (uint8_t chan) {
        /* Keep the linker happy */ ;
 }
 
index c9bf595..3f87b39 100644 (file)
@@ -85,11 +85,11 @@ void top_network_can_recv (void) {
        /* Keep the linker happy */ ;
 }
 
-void top_can_play (void) {
+void top_codec_can_play (uint8_t chan) {
        /* Keep the linker happy */ ;
 }
 
-void top_can_record (void) {
+void top_codec_can_record (uint8_t chan) {
        /* Keep the linker happy */ ;
 }
 
index 18181dc..e37fac7 100644 (file)
@@ -76,11 +76,11 @@ void top_network_can_recv (void) {
        /* Keep the linker happy */ ;
 }
 
-void top_can_play (uint16_t samples) {
+void top_codec_can_play (uint8_t chan) {
        /* Keep the linker happy */ ;
 }
 
-void top_can_record (uint16_t samples) {
+void top_codec_can_record (uint8_t chan) {
        /* Keep the linker happy */ ;
 }
 
index e5c4e7d..9e40b69 100644 (file)
@@ -74,11 +74,11 @@ void top_network_can_recv (void) {
        /* Keep the linker happy */ ;
 }
 
-void top_can_play (uint16_t samples) {
+void top_codec_can_play (uint8_t chan) {
        /* Keep the linker happy */ ;
 }
 
-void top_can_record (uint16_t samples) {
+void top_codec_can_record (uint8_t chan) {
        /* Keep the linker happy */ ;
 }
 
index c09d8d7..b5e71d3 100644 (file)
@@ -52,11 +52,11 @@ extern enum netcfgstate boot_state;
 static uint8_t nextround = 0x00;       // Local, only for test purposes
 
 
-void top_can_play (uint16_t samples) {
+void top_codec_can_play (uint8_t chan) {
        /* Keep the linker happy */ ;
 }
 
-void top_can_record (uint16_t samples) {
+void top_codec_can_record (uint8_t chan) {
        /* Keep the linker happy */ ;
 }