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