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));
