home *** CD-ROM | disk | FTP | other *** search
/ The Hacker's Encyclopedia 1998 / hackers_encyclopedia.iso / zines / phrack2 / p50_13.txt < prev    next >
Encoding:
Text File  |  2003-06-11  |  16.0 KB  |  686 lines

  1.                                 .oO Phrack 50 Oo.
  2.  
  3.                             Volume Seven, Issue Fifty
  4.  
  5.                                      13 of 16
  6.  
  7.                          ===============================
  8.                          DTMF Encoding and Decoding In C
  9.                                     by Mr. Blue
  10.                          ===============================
  11.  
  12.   
  13. Introduction
  14. ------------
  15.     DTMF tones are the sounds emitted when you dial a number on your touch 
  16. tone phone.  Modems have traditionally been the device used to generate
  17. these tones from a computer.  But the more sophisticated modems on the
  18. market today are nothing more than a DSP (digital signal processor) with
  19. accompanying built-in software to generate and interpet analog sounds into
  20. digital data.  The computers sitting on your desk have more cpu power,
  21. a more complex OS, and very often a just as sophisticated DSP.  There is
  22. no reason you can not duplicate the functionality of a modem from right
  23. inside of unix software, providing you with a lot easier to understand and
  24. modify code.  
  25.  
  26.     In this article I provide the source code to both encode and decode
  27. DTMF tones.  There are numerous uses for this code, for use in unix based 
  28. phone scanning and war dialing programs, voice mail software, automated
  29. pbx brute force hacking, and countless other legitimate and not so
  30. legitimate uses.
  31.  
  32.     I will not go into depth explaining the underlying mathematical
  33. theories behind this code.  If you are of a sufficient math background I
  34. would encourage you to research and learn about the algorithms used from
  35. your local college library; it is not my intent to summarize these
  36. algorithms, only to provide unix C code that can be used on its own or
  37. expanded to be used as part of a larger program.  
  38.  
  39.     Use the extract utility included with Phrack to save the individual
  40. source files out to the dtmf/ directory.  If you find this code useful, I
  41. would encourage you to show your appreciation by sharing some of your own
  42. knowledge with Phrack.    
  43.  
  44. <++> dtmf/detect.h
  45. /* 
  46.  *
  47.  * goertzel aglorithm, find the power of different
  48.  * frequencies in an N point DFT.
  49.  *
  50.  * ftone/fsample = k/N   
  51.  * k and N are integers.  fsample is 8000 (8khz)
  52.  * this means the *maximum* frequency resolution
  53.  * is fsample/N (each step in k corresponds to a
  54.  * step of fsample/N hz in ftone)
  55.  *
  56.  * N was chosen to minimize the sum of the K errors for
  57.  * all the tones detected...  here are the results :
  58.  *
  59.  * Best N is 240, with the sum of all errors = 3.030002
  60.  * freq  freq actual   k     kactual  kerr
  61.  * ---- ------------  ------ ------- -----
  62.  *  350 (366.66667)   10.500 (11)    0.500
  63.  *  440 (433.33333)   13.200 (13)    0.200
  64.  *  480 (466.66667)   14.400 (14)    0.400
  65.  *  620 (633.33333)   18.600 (19)    0.400
  66.  *  697 (700.00000)   20.910 (21)    0.090
  67.  *  700 (700.00000)   21.000 (21)    0.000
  68.  *  770 (766.66667)   23.100 (23)    0.100
  69.  *  852 (866.66667)   25.560 (26)    0.440
  70.  *  900 (900.00000)   27.000 (27)    0.000
  71.  *  941 (933.33333)   28.230 (28)    0.230
  72.  * 1100 (1100.00000)  33.000 (33)    0.000
  73.  * 1209 (1200.00000)  36.270 (36)    0.270
  74.  * 1300 (1300.00000)  39.000 (39)    0.000
  75.  * 1336 (1333.33333)  40.080 (40)    0.080
  76.  **** I took out 1477.. too close to 1500
  77.  * 1477 (1466.66667)  44.310 (44)    0.310
  78.  ****
  79.  * 1500 (1500.00000)  45.000 (45)    0.000
  80.  * 1633 (1633.33333)  48.990 (49)    0.010
  81.  * 1700 (1700.00000)  51.000 (51)    0.000
  82.  * 2400 (2400.00000)  72.000 (72)    0.000
  83.  * 2600 (2600.00000)  78.000 (78)    0.000
  84.  *
  85.  * notice, 697 and 700hz are indestinguishable (same K)
  86.  * all other tones have a seperate k value.  
  87.  * these two tones must be treated as identical for our
  88.  * analysis.
  89.  *
  90.  * The worst tones to detect are 350 (error = 0.5, 
  91.  * detet 367 hz) and 852 (error = 0.44, detect 867hz). 
  92.  * all others are very close.
  93.  *
  94.  */
  95.  
  96. #define FSAMPLE  8000
  97. #define N        240
  98.  
  99. int k[] = { 11, 13, 14, 19, 21, 23, 26, 27, 28, 33, 36, 39, 40,
  100.  /*44,*/ 45, 49, 51, 72, 78, };
  101.  
  102. /* coefficients for above k's as:
  103.  *   2 * cos( 2*pi* k/N )
  104.  */
  105. float coef[] = {
  106. 1.917639, 1.885283, 1.867161, 1.757634, 
  107. 1.705280, 1.648252, 1.554292, 1.520812, 1.486290, 
  108. 1.298896, 1.175571, 1.044997, 1.000000, /* 0.813473,*/ 
  109. 0.765367, 0.568031, 0.466891, -0.618034, -0.907981,  };
  110.  
  111. #define X1    0    /* 350 dialtone */
  112. #define X2    1    /* 440 ring, dialtone */
  113. #define X3    2    /* 480 ring, busy */
  114. #define X4    3    /* 620 busy */
  115.  
  116. #define R1    4    /* 697, dtmf row 1 */
  117. #define R2    5    /* 770, dtmf row 2 */
  118. #define R3    6    /* 852, dtmf row 3 */
  119. #define R4    8    /* 941, dtmf row 4 */
  120. #define C1   10    /* 1209, dtmf col 1 */
  121. #define C2   12    /* 1336, dtmf col 2 */
  122. #define C3   13    /* 1477, dtmf col 3 */
  123. #define C4   14    /* 1633, dtmf col 4 */
  124.  
  125. #define B1    4    /* 700, blue box 1 */
  126. #define B2    7    /* 900, bb 2 */
  127. #define B3    9    /* 1100, bb 3 */
  128. #define B4   11    /* 1300, bb4 */
  129. #define B5   13    /* 1500, bb5 */
  130. #define B6   15    /* 1700, bb6 */
  131. #define B7   16    /* 2400, bb7 */
  132. #define B8   17    /* 2600, bb8 */
  133.  
  134. #define NUMTONES 18 
  135.  
  136. /* values returned by detect 
  137.  *  0-9     DTMF 0 through 9 or MF 0-9
  138.  *  10-11   DTMF *, #
  139.  *  12-15   DTMF A,B,C,D
  140.  *  16-20   MF last column: C11, C12, KP1, KP2, ST
  141.  *  21      2400
  142.  *  22      2600
  143.  *  23      2400 + 2600
  144.  *  24      DIALTONE
  145.  *  25      RING
  146.  *  26      BUSY
  147.  *  27      silence
  148.  *  -1      invalid
  149.  */
  150. #define D0    0
  151. #define D1    1
  152. #define D2    2
  153. #define D3    3
  154. #define D4    4
  155. #define D5    5
  156. #define D6    6
  157. #define D7    7
  158. #define D8    8
  159. #define D9    9
  160. #define DSTAR 10
  161. #define DPND  11
  162. #define DA    12
  163. #define DB    13
  164. #define DC    14
  165. #define DD    15
  166. #define DC11  16
  167. #define DC12  17
  168. #define DKP1  18
  169. #define DKP2  19
  170. #define DST   20
  171. #define D24   21 
  172. #define D26   22
  173. #define D2426 23
  174. #define DDT   24
  175. #define DRING 25
  176. #define DBUSY 26
  177. #define DSIL  27
  178.  
  179. /* translation of above codes into text */
  180. char *dtran[] = {
  181.   "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
  182.   "*", "#", "A", "B", "C", "D", 
  183.   "+C11 ", "+C12 ", " KP1+", " KP2+", "+ST ",
  184.   " 2400 ", " 2600 ", " 2400+2600 ",
  185.   " DIALTONE ", " RING ", " BUSY ","" };
  186.  
  187. #define RANGE  0.1           /* any thing higher than RANGE*peak is "on" */
  188. #define THRESH 100.0         /* minimum level for the loudest tone */
  189. #define FLUSH_TIME 100       /* 100 frames = 3 seconds */
  190.  
  191. <-->
  192. <++> dtmf/detect.c
  193.  
  194. /*
  195.  * detect.c
  196.  * This program will detect MF tones and normal
  197.  * dtmf tones as well as some other common tones such
  198.  * as BUSY, DIALTONE and RING.
  199.  * The program uses a goertzel algorithm to detect
  200.  * the power of various frequency ranges.
  201.  *
  202.  * input is assumed to be 8 bit samples.  The program
  203.  * can use either signed or unsigned samples according
  204.  * to a compile time option:
  205.  *
  206.  *    cc  -DUNSIGNED detect.c -o detect
  207.  *
  208.  * for unsigned input (soundblaster) and:
  209.  *
  210.  *    cc  detect.c -o detect
  211.  *
  212.  * for signed input (amiga samples)
  213.  * if you dont want flushes,  -DNOFLUSH
  214.  * 
  215.  *                            Tim N.
  216.  */
  217.  
  218. #include <stdio.h>
  219. #include <math.h>
  220. #include "detect.h"
  221.  
  222. /*
  223.  * calculate the power of each tone according
  224.  * to a modified goertzel algorithm described in
  225.  *  _digital signal processing applications using the
  226.  *  ADSP-2100 family_ by Analog Devices
  227.  *
  228.  * input is 'data',  N sample values
  229.  *
  230.  * ouput is 'power', NUMTONES values
  231.  *  corresponding to the power of each tone 
  232.  */
  233. calc_power(data,power)
  234. #ifdef UNSIGNED
  235. unsigned char *data;
  236. #else
  237. char *data;
  238. #endif
  239. float *power;
  240. {
  241.   float u0[NUMTONES],u1[NUMTONES],t,in;
  242.   int i,j;
  243.   
  244.   for(j=0; j<NUMTONES; j++) {
  245.     u0[j] = 0.0;
  246.     u1[j] = 0.0;
  247.   }
  248.   for(i=0; i<N; i++) {   /* feedback */
  249. #ifdef UNSIGNED
  250.     in = ((int)data[i] - 128) / 128.0;
  251. #else
  252.     in = data[i] / 128.0;
  253. #endif
  254.     for(j=0; j<NUMTONES; j++) {
  255.       t = u0[j];
  256.       u0[j] = in + coef[j] * u0[j] - u1[j];
  257.       u1[j] = t;
  258.     }
  259.   }
  260.   for(j=0; j<NUMTONES; j++)   /* feedforward */
  261.     power[j] = u0[j] * u0[j] + u1[j] * u1[j] - coef[j] * u0[j] * u1[j]; 
  262.   return(0);
  263. }
  264.  
  265.  
  266. /*
  267.  * detect which signals are present.
  268.  *
  269.  * return values defined in the include file
  270.  * note: DTMF 3 and MF 7 conflict.  To resolve
  271.  * this the program only reports MF 7 between
  272.  * a KP and an ST, otherwise DTMF 3 is returned
  273.  */
  274. decode(data)
  275. char *data;
  276. {
  277.   float power[NUMTONES],thresh,maxpower;
  278.   int on[NUMTONES],on_count;
  279.   int bcount, rcount, ccount;
  280.   int row, col, b1, b2, i;
  281.   int r[4],c[4],b[8];
  282.   static int MFmode=0;
  283.   
  284.   calc_power(data,power);
  285.   for(i=0, maxpower=0.0; i<NUMTONES;i++)
  286.     if(power[i] > maxpower)
  287.       maxpower = power[i]; 
  288. /*
  289. for(i=0;i<NUMTONES;i++) 
  290.   printf("%f, ",power[i]);
  291. printf("\n");
  292. */
  293.  
  294.   if(maxpower < THRESH)  /* silence? */ 
  295.     return(DSIL);
  296.   thresh = RANGE * maxpower;    /* allowable range of powers */
  297.   for(i=0, on_count=0; i<NUMTONES; i++) {
  298.     if(power[i] > thresh) { 
  299.       on[i] = 1;
  300.       on_count ++;
  301.     } else
  302.       on[i] = 0;
  303.   }
  304.  
  305. /*
  306. printf("%4d: ",on_count);
  307. for(i=0;i<NUMTONES;i++)
  308.   putchar('0' + on[i]);
  309. printf("\n");
  310. */
  311.  
  312.   if(on_count == 1) {
  313.     if(on[B7]) 
  314.       return(D24);
  315.     if(on[B8])
  316.       return(D26);
  317.     return(-1);
  318.   }
  319.  
  320.   if(on_count == 2) {
  321.     if(on[X1] && on[X2])
  322.       return(DDT);
  323.     if(on[X2] && on[X3])
  324.       return(DRING);
  325.     if(on[X3] && on[X4])
  326.       return(DBUSY);
  327.     
  328.     b[0]= on[B1]; b[1]= on[B2]; b[2]= on[B3]; b[3]= on[B4];
  329.     b[4]= on[B5]; b[5]= on[B6]; b[6]= on[B7]; b[7]= on[B8];
  330.     c[0]= on[C1]; c[1]= on[C2]; c[2]= on[C3]; c[3]= on[C4];
  331.     r[0]= on[R1]; r[1]= on[R2]; r[2]= on[R3]; r[3]= on[R4];
  332.  
  333.     for(i=0, bcount=0; i<8; i++) {
  334.       if(b[i]) {
  335.         bcount++;
  336.         b2 = b1;
  337.         b1 = i;
  338.       }
  339.     }
  340.     for(i=0, rcount=0; i<4; i++) {
  341.       if(r[i]) {
  342.         rcount++;
  343.         row = i;
  344.       }
  345.     }
  346.     for(i=0, ccount=0; i<4; i++) {
  347.       if(c[i]) {
  348.         ccount++;
  349.         col = i;
  350.       }
  351.     }
  352.  
  353.     if(rcount==1 && ccount==1) {   /* DTMF */
  354.       if(col == 3)  /* A,B,C,D */
  355.         return(DA + row);
  356.       else {
  357.         if(row == 3 && col == 0 ) 
  358.            return(DSTAR);
  359.         if(row == 3 && col == 2 )
  360.            return(DPND);
  361.         if(row == 3)
  362.            return(D0);
  363.         if(row == 0 && col == 2) {   /* DTMF 3 conflicts with MF 7 */
  364.           if(!MFmode)
  365.             return(D3);
  366.         } else 
  367.           return(D1 + col + row*3);
  368.       }
  369.     }
  370.  
  371.     if(bcount == 2) {       /* MF */
  372.       /* b1 has upper number, b2 has lower */
  373.       switch(b1) {
  374.         case 7: return( (b2==6)? D2426: -1); 
  375.         case 6: return(-1);
  376.         case 5: if(b2==2 || b2==3)  /* KP */
  377.                   MFmode=1;
  378.                 if(b2==4)  /* ST */
  379.                   MFmode=0; 
  380.                 return(DC11 + b2);
  381.         /* MF 7 conflicts with DTMF 3, but if we made it
  382.          * here then DTMF 3 was already tested for 
  383.          */
  384.         case 4: return( (b2==3)? D0: D7 + b2);
  385.         case 3: return(D4 + b2);
  386.         case 2: return(D2 + b2);
  387.         case 1: return(D1);
  388.       }
  389.     }
  390.     return(-1);
  391.   }
  392.  
  393.   if(on_count == 0)
  394.     return(DSIL);
  395.   return(-1); 
  396. }
  397.  
  398. read_frame(fd,buf)
  399. int fd;
  400. char *buf;
  401. {
  402.   int i,x;
  403.  
  404.   for(i=0; i<N; ) {
  405.     x = read(fd, &buf[i], N-i);
  406.     if(x <= 0) 
  407.       return(0);
  408.     i += x;
  409.   } 
  410.   return(1);
  411. }
  412.  
  413. /*
  414.  * read in frames, output the decoded
  415.  * results
  416.  */
  417. dtmf_to_ascii(fd1, fd2)
  418. int fd1;
  419. FILE *fd2;
  420. {
  421.   int x,last= DSIL;
  422.   char frame[N+5];
  423.   int silence_time;
  424.  
  425.   while(read_frame(fd1, frame)) {
  426.     x = decode(frame); 
  427. /*
  428. if(x== -1) putchar('-');
  429. if(x==DSIL) putchar(' ');
  430. if(x!=DSIL && x!=-1) putchar('a' + x);
  431. fflush(stdout);
  432. continue;
  433. */
  434.  
  435.     if(x >= 0) {
  436.       if(x == DSIL)
  437.         silence_time += (silence_time>=0)?1:0 ;
  438.       else
  439.         silence_time= 0;
  440.       if(silence_time == FLUSH_TIME) {
  441.         fputs("\n",fd2);
  442.         silence_time= -1;   /* stop counting */
  443.       }
  444.  
  445.       if(x != DSIL && x != last &&
  446.          (last == DSIL || last==D24 || last == D26 ||
  447.           last == D2426 || last == DDT || last == DBUSY ||
  448.           last == DRING) )  { 
  449.         fputs(dtran[x], fd2);
  450. #ifndef NOFLUSH
  451.         fflush(fd2);
  452. #endif
  453.       }
  454.       last = x;
  455.     }
  456.   }
  457.   fputs("\n",fd2);
  458. }
  459.  
  460. main(argc,argv) 
  461. int argc;
  462. char **argv;
  463. {
  464.   FILE *output;
  465.   int input;
  466.  
  467.   input = 0;
  468.   output = stdout;
  469.   switch(argc) {
  470.     case 1:  break;
  471.     case 3:  output = fopen(argv[2],"w");
  472.              if(!output) {
  473.                perror(argv[2]);
  474.                return(-1);
  475.              }
  476.              /* fall through */
  477.     case 2:  input = open(argv[1],0);
  478.              if(input < 0) {
  479.                perror(argv[1]);
  480.                return(-1);
  481.              }
  482.              break;
  483.      default:
  484.         fprintf(stderr,"usage:  %s [input [output]]\n",argv[0]);
  485.         return(-1);
  486.   }
  487.   dtmf_to_ascii(input,output);
  488.   fputs("Done.\n",output);
  489.   return(0);
  490. }
  491.  
  492. <-->
  493. <++> dtmf/gen.c
  494.  
  495. /* -------- local defines (if we had more.. seperate file) ----- */
  496. #define FSAMPLE   8000   /* sampling rate, 8KHz */
  497.  
  498. /*
  499.  * FLOAT_TO_SAMPLE converts a float in the range -1.0 to 1.0 
  500.  * into a format valid to be written out in a sound file
  501.  * or to a sound device 
  502.  */
  503. #ifdef SIGNED
  504. #  define FLOAT_TO_SAMPLE(x)    ((char)((x) * 127.0))
  505. #else
  506. #  define FLOAT_TO_SAMPLE(x)    ((char)((x + 1.0) * 127.0))
  507. #endif
  508.  
  509. #define SOUND_DEV  "/dev/dsp"
  510. typedef char sample;
  511. /* --------------------------------------------------------------- */
  512.  
  513. #include <fcntl.h>
  514.  
  515. /*
  516.  * take the sine of x, where x is 0 to 65535 (for 0 to 360 degrees)
  517.  */
  518. float mysine(in)
  519. short in;
  520. {
  521.   static coef[] = {
  522.      3.140625, 0.02026367, -5.325196, 0.5446778, 1.800293 };
  523.   float x,y,res;
  524.   int sign,i;
  525.  
  526.   if(in < 0) {       /* force positive */
  527.     sign = -1;
  528.     in = -in;
  529.   } else
  530.     sign = 1;
  531.   if(in >= 0x4000)      /* 90 degrees */
  532.     in = 0x8000 - in;   /* 180 degrees - in */
  533.   x = in * (1/32768.0); 
  534.   y = x;               /* y holds x^i) */
  535.   res = 0;
  536.   for(i=0; i<5; i++) {
  537.     res += y * coef[i];
  538.     y *= x;
  539.   }
  540.   return(res * sign); 
  541. }
  542.  
  543. /*
  544.  * play tone1 and tone2 (in Hz)
  545.  * for 'length' milliseconds
  546.  * outputs samples to sound_out
  547.  */
  548. two_tones(sound_out,tone1,tone2,length)
  549. int sound_out;
  550. unsigned int tone1,tone2,length;
  551. {
  552. #define BLEN 128
  553.   sample cout[BLEN];
  554.   float out;
  555.   unsigned int ad1,ad2;
  556.   short c1,c2;
  557.   int i,l,x;
  558.    
  559.   ad1 = (tone1 << 16) / FSAMPLE;
  560.   ad2 = (tone2 << 16) / FSAMPLE;
  561.   l = (length * FSAMPLE) / 1000;
  562.   x = 0;
  563.   for( c1=0, c2=0, i=0 ;
  564.        i < l;
  565.        i++, c1+= ad1, c2+= ad2 ) {
  566.     out = (mysine(c1) + mysine(c2)) * 0.5;
  567.     cout[x++] = FLOAT_TO_SAMPLE(out);
  568.     if (x==BLEN) {
  569.       write(sound_out, cout, x * sizeof(sample));
  570.       x=0;
  571.     }
  572.   }
  573.   write(sound_out, cout, x);
  574. }
  575.  
  576. /*
  577.  * silence on 'sound_out'
  578.  * for length milliseconds
  579.  */
  580. silence(sound_out,length)
  581. int sound_out;
  582. unsigned int length;
  583. {
  584.   int l,i,x;
  585.   static sample c0 = FLOAT_TO_SAMPLE(0.0);
  586.   sample cout[BLEN];
  587.  
  588.   x = 0;
  589.   l = (length * FSAMPLE) / 1000;
  590.   for(i=0; i < l; i++) {
  591.     cout[x++] = c0;
  592.     if (x==BLEN) {
  593.       write(sound_out, cout, x * sizeof(sample));
  594.       x=0;
  595.     }
  596.   }
  597.   write(sound_out, cout, x);
  598. }
  599.  
  600. /*
  601.  * play a single dtmf tone
  602.  * for a length of time,
  603.  * input is 0-9 for digit, 10 for * 11 for #
  604.  */
  605. dtmf(sound_fd, digit, length)
  606. int sound_fd;
  607. int digit, length;
  608. {
  609.   /* Freqs for 0-9, *, # */
  610.   static int row[] = {
  611.     941, 697, 697, 697, 770, 770, 770, 852, 852, 852, 941, 941 };
  612.   static int col[] = {
  613.     1336, 1209, 1336, 1477, 1209, 1336, 1477, 1209, 1336, 1447,
  614.     1209, 1477 };
  615.  
  616.   two_tones(sound_fd, row[digit], col[digit], length);
  617. }
  618.  
  619. /*
  620.  * take a string and output as dtmf
  621.  * valid characters, 0-9, *, #
  622.  * all others play as 50ms silence 
  623.  */
  624. dial(sound_fd, number)
  625. int sound_fd;
  626. char *number;
  627. {
  628.   int i,x;
  629.   char c;
  630.  
  631.   for(i=0;number[i];i++) {
  632.      c = number[i];
  633.      x = -1;
  634.      if(c >= '0' && c <= '9')
  635.        x = c - '0';
  636.      else if(c == '*')
  637.        x = 10;
  638.      else if(c == '#')
  639.        x = 11;
  640.      if(x >= 0)
  641.        dtmf(sound_fd, x, 50);
  642.      silence(sound_fd,50);
  643.   }
  644. }
  645.  
  646. main()
  647. {
  648.   int sfd;
  649.   char number[100];
  650.  
  651.   sfd = open(SOUND_DEV,O_RDWR);
  652.   if(sfd<0) {
  653.     perror(SOUND_DEV);
  654.     return(-1);
  655.   }
  656.   printf("Enter fone number: ");
  657.   gets(number);
  658.   dial(sfd,number);
  659. }
  660. <-->
  661. <++> dtmf/Makefile
  662. #
  663. # Defines:
  664. #  UNSIGNED  -  use unsigned 8 bit samples
  665. #               otherwise use signed 8 bit samples
  666. #
  667.  
  668. CFLAGS= -DUNSIGNED
  669.  
  670. default:    detect gen
  671.  
  672. detect: detect.c
  673.     $(CC) detect.c -o detect
  674.  
  675. gen:    gen.c
  676.     $(CC) gen.c -o gen
  677.  
  678. clobber: clean
  679.     rm -rf detect gen 
  680.  
  681. clean:
  682.     rm -rf *.o core a.out
  683. <-->
  684.  
  685. EOF
  686.