# It is not necessary for the Courier modem.
#
Class2NRCmd: AT+FNR=1,1,1,0
+#
+# USR modems violate Class 2.0 specs and do not send RTC itself
+#
+Class2SendRTC: yes
do {
switch (frame.getRawFCF()) {
case FCF_NSF:
- recvNSF(NSF(frame.getFrameData(), frame.getFrameDataLength (), frameRev));
+ recvNSF(NSF(frame.getFrameData(), frame.getFrameDataLength(), frameRev));
break;
case FCF_CSI:
{ fxStr csi; recvCSI(decodeTSI(csi, frame)); }
tracePPR("SEND recv", ppr);
switch (ppr) {
case FCF_RTP: // ack, continue after retraining
+ ignore:
params.br = (u_int) -1; // force retraining above
/* fall thru... */
case FCF_MCF: // ack confirmation
emsg = "Remote fax disconnected prematurely";
return (send_retry);
case FCF_RTN: // nak, retry after retraining
- if ((++ntrys % 2) == 0) {
- /*
- * Drop to a lower signalling rate and retry.
- */
- if (params.br == BR_2400) {
- emsg = "Unable to transmit page"
- " (NAK at all possible signalling rates)";
- return (send_retry);
- }
- --params.br;
- curcap = NULL; // force sendTraining to reselect
- }
- if (!sendTraining(params, 3, emsg))
- return (send_retry);
- morePages = true; // force continuation
- next = params; // avoid retraining above
+ switch( conf.rtnHandling ){
+ case RTN_IGNORE:
+ goto ignore; // ignore error and try to send next page
+ // after retraining
+ case RTN_GIVEUP:
+ emsg = "Unable to transmit page"
+ " (giving up after RTN)";
+ return (send_failed); // "over and out"
+ }
+ // case RTN_RETRANSMIT
+ if (++ntrys >= 3) {
+ emsg = "Unable to transmit page"
+ " (giving up after 3 attempts)";
+ return (send_retry);
+ }
+ if (params.br == BR_2400) {
+ emsg = "Unable to transmit page"
+ "(NAK at all possible signalling rates)";
+ return (send_retry);
+ }
+ next.br--;
+ curcap = NULL; // force sendTraining to reselect
+ morePages = true; // retransmit page
break;
case FCF_PIN: // nak, retry w/ operator intervention
emsg = "Unable to transmit page"
totdata = totdata+ts - (dp-data);
} else
dp = data;
+
+ /*
+ * correct broken Phase C (T.4) data if neccessary
+ */
+ correctPhaseCData(dp, &totdata, fillorder, params);
+
/*
* Send the page of data. This is slightly complicated
* by the fact that we may have to add zero-fill before the
bool rc = sendPageData(tif, pageChop);
if (!rc)
abortDataTransfer();
- else
+ else if( conf.class2SendRTC )
rc = sendRTC(params.is2D());
if (flowControl == FLOW_XONXOFF)
setXONXOFF(getInputFlow(), FLOW_XONXOFF, ACT_DRAIN);
*/
if (hostDidCQ)
ppr = isQualityOK(params) ? PPR_MCF : PPR_RTN;
+#if 0
+ /*
+ * RTN debug code: always respond with RTN to sending facsimile
+ */
+ ppr = PPR_RTN;
+#endif
if (ppr & 1)
TIFFWriteDirectory(tif); // complete page write
else
gotParams = parseClass2Capabilities(skipStatus(rbuf), dis);
break;
case AT_FNSF:
- recvNSF(NSF(skipStatus(rbuf)));
+ recvNSF(NSF(skipStatus(rbuf)));
break;
case AT_FCSI:
recvCSI(stripQuotes(skipStatus(rbuf)));
case PPR_MCF: // page good
case PPR_PIP: // page good, interrupt requested
case PPR_RTP: // page good, retrain requested
+ ignore:
countPage(); // bump page count
notifyPageSent(tif);// update server
if (pph[2] == 'Z')
transferOK = true;
break;
case PPR_RTN: // page bad, retrain requested
+ switch( conf.rtnHandling ){
+ case RTN_IGNORE:
+ goto ignore; // ignore error and trying to send next page
+ case RTN_GIVEUP:
+ emsg = "Unable to transmit page"
+ " (giving up after RTN)";
+ goto failed; // "over and out"
+ }
+ // case RTN_RETRANSMIT
if (++ntrys >= 3) {
emsg = "Unable to transmit page"
" (giving up after 3 attempts)";
totdata = totdata+ts - (dp-data);
} else
dp = data;
+
+ /*
+ * correct broken Phase C (T.4) data if necessary
+ */
+ correctPhaseCData(dp, &totdata, fillorder, params);
+
beginTimedTransfer();
rc = putModemDLEData(dp, (u_int) totdata, bitrev, getDataTimeout());
endTimedTransfer();
if (curreq)
server.notifyPageSent(*curreq, TIFFFileName(tif));
}
+
+/*
+ * Phase C data correction
+ */
+
+#include "G3Decoder.h"
+#include "G3Encoder.h"
+#include "StackBuffer.h"
+#include "Class2Params.h"
+
+class MemoryDecoder : public G3Decoder {
+private:
+ u_char* bp;
+ u_int width;
+ u_int byteWidth;
+ u_long cc;
+
+ u_int fillorder;
+ bool is2D;
+
+ u_char* endOfData; // used by cutExtraRTC
+
+ tiff_runlen_t*
+ runs;
+ u_char* rowBuf;
+
+ int decodeNextByte();
+public:
+ MemoryDecoder(u_char* data, u_int wid, u_long n,
+ u_int fillorder, bool twoDim);
+ ~MemoryDecoder();
+ u_char* current() { return bp; }
+ void fixFirstEOL();
+ u_char* cutExtraRTC();
+};
+
+MemoryDecoder::MemoryDecoder(u_char* data, u_int wid, u_long n,
+ u_int order, bool twoDim)
+{
+ bp = data;
+ width = wid;
+ byteWidth = howmany(width, 8);
+ cc = n;
+
+ fillorder = order;
+ is2D = twoDim;
+
+ runs = new tiff_runlen_t[2*width]; // run arrays for cur+ref rows
+ rowBuf = new u_char[byteWidth];
+ setupDecoder(fillorder, is2D);
+ setRuns(runs, runs+width, width);
+}
+MemoryDecoder::~MemoryDecoder()
+{
+ delete rowBuf;
+ delete runs;
+}
+
+int
+MemoryDecoder::decodeNextByte()
+{
+ if (cc == 0)
+ raiseRTC(); // XXX don't need to recognize EOF
+ cc--;
+ return (*bp++);
+}
+
+#ifdef roundup
+#undef roundup
+#endif
+#define roundup(a,b) ((((a)+((b)-1))/(b))*(b))
+
+/*
+ * TIFF Class F specs say:
+ *
+ * "As illustrated in FIGURE 1/T.4 in Recommendation T.4 (the Red
+ * Book, page 20), facsimile documents begin with an EOL (which in
+ * Class F is byte-aligned)..."
+ *
+ * This is wrong! "Byte-aligned" first EOL means extra zero bits
+ * which are not allowed by T.4. Reencode first row to fix this
+ * "byte-alignment".
+ */
+void MemoryDecoder::fixFirstEOL()
+{
+ fxStackBuffer result;
+ G3Encoder enc(result);
+ enc.setupEncoder(fillorder, is2D);
+
+ memset(rowBuf, 0, byteWidth*sizeof(u_char)); // clear row to white
+ if(!RTCraised()) {
+ u_char* start = current();
+ (void)decodeRow(rowBuf, width);
+ /*
+ * syncronize to the next EOL and calculate pointer to it
+ * (see detailed explanation of look_ahead in TagLine.c++)
+ */
+ (void)isNextRow1D();
+ u_int look_ahead = roundup(getPendingBits(),8) / 8;
+ u_int decoded = current() - look_ahead - start;
+
+ enc.encode(rowBuf, width, 1);
+ u_int encoded = result.getLength();
+
+ while( encoded < decoded ){
+ result.put((char) 0);
+ encoded++;
+ }
+ if( encoded == decoded ){
+ memcpy(start, (const char*)result, encoded);
+ }
+ }
+}
+
+
+/*
+ * TIFF Class F specs say:
+ *
+ * "Aside from EOL's, TIFF Class F files contain only image data. This
+ * means that the Return To Control sequence (RTC) is specifically
+ * prohibited..."
+ *
+ * Nethertheless Ghostscript and possibly other TIFF Class F writers
+ * append RTC or single EOL to the last encoded line. Remove them.
+ */
+u_char* MemoryDecoder::cutExtraRTC()
+{
+ u_char* start = current();
+
+ /*
+ * We expect RTC near the end of data and thus
+ * do not check all image to save processing time.
+ * It's safe because we will resync on the first
+ * encountered EOL.
+ *
+ * NB: We expect G3Decoder::data==0 and
+ * G3Decoder::bit==0 (no data in the accumulator).
+ * As we cannot explicitly clear the accumulator
+ * (bit and data are private), cutExtraRTC()
+ * should be called immediately after
+ * MemoryDecoder() constructing.
+ */
+ const u_long CheckArea = 20;
+ if( cc > CheckArea ){
+ bp += (cc-CheckArea);
+ cc = CheckArea;
+ }
+
+ endOfData = NULL;
+ if(!RTCraised()) {
+ /*
+ * syncronize to the next EOL and calculate pointer to it
+ * (see detailed explanation of look_ahead in TagLine.c++)
+ */
+ (void)isNextRow1D();
+ u_int look_ahead = roundup(getPendingBits(),8) / 8;
+ endOfData = current() - look_ahead;
+ for (;;) {
+ if( decodeRow(NULL, width) ){
+ /*
+ * endOfData is now after last good row. Thus we correctly handle
+ * RTC, single EOL in the end, or no RTC/EOL at all
+ */
+ endOfData = current();
+ }
+ if( seenRTC() )
+ break;
+ }
+ }
+ return endOfData;
+}
+
+void
+FaxModem::correctPhaseCData(u_char* buf, u_long* pBufSize,
+ u_int fillorder, const Class2Params& params)
+{
+ MemoryDecoder dec1(buf, params.pageWidth(), *pBufSize, fillorder, params.is2D());
+ dec1.fixFirstEOL();
+ /*
+ * We have to construct new decoder. See comments to cutExtraRTC().
+ */
+ MemoryDecoder dec2(buf, params.pageWidth(), *pBufSize, fillorder, params.is2D());
+ u_char* endOfData = dec2.cutExtraRTC();
+ if( endOfData )
+ *pBufSize = endOfData - buf;
+}
class FaxFont;
class FaxServer;
+// NB: these would be enums in the FaxModem class
+// if there were a portable way to refer to them!
+typedef unsigned int RTNHandling; // RTN signal handling method
+
/*
* This is an abstract class that defines the interface to
* the set of modem drivers. Real drivers are derived from
fxStr tsi; // received TSI/CSI
fxStr sub; // received subaddressing string
fxStr pwd; // received password string
- NSF nsf; // received nonstandard facilities
+ NSF nsf; // received nonstandard facilities
// NB: remaining session state is below (params) or maintained by subclass
protected:
// NB: these are defined protected for convenience (XXX)
bool setupTagLineSlop(const Class2Params&);
u_int getTagLineSlop() const;
u_char* imageTagLine(u_char* buf, u_int fillorder, const Class2Params&);
+/*
+ * Correct if neccessary Phase C (T.4) data (remove extra RTC etc.)
+ */
+ void correctPhaseCData(u_char* buf, u_long* pBufSize,
+ u_int fillorder, const Class2Params& params);
public:
+ enum { // FaxModem::RTNHandling
+ RTN_RETRANSMIT = 0, // retransmit page after RTN until MCF/MPS
+ RTN_GIVEUP = 1, // immediately abort
+ RTN_IGNORE = 2, // ignore error and send next page
+ };
+
virtual ~FaxModem();
bool isFaxModem() const;
G3Encoder::encode(const void* vp, u_int w, u_int h)
{
u_int rowbytes = howmany(w, 8);
+ bool firstEOL = true;
while (h-- > 0) {
- if (bit != 4) // byte-align EOL
- putBits(0, (bit < 4) ? bit+4 : bit-4);
+ if( firstEOL ) // according to T.4 first EOL
+ firstEOL = false; // should not be aligned
+ else if (bit != 4)
+ putBits(0, (bit < 4) ? bit+4 : bit-4); // byte-align other EOLs
if (is2D)
putBits((EOL<<1)|1, 12+1);
else
class2SendRTC = false; // default per Class 2 spec
setVolumeCmds("ATM0 ATL0M1 ATL1M1 ATL2M1 ATL3M1");
recvDataFormat = DF_ALL; // default to no transcoding
+ rtnHandling = FaxModem::RTN_RETRANSMIT; // retransmit until MCF/MPS
}
void
return (df);
}
+bool
+ModemConfig::findRTNHandling(const char* cp, RTNHandling& rh)
+{
+ static const struct {
+ const char* name;
+ RTNHandling rh;
+ } rhnames[] = {
+ { "RETRANSMIT", FaxModem::RTN_RETRANSMIT },
+ { "GIVEUP", FaxModem::RTN_GIVEUP },
+ { "IGNORE", FaxModem::RTN_IGNORE },
+ { "H_POLLACK", FaxModem::RTN_IGNORE }, // inventor's name as an alias :-)
+ };
+ for (u_int i = 0; i < N(rhnames); i++)
+ if (valeq(cp, rhnames[i].name)) {
+ rh = rhnames[i].rh;
+ return (true);
+ }
+ return (false);
+}
+
+u_int
+ModemConfig::getRTNHandling(const char* cp)
+{
+ RTNHandling rh;
+ if (!findRTNHandling(cp, rh)) {
+ configError("Unknown RTN handling method \"%s\", using RETRANSMIT", cp);
+ rh = FaxModem::RTN_RETRANSMIT; // default
+ }
+ return (rh);
+}
+
void
ModemConfig::parseCID(const char* rbuf, CallerID& cid) const
{
minSpeed = getSpeed(value);
else if (streq(tag, "recvdataformat"))
recvDataFormat = getDataFormat(value);
+ else if (streq(tag, "rtnhandlingmethod"))
+ rtnHandling = getRTNHandling(value);
else
return (false);
return (true);
void setVolumeCmds(const fxStr& value);
u_int getSpeed(const char* value);
u_int getDataFormat(const char* value);
+ u_int getRTNHandling(const char* cp);
static bool findRate(const char*, BaudRate&);
static bool findATResponse(const char*, ATResponse&);
static bool findFlow(const char*, FlowControl&);
static bool findDataFormat(const char*, u_int&);
+ static bool findRTNHandling(const char*, RTNHandling&);
protected:
ModemConfig();
fxStr tagLineFontFile; // font file for imaging tag lines
u_int recvDataFormat; // received facsimile data format
- virtual ~ModemConfig();
+ RTNHandling rtnHandling; // RTN signal handling method
+
+ virtual ~ModemConfig();
void parseCID(const char*, CallerID&) const;
const fxStr& getFlowCmd(FlowControl) const;
RingFax string \- distinctive ring fax call identifier
RingsBeforeAnswer integer \s-10\s+1 rings to wait before answering phone
RingVoice string \- distinctive ring voice call identifier
+RTNHandlingMethod string \s-1Retransmit\s+1 RTN signal handling method
SendFaxCmd\(dg string \s-1bin/faxsend\s+1 fax transmit command script
SendPageCmd\(dg string \s-1bin/pagesend\s+1 pager transmit command script
SendUUCPCmd\(dg string \s-1bin/uucpsend\s+1 \s-1UUCP\s+1 transmit command script
Class2PTSCmd string \s-1AT+FPS\s+1 Class 2.0: command to set received page status
Class2RecvDataTrigger string \s-1``\e22''\s+1 Class 2.0: character to send to trigger recv
Class2RELCmd string \- Class 2.0: command to enable byte-aligned \s-1EOL\s+1 codes
+Class2SendRTC boolean \s-1No\s+1 Class 2.0: append \s-1RTC\s+1 to page data on transmit
Class2SFLOCmd string \s-1AT+FLO=1\s+1 Class 2.0: command to set software flow control
Class2SPLCmd string \s-1AT+FSP\s+1 Class 2.0: command to set polling request
Class2TBCCmd string \s-1AT+FPP=0\s+1 Class 2.0: command to enable stream mode
and
.BR RingFax .
.TP
+.B RTNHandlingMethod
+Specifies how to react to RTN signal, received from the remote;
+one of ``\s-1Retransmit\s+1'', ``\s-1Giveup\s+1'' and
+``\s-1Ignore\s+1''. ``\s-1Retransmit\s+1'' assumes that the
+page is not sent succesfully if RTN signal has been received.
+Hylafax will made up to 2 additional attempts to send the page,
+decreasing signalling rate and retraining. If RTN is still there,
+it will place up to 2 additional calls. So if the remote always respond with
+RTN, the page will be send 9 times. Although this algorithm comply with
+T.30 specs and was originally implemented by Sam Leffler as the only
+possible choice, real fax machines behave completely different. There is a
+non-written rule among fax developers, that RTN means ``over and out'' -- hang
+up immediately and never try to send the same page to the same destination
+again. That is because RTN usually indicates problems with flow control,
+incorrectly encoded T.4 data, incompatibility between local and remote
+equipment etc., but very rarely is caused by the real noise on the line.
+This ``over and out'' behaviour can be activated by ``Giveup'' value.
+There is also third option, not so radical as ``Giveup''. Yes, we will never
+retransmit the page, but we can try to send the next page, and let the
+remote to decide what to do (accept our decision or hang up). Thus one page will
+(or will not) be missed but we have a chance to successfully send all other pages.
+This behaviour can be activated by ``Ignore'' value.
+.TP
.B SendFaxCmd\(dg
The command to use to process outbound facsimile jobs; see
.IR faxsend (1M).
.B Class2SendRTC
Whether or not to append an explicit ``Return To Control'' (\s-1RTC\s+1)
signal to the page data when transmitting.
-The Class 2 spec (i.e. SP-2388-A) states the modem will append
+The Class 2 and Class 2.0 specs (i.e. SP-2388-A and TIA/EIA-592) state
+that the modem will append
.SM RTC
when it receives the post-page message command from the host; this
parameter is provided in case the modem does not correctly implement