File: class1.c

package info (click to toggle)
mgetty 1.2.1-4
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 6,872 kB
  • sloc: ansic: 42,728; sh: 6,487; perl: 6,262; makefile: 1,457; tcl: 756; lisp: 283
file content (802 lines) | stat: -rw-r--r-- 23,522 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
#ident "$Id: class1.c,v 4.21 2014/04/03 09:04:13 gert Exp $ Copyright (c) Gert Doering"

/* class1.c
 *
 * High-level functions to handle class 1 fax -- 
 * state machines for fax phase A, B, C, D. Error recovery.
 *
 * Uses library functions in class1lib.c, faxlib.c and modem.c
 *
 * $Log: class1.c,v $
 * Revision 4.21  2014/04/03 09:04:13  gert
 * rename FRAMESIZE to C1_FRAMESIZE, collision with AIX header file (grrr)
 *
 * Revision 4.20  2009/03/19 15:51:17  gert
 * bugfix: new NSF sending code was introducing extra "AT+FTH=3" commands
 * (due to confusion about internal requirements of fax1_send_frame())
 *
 * Revision 4.19  2009/03/19 15:33:59  gert
 * send NSF frames on reception (if modem quirk 0x40, for now)
 * handle different modem behaviour regarding AT+FTS=8;+FTH=3 gracefully
 *
 * Revision 4.18  2006/10/27 09:07:12  gert
 * add file name of page currently sent to log message
 *
 * Revision 4.17  2006/10/25 10:55:01  gert
 * class 1 receive: log current try #, send DIS only if CSI went without error
 *
 * Revision 4.16  2006/09/29 19:30:26  gert
 * properly calculate scan line time -> padding bytes
 * implement FTT -> retraining, with baud rate reduction
 *
 * Revision 4.15  2006/09/26 15:36:07  gert
 * - handle modems that don't get AT+FTS=8;+FTM=nn right
 *   (re-do AT+FTM if the first command yields "OK")
 * - switch on and off Xon/Xoff flow control before/after sending page data
 *
 * Revision 4.14  2006/09/25 22:26:54  gert
 * fax1_send_page(): move all the G3 file handling to g3file.c functions
 *  (-> cleanup code, build common infrastructure for class 1 + class 2)
 *
 * Revision 4.13  2006/03/22 14:13:12  gert
 * when sending, hand over received NSF to NSF decoder (faxlib.c/hyla_nsf.c)
 *
 * Revision 4.12  2006/03/07 21:31:50  gert
 * class 1 sending implementation:
 *   - handle end-of-page (return to phase B or phase C, or send DCN/hangup)
 *   - move sending of TSI and DCS inside fax1_send_page(), to be able to
 *     handle RTN/RTP transparently - somewhat sloppy "phase B" definition,
 *     but much cleaner this way
 *   - fix reading of G3 files - skip digifax header, also send last chunk
 *   - fix bit swapping (fax_send_swaptable)
 * -> class 1 sending now works, if the receiver doesn't need EOL padding
 *
 * Revision 4.11  2006/03/07 14:16:56  gert
 * fax1_dial_and_phase_AB(): add torture test code, refusing incoming CSI/DIS
 * 2 times before going on (mainly for testing receiver robustness)
 * fax1_send_page(): add log message to point out unimplemented stuff
 *
 * Revision 4.10  2006/01/04 21:07:12  gert
 * remove "speed" argument from fax1_send_dcs() (use fax1_max global)
 *
 * Revision 4.9  2006/01/03 09:12:20  gert
 * initialize "tries" in fax1_receive_page()
 *
 * Revision 4.8  2006/01/01 17:07:43  gert
 * change all fax1_send_dcn() to already set appropriate hangup code
 * add prototypes for local functions
 * add timeout handling and re-try logic to fax1_highlevel_receive(),
 *     fax1_receive_tcf() and fax1_receive_page()
 * add handling of incoming DCNs (give up)
 * add calculation of "how long should TCF be?" + checking to fax1_receive_tcf()
 *
 * Revision 4.7  2005/12/31 17:47:10  gert
 * use fax1_send_dis() instead of doing it here
 *
 * Revision 4.6  2005/12/31 15:52:08  gert
 * more comments
 * fax 1 receiver:
 *   * use global variable fax1_receive_have_connect to
 *     communicate answering status ("have we seen CONNECT?") from mgetty.c
 *   * prepare full T.30 receive state machine, including EOM->phase B
 *     and RTP/RTN->TCF (not complete)
 *   * set fax_hangup/fax_hangup_code correctly upon some error situations
 *
 * Revision 4.5  2005/12/30 23:05:05  gert
 * rework fax1_send_frame(): leading 0xff is now implicit
 *   (symmetric to fax1_receive_frame())
 *   change all callers to use new "frame" layout without 0xff
 * change lots of occurences of "3" to use "T30_CAR_V21"
 * rename fax1_receive() to fax1_highlevel_receive()
 * use "fd" and not "STDIN" everywhere
 * hand DCS frame to class1lib/fax1_parse_dcs()
 *   -> use proper carrier values for TCF and page reception (partially untested)
 * change "simple" frame sending to use fax1_send_simf_final()
 * add more comments about individual fax phases
 *
 * Revision 4.4  2005/12/28 21:53:08  gert
 * fix some compiler warnings and typos
 * adapt to changed fax1_send_idframe()
 * add CVS header
 * add (very rough and incomplete) fax class 1 implementation, consisting
 *  - fax1_receive() (to be called from faxrec.c)
 *  - fax1_receive_tcf() (handle TCF training frame + responses)
 *  - fax1_receive_page() (setup page reception, hand to fax_get_page_data())
 *
 */

#ifdef CLASS1

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>

#include "mgetty.h"
#include "fax_lib.h"
#include "tio.h"
#include "class1.h"
#include "policy.h"

enum T30_phases { Phase_A, Phase_B, Phase_C, Phase_D, Phase_E } fax1_phase;

/* maping of "st" codes to actual time (for normal res.) */
static int fax_scan_times[8] = { 0, 5, 10, 10, 20, 20, 40, 40 };

int fax1_dial_and_phase_AB _P2( (dial_cmd,fd),  char * dial_cmd, int fd )
{
char * p;			/* modem response */
uch framebuf[C1_FRAMESIZE];
int first;
int len;
#ifdef TORTURE_TEST
int t_tries=0;
#endif

    /* send dial command */
    if ( fax_send( dial_cmd, fd ) == ERROR )
    {
	fax_hangup = TRUE; fax_hangup_code = FHUP_ERROR;
	return ERROR;
    }

    /* wait for ERROR/NO CARRIER/CONNECT */
    signal( SIGALRM, fax1_sig_alarm );
    alarm(FAX_RESPONSE_TIMEOUT);

    while( !fax_hangup )
    {
        p = mdm_get_line ( fd );

	if ( p == NULL )
	    { lprintf( L_ERROR, "fax1_dial: hard error dialing out" );
	      fax_hangup = TRUE; fax_hangup_code = FHUP_ERROR; break; }

	lprintf( L_NOISE, "fax1_dial: string '%s'", p );

	if ( strcmp( p, "ERROR" ) == 0 ||
	     strcmp( p, "NO CARRIER" ) == 0 )
	    { fax_hangup = TRUE; fax_hangup_code = FHUP_ERROR; break; }

	if ( strcmp( p, "NO DIALTONE" ) == 0 ||
	     strcmp( p, "NO DIAL TONE" ) == 0 )
	    { fax_hangup = TRUE; fax_hangup_code = FHUP_NODIAL; break; }

	if ( strcmp( p, "BUSY" ) == 0 )
	    { fax_hangup = TRUE; fax_hangup_code = FHUP_BUSY; break; }

        if ( strcmp( p, "CONNECT" ) == 0 )		/* gotcha! */
	    { break; }
    }

    alarm(0);
    if ( fax_hangup ) return ERROR;

    /* now start fax negotiation (receive CSI, DIS, send DCS)
     * read all incoming frames until FINAL bit is set
     */
    first=TRUE;
again:
    do
    {
	if ( (len = fax1_receive_frame( fd, first? 0:3, 30, framebuf ) )
	       == ERROR )
	{
	    /*!!!! try 3 times! (flow diagram from T.30 / T30_T1 timeout) */
	    fax_hangup = TRUE; fax_hangup_code = 11; return ERROR;
	}
	switch ( framebuf[1] )		/* FCF */
	{
	    case T30_CSI: fax1_copy_id( framebuf ); break;
	    case T30_NSF: fax1_incoming_nsf( framebuf+2, len-2 ); break;
	    case T30_DIS: fax1_parse_dis( framebuf ); break;
	    case T30_DCN: fax1_send_dcn( fd, 20 ); return ERROR;
	    default:
	        lprintf( L_WARN, "unexpected frame type 0x%02x", framebuf[1] );
	}
	first=FALSE;
    }
    while( ( framebuf[0] & T30_FINAL ) == 0 );

#ifdef TORTURE_TEST
    while( ++t_tries < 3 )
	{ mdm_command( "AT+FRS=200", fd ); goto again; }
#endif


    fax1_phase = Phase_B;			/* Phase A done */

    return NOERROR;
}


/* fax1_send_page
 *
 * send a page of G3 data
 * - if phase is "B", include sending of DCS, TCF and possibly 
 *   baud rate stepdown and repeated transmission of DCS.
 * - if phase is "C", directly send page data
 */

int fax1_send_page _P5( (g3_file, bytes_sent, tio, ppm, fd),
		        char * g3_file, int * bytes_sent, TIO * tio,
		        Post_page_messages ppm, int fd )
{
uch framebuf[C1_FRAMESIZE];
char * line;
char cmd[40];
char dleetx[] = { DLE, ETX };
char rtc[] = { 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08 };
int pad_bytes, s_time;

    /* if we're in T.30 phase B, send DCS + training frame (TCF) now...
     * don't forget delay (75ms +/- 20ms)!
     *
     * NOTE: this is not strictly "phase B" - in T.30, phase B begins 
     * after sending the DCS, and before sending TCF.  But since RTN/RTP
     * return to sending DCS (bullet "D" in T.30 chart 5-2a), grouping it 
     * this way is much more logical to implement
     */

    /* calculate scan line time
     * in fine mode, some of the values get divided by 2
     */
    s_time = fax_scan_times[ remote_cap.st & 0x7 ];
    if ( remote_cap.vr == 1 && 
	(remote_cap.st & 0x1) == 0 ) s_time /= 2;

    if ( fax1_phase == Phase_B )
    {
        char train[150];
	int i, num;
	int tries=0;

retrain:			/* "(D)" in T.30/Figure 5-2a */

	/* send local id frame (TSI) */
	fax1_send_idframe( fd, T30_TSI|0x01, T30_CAR_V21 );

	/* send DCS */
	if ( fax1_send_dcs( fd, s_time ) == ERROR )
	{
	    fax_hangup = TRUE; fax_hangup_code = 10; return ERROR;
	}

	sprintf( cmd, "AT+FTS=8;+FTM=%d", dcs_btp->c_long );
	fax_send( cmd, fd );

	line = mdm_get_line( fd );
	if ( line != NULL && strcmp( line, cmd ) == 0 )
		line = mdm_get_line( fd );

	if ( strcmp( line, "OK" ) == 0 )
	{
	    lprintf( L_MESG, "fax1_send_page: unexpected OK, re-do AT+FTM" );

	    sprintf( cmd, "AT+FTM=%d", dcs_btp->c_long );
	    fax_send( cmd, fd );

	    line = mdm_get_line( fd );
	    if ( line != NULL && strcmp( line, cmd ) == 0 )
		line = mdm_get_line( fd );
	}

	if ( line == NULL || strcmp( line, "CONNECT" ) != 0 )
	{
	    lprintf( L_ERROR, "fax1_send_page: unexpected response 1: '%s'", line );
	    fax_hangup = TRUE; fax_hangup_code = 20; return ERROR;
	}

	/* send data for training (1.5s worth) */

	num = (dcs_btp->speed/8)*1.5;
	lprintf( L_NOISE, "fax1_send_page: send %d bytes training (TCF)", num );
	memset( train, 0, sizeof(train));

	for( i=0; i<num; i+=sizeof(train))
		write( fd, train, sizeof(train) );
	write( fd, dleetx, 2 );

	line = mdm_get_line( fd );
	if ( line == NULL || strcmp( line, "OK" ) != 0 )
	{
	    lprintf( L_ERROR, "fax1_send_page: unexpected response 2: '%s'", line );
	    fax_hangup = TRUE; fax_hangup_code = 20; return ERROR;
	}

	/* receive frame - FTT or CFR */
	/*!!! return code! TODO: retry logic - T.30 fig. 5-2a/p.16 */
	fax1_receive_frame( fd, 3, 30, framebuf );

	if ( ( framebuf[0] & T30_FINAL ) == 0 ||
	     framebuf[1] != T30_CFR )
	{
	    tries++;
	    lprintf( L_WARN, "fax1_send_frame: failed to train (%d)", tries);

	    if ( tries > 1 ) fax1_reduce_max();
	    if ( tries < 3 ) goto retrain;

	    /* give up */
	    fax1_send_dcn( fd, 27 ); return ERROR;
	}

	/* phase B done, go to phase C */
	fax1_phase = Phase_C;
    }

    if ( fax1_phase != Phase_C )
    {
        lprintf( L_ERROR, "fax1_send_page: internal error: not Phase C" );
	fax_hangup = TRUE; fax_hangup_code = FHUP_ERROR;
	return ERROR;
    }

    /* open G3 file, read first chunk, potentially skipping digifax header
     */
    if ( g3_open_read( g3_file ) < 0 )
    {
	/*!!! do something smart here...? */
	fax1_send_dcn( fd, FHUP_ERROR );
	return ERROR;
    }


    /* number of bytes to pad depend on scan time */
    pad_bytes = ((dcs_btp->speed/8) * s_time + 999) / 1000;
    lprintf( L_MESG, "scan line time: %d ms -> %d bytes/line at %d bps",
		s_time, pad_bytes, dcs_btp->speed );

    /* Phase C: send page data with high-speed carrier
     */
    sprintf( cmd, "AT+FTM=%d", dcs_btp->c_short );
    fax_send( cmd, fd );

    line = mdm_get_line( fd );
    if ( line != NULL && strcmp( line, cmd ) == 0 )
	    line = mdm_get_line( fd );

    if ( line == NULL || strcmp( line, "CONNECT" ) != 0 )
    {
	lprintf( L_ERROR, "fax1_send_page: unexpected response 3: '%s'", line );
	fax_hangup = TRUE; fax_hangup_code = 40; return ERROR;
    }

    lprintf( L_NOISE, "send page data (\"%s\")...", g3_file );

    /* turn on xon/xoff flow control now, for page data sending
     */
    if ( (FAXSEND_FLOW) & FLOW_SOFT )
    {
	tio_set_flow_control( fd, tio, (FAXSEND_FLOW) & (FLOW_HARD|FLOW_XON_OUT));
	tio_set( fd, tio );
    }

    /* read page data from file, invert byte order, 
     * insert padding bits (if scan line time > 0), 
     * at end-of-file, add RTC
     */
    if ( g3_send_file( g3_rf_chunk, fd, TRUE, TRUE, pad_bytes, 0 /*TODO!*/) < 0 )
    {
	lprintf( L_ERROR, "error in g3_send_file()" ); 
	return ERROR;
    }

    /*!!! ERROR HANDLING!! */
    /*!!! PARANOIA: alarm()!! */
    /* end of page: RTC */
    write( fd, rtc, sizeof(rtc) );

    /* end of data: DLE ETX */
    write( fd, dleetx, 2 );

    line = mdm_get_line( fd );
    if ( line == NULL || strcmp( line, "OK" ) != 0 )
    {
	lprintf( L_ERROR, "fax1_send_page: unexpected response 3a: '%s'", line );
	fax_hangup = TRUE; fax_hangup_code = 40; return ERROR;
    }

    /* turn off xon/xoff flow control (will interfere with received frames) */
    if ( (FAXSEND_FLOW) & FLOW_SOFT )
    {
	tio_set_flow_control( fd, tio, (FAXSEND_FLOW) & FLOW_HARD );
	tio_set( fd, tio );
    }

    /* now send end-of-page frame (MPS/EOM/EOP) and get pps */

    fax1_phase = Phase_D;
    lprintf( L_MESG, "page data sent, sending end-of-page frame (C->D)" );
    sprintf( cmd, "AT+FTS=8;+FTH=3" );
    fax_send( cmd, fd );

    line = mdm_get_line( fd );
    if ( line != NULL && strcmp( line, cmd ) == 0 )
	    line = mdm_get_line( fd );

    if ( line == NULL || strcmp( line, "CONNECT" ) != 0 )
    {
        if ( strcmp( line, "OK" ) == 0 ) goto tryanyway;
	lprintf( L_ERROR, "fax1_send_page: unexpected response 4: '%s'", line );
	fax_hangup = TRUE; fax_hangup_code = 50; return ERROR;
    }

    /* some modems seemingly can't handle AT+FTS=8;+FTH=3 (returning 
     * "OK" instead of "CONNECT"), so send AT+FTH=3 again for those.
     */
tryanyway:

    framebuf[0] = 0x03 | T30_FINAL;
    switch( ppm )
    {
        case pp_eom: framebuf[1] = T30_EOM | fax1_dis; break;
	case pp_eop: framebuf[1] = T30_EOP | fax1_dis; break;
	case pp_mps: framebuf[1] = T30_MPS | fax1_dis; break;
	default: 
	   lprintf( L_WARN, "fax1_send_page: canthappen(1) - PRI not supported" );
    }

    if ( strcmp( line, "OK" ) == 0 )	/* re-send AT+FTH=3 */
    {
        fax1_send_frame( fd, T30_CAR_V21, framebuf, 2 );
    }
    else				/* it got sent & acked */
    {
        fax1_send_frame( fd, T30_CAR_HAVE_V21, framebuf, 2 );
    }

    /* get MPS/RTP/RTN code */
    fax1_receive_frame( fd, T30_CAR_V21, 30, framebuf );

    /*!!! T.30 flow chart... */

    switch( framebuf[1] )
    {
        case T30_MCF:		/* page good */
		fax_page_tx_status = 1; fax1_phase = Phase_C; break;
	case T30_RTN:		/* retrain / negative */
		fax_page_tx_status = 2; fax1_phase = Phase_B; break;
	case T30_RTP:		/* retrain / positive */
		fax_page_tx_status = 3; fax1_phase = Phase_B; break;
	case T30_PIN:		/* procedure interrupt */
		fax_page_tx_status = 4; fax1_phase = Phase_B; break;
	case T30_PIP:
		fax_page_tx_status = 5; fax1_phase = Phase_C; break;
	default:
		lprintf( L_ERROR, "fax1_transmit_page: unexpected frame" );
		fax1_send_dcn(fd, 53); break;
    }

    /* if this was the last page, and the receiver is happy
     * (MCF or RTP), send DCN, and call it quits
     */
    if ( ppm == pp_eop && 
	 ( fax_page_tx_status == 1 || fax_page_tx_status == 3 || 
	   fax_page_tx_status == 5 ) )
    {
	fax1_send_dcn( fd, 0 ); 
	fax1_phase = Phase_E;
    }

    /* otherwise, nothing to do - caller will re-enter fax1_send_page(),
     * for next page / re-send of same page (we won't know)
     */
    return NOERROR;
}

boolean fax1_receive_have_connect = FALSE;

int fax1_receive_tcf _PROTO((int fd, int carrier, int wantbytes));
int fax1_receive_page _PROTO((int fd, int carrier, int * pagenum, 
			      char * dirlist, int uid, int gid, int mode ));

int fax1_highlevel_receive _P6( (fd, pagenum, dirlist, uid, gid, mode ),
			       int fd, int * pagenum, char * dirlist,
			       int uid, int gid, int mode)
{
int rc;
uch frame[C1_FRAMESIZE];
char * p;
Post_page_messages ppm = pp_mps;
boolean first = TRUE;
int tries;
boolean have_dcs;

    /* after ATA/CONNECT, first AT+FTH=3 is implicit -> send frames
       right away (first=TRUE) - when coming back to phase B after EOM,
       AT+FTH=3 must be sent */

    /* with +FAE=1, the sequence seems to be
     *  RING
     *     ATA
     *  FAX
     *  CONNECT
     */

    if ( !fax1_receive_have_connect )
    {
	alarm(10);
	p = mdm_get_line( fd );
	alarm(0);

	if ( p == NULL || strcmp( p, "CONNECT" ) != 0 )
	{
	    lprintf( L_WARN, "fax1_receive: initial CONNECT not seen" );
	    fax_hangup = TRUE; fax_hangup_code = FHUP_TIMEOUT;
	    return ERROR;
	}
    }

    *pagenum = 0;

    /* phase B: Fax negotiations
     */
    tries=0; have_dcs=FALSE;

receive_phase_b:
    lprintf( L_MESG, "fax1 T.30 receive phase B (try %d)", tries+1 );

    /* send NSF frame (if requested) */
    if ( modem_quirks & MQ_SHOW_NSF )
    {
	rc = fax1_send_nsf( fd, first?T30_CAR_HAVE_V21: T30_CAR_V21 );
    }

    /* send local ID frame (CSI) - non-final */
    rc = fax1_send_idframe( fd, T30_CSI, first?T30_CAR_HAVE_V21: T30_CAR_V21 );
    first = FALSE;

    /* send DIS (but only if we haven't seen an error sending CSI) */
    if ( rc == NOERROR )
        rc = fax1_send_dis( fd );

    /* now see what the other side has to say... */
wait_for_dcs:

    do
    {
        if ( fax1_receive_frame( fd, T30_CAR_V21, 30, frame ) == ERROR )
	{
	    if ( ++tries < 3 ) goto receive_phase_b;
	    fax1_send_dcn( fd, 70 );
	    return ERROR;
	}
	switch( frame[1] & 0xfe )		/* FCF, ignoring X-bit */
	{
	    case T30_TSI: fax1_copy_id( frame ); break;
	    case T30_NSF: break;
	    case T30_DCS: fax1_parse_dcs( frame ); have_dcs=TRUE; break;
	    case T30_DCN: fax1_send_dcn( fd, 70 ); 
			  return ERROR; break;
	    default:
		lprintf( L_WARN, "unexpected frame type 0x%02x", frame[1] );
	}
    }
    while( ( frame[0] & T30_FINAL ) == 0 );

    if ( !have_dcs )
    {
	lprintf( L_WARN, "T.30 B: final frame, but no DCS seen, re-send DIS" );
	if ( ++tries < 3 ) goto receive_phase_b;
	fax1_send_dcn( fd, 70 );
	return ERROR;
    }

    /* fax1_parse_dcs() has setup dcs_btp and fax_par_d for us */

    /* receive 1.5s training sequence */
    rc = fax1_receive_tcf( fd, dcs_btp->c_long, (dcs_btp->speed/8)*1.5 );

    /* error receiving TCF ("no carrier seen") -> redo DIS/DCS */
    if ( rc < 0 )
	goto receive_phase_b;

    /* TCF bad? send FTT (failure to train), wait for next DCS */
    if ( rc == 0 )
    {
	rc = fax1_send_simf_final( fd, T30_CAR_V21, T30_FTT );
	if ( ++tries < 10 ) goto wait_for_dcs;
	fax1_send_dcn( fd, 73 );
	return ERROR;
    }

    /* TCF good, send CFR frame (confirmation to receive) */
    rc = fax1_send_simf_final( fd, T30_CAR_V21, T30_CFR );

receive_next_page:

    /* phase C: start page reception & get page data 
     */
    lprintf( L_MESG, "fax1 T.30 receive phase C" );

    rc = fax1_receive_page( fd, dcs_btp->c_short, pagenum, 
			    dirlist, uid, gid, mode );

    /* if we have already hung up, not worth doing any retries etc.
     */
    if ( rc == ERROR && fax_hangup ) return ERROR;

    /* phase D: post-message status
     * switch back to low-speed carrier, get (PRI-)EOM/MPS/EOP code
     */

    tries=0;
receive_phase_d:
    lprintf( L_MESG, "fax1 T.30 receive phase D" );

    /* TODO: T.30 flow chart: will we ever hit non-final frames here?
     * what to do on error?
     */
    do
    {
        if ( fax1_receive_frame( fd, T30_CAR_V21, 30, frame ) == ERROR )
	{
	    /* retry post-page handshake 3 times, then give up */
	    if ( ++tries < 3 ) goto receive_phase_d;
	    fax1_send_dcn( fd, 100 );
	    return ERROR;
	}
	switch( frame[1] & 0xfe & ~T30_PRI )	/* FCF, ignoring X-bit */
	{
	    case T30_MPS: 
	        lprintf( L_MESG, "MPS: end of page, more to come" ); ppm = pp_mps;
	        break;
	    case T30_EOM: 
		lprintf( L_MESG, "EOM: back to phase B" ); ppm = pp_eom;
		break;
	    case T30_EOP: 
		lprintf( L_MESG, "EOP: end of transmission" ); 
		ppm = pp_eop;
		break;
	    case T30_DCN: 
		fax1_send_dcn( fd, 101 ); 
	        return ERROR; 
		break;
	    default:
		lprintf( L_WARN, "unexpected frame type 0x%02x", frame[1] );
	}
    }
    while( ( frame[0] & T30_FINAL ) == 0 );

    /* send back page good/bad return code (TODO: RTN/RTP codes) */
    /* TODO: for RTN/RTP, restart training sequence (TCF) (??) */
    rc = fax1_send_simf_final( fd, T30_CAR_V21, T30_MCF );

    /* go back to phase B (EOM), go to next page (MPS), done (EOP) */
    if ( ppm == pp_eom )
	{ goto receive_phase_b; }
    if ( ppm == pp_mps )
	{ goto receive_next_page; }

    /* EOP - get goodbye frame (DCN) from remote end, hang up */
    fax1_receive_frame( fd, T30_CAR_V21, 30, frame );

    if ( (frame[1] & 0xfe) != T30_DCN )
    {
	lprintf( L_WARN, "fax1_receive: unexpected frame 0x%02x 0x%02x after EOP", frame[0], frame[1] );
    }

    fax_hangup = TRUE;
    fax_hangup_code = 0;
    return NOERROR;
}

int fax1_receive_tcf _P3((fd,carrier,wantbytes), 
			  int fd, int carrier, int wantbytes)
{
int rc, count, notnull;
char c, *p;
boolean wasDLE=FALSE;
int tries;

    tries=0;

    lprintf( L_NOISE, "fax1_r_tcf: carrier=%d", carrier );

get_carrier:
    rc = fax1_init_FRM( fd, carrier );
    if ( rc == ERROR ) 
    {
	while( ++tries<3 ) goto get_carrier;
	return ERROR;
    }

    /* TODO: proper timeout settings as per T.30 (?) */
    alarm(5);
    count = notnull = 0;

    lprintf( L_JUNK, "fax1_r_tcf: got: " );
    while(1)
    {
	if ( mdm_read_byte( fd, &c ) != 1 ) 
	{
	    /* timeout? corrupted DLE/ETX?  let caller send FTT and retry */
	    lprintf( L_ERROR, "fax1_r_tcf: cannot read byte, return" );
	    return 0;
	}
	if ( c != 0 ) lputc( L_JUNK, c );

	if ( wasDLE ) 
	{
	    if ( c == ETX ) break;
	    wasDLE = 0;
	}
	if ( c == DLE ) { wasDLE = 1; continue; }

	count++;
	if ( c != 0 ) notnull++;
    }

    /* read post-frame "NO CARRIER" message */
    p = mdm_get_line( fd );
    alarm(0);
    if ( p == NULL || strcmp( p, "NO CARRIER" ) != 0 )
	lprintf( L_WARN, "unexpected post-TCF modem response: '%s'", p );

    if ( count < (wantbytes*3)/4 ||		/* not enough bytes */
	 notnull > count/10  )			/* or too many errors */
    {
	lprintf( L_NOISE, "TCF: want %d, got %d, %d non-null -> retry",
		 wantbytes, count, notnull );
	return 0;				/* try again */
    }

    lprintf( L_NOISE, "TCF: want %d, got %d, %d non-null -> OK", 
	     wantbytes, count, notnull );
    return 1;					/* acceptable, go ahead */
}

int fax1_receive_page _P7( (fd,carrier,pagenum,dirlist,uid,gid,mode), 
				int fd, int carrier,
				int * pagenum, char * dirlist,
				int uid, int gid, int mode )
{
int rc;
char *p;
int tries;

char directory[MAXPATH];

    fax_find_directory( dirlist, directory, sizeof(directory) );

    tries=0;

    lprintf( L_NOISE, "fax1_rp: carrier=%d", carrier );

get_carrier:
    /* TODO: proper timeout settings as per T.30 */
    alarm(10);
    rc = fax1_init_FRM( fd, carrier );
    alarm(0);

    if ( rc == ERROR ) 
    { 
	if ( ++tries < 3 ) goto get_carrier;
	fax1_send_dcn( fd, 90 );
	return ERROR;
    }

    /* now get page data (common function for class 1 and class 2)
     * note: fax_get_page_data() has its own alarm/timeout handling
     */
    rc = fax_get_page_data( fd, ++(*pagenum), directory, uid, gid, mode );

    /* read post-page "NO CARRIER" message */
    alarm(10);
    p = mdm_get_line( fd );
    alarm(0);
    if ( p == NULL || strcmp( p, "NO CARRIER" ) != 0 )
	lprintf( L_WARN, "unexpected post-page modem response: '%s'", p );

    if ( rc == ERROR )
	{ fax1_send_dcn( fd, 90 ); return ERROR; }

    /* TODO: copy quality checking */

    return NOERROR;
}

#endif /* CLASS 1 */