1 /*	$Id$ */
2 /*
3  * Copyright (c) 1994-1996 Sam Leffler
4  * Copyright (c) 1994-1996 Silicon Graphics, Inc.
5  * HylaFAX is a trademark of Silicon Graphics
6  *
7  * Permission to use, copy, modify, distribute, and sell this software and
8  * its documentation for any purpose is hereby granted without fee, provided
9  * that (i) the above copyright notices and this permission notice appear in
10  * all copies of the software and related documentation, and (ii) the names of
11  * Sam Leffler and Silicon Graphics may not be used in any advertising or
12  * publicity relating to the software without the specific, prior written
13  * permission of Sam Leffler and Silicon Graphics.
14  *
15  * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
16  * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
17  * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
18  *
19  * IN NO EVENT SHALL SAM LEFFLER OR SILICON GRAPHICS BE LIABLE FOR
20  * ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
21  * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
22  * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF
23  * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
24  * OF THIS SOFTWARE.
25  */
26 #include "Class20.h"
27 #include "ModemConfig.h"
28 #include "StackBuffer.h"
29 
30 #include <stdlib.h>
31 #include <ctype.h>
32 
Class20Modem(FaxServer & s,const ModemConfig & c)33 Class20Modem::Class20Modem(FaxServer& s, const ModemConfig& c) : Class2Modem(s,c)
34 {
35     serviceType = SERVICE_CLASS20;
36     setupDefault(classCmd,	conf.class2Cmd,		"AT+FCLASS=2.0");
37     setupDefault(mfrQueryCmd,	conf.mfrQueryCmd,	"AT+FMI?");
38     setupDefault(modelQueryCmd,	conf.modelQueryCmd,	"AT+FMM?");
39     setupDefault(revQueryCmd,	conf.revQueryCmd,	"AT+FMR?");
40     setupDefault(dccQueryCmd,	conf.class2DCCQueryCmd, "AT+FCC=?");
41     setupDefault(abortCmd,	conf.class2AbortCmd,	"AT+FKS");
42 
43     setupDefault(borCmd,	conf.class2BORCmd,	"AT+FBO=0");
44     setupDefault(tbcCmd,	conf.class2TBCCmd,	"AT+FPP=0");
45     setupDefault(crCmd,		conf.class2CRCmd,	"AT+FCR=1");
46     setupDefault(phctoCmd,	conf.class2PHCTOCmd,	"AT+FCT=30");
47     setupDefault(bugCmd,	conf.class2BUGCmd,	"AT+FBU=1");
48     setupDefault(lidCmd,	conf.class2LIDCmd,	"AT+FLI");
49     setupDefault(dccCmd,	conf.class2DCCCmd,	"AT+FCC");
50     setupDefault(disCmd,	conf.class2DISCmd,	"AT+FIS");
51     setupDefault(cigCmd,	conf.class2CIGCmd,	"AT+FPI");
52     setupDefault(splCmd,	conf.class2SPLCmd,	"AT+FSP");
53     setupDefault(ptsCmd,	conf.class2PTSCmd,	"AT+FPS");
54     setupDefault(ptsQueryCmd,	conf.class2PTSQueryCmd,	"AT+FPS?");
55     setupDefault(minspCmd,	conf.class2MINSPCmd,	"AT+FMS");
56 
57     setupDefault(noFlowCmd,	conf.class2NFLOCmd,	"AT+FLO=0");
58     setupDefault(softFlowCmd,	conf.class2SFLOCmd,	"AT+FLO=1");
59     setupDefault(hardFlowCmd,	conf.class2HFLOCmd,	"AT+FLO=2");
60 
61     // ignore procedure interrupts
62     setupDefault(pieCmd,	conf.class2PIECmd,	"AT+FIE=0");
63     // enable reporting of everything
64     setupDefault(nrCmd,		conf.class2NRCmd,	"AT+FNR=1,1,1,1");
65 }
66 
~Class20Modem()67 Class20Modem::~Class20Modem()
68 {
69 }
70 
71 ATResponse
atResponse(char * buf,long ms)72 Class20Modem::atResponse(char* buf, long ms)
73 {
74     if (FaxModem::atResponse(buf, ms) == AT_OTHER &&
75       (buf[0] == '+' && buf[1] == 'F')) {
76 	if (strneq(buf, "+FHS:", 5)) {
77 	    processHangup(buf+5);
78 	    lastResponse = AT_FHNG;
79 	    hadHangup = true;
80 	} else if (strneq(buf, "+FCO", 4))
81 	    lastResponse = AT_FCON;
82 	else if (strneq(buf, "+FPO", 4))
83 	    lastResponse = AT_FPOLL;
84 	else if (strneq(buf, "+FVO", 4))
85 	    lastResponse = AT_FVO;
86 	else if (strneq(buf, "+FIS:", 5))
87 	    lastResponse = AT_FDIS;
88 	else if (strneq(buf, "+FNF:", 5))
89 	    lastResponse = AT_FNSF;
90 	else if (strneq(buf, "+FCI:", 5))
91 	    lastResponse = AT_FCSI;
92 	else if (strneq(buf, "+FPS:", 5))
93 	    lastResponse = AT_FPTS;
94 	else if (strneq(buf, "+FCS:", 5))
95 	    lastResponse = AT_FDCS;
96 	else if (strneq(buf, "+FNS:", 5))
97 	    lastResponse = AT_FNSS;
98 	else if (strneq(buf, "+FTI:", 5))
99 	    lastResponse = AT_FTSI;
100 	else if (strneq(buf, "+FET:", 5))
101 	    lastResponse = AT_FET;
102 	else if (strneq(buf, "+FPA:", 5))
103 	    lastResponse = AT_FPA;
104 	else if (strneq(buf, "+FSA:", 5))
105 	    lastResponse = AT_FSA;
106 	else if (strneq(buf, "+FPW:", 5))
107 	    lastResponse = AT_FPW;
108     }
109     return (lastResponse);
110 }
111 
112 /*
113  * Abort a data transfer in progress.
114  */
115 void
abortDataTransfer()116 Class20Modem::abortDataTransfer()
117 {
118     protoTrace("SEND abort data transfer");
119     char c = CAN;
120     putModemData(&c, 1);
121 }
122 
123 /*
124  * Send a page of data using the ``stream interface''.
125  */
126 bool
sendPage(TIFF * tif,u_int pageChop,bool cover)127 Class20Modem::sendPage(TIFF* tif, u_int pageChop, bool cover)
128 {
129     /*
130      * Support MT5634ZBA-V92 real-time fax compression conversion:
131      * AT+FFC=? gives us non-zero data if RTFCC is supported.
132      * Firstly, we must have set our FCC to MMR support (+FCC=,,,,3), and
133      * we may need to have ECM enabled (+FCC=,,,,,1) if we intend to
134      * allow RTFCC to send in MMR.  Now we send <DLE><char> where char =
135      *    6Bh  -  if we formatted the image in MH
136      *    6Ch  -  if we formatted the image in MR
137      *    6Eh  -  if we formatted the image in MMR
138      */
139     if (conf.class2RTFCC) {
140 	protoTrace("Enable Real-Time Fax Compression Conversion");
141 	uint16 compression;
142 	char rtfcc[2];
143 	rtfcc[0] = DLE;
144 	TIFFGetField(tif, TIFFTAG_COMPRESSION, &compression);
145 	if (compression != COMPRESSION_CCITTFAX4) {
146 	    uint32 g3opts = 0;
147 	    TIFFGetField(tif, TIFFTAG_GROUP3OPTIONS, &g3opts);
148 	    if ((g3opts & GROUP3OPT_2DENCODING) == DF_2DMR) {
149 		rtfcc[1] = 0x6C;	// MR
150 		protoTrace("Reading MR-compressed image file");
151 	    } else {
152 		rtfcc[1] = 0x6B;	// MH
153 		protoTrace("Reading MH-compressed image file");
154 	    }
155 	} else {
156 	    rtfcc[1] = 0x6E;		// MMR
157 	    protoTrace("Reading MMR-compressed image file");
158 	}
159 	putModemData(rtfcc, sizeof (rtfcc));
160     }
161 
162     protoTrace("SEND begin page");
163     if (flowControl == FLOW_XONXOFF)
164 	setXONXOFF(FLOW_XONXOFF, FLOW_NONE, ACT_FLUSH);
165     bool rc = sendPageData(tif, pageChop, cover);
166     if (!rc)
167 	abortDataTransfer();
168     else if( conf.class2SendRTC )
169 	rc = sendRTC(params);
170     if (flowControl == FLOW_XONXOFF)
171 	setXONXOFF(getInputFlow(), FLOW_XONXOFF, ACT_DRAIN);
172     protoTrace("SEND end page");
173     return (rc);
174 }
175 
176 /*
177  * Handle the page-end protocol.  Class 2.0 returns
178  * OK/ERROR according to the post-page response.  We
179  * should query the modem to get the actual code, but
180  * some modems don't support +FPS? and so instead we
181  * synthesize codes, ignoring whether or not the
182  * modem does retraining on the next page transfer.
183  */
184 bool
pageDone(u_int ppm,u_int & ppr)185 Class20Modem::pageDone(u_int ppm, u_int& ppr)
186 {
187     static char ppmCodes[3] = { 0x2C, 0x3B, 0x2E };
188     char eop[2];
189 
190     if (ppm == PPH_SKIP)
191     {
192 	ppr = PPR_MCF;
193         return true;
194     }
195 
196     eop[0] = DLE;
197     eop[1] = ppmCodes[ppm];
198 
199     ppr = 0;            // something invalid
200     if (putModemData(eop, sizeof (eop))) {
201         for (;;) {
202 	    switch (atResponse(rbuf, conf.pageDoneTimeout)) {
203             case AT_FHNG:
204 		waitFor(AT_OK);
205                 if (!isNormalHangup()) {
206                     return (false);
207                 }
208                 ppr = PPR_MCF;
209                 return (true);
210             case AT_OK:
211                 /*
212                  * We do explicit status query e.g. to
213                  * distinguish between MCF, RTP, and PIP.
214 		 * If we don't understand the response,
215 		 * assume that our query isn't supported.
216                  */
217                 {
218 		    if (strcasecmp(conf.class2PTSQueryCmd, "none") != 0) {
219 			fxStr s;
220 			if(!atQuery(conf.class2PTSQueryCmd, s) ||
221 			    sscanf(s, "%u", &ppr) != 1) {
222 			    protoTrace("MODEM protocol botch (\"%s\"), %s",
223 			    (const char*)s, "can not parse PPR");
224 			    ppr = PPR_MCF;
225 			}
226 		    } else
227 			ppr = PPR_MCF;	// could be PPR_RTP/PPR_PIP
228                 }
229                 return (true);
230             case AT_ERROR:
231                 /*
232                  * We do explicit status query e.g. to
233                  * distinguish between RTN and PIN
234 		 * If we don't understand the response,
235 		 * assume that our query isn't supported.
236                  */
237                 {
238 		    if (strcasecmp(conf.class2PTSQueryCmd, "none") != 0) {
239 			fxStr s;
240 			if(!atQuery(conf.class2PTSQueryCmd, s) ||
241 			    sscanf(s, "%u", &ppr) != 1) {
242 			    protoTrace("MODEM protocol botch (\"%s\"), %s",
243 			    (const char*)s, "can not parse PPR");
244 			    ppr = PPR_RTN;
245 			}
246 		    } else
247 			ppr = PPR_RTN;	// could be PPR_PIN
248                 }
249                 return (true);
250             case AT_EMPTYLINE:
251             case AT_TIMEOUT:
252             case AT_NOCARRIER:
253             case AT_NODIALTONE:
254             case AT_NOANSWER:
255                 goto bad;
256             }
257         }
258     }
259 bad:
260     processHangup("50");        // Unspecified Phase D error
261     return (false);
262 }
263 
264 /*
265  * Class 2.0 must override the default behaviour used
266  * Class 1+2 modems in order to do special handling of
267  * <DLE><SUB> escape (translate to <DLE><DLE>).
268  */
269 int
nextByte()270 Class20Modem::nextByte()
271 {
272     int b;
273     if (bytePending & 0x100) {
274 	b = bytePending & 0xff;
275 	bytePending = 0;
276     } else {
277 	b = getModemDataChar();
278 	if (b == EOF)
279 	    raiseEOF();
280     }
281     if (b == DLE) {
282 	switch (b = getModemDataChar()) {
283 	    case 0x01:			// +FDB=1 support
284 		{
285 		    fxStr dbdata;
286 		    bool notdone = true;
287 		    do {
288 			b = getModemDataChar();
289 			if (b == DLE) {
290 			    b = getModemDataChar();
291 			    if (b == 0x04) {
292 				notdone = false;
293 				protoTrace("DCE DEBUG: %s", (const char*) dbdata);
294 			    } else {
295 				dbdata.append(DLE);
296 			    }
297 			}
298 			if (b != '\0' && b != '\r' && b != '\n')
299 			    dbdata.append(b);
300 		    } while (notdone);
301 		    b = nextByte();
302 		}
303 	    break;
304 	case EOF: raiseEOF();
305 	case ETX: raiseRTC();		// RTC
306 	case DLE: break;		// <DLE><DLE> -> <DLE>
307 	case SUB: b = DLE;		// <DLE><SUB> -> <DLE><DLE>
308 	    /* fall thru... */
309 	default:
310 	    bytePending = b | 0x100;
311 	    b = DLE;
312 	    break;
313 	}
314     }
315     b = getBitmap()[b];
316     if (recvBuf)
317 	recvBuf->put(b);
318     return (b);
319 }
320