Hylafax Developers Mailing List Archives

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]

[hylafax-devel] My RTN patch and many other changes/enhancements, third edition :-)



Now against the current CVS!

The following is included:

1. Method FaxModem::correctPhaseCData() to correct broken T.4 data,
stored in TIFF Class F, and t4test utility to test this method is
added (faxd/FaxModem.h, faxd/FaxModem.c++, faxd/G3Encoder.c++,
faxd/t4test.c++) -- RTN patch, part one

2. Class2SendRTC configuration parameter now affects Class2.0
operation (according to the standards DCE has to appends RTC
itself). (faxd/Class20.c++, man/config.4f) -- RTN patch, part two

3. RTNHandlingMethod configuration parameter and the related code
is added. Quote from config.4f:

[---cut---]
RTNHandlingMethod

Specifies how to react to RTN signal, received from the remote;
one of ``Retransmit'', ``Giveup'' and ``Ignore''. ``Retransmit''
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.
[---cut---]

Maybe "Ignore" option should be default?

(faxd/ModemConfig.h, faxd/ModemConfig.c++, faxd/Class1Send.c++,
faxd/Class2Send.c++, man/config.4f) -- RTN patch, part three

4. Class2.0 post-page-handling code corrected. (faxd/Class20.c++)

5. Class1 post-page-handling code corrected. In case of RTN it behaves the
same (correct) way as in Class2/2.0. (faxd/Class1Send.c++)

6. Zyxel & USR template updates. Due to the USR firmware bug parameter
Class2SendRTC: "yes" is neccessary for USR Class 2.0 after applying the
patch. (config/zyxel-1496e, config/zyxel-1496e-2.0, config/zyxel-1,
config/usr-2.0)

7. NSF decoding routines, identifing remote fax equipment and remote
station ID are added. E.g., here is a quote from my log:

[---cut---]
Apr 13 19:56:43.17: [17273]: --> [4:+FCO]
Apr 13 19:56:43.17: [17273]: --> [58:+FNF:00 00 0E 00 00 00 16 0F 01 03 00 10 05 02 95 C8 08   ]
Apr 13 19:56:43.17: [17273]: REMOTE NSF "00 00 0E 00 00 00 16 0F 01 03 00 10 05 02 95 C8 08"
Apr 13 19:56:43.18: [17273]: NSF remote fax equipment: Panasonic KX-F780
Apr 13 19:56:43.18: [17273]: --> [26:+FCI:                     ]
Apr 13 19:56:43.18: [17273]: REMOTE CSI ""
Apr 13 19:56:43.18: [17273]: --> [20:+FIS:1,3,0,2,1,0,0,4]
Apr 13 19:56:43.18: [17273]: --> [2:OK]
[---cut---]

(faxd/NSF.h, faxd/NSF.c++, faxd/FaxModem.h,
faxd/FaxModeM.c++). Contribution to my NSF list is welcome! :-)

8. Default value for ModemResetCmds is now "ATZ". (ModemSoftResetCmd
parameter was never used :-)) (faxd/ModemConfig.c++, man/config.4f)

9. Unused ModemResetCmd parameter has been deleted. (faxd/ModemConfig.c++) 

10. Default value for ModemFlowControl is now XONXOFF -- I think, reasonable
default for the most modems. (faxd/ModemConfig.c++, man/config.4f)

Proposed changes are significant enough, and although I have carefully
tested them, I would like to know your opinion before you commit them :-) I 
also would be quite happy if someone read my changes to config.4f and
correct my broken English, where necessary :-)

I think I have made everything I promised for beta 3. Now it's your turn! :-))

Hope to hear from you soon,
Dmitry

diff -uNr hylafax-cvs/config/usr-2.0 hylafax-work/config/usr-2.0
--- hylafax-cvs/config/usr-2.0	Mon Jan  4 13:19:07 1999
+++ hylafax-work/config/usr-2.0	Sat Apr  1 23:42:27 2000
@@ -78,3 +78,7 @@
 # It is not necessary for the Courier modem.
 #
 Class2NRCmd:    AT+FNR=1,1,1,0
+#
+# Modem violates the Class 2.0 specs and do not send RTC itself
+#
+Class2SendRTC:	yes
diff -uNr hylafax-cvs/config/zyxel-1496e hylafax-work/config/zyxel-1496e
--- hylafax-cvs/config/zyxel-1496e	Tue Oct 13 00:47:48 1998
+++ hylafax-work/config/zyxel-1496e	Thu Apr 13 18:03:30 2000
@@ -66,16 +66,9 @@
 #
 # Additional reset commands:
 #
-# &B1	  DTE-DCE rate is fixed at DTE setting
-# &N0	  Auto-negotiate highest possible DCE-DCE link rate
-# &S0	  DSR always on
-# *F0	  Deny remote configuration
-# S18=2	  Receive at 38400
-# S38.3=1 DCD on/off sequence follows UNIX standard; also
-#	  fix receiving baud rate at S18 value
-# S39=0	  (avoid Class 2 compatibility hacks)
+# S38.3=1 DCD on/off sequence follows UNIX standard
 # 
-ModemResetCmds:		AT&B1&N0&S0*F0S18=2S38.3=1S39=0
+ModemResetCmds:		ATZS38.3=1
 #
 # We normally append the "@" symbol to the dial string so that
 # the modem will wait 5 seconds before attempting to connect
@@ -106,8 +99,17 @@
 #
 PagerSetupCmds:		AT&K0&N15	# use V.22 at 1200 bps (PageNet)
 #
-# Rev 6.1x firmware have a bug in the ECM support so
-# explicitly disable it's use.  To re-enable its use just
-# comment out the following line.
+# ECM is not implemented in U1496 despite AT+FCC=? says
 #
 Class2DCCQueryCmd:	"!(0,1),(0-5),(0-4),(0-2),(0,1),(0),(0),(0-7)"
+#                                                          
+# Quality Checking is not implemented in U1496 despite AT+FCQ=? says
+#                                                          
+Class2CQQueryCmd:       "!(0),(0)"
+#
+# Disable unnecessary, not implemented and possibly dangerous commands
+#
+Class2BORCmd:           "AT"
+Class2PIECmd:           "AT"                               
+Class2PHCTOCmd:         "AT"                               
+Class2TBCCmd:           "AT"                               
diff -uNr hylafax-cvs/config/zyxel-1496e-1 hylafax-work/config/zyxel-1496e-1
--- hylafax-cvs/config/zyxel-1496e-1	Tue Oct 13 00:47:48 1998
+++ hylafax-work/config/zyxel-1496e-1	Thu Apr 13 18:04:58 2000
@@ -55,13 +55,9 @@
 #
 # Additional reset commands:
 #
-# &B1	  DTE-DCE rate is fixed at DTE setting
-# &N0	  Auto-negotiate highest possible DCE-DCE link rate
-# &S0	  DSR always on
-# *F0	  Deny remote configuration
 # S38.3=1 DCD on/off sequence follows UNIX standard
 # 
-ModemResetCmds:		AT&B1&N0&S0*F0S38.3=1
+ModemResetCmds:		ATZS38.3=1
 #
 # We normally append the "@" symbol to the dial string so that
 # the modem will wait 5 seconds before attempting to connect
diff -uNr hylafax-cvs/config/zyxel-1496e-2.0 hylafax-work/config/zyxel-1496e-2.0
--- hylafax-cvs/config/zyxel-1496e-2.0	Tue Oct 13 00:47:48 1998
+++ hylafax-work/config/zyxel-1496e-2.0	Thu Apr 13 18:03:51 2000
@@ -61,16 +61,9 @@
 #
 # Additional reset commands:
 #
-# &B1	  DTE-DCE rate is fixed at DTE setting
-# &N0	  Auto-negotiate highest possible DCE-DCE link rate
-# &S0	  DSR always on
-# *F0	  Deny remote configuration
-# S18=2	  Receive at 38400
-# S38.3=1 DCD on/off sequence follows UNIX standard; also
-#	  fix receiving baud rate at S18 value
-# S39=0	  (avoid Class 2 compatibility hacks)
+# S38.3=1 DCD on/off sequence follows UNIX standard
 # 
-ModemResetCmds:		AT&B1&N0&S0*F0S18=2S38.3=1S39=0
+ModemResetCmds:		ATZS38.3=1
 #
 ModemDialCmd:		ATDT%s		# no '@' 'cuz then busy not recognized
 NoCarrierRetrys:	3		# retry 3 times on no carrier
@@ -85,8 +78,17 @@
 #
 PagerSetupCmds:		AT&K0&N15	# use V.22 at 1200 bps (PageNet)
 #
-# Rev 6.1x firmware have a bug in the ECM support so
-# explicitly disable it's use.  To re-enable its use just
-# comment out the following line.
+# ECM is not implemented in U1496 despite AT+FCC=? says
 #
 Class2DCCQueryCmd:	"!(0,1),(0-5),(0-4),(0-2),(0,1),(0),(0),(0-7)"
+#                                                          
+# Quality Checking is not implemented in U1496 despite AT+FCQ=? says
+#                                                          
+Class2CQQueryCmd:       "!(0),(0)"
+#
+# Disable unnecessary, not implemented and possibly dangerous commands
+#
+Class2BORCmd:           "AT"
+Class2PIECmd:           "AT"                               
+Class2PHCTOCmd:         "AT"                               
+Class2TBCCmd:           "AT"                               
diff -uNr hylafax-cvs/faxd/Class1Send.c++ hylafax-work/faxd/Class1Send.c++
--- hylafax-cvs/faxd/Class1Send.c++	Sun Jun 13 11:41:00 1999
+++ hylafax-work/faxd/Class1Send.c++	Wed Apr 12 20:35:03 2000
@@ -115,6 +115,7 @@
 	    do {
 		switch (frame.getRawFCF()) {
 		case FCF_NSF:
+                    recvNSF(NSF(frame.getFrameData(), frame.getFrameDataLength(), frameRev));
 		    break;
 		case FCF_CSI:
 		    { fxStr csi; recvCSI(decodeTSI(csi, frame)); }
@@ -275,6 +276,7 @@
 	    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
@@ -305,22 +307,29 @@
 		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"
@@ -733,6 +742,12 @@
 	    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
diff -uNr hylafax-cvs/faxd/Class20.c++ hylafax-work/faxd/Class20.c++
--- hylafax-cvs/faxd/Class20.c++	Sun Jun 13 11:41:01 1999
+++ hylafax-work/faxd/Class20.c++	Thu Apr 13 15:49:15 2000
@@ -131,7 +131,7 @@
     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);
@@ -162,12 +162,24 @@
 	    case AT_FHNG:
 		if (!isNormalHangup())
 		    return (false);
-		/* fall thru... */
-	    case AT_OK:				// page data good
-		ppr = PPR_MCF;			// could be PPR_RTP/PPR_PIP
+		ppr = PPR_MCF;
 		return (true);
-	    case AT_ERROR:			// page data bad
-		ppr = PPR_RTN;			// could be PPR_PIN
+	    case AT_OK:
+	    case AT_ERROR:
+		/*
+		 * Despite of the (wrong) comment above,
+		 * we do explicit status query e.g. to
+		 * distinguish between RTN and PIN 
+		 */
+		{
+		    fxStr s;
+		    if(!atQuery("AT+FPS?", s) ||
+		       sscanf(s, "%u", &ppr) != 1){
+		        protoTrace("MODEM protocol botch (\"%s\"), %s",
+		    		   (const char*)s, "can not parse PPR");
+		        return (false);		// force termination
+		    }
+		}
 		return (true);
 	    case AT_EMPTYLINE:
 	    case AT_TIMEOUT:
diff -uNr hylafax-cvs/faxd/Class2Recv.c++ hylafax-work/faxd/Class2Recv.c++
--- hylafax-cvs/faxd/Class2Recv.c++	Sun Jun 13 11:41:01 1999
+++ hylafax-work/faxd/Class2Recv.c++	Wed Apr 12 19:57:20 2000
@@ -212,6 +212,12 @@
 	 */
 	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
diff -uNr hylafax-cvs/faxd/Class2Send.c++ hylafax-work/faxd/Class2Send.c++
--- hylafax-cvs/faxd/Class2Send.c++	Sun Jun 13 11:41:01 1999
+++ hylafax-work/faxd/Class2Send.c++	Wed Apr 12 19:17:04 2000
@@ -131,7 +131,7 @@
 	    gotParams = parseClass2Capabilities(skipStatus(rbuf), dis);
 	    break;
 	case AT_FNSF:
-	    recvNSF(skipStatus(rbuf));
+	    recvNSF(NSF(skipStatus(rbuf)));
 	    break;
 	case AT_FCSI:
 	    recvCSI(stripQuotes(skipStatus(rbuf)));
@@ -250,6 +250,7 @@
 		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 @@
 		    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 @@
 	    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();
diff -uNr hylafax-cvs/faxd/ClassModem.c++ hylafax-work/faxd/ClassModem.c++
--- hylafax-cvs/faxd/ClassModem.c++	Sun Jun 13 11:41:02 1999
+++ hylafax-work/faxd/ClassModem.c++	Thu Apr 13 18:21:17 2000
@@ -131,6 +131,8 @@
     const fxStr& flow = conf.getFlowCmd(conf.flowControl);
     resetCmds = "AT"
 	      | stripAT(conf.resetCmds)		// prepend to insure our needs
+              | "\nAT"                          // some modems do not allow any command
+                                                // after ATZ 
 	      | stripAT(conf.echoOffCmd)
 	      | stripAT(conf.verboseResultsCmd)
 	      | stripAT(conf.resultCodesCmd)
diff -uNr hylafax-cvs/faxd/FaxModem.c++ hylafax-work/faxd/FaxModem.c++
--- hylafax-cvs/faxd/FaxModem.c++	Sun Jun 13 11:41:03 1999
+++ hylafax-work/faxd/FaxModem.c++	Thu Apr 13 19:34:29 2000
@@ -164,16 +164,24 @@
 	return (false);
 }
 void
-FaxModem::recvNSF(const fxStr& s)
+FaxModem::recvNSF( const NSF& aNsf )
 {
-    nsf = s;
-    protoTrace("REMOTE NSF \"%s\"", (const char*) nsf);
+    nsf = aNsf;
+    optFrames |= 0x8;
+    protoTrace("REMOTE NSF \"%s\"", (const char*) nsf.getHexNsf() );
+    protoTrace("NSF remote fax equipment: %s %s", 
+               (const char*)nsf.getVendor(),
+               (const char*)nsf.getModel());
+    if( nsf.stationIdFound() )
+        protoTrace("NSF %sremote station ID: \"%s\"",
+                   nsf.vendorFound()? "": "possible ",
+                   nsf.getStationId());
 }
 bool
-FaxModem::getSendNSF(fxStr& s)
+FaxModem::getSendNSF(NSF& aNsf)
 {
     if (optFrames & 0x8) {
-	s = nsf;
+	aNsf = nsf;
 	return (true);
     } else
 	return (false);
@@ -623,4 +631,187 @@
 {
     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
+
+    uint16*     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 uint16[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();
+            }
+        }
+    }
+    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;
 }
diff -uNr hylafax-cvs/faxd/FaxModem.h hylafax-work/faxd/FaxModem.h
--- hylafax-cvs/faxd/FaxModem.h	Sun Jun 13 11:41:03 1999
+++ hylafax-work/faxd/FaxModem.h	Tue Apr 11 21:39:12 2000
@@ -33,12 +33,17 @@
 #include "tiffio.h"
 #include "G3Decoder.h"
 #include "FaxSendStatus.h"
+#include "NSF.h"
 
 class FaxMachineInfo;
 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
@@ -80,7 +85,7 @@
     fxStr	tsi;		// received TSI/CSI
     fxStr	sub;		// received subaddressing string
     fxStr	pwd;		// received password string
-    fxStr	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)
@@ -108,7 +113,7 @@
     void	recvTSI(const fxStr&);
     void	recvPWD(const fxStr&);
     void	recvSUB(const fxStr&);
-    void	recvNSF(const fxStr&);
+    void	recvNSF(const NSF&);
     void	recvCSI(const fxStr&);
     void	recvDCS(const Class2Params&);
     void	recvSetupTIFF(TIFF* tif, long group3opts, int fillOrder);
@@ -132,7 +137,18 @@
     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 {
+        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;
@@ -196,7 +212,7 @@
     virtual void sendAbort() = 0;
     // query interfaces for optional state
     virtual bool getSendCSI(fxStr&);
-    virtual bool getSendNSF(fxStr&);
+    virtual bool getSendNSF(NSF&);
 
     /*
      * Fax receive protocol.  The expected sequence is:
diff -uNr hylafax-cvs/faxd/G3Encoder.c++ hylafax-work/faxd/G3Encoder.c++
--- hylafax-cvs/faxd/G3Encoder.c++	Sun Jun 13 11:41:03 1999
+++ hylafax-work/faxd/G3Encoder.c++	Mon Mar 27 13:19:51 2000
@@ -71,10 +71,13 @@
 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
diff -uNr hylafax-cvs/faxd/Makefile.in hylafax-work/faxd/Makefile.in
--- hylafax-cvs/faxd/Makefile.in	Thu Sep  2 14:16:22 1999
+++ hylafax-work/faxd/Makefile.in	Wed Apr 12 22:16:16 2000
@@ -76,6 +76,7 @@
 	HDLCFrame.c++ \
 	Job.c++ \
 	Modem.c++ \
+	NSF.c++ \
 	ModemConfig.c++ \
 	PCFFont.c++ \
 	QLink.c++ \
@@ -91,6 +92,7 @@
 	faxSendApp.c++ \
 	tagtest.c++ \
 	tsitest.c++ \
+	t4test.c++ \
 	pageSendApp.c++
 MODEM_OBJS=ClassModem.o \
 	FaxModem.o \
@@ -110,6 +112,7 @@
 	G3Encoder.o \
 	HDLCFrame.o \
 	ModemConfig.o \
+	NSF.o \
 	FaxFont.o \
 	PCFFont.o \
 	TagLine.o
@@ -143,7 +146,7 @@
 FAXGETTYOBJS= Getty.o Getty@GETTY@.o faxGettyApp.o
 TARGETS=libfaxserver.${DSO} \
 	faxq faxsend faxgetty pagesend faxqclean \
-	tsitest tagtest cqtest choptest
+	tsitest tagtest cqtest choptest t4test
 
 default all::
 	@${MAKE} incdepend
@@ -193,10 +196,12 @@
 	${C++F} -o $@ tsitest.o libfaxserver.${DSO} ${LDFLAGS}
 trigtest: trigtest.o libfaxserver.${DSO} ${LIBS}
 	${C++F} -o $@ trigtest.o libfaxserver.${DSO} ${LDFLAGS}
+t4test: t4test.o libfaxserver.${DSO} ${LIBS}
+	${C++F} -o $@ t4test.o libfaxserver.${DSO} ${LDFLAGS}
 
 PUTSERV=${INSTALL} -idb ${PRODUCT}.sw.server
 
 install: default
 	${PUTSERV} -F ${SBIN} -m 755 -O faxq faxqclean
 	${PUTSERV} -F ${LIBEXEC} -m 755 -O faxgetty faxsend pagesend
-	${PUTSERV} -F ${SBIN} -m 755 -O tsitest tagtest cqtest choptest
+	${PUTSERV} -F ${SBIN} -m 755 -O tsitest tagtest cqtest choptest t4test
diff -uNr hylafax-cvs/faxd/ModemConfig.c++ hylafax-work/faxd/ModemConfig.c++
--- hylafax-cvs/faxd/ModemConfig.c++	Mon Aug 23 15:19:33 1999
+++ hylafax-work/faxd/ModemConfig.c++	Thu Apr 13 17:50:20 2000
@@ -80,8 +80,7 @@
 { "modemanswerfaxbegincmd",	&ModemConfig::answerFaxBeginCmd },
 { "modemanswerdatabegincmd",	&ModemConfig::answerDataBeginCmd },
 { "modemanswervoicebegincmd",	&ModemConfig::answerVoiceBeginCmd },
-{ "modemresetcmds",		&ModemConfig::resetCmds },
-{ "modemresetcmd",		&ModemConfig::resetCmds },
+{ "modemresetcmds",		&ModemConfig::resetCmds,        "ATZ" },
 { "modemdialcmd",		&ModemConfig::dialCmd,		"ATDT%s" },
 { "modemnoflowcmd",		&ModemConfig::noFlowCmd },
 { "modemsoftflowcmd",		&ModemConfig::softFlowCmd },
@@ -206,7 +205,7 @@
     for (i = N(numbers)-1; i >= 0; i--)
 	(*this).*numbers[i].p = numbers[i].def;
 
-    flowControl		= ClassModem::FLOW_NONE;// no flow control
+    flowControl		= ClassModem::FLOW_XONXOFF;// software flow control
     maxRate		= ClassModem::BR19200;	// reasonable for most modems
     minSpeed		= BR_2400;		// minimum transmit speed
     waitForConnect	= false;		// unique modem answer response
@@ -214,6 +213,7 @@
     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
@@ -463,6 +463,37 @@
     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
 {
@@ -501,6 +532,8 @@
 	minSpeed = getSpeed(value);
     else if (streq(tag, "recvdataformat"))
 	recvDataFormat = getDataFormat(value);
+    else if (streq(tag, "rtnhandlingmethod"))
+	rtnHandling = getRTNHandling(value);
     else
 	return (false);
     return (true);
diff -uNr hylafax-cvs/faxd/ModemConfig.h hylafax-work/faxd/ModemConfig.h
--- hylafax-cvs/faxd/ModemConfig.h	Sun Jun 13 11:41:04 1999
+++ hylafax-work/faxd/ModemConfig.h	Tue Apr 11 21:39:08 2000
@@ -39,11 +39,13 @@
     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,6 +172,8 @@
     fxStr	tagLineFontFile;	// font file for imaging tag lines
     u_int	recvDataFormat;		// received facsimile data format
 
+    RTNHandling rtnHandling;            // RTN signal handling method
+    
     virtual ~ModemConfig();
 
     void parseCID(const char*, CallerID&) const;
diff -uNr hylafax-cvs/faxd/NSF.c++ hylafax-work/faxd/NSF.c++
--- hylafax-cvs/faxd/NSF.c++	Thu Jan  1 03:00:00 1970
+++ hylafax-work/faxd/NSF.c++	Thu Apr 13 19:35:27 2000
@@ -0,0 +1,209 @@
+/* 
+ * This file does not exist in the original Hylafax distribution.
+ * 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.
+ */
+
+#include <ctype.h>
+#include "NSF.h"
+
+struct ModelData 
+{
+    const char* modelId;
+    const char* modelName;
+};
+
+struct NSFData {
+    const char* vendorId;
+    static
+    const int   vendorIdSize = 3; // Country & provider code (T.35)
+    const char* vendorName;
+    bool        inverseStationIdOrder;
+    int         modelIdPos;
+    int         modelIdSize;
+    const ModelData* knownModels;
+};
+
+static const ModelData Panasonic0E[] =
+{{"\x00\x00\x00\x96", "KX-F90" },
+ {"\x00\x00\x00\x16", "KX-F780" },
+ {NULL}};
+
+static const ModelData Panasonic79[] =
+{{"\x00\x00\x00\x02", "UF-S10" },
+ {NULL}};
+
+static const ModelData Samsung8C[] =
+{{"\x00\x00\x01\x00", "SF-2010" },
+ {NULL}};
+
+static const NSFData KnownNSF[] =
+{
+    {"\x00\x00\x09", NULL,        false },
+    {"\x00\x00\x0E", "Panasonic", false, 3, 4, Panasonic0E },
+    {"\x00\x00\x11", "Canon",     false },
+    {"\x00\x00\x19", "Xerox",     true  },
+    {"\x00\x00\x21", "Lanier?",   true  },
+    {"\x00\x00\x25", "Ricoh",     true  },
+    {"\x00\x00\x26", NULL,        false },
+    {"\x00\x00\x45", "Muratec",   false },
+    {"\x00\x00\x51", "Sanyo",     false },
+    {"\x00\x00\x66", "UTAX",      true  },
+    {"\x00\x00\x79", "Panasonic", false, 3, 4, Panasonic79 },
+    {"\x20\x41\x59", "Siemens",   false },
+    {"\x59\x59\x01", NULL,        false },
+    {"\x86\x00\x8C", "Samsung",   false, 3, 4, Samsung8C },
+    {"\x86\x00\x98", "Samsung",   false },
+    {"\xAD\x00\x36", "HP",        false },
+    {"\xB5\x00\x2E", "Delrina",   false },
+    {NULL}
+};
+
+NSF::NSF()
+{
+    clear();
+}
+
+
+NSF::NSF( const char* hexNSF )
+{
+    clear();
+    loadHexData( hexNSF );
+    decode();
+}
+
+NSF::NSF( const u_char* rawData, int size, const u_char* revTab )
+{
+    clear();
+    loadRawData( rawData, size, revTab );
+    decode();
+}
+
+void NSF::clear()
+{
+    nsf.resize(0);
+    hexNsf.resize(0);
+    vendor = "unknown";
+    model = "";
+    stationId = "";
+    vendorDecoded = false;
+    stationIdDecoded = false;
+}
+
+void NSF::loadHexData( const char* hexNSF )
+{
+    hexNsf.append( hexNSF );
+    const char *p = hexNSF;
+    char *pNext = NULL;
+    for( ;; ){
+        int val = strtol( p, &pNext, 16 );
+        if( pNext == p )
+            break;
+        p = pNext;
+        nsf.append( (unsigned char)val );
+    }
+}
+
+void NSF::loadRawData( const u_char* rawData, int size, const u_char* revTab )
+{
+    nsf.append( (const char*)rawData, size );
+    u_char *p   = (u_char*)(const char*)nsf;
+    u_char *end = p+size;
+    for( ; p < end; p++ ){
+        *p = revTab[*p];
+        char buf[10];
+        sprintf( buf, "%02X ", *p );
+        hexNsf.append(buf);
+    }
+    // remove trailing space
+    hexNsf.resize( hexNsf.length() - 1 );
+}
+
+void NSF::decode()
+{
+    for( const NSFData* p = KnownNSF; p->vendorId; p++ ){
+        if( !memcmp( p->vendorId, &nsf[0], p->vendorIdSize ) ){
+            vendor = p->vendorName;
+            if( p->knownModels ){
+                for( const ModelData* pp = p->knownModels; pp->modelId; pp++ )
+                    if( !memcmp( pp->modelId, &nsf[p->modelIdPos], p->modelIdSize ) )
+                        model = pp->modelName;
+            }
+            findStationId( p->inverseStationIdOrder );
+            vendorDecoded = true;
+        }
+    }
+    if( !vendorFound() )
+	findStationId( 0 );
+}
+
+void NSF::findStationId( bool reverseOrder )
+{
+    const char* id = NULL;
+    u_int       idSize = 0;
+    const char* maxId = NULL;
+    u_int       maxIdSize = 0;
+    /*
+     * Trying to find the longest printable ASCII sequence
+     */
+    for( const char *p = (const char*)nsf, *end = p + nsf.length(); p < end; p++ ){
+        if( isprint(*p) ){
+            if( !idSize++ ) 
+                id = p;
+            if( idSize > maxIdSize ){
+                maxId = id;
+                maxIdSize = idSize;
+            }
+        }
+        else {
+            id = NULL;
+            idSize = 0;
+        }
+    }
+    
+    /*
+     * Minimum acceptable id length
+     */
+    const u_int MinIdSize = 4;
+    if( maxIdSize >= MinIdSize ){
+        stationId.resize(0);
+        const char* p;
+        int dir;
+        if( reverseOrder ){
+            p   = maxId + maxIdSize - 1;
+            dir = -1;
+        }
+        else {
+            p   = maxId;
+            dir = 1;
+        }
+        for( u_int i = 0; i < maxIdSize; i++ ){
+            stationId.append( *p );
+            p += dir;
+        }
+        stationIdDecoded = true;
+    }
+}
diff -uNr hylafax-cvs/faxd/NSF.h hylafax-work/faxd/NSF.h
--- hylafax-cvs/faxd/NSF.h	Thu Jan  1 03:00:00 1970
+++ hylafax-work/faxd/NSF.h	Thu Apr 13 19:42:12 2000
@@ -0,0 +1,66 @@
+/* 
+ * This file does not exist in the original Hylafax distribution.
+ * 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.
+ */
+
+#ifndef NSF_H
+#define NSF_H
+
+#include "Str.h"
+
+class NSF {
+    fxStr nsf;
+    fxStr hexNsf;
+    fxStr vendor;
+    fxStr model;
+    fxStr stationId;
+    bool  vendorDecoded;
+    bool  stationIdDecoded;
+public:
+    NSF();
+    NSF( const char* hexNSF );
+    NSF( const u_char* rawData, int size, const u_char* revTab );
+    /*
+     * We are happy with default copy constructor and copy assignment,
+     * so do not explicitly define them (but will use)
+     */
+    const fxStr& getRawNsf(){ return nsf; }
+    const fxStr& getHexNsf(){ return hexNsf; }
+    bool  vendorFound(){ return vendorDecoded; }
+    bool  stationIdFound(){ return stationIdDecoded; }
+    const char* getVendor(){ return (const char*)vendor; }
+    const char* getModel(){ return (const char*)model; }
+    const char* getStationId(){ return (const char*)stationId; }
+private:
+    void clear();
+    void loadHexData( const char* hexNSF );
+    void loadRawData( const u_char* rawData, int size, const u_char* revTab );
+    void findStationId( bool reverseOrder );
+    void decode();
+};
+
+#endif /* NSF_H */
diff -uNr hylafax-cvs/faxd/t4test.c++ hylafax-work/faxd/t4test.c++
--- hylafax-cvs/faxd/t4test.c++	Thu Jan  1 03:00:00 1970
+++ hylafax-work/faxd/t4test.c++	Thu Apr 13 16:24:30 2000
@@ -0,0 +1,405 @@
+/* 
+ * This file does not exist in the original Hylafax distribution.
+ * Created by Dmitry Bely, March 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.
+ */
+/*
+ * Program for testing T.4 data correction routines
+ *
+ * Usage: t4test [-o output.tif] input.tif
+ */
+#include "Sys.h"
+
+#include "G3Decoder.h"
+#include "G3Encoder.h"
+#include "StackBuffer.h"
+#include "tiffio.h"
+#include "Class2Params.h"
+#if HAS_LOCALE
+extern "C" {
+#include <locale.h>
+}
+#endif
+
+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
+
+    uint16*     runs;
+    u_char*     rowBuf;
+
+    int		decodeNextByte();
+
+    void	invalidCode(const char* type, int x);
+    void	badPixelCount(const char* type, int got, int expected);
+    void	badDecodingState(const char* type, int x);
+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 uint16[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++);
+}
+void
+MemoryDecoder::invalidCode(const char* type, int x)
+{
+    printf("Invalid %s code word, x %d\n", type, x);
+}
+void
+MemoryDecoder::badPixelCount(const char* type, int got, int expected)
+{
+    printf("Bad %s pixel count, got %d, expected %d\n",
+	type, got, expected);
+}
+void
+MemoryDecoder::badDecodingState(const char* type, int x)
+{
+    printf("Panic, bad %s decoding state, x %d\n", type, x);
+}
+
+#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()
+{
+    printf( "fixFirstEOL()\n" );
+    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);
+            printf( "Ok\n" );
+        }
+    }
+}
+
+
+/*
+ * 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()
+{
+    printf( "cutExtraRTC()\n" );
+    printf("initial size = %ul\n", cc );
+    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;
+        printf( "endOfData = %p\n", endOfData );
+        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();
+                printf( "endOfData = %p\n", endOfData );
+            }
+        }
+    }
+    
+    printf("endOfData = %p, size after RTC cut = %ul\n", endOfData, endOfData-start );
+    return endOfData;
+}
+
+void
+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;
+}
+
+void
+vlogError(const char* fmt, va_list ap)
+{
+    vfprintf(stderr, fmt, ap);
+    fputs(".\n", stderr);
+}
+
+// NB: must duplicate this to avoid pulling faxApp&co.
+
+extern "C" void
+_fxassert(const char* msg, const char* file, int line)
+{
+    fprintf(stderr, "Assertion failed \"%s\", file \"%s\" line %d.\n", 
+	msg, file, line);
+    abort();
+    /*NOTREACHED*/
+}
+
+const char* appName;
+
+void
+usage()
+{
+    fprintf(stderr,
+	"usage: %s [-m format] [-o t.tif] [-f font.pcf] input.tif\n",
+	appName);
+    exit(-1);
+}
+
+void
+fatal(const char* fmt ...)
+{
+    fprintf(stderr, "%s: ", appName);
+    va_list ap;
+    va_start(ap, fmt);
+    vfprintf(stderr, fmt, ap);
+    va_end(ap);
+    fputs(".\n", stderr);
+    exit(-1);
+}
+
+int
+main(int argc, char* argv[])
+{
+    extern int optind, opterr;
+    extern char* optarg;
+    int c;
+    const char* output = "t.tif";
+
+#ifdef LC_CTYPE
+    setlocale(LC_CTYPE, "");			// for <ctype.h> calls
+#endif
+#ifdef LC_TIME
+    setlocale(LC_TIME, "");			// for strftime calls
+#endif
+    appName = argv[0];
+    while ((c = getopt(argc, argv, "o:")) != -1)
+	switch (c) {
+	case 'o':
+	    output = optarg;
+	    break;
+	case '?':
+	    usage();
+	    /*NOTREACHED*/
+	}
+    if (argc - optind != 1)
+	usage();
+    TIFF* tif = TIFFOpen(argv[optind], "r");
+    if (!tif)
+	fatal("%s: Cannot open, or not a TIFF file", argv[optind]);
+    uint16 comp;
+    TIFFGetField(tif, TIFFTAG_COMPRESSION, &comp);
+    if (comp != COMPRESSION_CCITTFAX3)
+	fatal("%s: Not a Group 3-encoded TIFF file", argv[optind]);
+
+    TIFF* otif = TIFFOpen(output, "w");
+    if (!otif)
+	fatal("%s: Cannot create output file", output);
+    TIFFSetDirectory(tif, 0);
+
+    Class2Params params;
+    params.vr = VR_NORMAL;
+    params.wd = WD_1728;
+    params.ln = LN_INF;
+    params.df = DF_1DMR;
+
+    do {
+	TIFFSetField(otif, TIFFTAG_SUBFILETYPE, FILETYPE_PAGE);
+	TIFFSetField(otif, TIFFTAG_IMAGEWIDTH, 1728);
+	uint32 l;
+	TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &l);
+	params.vr = (l < 1500 ? VR_NORMAL : VR_FINE);
+	TIFFSetField(otif, TIFFTAG_XRESOLUTION, 204.);
+	TIFFSetField(otif, TIFFTAG_YRESOLUTION, (float) params.verticalRes());
+	TIFFSetField(otif, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH);
+	TIFFSetField(otif, TIFFTAG_IMAGELENGTH, l);
+	TIFFSetField(otif, TIFFTAG_BITSPERSAMPLE, 1);
+	TIFFSetField(otif, TIFFTAG_SAMPLESPERPIXEL, 1);
+	TIFFSetField(otif, TIFFTAG_FILLORDER, FILLORDER_LSB2MSB);
+	TIFFSetField(otif, TIFFTAG_COMPRESSION, COMPRESSION_CCITTFAX3);
+	TIFFSetField(otif, TIFFTAG_FILLORDER, FILLORDER_LSB2MSB);
+	uint32 r;
+	TIFFGetFieldDefaulted(tif, TIFFTAG_ROWSPERSTRIP, &r);
+        /*
+	 * output all data in a single strip
+	 */
+	TIFFSetField(otif, TIFFTAG_ROWSPERSTRIP, uint32(-1));
+	TIFFSetField(otif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
+	TIFFSetField(otif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISWHITE);
+	uint32 opts = 0;
+	TIFFGetField(tif, TIFFTAG_GROUP3OPTIONS, &opts);
+	params.df = (opts & GROUP3OPT_2DENCODING) ? DF_2DMR : DF_1DMR;
+	TIFFSetField(otif, TIFFTAG_GROUP3OPTIONS, opts);
+	uint16 o;
+	if (TIFFGetField(otif, TIFFTAG_ORIENTATION, &o))
+	    TIFFSetField(tif, TIFFTAG_ORIENTATION, o);
+
+	uint16 fillorder;
+	TIFFGetFieldDefaulted(tif, TIFFTAG_FILLORDER, &fillorder);
+	/*
+	 * Calculate total amount of space needed to read
+	 * the image into memory (in its encoded format).
+	 */
+	uint32* stripbytecount;
+	(void) TIFFGetField(tif, TIFFTAG_STRIPBYTECOUNTS, &stripbytecount);
+	tstrip_t nstrips = TIFFNumberOfStrips(tif);
+	tstrip_t strip;
+	uint32 totdata = 0;
+	for (strip = 0; strip < nstrips; strip++)
+	    totdata += stripbytecount[strip];
+	/*
+	 * Read the image into memory.
+	 */
+	u_char* data = new u_char[totdata];
+	u_int off = 0;			// skip tag line slop area
+	for (strip = 0; strip < nstrips; strip++) {
+	    uint32 sbc = stripbytecount[strip];
+	    if (sbc > 0 && TIFFReadRawStrip(tif, strip, data+off, sbc) >= 0)
+		off += (u_int) sbc;
+	}
+
+        correctPhaseCData( data, &totdata, fillorder, params );
+
+        /*
+	 * write result as a single strip to
+	 * the output file
+	 */
+        if (fillorder != FILLORDER_LSB2MSB)
+            TIFFReverseBits(data, totdata);
+        if (TIFFWriteRawStrip(otif, 0, data, totdata) == -1)
+            fatal("%s: Write error at strip %u, writing %lu bytes", 
+                  output, strip, (u_long) totdata);
+        delete data;
+    } while (TIFFReadDirectory(tif) && TIFFWriteDirectory(otif));
+    TIFFClose(otif);
+    return (0);
+}
diff -uNr hylafax-cvs/man/config.4f hylafax-work/man/config.4f
--- hylafax-cvs/man/config.4f	Wed Aug 18 13:35:11 1999
+++ hylafax-work/man/config.4f	Thu Apr 13 19:05:14 2000
@@ -174,6 +174,7 @@
 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
@@ -207,7 +208,7 @@
 ModemDialCmd	string	\s-1ATDT%s\s+1	command for dialing (%s for number to dial)
 ModemDialResponseTimeout	integer	\s-1180000\s+1	dialing command timeout (ms)
 ModemEchoOffCmd	string	\s-1ATE0\s+1	command for disabling command echo
-ModemFlowControl	string	\s-1None\s+1	\s-1DTE-DCE\s+1 flow control scheme
+ModemFlowControl	string	\s-1XONXOFF\s+1	\s-1DTE-DCE\s+1 flow control scheme
 ModemFrameFillOrder	string	\s-1LSB2MSB\s+1	bit order for \s-1HDLC\s+1 frames
 ModemHardFlowCmd	string	\-	command for setting hardware flow control between \s-1DTE\s+1 and \s-1DCE\s+1
 ModemMinSpeed	string	\s-12400\s+1	minimum acceptable transmit speed
@@ -220,7 +221,7 @@
 ModemPageStartTimeout	integer	\s-1180000\s+1	page send/receive timeout (ms)
 ModemRate	integer	\s-119200\s+1	baud rate to use for \s-1DCE-DTE\s+1 communication
 ModemRecvFillOrder	string	\s-1LSB2MSB\s+1	bit order for received facsimile data
-ModemResetCmds	string	\-	additional commands when resetting modem
+ModemResetCmds	string	\s-1ATZ\s+1	additional commands when resetting modem
 ModemResetDelay	integer	\s-12600\s+1	delay (ms) after sending modem reset commands
 ModemResultCodesCmd	string	\s-1ATQ0\s+1	command for enabling result codes
 ModemRevQueryCmd	string	\s-1\fIsee below\fP\s+1	command for querying modem firmware revision
@@ -304,6 +305,7 @@
 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 @@
 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).
@@ -1511,6 +1536,11 @@
 communication.
 However, beware that many modems only support software
 flow control when sending or receiving facsimile.
+.IP
+Note that modems usually support software flow control even
+if they have no explicit AT-command to activate it; in this case
+it is switched on when the modem enters fax mode, having
+AT+FCLASS=... from \s-1DTE\s+1.
 .TP
 .B ModemFrameFillOrder
 The bit order to expect for received
@@ -1630,9 +1660,7 @@
 .SM DTR
 handling, etc. should be specified through the appropriate
 configuration parameters and not through this parameter.
-In addition the soft reset command (usually ``\s-1ATZ\s+1'')
-should not be included in this string; the servers issue this
-command explicitly.
+Recommended default value is ``\s-1ATZ\s+1''. 
 .TP
 .B ModemResetDelay
 The time, in milliseconds, to pause after resetting a modem
@@ -1730,7 +1758,7 @@
 .SM DCE.
 .TP
 .B ModemSoftResetCmd
-The command to force a soft reset of the modem.
+The command to force a soft reset of the modem (currenty is not used).
 .TP
 .B ModemType
 This parameter must be set to one of: ``Class2'', ``Class2.0'',
@@ -2091,7 +2119,8 @@
 .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



Home
Report any problems to webmaster@hylafax.org

HylaFAX is a trademark of Silicon Graphics Corporation.
Internet connectivity for hylafax.org is provided by:
VirtuALL Private Host Services