]> git.ipfire.org Git - thirdparty/HylaFAX.git/commitdiff
"endless RTN" bug related changes
authorDmitry Bely <dbely@mail.ru>
Sat, 26 Aug 2000 22:08:34 +0000 (22:08 +0000)
committerDmitry Bely <dbely@mail.ru>
Sat, 26 Aug 2000 22:08:34 +0000 (22:08 +0000)
---------------------------------
Reencoding first EOL and removing extra RTC/EOL in the end of TIFF data.
Class2SendRTC now affects Class 2.0.
Class1 RTN algorithm changed.
Class2 RTN algorithm changed.
"RTNHandlingMethod" parameter added.
usr-2.0 template updated to compensate USR's RTC-related firware bug.

config/usr-2.0
faxd/Class1Send.c++
faxd/Class20.c++
faxd/Class2Recv.c++
faxd/Class2Send.c++
faxd/FaxModem.c++
faxd/FaxModem.h
faxd/G3Encoder.c++
faxd/ModemConfig.c++
faxd/ModemConfig.h
man/config.4f

index 51c413654802a828099322683de2a1d83df6fb5a..1f197987101a9acbe24680cea8e0a9309d04f8b3 100644 (file)
@@ -78,3 +78,7 @@ Class2CQQueryCmd:     !(0),(0)        # override modem response
 # 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
index 16663d9015f83003682f2a470844cd2755561434..2ad47fea1ce2e9600f9fe446a45031afefbd2429 100644 (file)
@@ -113,7 +113,7 @@ Class1Modem::getPrologue(Class2Params& params, bool& hasDoc, fxStr& emsg)
            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)); }
@@ -274,6 +274,7 @@ Class1Modem::sendPhaseB(TIFF* tif, Class2Params& next, FaxMachineInfo& info,
            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
@@ -304,22 +305,29 @@ Class1Modem::sendPhaseB(TIFF* tif, Class2Params& next, FaxMachineInfo& info,
                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"
@@ -741,6 +749,12 @@ Class1Modem::sendPage(TIFF* tif, const Class2Params& params, u_int pageChop, fxS
            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
index 5998f8829a27141a70c88983ee91132bf4d7b06a..3c9d016b658eb91cd05b2abb5c3c6235f0ee6545 100644 (file)
@@ -131,7 +131,7 @@ Class20Modem::sendPage(TIFF* tif, u_int pageChop)
     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);
index 3fdb2f55e82cb37775d986d400eddaaf10c803a5..abdb594d62b1bf6ce6391cbd7080f531c72743a9 100644 (file)
@@ -212,6 +212,12 @@ Class2Modem::recvPage(TIFF* tif, int& ppm, fxStr& emsg)
         */
        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
index bc1a05f1899aeacc5b94229baedb86fa01d9e1ee..ac70a2c23e1ec61755fcf41899bf8a48fd7e5e98 100644 (file)
@@ -131,7 +131,7 @@ Class2Modem::getPrologue(Class2Params& dis, bool& hasDoc, fxStr& emsg)
            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)));
@@ -250,6 +250,7 @@ Class2Modem::sendPhaseB(TIFF* tif, Class2Params& next, FaxMachineInfo& info,
                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')
@@ -276,6 +277,15 @@ Class2Modem::sendPhaseB(TIFF* tif, Class2Params& next, FaxMachineInfo& info,
                    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)";
@@ -387,6 +397,12 @@ Class2Modem::sendPageData(TIFF* tif, u_int pageChop)
            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();
index 22e4c09ff05ba5df9a68ce784d1f1b730864cec7..f834b53731a26c8c977a10485b8d2c42adf38d13 100644 (file)
@@ -635,3 +635,189 @@ FaxModem::notifyPageSent(TIFF* tif)
     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;
+}
index b70c4868a8edbbd2756197f2c1bdf7de562adba4..068fc6f4df05b6b4474f124074292f2bf254fcf2 100644 (file)
@@ -40,6 +40,10 @@ class fxStackBuffer;
 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
@@ -81,7 +85,7 @@ private:
     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)
@@ -133,7 +137,18 @@ protected:
     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;
index 41b323b022645c4f8990694f8cfc066bccb2c191..08495bfc6fe725c00ea8a4b6f9dfe17f7125e870 100644 (file)
@@ -71,10 +71,13 @@ void
 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
index 24caedb2d6dd03a574c5d8b77eb3dbff92a43c1a..fd119a156b971e71fe280a2f52bb4772a5cd78df 100644 (file)
@@ -213,6 +213,7 @@ ModemConfig::setupConfig()
     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
@@ -462,6 +463,37 @@ ModemConfig::getDataFormat(const char* cp)
     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
 {
@@ -500,6 +532,8 @@ ModemConfig::setConfigItem(const char* tag, const char* value)
        minSpeed = getSpeed(value);
     else if (streq(tag, "recvdataformat"))
        recvDataFormat = getDataFormat(value);
+    else if (streq(tag, "rtnhandlingmethod"))
+        rtnHandling = getRTNHandling(value);
     else
        return (false);
     return (true);
index 413d1f684aa649d573a8ea107e3f3d61f604bdee..610ae849c0b377c5838b5ab351d8d3a05654535c 100644 (file)
@@ -39,11 +39,13 @@ private:
     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();
 
@@ -170,7 +172,9 @@ public:
     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;
index 507201b85e4fc29d02a1eb5d00b4806f217d07d6..8ba9f96a58ab83215e71f63ad81aaa75aaec276c 100644 (file)
@@ -174,6 +174,7 @@ RingData    string  \-      distinctive ring data call identifier
 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
@@ -304,6 +305,7 @@ Class2PIECmd        string  \s-1AT+FIE=0\s+1        Class 2.0: command to set procedure interru
 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
@@ -975,6 +977,29 @@ See also
 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).
@@ -2091,7 +2116,8 @@ not (necessarily) having byte-aligned codes.
 .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