Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package hylafax+ for openSUSE:Factory 
checked in at 2025-10-06 18:08:24
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/hylafax+ (Old)
 and      /work/SRC/openSUSE:Factory/.hylafax+.new.11973 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "hylafax+"

Mon Oct  6 18:08:24 2025 rev:50 rq:1309185 version:7.0.11

Changes:
--------
--- /work/SRC/openSUSE:Factory/hylafax+/hylafax+.changes        2025-02-03 
21:45:06.942449870 +0100
+++ /work/SRC/openSUSE:Factory/.hylafax+.new.11973/hylafax+.changes     
2025-10-06 18:08:59.261436625 +0200
@@ -1,0 +2,18 @@
+Sat Oct  4 07:55:48 UTC 2025 - Axel Braun <[email protected]>
+
+- version 7.0.11
+  * create seqf files with expected 0644 permissions (18 Apr 2025)
+  * correct skipped page counting for proxied jobs (14 Apr 2025)
+  * remove unnecessary switching pause on outbound handshaking loop (14 Apr 
2025)
+  * add Class1FRHNeedsReset (11-12 Apr 2025)
+  * add Class1FullDuplexTrainingSync, +FAR=2 iaxmodem feature (20-21 Feb, 7 
Mar 2025)
+  * adjust the wait time for the initial response when receiving (19 Feb 2025)
+  * cope with ECM Phase C carrier fast restart when receiving (12-13 Feb 2025)
+  * try to cope with broken TSI+DCS and CSI+DIS signals (10 Feb 2025)
+  * try to cope with some types of poor HDLC frame formatting (8 Feb 2025)
+  * correct recording of ntries, tottries, ndials, totdials when a proxy was 
used (4 Feb 2025)
+  * cope with +FRCNG results from iaxmodem (1, 12 Feb 2025)
+  * add Class1PhaseBRecvTimeoutCmd configuration parameter (30 Jan 2025)
+  * add Class1ExpectedPageSize configuration parameter (24 Jan 2025)
+
+-------------------------------------------------------------------

Old:
----
  hylafax-7.0.10.tar.gz

New:
----
  hylafax-7.0.11.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ hylafax+.spec ++++++
--- /var/tmp/diff_new_pack.U7R2rm/_old  2025-10-06 18:09:00.273479120 +0200
+++ /var/tmp/diff_new_pack.U7R2rm/_new  2025-10-06 18:09:00.277479288 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package hylafax+
 #
-# Copyright (c) 2025 SUSE LLC
+# Copyright (c) 2025 SUSE LLC and contributors
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -19,7 +19,7 @@
 %global faxspool    %{_localstatedir}/spool/hylafax
 %define lib_version %(echo %{version} | tr \. _)
 Name:           hylafax+
-Version:        7.0.10
+Version:        7.0.11
 Release:        0
 Summary:        A fax server
 License:        BSD-3-Clause

++++++ hylafax-7.0.10.tar.gz -> hylafax-7.0.11.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hylafax-7.0.10/CHANGES new/hylafax-7.0.11/CHANGES
--- old/hylafax-7.0.10/CHANGES  2025-01-23 21:00:58.000000000 +0100
+++ new/hylafax-7.0.11/CHANGES  2025-04-18 21:05:04.000000000 +0200
@@ -2,6 +2,22 @@
 
 New Changes
 
+* create seqf files with expected 0644 permissions (18 Apr 2025)
+* correct skipped page counting for proxied jobs (14 Apr 2025)
+* remove unnecessary switching pause on outbound handshaking loop (14 Apr 2025)
+* add Class1FRHNeedsReset (11-12 Apr 2025)
+* add Class1FullDuplexTrainingSync, +FAR=2 iaxmodem feature (20-21 Feb, 7 Mar 
2025)
+* adjust the wait time for the initial response when receiving (19 Feb 2025)
+* cope with ECM Phase C carrier fast restart when receiving (12-13 Feb 2025)
+* try to cope with broken TSI+DCS and CSI+DIS signals (10 Feb 2025)
+* try to cope with some types of poor HDLC frame formatting (8 Feb 2025)
+* correct recording of ntries, tottries, ndials, totdials when a proxy was 
used (4 Feb 2025)
+* cope with +FRCNG results from iaxmodem (1, 12 Feb 2025)
+* add Class1PhaseBRecvTimeoutCmd configuration parameter (30 Jan 2025)
+* add Class1ExpectedPageSize configuration parameter (24 Jan 2025)
+
+(7.0.10)
+
 * fix build for GCC v15 on Fedora 42 (23 Jan 2025)
 * identify a sender as fumbling ECM if they don't get any data through in four
   attempts at 2400 bps (16 Jan 2025)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hylafax-7.0.10/VERSION new/hylafax-7.0.11/VERSION
--- old/hylafax-7.0.10/VERSION  2025-01-23 21:00:58.000000000 +0100
+++ new/hylafax-7.0.11/VERSION  2025-02-02 05:15:33.000000000 +0100
@@ -1 +1 @@
-7.0.10
+7.0.11
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hylafax-7.0.10/config/silabs 
new/hylafax-7.0.11/config/silabs
--- old/hylafax-7.0.10/config/silabs    2025-01-23 21:00:58.000000000 +0100
+++ new/hylafax-7.0.11/config/silabs    2025-04-14 19:49:13.000000000 +0200
@@ -54,11 +54,20 @@
 #    previous +FCERROR result leads to unexpected behavior, possibly because 
of a need for a "state change" wait,
 #    mentioned above.  So, Class1RMPersistence should be set to 1.
 #
+# 7) If a multi-frame V.21 HDLC signal (such as NSF+CSI+DIS) is interrupted 
somehow, say by jitter, then it's
+#    perhaps expected for the acquisition of those frames with repeated +FRH=3 
to be successful on the first
+#    and perhaps second frames and not immediately pick up the second or third 
which follows the interruption,
+#    but in this situation it's possible for the +FRH=3 to then even not pick 
up any subsequent V.21 HDLC signal
+#    unless the +FRH=3 is re-issued.  Thus, Class1FRHNeedsReset is used to 
somewhat quickly time-out the +FRH=3
+#    when the interruption occurs so that it can be re-issued.  The Si2435 
seems to keep track of which prologue
+#    frames it has already reported and does not tend to repeat those which it 
has already reported.
+#
 
 FaxT4Timer: 3500
 Class1RMPersistence: 1
 Class1Cmd: "AT+FCLASS=1\n<delay:10>ATS30=59"
 Class1RecvAbortOK: 0
+Class1FRHNeedsReset: true
 
 # If your line supports Caller-ID, you may want to uncomment this...
 # ModemResetCmds:      AT+VCID=1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hylafax-7.0.10/config/silabs-10 
new/hylafax-7.0.11/config/silabs-10
--- old/hylafax-7.0.10/config/silabs-10 2025-01-23 21:00:58.000000000 +0100
+++ new/hylafax-7.0.11/config/silabs-10 2025-04-14 19:49:08.000000000 +0200
@@ -61,11 +61,20 @@
 #    previous +FCERROR result leads to unexpected behavior, possibly because 
of a need for a "state change" wait,
 #    mentioned above.  So, Class1RMPersistence should be set to 1.
 #
+# 7) If a multi-frame V.21 HDLC signal (such as NSF+CSI+DIS) is interrupted 
somehow, say by jitter, then it's    
+#    perhaps expected for the acquisition of those frames with repeated +FRH=3 
to be successful on the first
+#    and perhaps second frames and not immediately pick up the second or third 
which follows the interruption,
+#    but in this situation it's possible for the +FRH=3 to then even not pick 
up any subsequent V.21 HDLC signal
+#    unless the +FRH=3 is re-issued.  Thus, Class1FRHNeedsReset is used to 
somewhat quickly time-out the +FRH=3
+#    when the interruption occurs so that it can be re-issued.  The Si2435 
seems to keep track of which prologue
+#    frames it has already reported and does not tend to repeat those which it 
has already reported.
+#
 
 FaxT4Timer: 3500
 Class1RMPersistence: 1
 Class1Cmd: "AT+FCLASS=1.0\n<delay:10>ATS30=59"
 Class1RecvAbortOK: 0
+Class1FRHNeedsReset: true
 
 # If your line supports Caller-ID, you may want to uncomment this...
 # ModemResetCmds:      AT+VCID=1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hylafax-7.0.10/faxd/Class1.c++ 
new/hylafax-7.0.11/faxd/Class1.c++
--- old/hylafax-7.0.10/faxd/Class1.c++  2025-01-23 21:00:58.000000000 +0100
+++ new/hylafax-7.0.11/faxd/Class1.c++  2025-04-07 14:47:06.000000000 +0200
@@ -388,6 +388,7 @@
     wasSSLFax = false;
     suppressSSLFax = false;
     usingSSLFaxProxy = false;
+    fullDuplex = false;
     remoteCSAinfo = "";
     sslFaxPasscode = randomString(10);
     remoteCSAType = 0x00;
@@ -417,6 +418,7 @@
     wasSSLFax = false;
     suppressSSLFax = false;
     usingSSLFaxProxy = false;
+    fullDuplex = false;
     sslRcvCC = sslRcvNext = sslRcvBit = sslGotByte = 0;
     sslSawBlockEnd = false;
     sslWatchModem = false;
@@ -1226,14 +1228,37 @@
        protoTrace("HDLC frame too short (%u bytes)", frame.getLength());
        return (false);
     }
-    if ((frame[1]&0xf7) != 0xc0) {
-       protoTrace("HDLC frame with bad control field %#x", frame[1]);
-       return (false);
-    }
     if ((isSSLFax || conf.class1ValidateV21Frames) && !frame.checkCRC()) {
        protoTrace("FCS error (calculated)");
        return (false);
     }
+    if ((frame[1]&0xf7) != 0xc0) {
+       protoTrace("HDLC frame with bad control field %#x", frame[1]);
+       if (frame[1] == 0xff) {
+           /*
+            * The frame passed CRC checking, so it's very likely not corrupt 
data.
+            * Try to cope with senders that include superfluous 0xff and 0x00 
bytes
+            * between the flag and control fields.  They seem to always start 
with
+            * an initial extra 0xff.
+            */
+           u_short i = 2;
+           while (i < frame.getLength() && (frame[i] == 0xff || frame[i] == 
0x00)) i++;
+           if ((frame[i]&0xf7) == 0xc0) {
+               protoTrace("Trying to cope with poor HDLC frame formatting.");
+               HDLCFrame frametmp(conf.class1FrameOverhead);
+               frametmp.put(0xff);
+               while (i < frame.getLength()) frametmp.put(frame[i++]);
+               frame.reset();
+               i = 0;
+               while (i < frametmp.getLength()) frame.put(frametmp[i++]);
+               // The CRC will be bad for the reconstructed frame, but that 
shouldn't matter now.
+           } else {
+               return (false);
+           }
+       } else {
+           return (false);
+       }
+    }
     frameRcvd = "";
     for (u_int i = 0; i < frame.getLength(); i++) frameRcvd.append(frame[i]);
     frame.setOK(true);
@@ -1297,6 +1322,7 @@
 bool
 Class1Modem::recvECMFrame(HDLCFrame& frame)
 {
+    fakedRCP = false;
     if (!isSSLFax && useV34) {
        int c;
        for (;;) {
@@ -1416,6 +1442,7 @@
            frame.reset();
            frame.put(0xff); frame.put(0xc0); frame.put(0x61); frame.put(0x96); 
frame.put(0xd3);
            rcpframe = true;
+           fakedRCP = true;
        }
     } while (ones != 6 && bit != EOF && !rcpframe && frame.getLength() < 
frameSize+6);
     if (ones == 6) bit = getModemBit(60000);                   // trailing bit 
on flag
@@ -1458,6 +1485,12 @@
     if ((frame[1]&0xf7) != 0xc0) {
        if (frame[1] == 0xff) {
            protoTrace("HDLC frame with bad control field %#x, frame size: %d", 
frame[1], frame.getLength());
+       } else if (frame[1] == 0x03 && frame.getLength() == 5 && frame[2] == 
0x86) {
+           protoTrace("RECV received RCP frame (reversed)");
+           // Correct the reversed RCP frame
+           frame.reset();
+           frame.put(0xff); frame.put(0xc0); frame.put(0x61); frame.put(0x96); 
frame.put(0xd3);
+           return (true);
        } else {
            protoTrace("HDLC frame with bad control field %#x", frame[1]);
        }
@@ -1717,7 +1750,7 @@
            ok = false;
            u_short attempts = 0;
            lastResponse = AT_NOTHING;
-           while (!isSSLFax && !ok && lastResponse != AT_NOCARRIER && 
attempts++ < 3) {
+           while (!isSSLFax && !ok && lastResponse != AT_NOCARRIER && 
lastResponse != AT_FRH3 && attempts++ < 3) {
                ok = waitFor(AT_OK, 60*1000);   // wait up to 60 seconds for 
"OK"
            }
        }
@@ -1728,6 +1761,20 @@
 }
 
 /*
+ * Wrapper for recvFrame.
+ */
+bool
+Class1Modem::recvFrame(HDLCFrame& frame, u_char dir, long ms, bool 
readPending, bool docrp, bool usehooksensitivity, u_int echofcf, bool docng, 
bool handlestutter, u_int expectfcf)
+{
+    bool fr = recvFrame2(frame, dir, ms, readPending, docrp, 
usehooksensitivity, echofcf, docng, handlestutter, expectfcf);
+    if (!docrp && !fr && !wasTimeout() && lastResponse == AT_OK && 
!frame.getLength()) {
+       // Cope with broken behavior where they send somehing short, stop, and 
then send the actual signal.
+       fr = recvFrame2(frame, dir, ms, readPending, docrp, usehooksensitivity, 
echofcf, docng, handlestutter, expectfcf);
+    }
+    return (fr);
+}
+
+/*
  * Receive an HDLC frame.  The timeout is against
  * the receipt of the HDLC flags; the frame itself must
  * be received within 3 seconds (per the spec).
@@ -1738,10 +1785,10 @@
  * If the frame is received in error, then in instances
  * where the other end is likely to be able to receive
  * it, we can transmit CRP to get the other end to
- * retransmit the frame. 
+ * retransmit the frame.
  */
 bool
-Class1Modem::recvFrame(HDLCFrame& frame, u_char dir, long ms, bool 
readPending, bool docrp, bool usehooksensitivity, u_int echofcf, bool docng, 
bool handlestutter)
+Class1Modem::recvFrame2(HDLCFrame& frame, u_char dir, long ms, bool 
readPending, bool docrp, bool usehooksensitivity, u_int echofcf, bool docng, 
bool handlestutter, u_int expectfcf)
 {
     bool gotframe;
     u_short crpcnt = 0, rhcnt = 0;
@@ -1854,12 +1901,21 @@
            }
            frame.reset();
             gotframe = recvRawFrame(frame);
-           if ((echofcf && gotframe && frame.getFCF() == echofcf) || (docrp && 
crpcnt && crpcnt < 3 && frame.getFCF() == FCF_CRP)) {
+           if ((echofcf && gotframe && frame.getFCF() == echofcf) || (docrp && 
crpcnt && crpcnt < 3 && frame.getRawFCF() == (FCF_CRP|dir))) {
                traceFCF(dir == FCF_SNDR ? "SEND echo" : "RECV echo", 
frame.getFCF());
                gotecho = true;
                gotframe = false;
                echofcf = 0;    // only expect one possible echo
            }
+           if (!gotframe && (expectfcf == FCF_TSI || expectfcf == FCF_CSI)) {
+               /*
+                * If the remote system's TSI+DCS or CSI+DIS signal is broken 
early enough in the
+                * signal, then it may be possible to restart our frame 
reception and pick up DCS
+                * or DIS which is acceptable because it's all that we really 
need to proceed.
+                */
+               expectfcf = 0;
+               gotecho = true; // it wasn't echo, but we want the same response
+           }
            /*
             * Some modems aren't very particular about reporting CONNECT after 
AT+FRH=3.
             * So, for such modems CONNECT may come with V.17 modulated audio 
or any noise
@@ -1978,6 +2034,8 @@
        lastResponse = AT_FCERROR;
     if (lastResponse == AT_OTHER && strneq(buf, "+FRH:3", 6))
        lastResponse = AT_FRH3;
+    if (lastResponse == AT_OTHER && strneq(buf, "+FRCNG", 6))
+       lastResponse = AT_FRCNG;
     if (lastResponse == AT_OTHER && strneq(buf, "+F34:", 5)) {
        /*
         * V.8 handshaking was successful.  The rest of the
@@ -2029,6 +2087,7 @@
        case AT_OTHER:
        case AT_FCERROR:
        case AT_FRH3:
+       case AT_FRCNG:
        case AT_OK:
        case AT_CONNECT:
            return (false);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hylafax-7.0.10/faxd/Class1.h 
new/hylafax-7.0.11/faxd/Class1.h
--- old/hylafax-7.0.10/faxd/Class1.h    2025-01-23 21:00:58.000000000 +0100
+++ new/hylafax-7.0.11/faxd/Class1.h    2025-03-07 19:11:59.000000000 +0100
@@ -113,6 +113,8 @@
     int                imagefd;                // file descriptor for raw image
     bool       triggerInterrupt;       // flag to trigger procedural interrupt
     bool       checkReadOnWrite;       // flag to check modem read before 
writing
+    bool       fakedRCP;               // whether or not the RCP frame was 
falsified by us
+    bool       fullDuplex;             // whether or not the modem is in full 
duplex mode
 
     static const u_int modemPFMCodes[8];// map T.30 FCF to Class 2 PFM
     static const u_int modemPPMCodes[8];// map T.30 FCF to Class 2 PPM
@@ -197,7 +199,8 @@
 // miscellaneous
     enum {                     // Class 1-specific AT responses
        AT_FCERROR      = 100,  // "+FCERROR"
-       AT_FRH3         = 101   // "+FRH:3"
+       AT_FRH3         = 101,  // "+FRH:3"
+       AT_FRCNG        = 102   // "+FRCNG"
     };
     virtual ATResponse atResponse(char* buf, long ms = 30*1000);
     virtual bool waitFor(ATResponse wanted, long ms = 30*1000);
@@ -232,7 +235,8 @@
     bool       sendClass1Data(const u_char* data, u_int cc, const u_char* 
bitrev, bool eod, long ms);
     bool       sendClass1ECMData(const u_char* data, u_int cc,
                     const u_char* bitrev, bool eod, u_int ppmcmd, fxStr& emsg);
-    bool       recvFrame(HDLCFrame& frame, u_char dir, long ms = 10*1000, bool 
readPending = false, bool docrp = true, bool usehooksensitivity = true, u_int 
echofcf = 0, bool docng = false, bool handlestutter = false);
+    bool       recvFrame(HDLCFrame& frame, u_char dir, long ms = 10*1000, bool 
readPending = false, bool docrp = true, bool usehooksensitivity = true, u_int 
echofcf = 0, bool docng = false, bool handlestutter = false, u_int expectfcf = 
0);
+    bool       recvFrame2(HDLCFrame& frame, u_char dir, long ms = 10*1000, 
bool readPending = false, bool docrp = true, bool usehooksensitivity = true, 
u_int echofcf = 0, bool docng = false, bool handlestutter = false, u_int 
expectfcf = 0);
     bool       recvTCF(int br, HDLCFrame&, const u_char* bitrev, long ms);
     bool       recvRawFrame(HDLCFrame& frame);
     bool       recvECMFrame(HDLCFrame& frame);
@@ -273,6 +277,7 @@
     bool       recvBegin(FaxSetup* setupinfo, fxStr& emsg);
     bool       recvEOMBegin(FaxSetup* setupinfo, fxStr& emsg);
     bool       performProceduralInterrupt(bool disableECM, bool positive, 
fxStr& emsg);
+    bool       respondToCNG(fxStr& emsg);
     bool       recvPage(TIFF*, u_int& ppm, fxStr& emsg, const fxStr& id, u_int 
maxPages);
     bool       recvEnd(FaxSetup* setupinfo, fxStr& emsg);
     void       recvAbort();
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hylafax-7.0.10/faxd/Class1Recv.c++ 
new/hylafax-7.0.11/faxd/Class1Recv.c++
--- old/hylafax-7.0.10/faxd/Class1Recv.c++      2025-01-23 21:00:58.000000000 
+0100
+++ new/hylafax-7.0.11/faxd/Class1Recv.c++      2025-04-09 17:12:41.000000000 
+0200
@@ -155,7 +155,7 @@
     bool ok = FaxModem::recvBegin(setupinfo, emsg) && recvIdentification(
        0, fxStr::null,
        0, fxStr::null,
-       FCF_NSF|FCF_RCVR, nsf,
+       conf.class1SendNSF ? FCF_NSF|FCF_RCVR : 0, nsf,
        FCF_CSI|FCF_RCVR, lid,
        FCF_DIS|FCF_RCVR, dis,
        conf.class1RecvIdentTimer, false, emsg);
@@ -212,6 +212,10 @@
     HDLCFrame frame(conf.class1FrameOverhead);
     bool framesSent = false;
     u_int onhooks = 0;
+    bool gotCNG = false;
+    bool repeatCED = true;
+    bool readPending = false;
+    bool fullduplex = false;
 
     /* Here, "features" defines HylaFAX-specific features. */
     u_char features[1] = { 0 };
@@ -264,12 +268,46 @@
                stopTimeout("sending DIS/DCS frame");
            }
        }
+       if (lastResponse == AT_FRH3) {
+           if (!waitFor(AT_CONNECT, 2*1000)) {
+               emsg = "No CONNECT after +FRH:3";
+               protoTrace(emsg);
+               return (false);
+           }
+           framesSent = true;
+           readPending = true;
+       }
        if (framesSent || notransmit) {
            /*
-            * Wait for a response to be received.  We wait T2
-            * rather than T4 due to empirical evidence for that need.
+            * Wait for a response to be received.
+            *
+            * If somehow we missed the sender's initial response they should
+            * repeat their response after T4 lapses, somewhere between 2550
+            * and 5175 ms later (more probably between 2550 and 3450 ms).
+            * But if they somehow missed our initial signal then they could
+            * be expecting us to repeat them before T2 lapses (6 +/- 1s)
+            * before they give up.
+            *
+            * So, historically we waited T4 here and then changed it to T2
+            * because our T4 can be shorter than theirs and thus we'll miss
+            * their repeated response.  But, waiting T2 can also be a problem
+            * in the event that they did not detect our initial signal enough
+            * to compose a response or we did not detect their initial response
+            * because then we'll both be waiting T2 and may likely end up
+            * signaling over each other until giving up.
+            *
+            * Unfortunately, T4 and T2 overlap between 5000 and 5175 ms.
+            * However, the sender's T4 is likely the automatic values, so our
+            * wait here probably needs to be at least 3450 but less than 5000
+            * ms. Furthermore, the wait time should be somewhat variable so
+            * that we don't always happen to be be waiting the same as them.
+            *
+            * In order to accomplish this and avoid yet another configuration
+            * feature we'll wait midway between our T4 and T2 settings (so 5050
+            * ms, by default) lessened by a random time up to 550 ms.
             */
-           if (recvFrame(frame, FCF_RCVR, conf.t2Timer, false, true, false, 0, 
false, true)) {
+           u_int wait = (conf.t2Timer + conf.t4Timer)/2 - (rand() % 550);
+           if (recvFrame(frame, FCF_RCVR, wait, readPending, true, false, 0, 
false, true, FCF_TSI)) {
                do {
                    /*
                     * Verify a DCS command response and, if
@@ -293,11 +331,13 @@
                                    if (!switchingPause(emsg)) return (false);
                                    transmitFrame(signalSent);
                                    traceFCF("RECV send", (u_char) 
signalSent[2]);
+                                   if (signalSent[2] == FCF_RTN) gotframe = 
false;
                                    break;
                                case FCF_FTT:
                                    /* probably just our own echo */
                                    break;
                                case FCF_CRP:
+                                   if (!switchingPause(emsg)) return (false);
                                    /* do nothing here, just let us repeat NSF, 
CSI, DIS */
                                    break;
                                default:        // XXX DTC/DIS not handled
@@ -308,6 +348,11 @@
                        }
                        gotframe = false;
                        if (recvTraining()) {
+                           if (fullduplex) {
+                               // Turn full-duplex off now since we seem to be 
synced.
+                               checkReadOnWrite = true;
+                               atCmd("AT+FAR=1", AT_OK);
+                           }
                            emsg = "";
                            return (true);
                        } else {
@@ -321,6 +366,11 @@
                                     * The TCF signal is yet to come.  So, try 
again.
                                     */
                                    if (recvTraining()) {
+                                       if (fullduplex) {
+                                           // Turn full-duplex off now since 
we seem to be synced.
+                                           checkReadOnWrite = true;
+                                           atCmd("AT+FAR=1", AT_OK);
+                                       }
                                        emsg = "";
                                        return (true);
                                    }
@@ -348,6 +398,39 @@
                    t1 = howmany(conf.t1Timer, 1000);
                } while (recvFrame(frame, FCF_RCVR, conf.t2Timer, false, true, 
false));
            }
+           if (lastResponse == AT_FRH3) {
+               if (!waitFor(AT_CONNECT, 2*1000)) {
+                   emsg = "No CONNECT after +FRH:3";
+                   protoTrace(emsg);
+                   return (false);
+               }
+               framesSent = true;
+               notransmit = true;
+               readPending = true;
+               continue;
+           }
+           if (conf.class1FullDuplexTrainingSync && !fullduplex) {
+               /*
+                * The sender isn't syncing with us easily.  Enable full-duplex
+                * mode to detect V.21 HDLC signals while transmitting to 
assist us.
+                * We turn checkReadOnWrite off because we expect there to be 
times
+                * when the +FRH:3 response comes before we're done writing HDLC
+                * data to the modem, and we need to pick up the response 
afterwards.
+                */
+               atCmd("AT+FAR=2", AT_OK);
+               fullduplex = true;
+               checkReadOnWrite = false;
+           }
+       }
+       readPending = false;
+       notransmit = false;
+       if (wasTimeout() && conf.class1PhaseBRecvTimeoutCmd.length()) {
+           /*
+            * For some unintelligible reason, some callers require us
+            * to do something actively (like Google Voice can require
+            * the callee to press "1") in order to bridge the call.
+            */
+           atCmd(conf.class1PhaseBRecvTimeoutCmd, AT_OK);
        }
        if (gotEOT && ++onhooks > conf.class1HookSensitivity) {
            emsg = "RSPREC error/got EOT {E106}";
@@ -358,8 +441,15 @@
         * DCS from the other side.  First verify there is
         * time to make another attempt...
         */
-       if ((u_int) Sys::now()-start >= t1)
+       if ((u_int) Sys::now()-start >= t1) {
+           if (fullduplex) {
+               // Turn full-duplex off since we're giving up.
+               checkReadOnWrite = true;
+               atCmd("AT+FAR=1", AT_OK);
+           }
            return (false);
+       }
+       if (lastResponse == AT_FRCNG) gotCNG = true;
        if (frame.getFCF() != FCF_CRP) {
            /*
             * Historically we waited "Class1TrainingRecovery" (1500 ms)
@@ -370,7 +460,7 @@
             * The best way to do that is to make sure that there is
             * silence on the line, and  we do that with Class1SwitchingCmd.
             */
-           if (!switchingPause(emsg)) {
+           if (!switchingPause(emsg, fullduplex ? 1 : 3)) {
                return (false);
            }
        }
@@ -378,14 +468,44 @@
            /*
             * Retransmit ident frames.
             */
-           if (f1)
-               framesSent = transmitFrame(f1, pwd, false);
-           else if (f2)
-               framesSent = transmitFrame(f2, addr, false);
-           else if (f3)
-               framesSent = transmitFrame(f3, (const u_char*)HYLAFAX_NSF, 
(const u_char*) features, nsf, false);
-           else
-               framesSent = transmitFrame(f4, id, false);
+           if (gotCNG && repeatCED) {
+               if (!(atCmd(conf.CEDCmd, AT_NOTHING) && (atResponse(rbuf, 0) == 
AT_CONNECT || lastResponse == AT_FRH3))) {
+                   emsg = "Failure to raise V.21 transmission carrier. {E101}";
+                   protoTrace(emsg);
+                   return (false);
+               }
+               if (lastResponse == AT_CONNECT) {
+                   if (f1)
+                       framesSent = sendFrame(f1, pwd, false);
+                   else if (f2)
+                       framesSent = sendFrame(f2, addr, false);
+                   else if (f3)
+                       framesSent = sendFrame(f3, (const u_char*)HYLAFAX_NSF, 
(const u_char*) features, nsf, false);
+                   else
+                       framesSent = sendFrame(f4, id, false);
+               }
+           } else {
+               if (f1)
+                   framesSent = transmitFrame(f1, pwd, false);
+               else if (f2)
+                   framesSent = transmitFrame(f2, addr, false);
+               else if (f3)
+                   framesSent = transmitFrame(f3, (const u_char*)HYLAFAX_NSF, 
(const u_char*) features, nsf, false);
+               else
+                   framesSent = transmitFrame(f4, id, false);
+           }
+           if (lastResponse == AT_FRH3) {
+               if (!waitFor(AT_CONNECT, 2*1000)) {
+                   emsg = "No CONNECT after +FRH:3";
+                   protoTrace(emsg);
+                   return (false);
+               }
+               framesSent = true;
+               notransmit = true;
+               readPending = true;
+           }
+           gotCNG = false;
+           repeatCED = !repeatCED;
        }
     }
     return (false);
@@ -968,19 +1088,36 @@
                                         * so, this will accommodate those.
                                         */
                                        retryrmcmd = true;
-                                       prevPage--;
                                    }
                                }
                            }
-                           timer = BIT(curcap->br) & BR_ALL ? 273066 / 
(curcap->br+1) : conf.t2Timer;  // wait longer for PPM (estimate 80KB)
+                           timer = conf.t2Timer;
+                           if ((BIT(curcap->br) & BR_ALL) && !getSeenRTC()) {
+                               timeval delta = pageend - pagestart;
+                               time_t duration = delta.tv_sec * 1000 + 
delta.tv_usec / 1000;
+                               /*
+                                * Without getting RTC and without ECM we don't 
have a precise expectation for
+                                * how long we need to wait for Phase D 
signals.  So, we calculate an additional
+                                * wait time based on an expected page size 
plus the T2 timer.  We can deduct
+                                * the time already spent in Phase C reception.
+                                */
+                               timer += conf.class1ExpectedPageSize * 10 / (3 
* (curcap->br+1)) - duration;
+                               if (timer < conf.t2Timer) timer = conf.t2Timer;
+                           }
                        }
+                   } else if (rmResponse == AT_FRCNG) {
+                       if (!respondToCNG(emsg)) return (false);
+                       messageReceived = false;
+                       sendCFR = true;
                    } else {
                        if (wasTimeout()) {
                            abortReceive();             // return to command 
mode
                            setTimeout(false);
                        }
                        bool getframe = false;
-                       long wait = BIT(curcap->br) & BR_ALL ? 273066 / 
(curcap->br+1) : conf.t2Timer;
+                       long wait = conf.t2Timer;
+                       // We didn't get RTC, so we have to account for 
additional wait, as above.
+                       if (BIT(curcap->br) & BR_ALL) wait += 
conf.class1ExpectedPageSize * 10 / (3 * (curcap->br+1));
                        if (rmResponse == AT_FRH3) getframe = 
waitFor(AT_CONNECT, wait);
                        else if (!isSSLFax && rmResponse != AT_NOCARRIER && 
rmResponse != AT_ERROR) getframe = atCmd(rhCmd, AT_CONNECT, wait);  // wait 
longer
                        if (getframe) {
@@ -1018,7 +1155,7 @@
                    (void) setXONXOFF(FLOW_NONE, FLOW_NONE, ACT_DRAIN);
                setInputBuffering(false);
            }
-           if (!messageReceived && rmResponse != AT_FCERROR && rmResponse != 
AT_FRH3) {
+           if (!messageReceived && rmResponse != AT_FCERROR && rmResponse != 
AT_FRH3 && rmResponse != AT_FRCNG) {
                if (rmResponse != AT_ERROR) {
                    /*
                     * One of many things may have happened:
@@ -1130,6 +1267,12 @@
                            senderConfusesRTN = true;
                        }
                    }
+               } else if (lastResponse == AT_FRCNG) {
+                   if (!respondToCNG(emsg)) return (false);
+                   messageReceived = false;
+                   sendCFR = true;
+                   gotCONNECT = true;
+                   continue;
                }
                /*
                 * To combat premature carrier loss leading to MCF instead of 
RTN on short/partial pages,
@@ -1708,6 +1851,14 @@
                            abortReceive();             // return to command 
mode
                            setTimeout(false);
                        }
+                       if (lastResponse == AT_FRCNG) {
+                           if (!respondToCNG(emsg)) return (false);
+                           messageReceived = false;
+                           sendCFR = true;
+                           abortPageECMRecv(tif, params, block, fcount, seq, 
pagedataseen, emsg);
+                           prevPage--;         // counteract the forthcoming 
increment
+                           return (true);
+                       }
                        long wait = BIT(curcap->br) & BR_ALL ? 273066 / 
(curcap->br+1) : conf.t2Timer;
                        if (lastResponse != AT_NOCARRIER && atCmd(rhCmd, 
AT_CONNECT, wait)) {   // wait longer
                            // sender is transmitting V.21 instead, we may have
@@ -2005,6 +2156,10 @@
                (void) setXONXOFF(FLOW_NONE, FLOW_XONXOFF, ACT_NOW);
            gotoPhaseD = false;
            carrierup = false;
+
+           timeval pagestart;
+           gettimeofday(&pagestart, 0);
+
            if (!sendERR && ((!isSSLFax && useV34) || syncECMFrame())) {        
// no synchronization needed w/V.34-fax
                time_t start = Sys::now();
                do {
@@ -2086,6 +2241,23 @@
                        if (lastResponse == AT_OK) gotnocarrier = true; // some 
modems may return to command-mode in some instances
                    } while (!gotnocarrier && lastResponse != AT_EMPTYLINE && 
Sys::now() < (nocarrierstart + 5));
                }
+
+               if (!isSSLFax) {
+                   timeval pageend;
+                   gettimeofday(&pageend, 0);
+                   timeval delta = pageend - pagestart;
+                   time_t duration = delta.tv_sec * 1000 + delta.tv_usec / 
1000;
+                   if (duration < 500 && fakedRCP) {
+                       /*
+                        * The sender's Phase C carrier started and then 
stopped almost immediately.
+                        * There are cases like this where the sender then just 
restarts the carrier
+                        * so, this will accommodate those.
+                        */
+                       signalRcvd = lastSignalRcvd;
+                       continue;
+                   }
+               }
+
                bool gotpps = false;
                sslSawBlockEnd = false;
                wasSSLFax = false;
@@ -2164,10 +2336,10 @@
                         * to do just that.  Consequently, the frame count 
values of 0x00
                         * and 0xFF need consideration as to whether they 
represent 0, 1, 
                         * or 256.  To allow for the bizarre situation where a 
sender may
-                        * signal PPS-NULL with a frame count less than 256 we 
trust the
-                        * PPS-NULL frame count except in cases where it is 
determined to
-                        * be "1" because most-likely that determination only 
comes from
-                        * some garbage detected during the high-speed carrier.
+                        * signal an initial PPS-NULL with a frame count less 
than 256 we
+                        * trust the PPS-NULL frame count except in cases where 
it is
+                        * determined to be "1" because most-likely that 
determination only
+                        * comes from some garbage detected during the 
high-speed carrier.
                         */
                        case FCF_PPS:
                            if (triggerInterrupt && ppsframe.getFCF2() != 0x00 
&& !prevPage) {
@@ -2186,7 +2358,7 @@
                                u_int fc = ppsframe.getFCF() == FCF_CRP ? 256 : 
frameRev[ppsframe[6]] + 1;
                                if ((fc == 256 || fc == 1) && !dataseen) fc = 
0;        // distinguish 0 from 1 and 256
                                // See comment above.  It's extremely unlikely 
to get PPS-NULL with a frame-count meaning "1"...
-                               if (ppsframe.getFCF() == FCF_PPS && 
ppsframe.getFCF2() == 0x00 && fc == 1) fc = 0;
+                               if (ppsframe.getFCF() == FCF_PPS && 
ppsframe.getFCF2() == 0x00 && fc == 1 && !pprcnt) fc = 0;
                                if (fcount < fc) fcount = fc;
                                if (ppsframe.getFCF() == FCF_CRP) {
                                    if (fc) ppsframe[3] = 0x00;         // FCF2 
= NULL
@@ -2628,6 +2800,32 @@
 }
 
 /*
+ * CNG detection triggers us to return to the beginning of Phase B and with 
CED.
+ */
+bool
+Class1Modem::respondToCNG(fxStr& emsg)
+{
+    (void) switchingPause(emsg);
+    if (!(atCmd(conf.CEDCmd, AT_NOTHING) && atResponse(rbuf, 0) == 
AT_CONNECT)) {
+       emsg = "Failure to raise V.21 transmission carrier. {E101}";
+       protoTrace(emsg);
+       return (false);
+    }
+    if (gotEOT) return (false);
+
+    FaxSetup setupinfo;
+    setupinfo.senderConfusesRTN = senderConfusesRTN;
+    setupinfo.senderConfusesPIN = senderConfusesPIN;
+    setupinfo.senderSkipsV29 = senderSkipsV29;
+    setupinfo.senderHasV17Trouble = senderHasV17Trouble;
+    setupinfo.senderDataSent = dataSent;
+    setupinfo.senderDataMissed = dataMissed;
+    setupinfo.senderFumblesECM = senderFumblesECM;
+
+    return Class1Modem::recvBegin(&setupinfo, emsg);
+}
+
+/*
  * Perform a procedure interrupt (PIN or PIP).
  *
  * A procedure interrupt is designed to allow human operators to suspend fax 
operations
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hylafax-7.0.10/faxd/Class1Send.c++ 
new/hylafax-7.0.11/faxd/Class1Send.c++
--- old/hylafax-7.0.10/faxd/Class1Send.c++      2025-01-23 21:00:58.000000000 
+0100
+++ new/hylafax-7.0.11/faxd/Class1Send.c++      2025-04-14 06:21:03.000000000 
+0200
@@ -246,7 +246,7 @@
        // T.38 invite/transfer will often cause a "stutter" in the audio 
carrying the initial V.21 HDLC
        // To cope with that we deliberately tell recvFrame to handle stutter 
by re-issuing +FRH=3 if
        // an immediate NO CARRIER result is had after the CONNECT.
-       framerecvd = recvFrame(frame, FCF_SNDR, conf.t1Timer, true, true, true, 
0, false, true);        // T1 is used here, handle stutter
+       framerecvd = recvFrame(frame, FCF_SNDR, conf.t1Timer, true, true, true, 
0, false, true, FCF_CSI);       // T1 is used here, handle stutter
     } else if (docng) {
        framerecvd = recvFrame(frame, FCF_SNDR, conf.t1Timer, false, true, 
true, 0, docng);
     } else {                                   // receive carrier not raised
@@ -313,7 +313,7 @@
                    curcap = NULL;                      // force initial setup
                    break;
                }
-           } while (frame.moreFrames() && recvFrame(frame, FCF_SNDR, 
((t1-(Sys::now()-start))*1000 + conf.t2Timer)));  // wait to T1 timeout, but at 
least T2
+           } while (frame.moreFrames() && recvFrame(frame, FCF_SNDR, 
conf.class1FRHNeedsReset ? conf.t4Timer : ((t1-(Sys::now()-start))*1000 + 
conf.t2Timer)));        // wait to T1 timeout, but at least T2 unless reset 
needed
            if (frame.isOK()) {
                switch (frame.getRawFCF()) {
                case FCF_DIS:
@@ -351,7 +351,6 @@
        time_t now = Sys::now();
        if ((unsigned) now-start >= t1)
            break;
-       (void) switchingPause(emsg);
        framerecvd = recvFrame(frame, FCF_SNDR, ((t1-(now-start)))*1000 + 
conf.t2Timer);        // wait to T1 timeout, but wait at least T2
     }
     if (emsg == "") {
@@ -1133,20 +1132,25 @@
         */
        int t = 2;
        do {
+           bool readPending = false;
            if (!useV34 && !isSSLFax) protoTrace("SEND training at %s %s",
                modulationNames[curcap->mod],
                Class2Params::bitRateNames[curcap->br]);
            if (!sendPrologue(params, lid)) {
                if (abortRequested() || gotEOT)
                    goto done;
-               protoTrace("Error sending T.30 prologue frames");
-               continue;
+               if (lastResponse == AT_FRH3 && waitFor(AT_CONNECT, 2*1000)) {
+                   readPending = true;
+               } else {
+                   protoTrace("Error sending T.30 prologue frames");
+                   continue;
+               }
            }
 
            /*
             * V.8 handshaking provides training for V.34-fax connections
             */
-           if (!isSSLFax && !useV34) {
+           if (!isSSLFax && !useV34 && !readPending) {
                /*
                 * Delay before switching to high speed carrier
                 * to send the TCF data as required by T.30 chapter
@@ -1157,17 +1161,25 @@
                 * Class1PPMWaitCmd above.
                 */
                if (!atCmd(conf.class1TCFWaitCmd, AT_OK)) {
-                   emsg = "Stop and wait failure (modem on hook) {E127}";
-                   protoTrace(emsg);
-                   if (lastResponse == AT_ERROR) gotEOT = true;
-                   return (send_retry);
+                   if (lastResponse == AT_FRH3 && waitFor(AT_CONNECT, 2*1000)) 
{
+                       readPending = true;
+                   } else {
+                       emsg = "Stop and wait failure (modem on hook) {E127}";
+                       protoTrace(emsg);
+                       if (lastResponse == AT_ERROR) gotEOT = true;
+                       return (send_retry);
+                   }
                }
 
                setDataTimeout(5, params.br);           // TCF data is only 1.5 
s
-               if (!sendTCF(params, TCF_DURATION)) {
-                   if (abortRequested())
-                       goto done;
-                   protoTrace("Problem sending TCF data");
+               if (!readPending && !sendTCF(params, TCF_DURATION)) {
+                   if (lastResponse == AT_FRH3 && waitFor(AT_CONNECT, 2*1000)) 
{
+                       readPending = true;
+                   } else {
+                       if (abortRequested())
+                           goto done;
+                       protoTrace("Problem sending TCF data");
+                   }
                }
 
                /*
@@ -1180,7 +1192,7 @@
                 * between USR modems and certain HP OfficeJets, in 
                 * particular.
                 */
-               if (conf.class1ResponseWaitCmd != "") {
+               if (conf.class1ResponseWaitCmd != "" && !readPending) {
                    atCmd(conf.class1ResponseWaitCmd, AT_OK);
                }
            }
@@ -1196,7 +1208,7 @@
             * FTT, or CFR; and also a premature DCN.
             */
            HDLCFrame frame(conf.class1FrameOverhead);
-           bool gf = recvFrame(frame, FCF_SNDR, conf.t4Timer, false, false);
+           bool gf = recvFrame(frame, FCF_SNDR, conf.t4Timer, readPending, 
false);
 #if defined(HAVE_SSL)
            if (!isSSLFax && useSSLFax) {
                SSLFax sslfax;
@@ -1265,6 +1277,13 @@
                        break;
                    }
                } while (frame.moreFrames() && recvFrame(frame, FCF_SNDR, 
conf.t2Timer));
+           } else if (!fullDuplex && conf.class1FullDuplexTrainingSync) {
+               /*
+                * The receiver isn't syncing with us easily.  Try full duplex.
+                */
+               atCmd("AT+FAR=2", AT_OK);
+               fullDuplex = true;
+               checkReadOnWrite = false;
            }
            if (gotEOT) goto done;
            if (frame.isOK()) {
@@ -1272,6 +1291,12 @@
                case FCF_CFR:           // training confirmed
                    if (!isSSLFax && !useV34) protoTrace("TRAINING succeeded");
                    setDataTimeout(60, params.br);
+                   if (fullDuplex) {
+                       // Turn full-duplex off now since we are done.
+                       fullDuplex = false;
+                       checkReadOnWrite = true;
+                       atCmd("AT+FAR=1", AT_OK);
+                   }
                    return (true);
                case FCF_RTN:           // confusing signal to receive in Phase 
B, but this is how to handle it
                case FCF_FTT:           // failure to train, retry
@@ -1296,10 +1321,29 @@
                            checkReceiverDIS(params);
                            curcap = NULL;
                        }
+                       if (!fullDuplex && conf.class1FullDuplexTrainingSync) {
+                           /*
+                            * The receiver isn't syncing with us easily.  
Enable full-duplex
+                            * mode to detect V.21 HDLC signals while 
transmitting to assist us.
+                            * We turn checkReadOnWrite off because we expect 
there to be times
+                            * when the +FRH:3 response comes before we're done 
writing data
+                            * to the modem, and we need to pick up the 
response afterwards.
+                            */
+                           atCmd("AT+FAR=2", AT_OK);
+                           fullDuplex = true;
+                           checkReadOnWrite = false;
+                       }
                        // If it's our last attempt, disable SSL Fax in case 
that's being blocked.
                        if (tries == 2) suppressSSLFax = true;
+                       bool tr = sendTraining(params, --tries, emsg);
+                       if (tr && fullDuplex) {
+                           // Turn full-duplex off now since we are done.
+                           fullDuplex = false;
+                           checkReadOnWrite = true;
+                           atCmd("AT+FAR=1", AT_OK);
+                       }
+                       return (tr);
                    }
-                   return (sendTraining(params, --tries, emsg));
                default:
                    if (frame.getFCF() == FCF_DCN) {
                        /*
@@ -1323,6 +1367,12 @@
                         * for us to connect to them, instead.  Since CSA should
                         * only ever preced CFR, let's assume we got CFR.
                         */
+                       if (fullDuplex) {
+                           // Turn full-duplex off now since we are done.
+                           fullDuplex = false;
+                           checkReadOnWrite = true;
+                           atCmd("AT+FAR=1", AT_OK);
+                       }
                        return (true);
                    } else
                        emsg = "RSPREC invalid response received {E104}";
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hylafax-7.0.10/faxd/FaxSend.c++ 
new/hylafax-7.0.11/faxd/FaxSend.c++
--- old/hylafax-7.0.10/faxd/FaxSend.c++ 2025-01-23 21:00:58.000000000 +0100
+++ new/hylafax-7.0.11/faxd/FaxSend.c++ 2025-02-04 05:34:50.000000000 +0100
@@ -578,6 +578,7 @@
            fax.status = modem->sendPhaseB(tif, clientParams, clientInfo,
                fax.pagehandling, fax.notice, batched);
            modem->getDataStats(&setupinfo);
+           traceProtocol("SEND data sent: %d, data missed: %d", 
setupinfo.senderDataSent, setupinfo.senderDataMissed);
            clientInfo.setDataSent2(clientInfo.getDataSent1());
            clientInfo.setDataSent1(clientInfo.getDataSent());
            clientInfo.setDataSent(setupinfo.senderDataSent);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hylafax-7.0.10/faxd/ModemConfig.c++ 
new/hylafax-7.0.11/faxd/ModemConfig.c++
--- old/hylafax-7.0.10/faxd/ModemConfig.c++     2025-01-23 21:00:58.000000000 
+0100
+++ new/hylafax-7.0.11/faxd/ModemConfig.c++     2025-04-11 21:41:09.000000000 
+0200
@@ -125,6 +125,7 @@
 { "class1eopwaitcmd",          &ModemConfig::class1EOPWaitCmd, "AT+FTS=9" },
 { "class1msgrecvhackcmd",      &ModemConfig::class1MsgRecvHackCmd, "" },
 { "class1tcfrecvhackcmd",      &ModemConfig::class1TCFRecvHackCmd, "" },
+{ "class1phasebrecvtimeoutcmd",        
&ModemConfig::class1PhaseBRecvTimeoutCmd, "" },
 { "class1switchingcmd",                &ModemConfig::class1SwitchingCmd, 
"AT+FRS=7" },
 { "class2cmd",                 &ModemConfig::class2Cmd },
 { "class2borcmd",              &ModemConfig::class2BORCmd },
@@ -230,6 +231,7 @@
 { "class1pagelengthsupport",   &ModemConfig::class1PageLengthSupport, LN_ALL },
 { "class1pagewidthsupport",    &ModemConfig::class1PageWidthSupport, WD_ALL },
 { "class1restrictpoorsenders", &ModemConfig::class1RestrictPoorSenders, 0 },
+{ "class1expectedpagesize",    &ModemConfig::class1ExpectedPageSize, 100000 },
 };
 static struct {
     const char*                 name;
@@ -260,11 +262,14 @@
 { "class1mrsupport",           &ModemConfig::class1MRSupport,          true },
 { "class1mmrsupport",          &ModemConfig::class1MMRSupport,         true },
 { "class1persistentecm",       &ModemConfig::class1PersistentECM,      true },
+{ "class1sendnsf",             &ModemConfig::class1SendNSF,            true },
 { "class1validatev21frames",   &ModemConfig::class1ValidateV21Frames,  false },
 { "class1modemhasdlebug",      &ModemConfig::class1ModemHasDLEBug,     false },
 { "class1hasrhconnectbug",     &ModemConfig::class1HasRHConnectBug,    false },
 { "class1hasrmhookindication", &ModemConfig::class1HasRMHookIndication,        
true },
 { "class10autofallback",       &ModemConfig::class10AutoFallback,      true },
+{ "class1fullduplextrainingsync",      
&ModemConfig::class1FullDuplexTrainingSync,     false },
+{ "class1frhneedsreset",       &ModemConfig::class1FRHNeedsReset,      false },
 { "saveunconfirmedpages",      &ModemConfig::saveUnconfirmedPages,     true },
 { "modemsoftrtfcc",            &ModemConfig::softRTFCC,                true },
 { "modemdophasecdebug",                &ModemConfig::doPhaseCDebug,            
false },
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hylafax-7.0.10/faxd/ModemConfig.h 
new/hylafax-7.0.11/faxd/ModemConfig.h
--- old/hylafax-7.0.10/faxd/ModemConfig.h       2025-01-23 21:00:58.000000000 
+0100
+++ new/hylafax-7.0.11/faxd/ModemConfig.h       2025-04-11 21:41:09.000000000 
+0200
@@ -169,6 +169,7 @@
     fxStr      class1SSLFaxCert;       // SSL encryption certificate for "SSL 
Fax" feature
     fxStr      class1SSLFaxProxy;      // password@host:port information for 
"SSL Fax" proxy
     fxStr      class1TCFRecvHackCmd;   // cmd to avoid +FCERROR before TCF
+    fxStr      class1PhaseBRecvTimeoutCmd; // cmd to issue after each Phase B 
recv timeout
     u_int      class1TCFRecvTimeout;   // timeout receiving TCF
     u_int      class1RecvAbortOK;      // if non-zero, OK sent after recv abort
     u_int      class1RMPersistence;    // how many times to persist through 
+FCERROR
@@ -186,6 +187,7 @@
     u_int      class1PageLengthSupport;// page length support
     u_int      class1PageWidthSupport; // page width support
     u_int      class1RestrictPoorSenders;// restrict senders with poor audio 
from certain features
+    u_int      class1ExpectedPageSize; // expected page size for non-ECM Phase 
D waiting
     bool       class1ECMCheckFrameLength;// check frame length and not just CRC
     bool       class1GreyJPEGSupport;  // Greyscale JPEG support
     bool       class1ColorJPEGSupport; // Full-color JPEG support
@@ -193,11 +195,14 @@
     bool       class1MRSupport;        // support 2-D MR
     bool       class1MMRSupport;       // support 2-D MMR
     bool       class1PersistentECM;    // continue to correct
+    bool       class1SendNSF;          // whether or not to send NSF
     bool       class1ValidateV21Frames;// check received FCS values in V.21
     bool       class1ModemHasDLEBug;   // modem doesn't double-up DLEs in V.21
     bool       class1HasRHConnectBug;  // modem reports CONNECT after +FRH=3 
to non-V.21-HDLC data
     bool       class1HasRMHookIndication;      // modem reports ERROR after 
+FRM=<mod> when on hook
     bool       class10AutoFallback;    // modem automatically falls back to G3 
mode if no ANSam detected
+    bool       class1FullDuplexTrainingSync;   // switch to full duplex during 
difficult training periods
+    bool       class1FRHNeedsReset;    // modem's +FRH=3 needs to be reset 
sometimes
                                        // for class 2 and 2.0:
     fxStr      class2Cmd;              // cmd for setting Class 2/2.0
     fxStr      class2DCCQueryCmd;      // cmd to query modem capabilities
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hylafax-7.0.10/faxd/ModemServer.c++ 
new/hylafax-7.0.11/faxd/ModemServer.c++
--- old/hylafax-7.0.10/faxd/ModemServer.c++     2025-01-23 21:00:58.000000000 
+0100
+++ new/hylafax-7.0.11/faxd/ModemServer.c++     2025-02-14 02:02:37.000000000 
+0100
@@ -1482,16 +1482,19 @@
 {
     int c;
     u_int cc = 0;
+    bool isprintable = true;
     if (ms) startTimeout(ms);
     do {
-       while ((c = getModemChar(0)) != EOF && c != '\n' && !timer.wasTimeout())
+       while ((c = getModemChar(0)) != EOF && c != '\n' && 
!timer.wasTimeout()) {
            if (c != '\0' && c != '\r' && cc < bufSize)
                rbuf[cc++] = c;
+           if (c < 0x07 || c > 0x7e) isprintable = false;
+       }
     } while (!timer.wasTimeout() && cc == 0 && c != EOF);
     rbuf[cc] = '\0';
     if (ms) stopTimeout("reading line from modem");
     if (!timeout && !sslFaxConnection)
-       traceStatus(FAXTRACE_MODEMCOM, "--> [%d:%s]", cc, rbuf);
+       traceStatus(FAXTRACE_MODEMCOM, "--> [%d:%s]", cc, isprintable ? rbuf : 
"<non-printable data>");
     return (cc);
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hylafax-7.0.10/faxd/faxQueueApp.c++ 
new/hylafax-7.0.11/faxd/faxQueueApp.c++
--- old/hylafax-7.0.10/faxd/faxQueueApp.c++     2025-01-23 21:00:58.000000000 
+0100
+++ new/hylafax-7.0.11/faxd/faxQueueApp.c++     2025-04-15 01:00:23.000000000 
+0200
@@ -2878,13 +2878,14 @@
                                r = client->getLastResponse(); r.remove(0, 
r.length() > 4 ? 4 : r.length());
                                req.duration = atoi((const char*) r);
                            }
+                           int pagessent = 0;
                            if (client->jobParm("npages")) {
                                r = client->getLastResponse(); r.remove(0, 
r.length() > 4 ? 4 : r.length());
-                               int pagessent = atoi((const char*) r);
+                               pagessent = atoi((const char*) r);
                                // The following are for preparePageHandling 
below
                                req.npages += pagessent;
-                               req.skippages = pagessent;
-                               req.skippedpages -= pagessent;
+                               req.skippedpages += req.skippages;
+                               req.skippages = 0;
                                req.pagehandling = "";
                            }
                            if (client->jobParm("totpages")) {
@@ -2902,20 +2903,47 @@
                            } else {
                                req.notice = "unknown status on proxy";
                            }
+                           u_int tries = 0;
                            if (client->jobParm("ntries")) {
                                r = client->getLastResponse(); r.remove(0, 
r.length() > 4 ? 4 : r.length());
-                               u_int tries = atoi((const char*) r);
+                               tries = atoi((const char*) r);
                                if (tries < (u_int) maxTries && strstr((const 
char*) req.notice, "oo many attempts to send")) tries = maxTries; // caught in 
a lie
                                if (tries < (u_int) maxTries && strstr((const 
char*) req.notice, "oo many attempts to transmit")) tries = maxTries;     // 
caught in a lie
-                               req.ntries += tries;
-                               req.tottries += tries;
+                               /*
+                                * Because ntries refers to the number of 
attempts to send the current page
+                                * we need to determine if the page counter 
incremented or not and modify
+                                * ntries accordingly.
+                                */
+                               if (pagessent > 0) {
+                                   req.ntries = tries;
+                               } else {
+                                   req.ntries += tries;
+                               }
+                           }
+                           if (client->jobParm("tottries")) {
+                               r = client->getLastResponse(); r.remove(0, 
r.length() > 4 ? 4 : r.length());
+                               u_int tottries = atoi((const char*) r);
+                               req.tottries += tottries;
                            }
                            if (client->jobParm("ndials")) {
                                r = client->getLastResponse(); r.remove(0, 
r.length() > 4 ? 4 : r.length());
                                u_int dials = atoi((const char*) r);
                                if (dials < (u_int) maxDials && strstr((const 
char*) req.notice, "oo many attempts to dial")) dials = maxDials; // caught in 
a lie
-                               req.ndials += dials;
-                               req.totdials += dials;
+                               /*
+                                * Because ndials refers to the number of 
consecutive failed attempts to place
+                                * a call we need to determine if there were 
any tries made for this page and
+                                * and modify ndials accordingly.
+                                */
+                               if (tries > 0) {
+                                   req.ndials = dials;
+                               } else {
+                                   req.ndials += dials;
+                               }
+                           }
+                           if (client->jobParm("totdials")) {
+                               r = client->getLastResponse(); r.remove(0, 
r.length() > 4 ? 4 : r.length());
+                               u_int totdials = atoi((const char*) r);
+                               req.totdials += totdials;
                            }
                            if (req.notice.length() && 
client->jobParm("errorcode")) {
                                r = client->getLastResponse(); r.remove(0, 
r.length() > 4 ? 4 : r.length());
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hylafax-7.0.10/man/hylafax-config.4f 
new/hylafax-7.0.11/man/hylafax-config.4f
--- old/hylafax-7.0.10/man/hylafax-config.4f    2025-01-23 21:00:58.000000000 
+0100
+++ new/hylafax-7.0.11/man/hylafax-config.4f    2025-04-11 21:41:09.000000000 
+0200
@@ -323,10 +323,13 @@
 Class1PersistentECM    boolean \s-1Yes\s+1     Class 1/1.0: to continue to 
correct while in ECM
 Class1ECMFrameSize     integer \s-1256\s+1     Class 1/1.0: image frame size 
in ECM protocol
 Class1ExtendedRes      boolean \-      Class 1/1.0: enable extended resolution 
support
+Class1ExpectedPageSize integer \s-1100000\s+1  Class 1/1.0: expected page size 
in bytes
 Class1HasRHConnectBug  boolean \s-1No\s+1      Class 1/1.0: modem can report 
CONNECT incorrectly
 Class1HasRMHookIndication      boolean \s-1Yes\s+1     Class 1/1.0: modem 
reports ERROR correctly
 Class1HFLOCmd  string  \-      Class 1/1.0: command to set hardware flow 
control
 Class1FrameOverhead    integer \s-14\s+1       Class 1/1.0: extra bytes in a 
received \s-1HDLC\s+1 frame
+Class1FRHNeedsReset    boolean \s-1No\s+1      Class1/1.0: +FRH=3 command 
needs resetting sometimes
+Class1FullDuplexTrainingSync   boolean \s-1No\s+1      Class 1/1.0: to enable 
full-duplex during training sync
 Class1GreyJPEGSupport  boolean \s-1No\s+1      Class 1/1.0: to enable grey 
JPEG fax support
 Class1HookSensitivity  integer \s-10\s+1       Class 1/1.0: times to ignore 
on-hook detection
 Class1JBIGSupport      string  \s-1\fIsee below\fP\s+1 Class 1/1.0: to enable 
monochrome JBIG fax support
@@ -340,8 +343,10 @@
 Class1RestrictPoorDestinations¹        integer \s-10\s+1       Class 1/1.0: 
restrict features for destinations with poor quality
 Class1RestrictPoorSenders      integer \s-10\s+1       Class 1/1.0: restrict 
features for senders with poor quality
 Class1RMPersistence    integer \s-12\s+1       Class 1/1.0: times to attempt 
high-speed carrier recv
+Class1SendNSF  boolean \s-1Yes\s+1     Class 1/1.0: whether or not to send NSF
 Class1SFLOCmd  string  \-      Class 1/1.0: command to set software flow 
control
 Class1PPMWaitCmd       string  \s-1AT+FTS=7\s+1        Class 1/1.0: command to 
stop and wait before PPM
+Class1PhaseBRecvTimeoutCmd     string  \-      Class 1/1.0: command to issue 
if Phase B receive times out
 Class1ResponseWaitCmd  string  \-      Class 1/1.0: command to wait before TCF 
response
 Class1Resolutions      integer \s-10x7F\s+1    Class 1/1.0: bitmap of 
supported resolutions
 Class1RMQueryCmd       string  \s-1AT+FRM=?\s+1        Class 1/1.0: command to 
query modem data reception rates
@@ -2858,6 +2863,11 @@
 This option has been deprecated by
 .B Class1Resolutions.
 .TP
+.B Class1ExpectedPageSize
+The expected receive page size in bytes which is used to calculate
+wait times for non-ECM Phase D if a premature Phase C carrier
+loss occurs.
+.TP
 .B Class1FrameOverhead
 The number of extraneous bytes in
 .SM HDLC
@@ -2865,6 +2875,15 @@
 For modems that properly implement the Class 1 interface, this
 number should be 4 (the default).
 .TP
+.B Class1FRHNeedsReset
+If the modem's +FRH=3 command needs to sometimes be reset in order
+to do its job, then enable this.  Doing so limits the wait time for
+the +FRH=3 command before re-issuing it.
+.TP
+.B Class1FullDuplexTrainingSync
+Whether or not to enable support for a full-duplex modem feature
+while receiving training when the sender doesn't readily sync.
+.TP
 .B Class1GreyJPEGSupport
 Whether or not to enable support for T.30-E greyscale facsimile
 with JPEG compression.  This is always enabled if
@@ -2949,6 +2968,11 @@
 stopped completely.
 According to T.30, Chapter 5, Note 4, this delay should be 75 +/- 20 ms.
 .TP
+.B Class1PhaseBRecvTimeoutCmd
+In the event that Phase B receive times out without receiving signaling
+this command is issued after each attempt.  Some callers can require
+the callee to do something (besides the usual fax signaling) to bridge the 
call.
+.TP
 .B Class1ResponseWaitCmd
 The command used to stop and wait after sending TCF, before attempting to 
 receive a training response from the remote.  Set this to ``AT+FTS=1'' if 
@@ -3092,6 +3116,9 @@
 off its modulator (i.e. loss-of-carrier) 
 as recommended by T.31: Appendix II.1.
 .TP
+.B Class1SendNSF
+Whether or not to send an NSF signal when answering a fax call.
+.TP
 .B Class1SFLOCmd
 The command to setup software (\s-1XON/XOFF\s+1) flow control between
 .SM DTE
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hylafax-7.0.10/util/Sequence.c++ 
new/hylafax-7.0.11/util/Sequence.c++
--- old/hylafax-7.0.10/util/Sequence.c++        2025-01-23 21:00:58.000000000 
+0100
+++ new/hylafax-7.0.11/util/Sequence.c++        2025-04-18 21:01:32.000000000 
+0200
@@ -42,6 +42,7 @@
     struct stat sb;
     int fd;
     int rtn = lstat(name, &sb);
+    mode_t mask = umask(S_IWGRP | S_IWOTH);    // prevent umask from 
interfering with permissions
     if (rtn != 0 && errno == ENOENT) {
         fd = Sys::open(name, O_CREAT | O_RDWR | O_EXCL, 0644);
     } else if (rtn == 0 && S_ISREG(sb.st_mode)) {
@@ -58,6 +59,7 @@
         //XXX some kind of error opening file
         fd = -1;
     }
+    umask(mask);       // restore previous umask
     if (fd < 0) {
         emsg = fxStr::format("Unable to open sequence number file %s; %s.",
             name, strerror(errno));

Reply via email to