home *** CD-ROM | disk | FTP | other *** search
/ Millennium Time Capsule / AC2000.BIN / disks / hbasic_1 / texts / mod_prog.txt < prev    next >
Encoding:
Text File  |  1997-11-18  |  28.2 KB  |  755 lines

  1. Modem programming guide
  2.  
  3. The Atari ST modem programming guide
  4. October 12, 1991
  5.  
  6. Copyright 1991 Steve Yelvington
  7.         Internet: <steve@thelake.mn.org>
  8.         GEnie:    S.YELVINGTO2
  9.  
  10.  
  11. Introduction
  12. ------------
  13.  
  14. Modem i/o on the ST is isn't difficult. In fact, compared with other
  15. personal systems, the ST is unusually easy to program for serial
  16. communications.
  17.  
  18. The hard work is done for you. TOS includes interrupt-driven serial
  19. port routines. This means that when a character arrives at the serial
  20. port, TOS breaks away from whatever it's doing, grabs the character,
  21. stores it in a buffer, and returns to the task that was interrupted.
  22. You don't need to write code to do that.
  23.  
  24. The serial port buffers -- one for input, one for output -- are FIFO
  25. (First In, First Out) queues. TOS provides functions for reading from
  26. the input buffer, writing to the output buffer, and even changing the
  27. size of the buffers.
  28.  
  29. All you have to do is read from or write to the serial port as if it
  30. were a console.
  31.  
  32. This document contains examples in C. If you're programming in another
  33. language, don't despair, because C is used here only to string together
  34. TOS functions. The strategies don't change when you shift to another
  35. system of notation.
  36.  
  37. The modem i/o routines
  38. ----------------------
  39.  
  40. Both BIOS and GEMDOS provide functions for single-character serial
  41. i/o that work exactly like their console equivalents.
  42.  
  43. >From GEMDOS:
  44.  
  45.    Console      Serial         Description
  46.    -------      ------         -----------
  47.    Cconin                      Read a character with echo
  48.    Cnecin       Cauxin         Read a character without echo
  49.    Cconout      Cauxout        Write a character
  50.    Cconis       Cauxis         Return TRUE if a character is available         
  51.    Cconos       Cauxos         Return TRUE if output is possible, i.e.,
  52.                                the output buffer has room for more data
  53.  
  54. There is not a serial port equivalent for Cconws, but you should be
  55. able to write one easily enough:
  56.  
  57. #include <osbind.h>
  58. void Cauxws(s) /* write a nul-terminated string to the aux port */
  59.         register char *s;
  60.         {
  61.         register char c;
  62.         while (c=*s++)
  63.                 Cauxout(c);
  64.         }
  65.  
  66.  
  67. In C, GEMDOS functions usually are implemented as preprocessor
  68. macros that translate into gemdos() function calls. Other
  69. languages may require that you write GEMDOS calls explicitly. If
  70. you're coding in assembler, you should be able to translate the
  71. following by keeping in mind that you push the arguments onto
  72. the stack from right to left and then call trap #1 for gemdos.
  73.  
  74. Here are common C preprocessor definitions for the
  75. above-mentioned operations (plus a few more).
  76.  
  77. #define Cconin()        (long)  gemdos(0x1)
  78. #define Cconout(a)      (void)  gemdos(0x2,(short)(a))
  79. #define Cauxin()        (short) gemdos(0x3)
  80. #define Cauxout(a)      (void)  gemdos(0x4,(short)(a))
  81. #define Cprnout(a)      (void)  gemdos(0x5,(short)(a))
  82. #define Cnecin()        (long)  gemdos(0x8)
  83. #define Crawio(a)       (long)  gemdos(0x6,(short)(a))
  84. #define Crawcin()       (long)  gemdos(0x7)
  85. #define Cconws(a)       (void)  gemdos(0x9,(char*)(a))
  86. #define Cconrs(a)       (void)  gemdos(0x0a,(char*)(a))
  87. #define Cconis()        (short) gemdos(0x0b)
  88. #define Cconos()        (short) gemdos(0x10)
  89. #define Cprnos()        (short) gemdos(0x11)
  90. #define Cauxis()        (short) gemdos(0x12)
  91. #define Cauxos()        (short) gemdos(0x13)
  92.  
  93.  
  94. What about BIOS?
  95.  
  96. >From BIOS, the approach is slightly different. BIOS provides one
  97. function for each operation, with the device indicated by an
  98. argument. The first argument to the function is the device
  99. number: 
  100.  
  101.    Console      Serial         Description
  102.    -------      ------         -----------
  103.    Bconin(2)    Bconin(1)      Read a character
  104.    Bconout(2,c) Bconout(1,c)   Write a character c
  105.    Bconstat(2)  Bconstat(1)    Return TRUE if a character is available         
  106.    Bcostat(2)   Bcostat(1)     Return TRUE if output is possible
  107.  
  108. Note that the device numbers used by the BIOS are different from the
  109. file handles used at the GEMDOS level. The BIOS device numbers may be
  110. defined in this fashion:
  111.  
  112. #define BCON_PRT        0 /* printer */
  113. #define BCON_AUX        1 /* aux (modem) port */
  114. #define BCON_CON        2 /* console */
  115. #define BCON_MIDI       3 /* MIDI port */
  116. #define BCON_KBD        4 /* intelligent keyboard device output */
  117. #define BCON_RAW        5 /* raw console output, no VT52 */
  118.  
  119. The BIOS macros are defined as follows:
  120.  
  121. #define Bconstat(dev)   bios(1,(short)(dev))
  122. #define Bconin(dev)     bios(2,(short)(dev))
  123. #define Bconout(dev,ch) bios(3,(short)(dev),(short)(ch))
  124. #define Bcostat(dev)    bios(8,(short)(dev))
  125.  
  126. If you're using assembly language, the bios trap is #13.
  127.  
  128. There is a no function to read a string from the serial port at
  129. either the GEMDOS level or the BIOS level. For reasons that will
  130. become clear shortly, you should write your own.
  131.  
  132. All of the GEMDOS and BIOS input functions return 32-bit LONG values.
  133.  
  134. In the case of the serial port, only the low 8 bits are interesting.
  135.  
  136. In the case of the console, the low 8 bits will contain the
  137. ASCII code corresponding to the key -- if there is one. If the
  138. keystroke is a nonstandard key, such as a function key, HELP or
  139. UNDO, the low eight bits will be an ASCII nul value. In all
  140. cases, the upper 24 bits contain a unique keyscan code and
  141. information regarding the state of the shift keys.
  142.  
  143. A simple terminal program
  144. ------------------------- 
  145.  
  146. A ``dumb'' terminal program using these functions is simple to write.
  147. All you need is a loop in which you will ``poll'' the console and the
  148. serial port. It might look like this:
  149.  
  150. #include <osbind.h>
  151. #define BANNER "\033EIncredibly dumb full-duplex terminal version 0.1\r\n"
  152.  
  153. main()
  154.         {
  155.         int c;
  156.         Cconws(BANNER);
  157.         for(;;)
  158.                 {
  159.                 if (Cauxstat()) /* data waiting at the aux port */
  160.                         {
  161.                         c = (int) (Cauxin() & 0xFF); /* get it and clean it */
  162.                         Cconout(c);
  163.                         }
  164.                 if (Cconstat()) /* data waiting at the console */
  165.                         {
  166.                         c = (int) (Cnecin() & 0xFF); /* get it and clean it */
  167.                         if (c) /* if it's not a NUL from a function key */
  168.                                 Cauxout(c);
  169.                         }
  170.                 }
  171.         }
  172.  
  173. You'll notice that the above example runs forever. Well, not quite.
  174. GEMDOS console i/o processes control-characters, and a control-C will
  175. kill the program.
  176.  
  177. Of course, that means you can't send control-C out the modem port.
  178. That's one of many reasons it's called an ``incredibly dumb''
  179. terminal.
  180.  
  181. You'll probably want to rewrite the program using BIOS functions and
  182. test for use of the UNDO or F10 keys.
  183.  
  184.  
  185. Whatever happened to GEM?
  186. -------------------------
  187.  
  188. None of this discussion has dealt with GEM. Why? Because so far as
  189. GEM is concerned, the serial port doesn't exist.
  190.  
  191. The AES Event Manager doesn't poll the aux port to determine whether
  192. an event has occurred.
  193.  
  194. If you want to write a GEM event-driven communications program that
  195. will cooperate with desk accessories for multitasking, you'll need to
  196. use the timer with a time value of 0. This will allow a task switch
  197. to take place, and when you regain control you can check the serial
  198. port's status with GEMDOS or BIOS.
  199.  
  200. If you're worried that the serial input buffer will overflow while
  201. some system-hog task is running, resize the buffer as described
  202. below.
  203.  
  204. The real world intrudes
  205. -----------------------
  206.  
  207. None of the above functions cares whether the modem is live or dead.
  208. They only deal with data. If your caller falls asleep at the keyboard
  209. or abruptly hangs up, the above functions won't worry about a thing.
  210. If you're writing a communications program or a BBS, you'll have to
  211. deal with these real-world conditions. Modems and phone lines are not
  212. perfect.
  213.  
  214. There are many ways to handle such errors.  In C, you might want to
  215. use the setjmp and longjmp functions to ``bail out'' and restart your
  216. program. Assuming you have set up a jump buffer called ``panic,'' you
  217. might write a low-level aux port input function that looks like this:
  218.  
  219. #include <time.h>
  220. #include <osbind.h>
  221. #define TIMEOUT         60      /* 60-second timeout */
  222.  
  223. int auxin()
  224.         {
  225.         clock_t time1,elapsed;
  226.         if (!rs232cd())
  227.                 {
  228.                 hangup();
  229.                 longjmp(panic);
  230.                 }
  231.         time1 = clock();  /* get tick time */
  232.  
  233.         /* loop for up to TIMEOUT seconds, waiting for aux input */
  234.         for(;;)
  235.                 {
  236.                 if (Cauxis()) /* these could just as well be BIOS calls */
  237.                         return Cauxin();
  238.  
  239.                 /* no activity yet, so ... */
  240.                 /* get new tick time and convert to seconds */
  241.  
  242.                 elapsed = (clock() - time1) / CLK_TCK;
  243.  
  244.                 /* if too much time has elapsed, restart */
  245.  
  246.                 if (elapsed > TIMEOUT)
  247.                         {
  248.                         hangup();
  249.                         longjmp(panic);
  250.                         }
  251.                 } /* continue the loop */       
  252.         }
  253.  
  254.  
  255. The above code has used a couple of functions that aren't in the
  256. library and bear some explaining. Here is the function that checks
  257. the carrier-detect status of the aux port by examining a hardware
  258. register:
  259.  
  260. #include <osbind.h>
  261. int rs232cd()                   /* state of rs232 carrier detect line */
  262.         {
  263.         register long ssp;
  264.         register int *mfp, status;
  265.         mfp = ((int *) 0xFFFFFA00L);            /* base address of MFP */
  266.         ssp = Super(0L);                        /* enter supervisor mode */
  267.         status = *mfp;                          /* get MFP status */
  268.         Super(ssp);                             /* return to user mode */
  269.         return(!(status & 0x0002));             /* check for carrier */
  270.         }
  271.  
  272.  
  273. Here is a function that hangs up the phone line by dropping the DTR
  274. (Data Terminal Ready) signal. Note that most modems are shipped from
  275. the factory with DTR sensitivity disabled. You should be able to turn
  276. it back on by toggling a DIP switch or with a command from the
  277. keyboard.
  278.  
  279.  
  280. #include <osbind.h>
  281. void hangup()
  282.         {
  283.         Ongibit(0x10);  /* dtr off */
  284.         sleep(1);       /* wait one second */
  285.         Offgibit(0xEF); /* dtr on */
  286.         }
  287.  
  288. Now, what about getting a string from the modem port? You'll need to
  289. call your custom auxin() function, as above. Here is an auxgets
  290. function modified from the dLibs getln. Note that it uses a function
  291. ``auxput'' for output; you can rewrite or #define it to mirror the
  292. BIOS or GEMDOS-level routine you used for input:
  293.  
  294. #define KEY_CR          0x0D /* carriage return */
  295. #define KEY_LF          0x0A /* linefeed */
  296. #define KEY_BS          0x08 /* backspace */
  297. #define KEY_DEL         0x7F /* delete */
  298.  
  299. char *auxgets(buffer, limit)
  300.         char *buffer; /* where keystrokes will be stored */
  301.         register int limit; /* size of the buffer */
  302.         {
  303.         register char *bp = buffer;
  304.         register int c, i = 0;
  305.         for(;;)
  306.                 {
  307.                 c = auxin() & 0xFF; /* mask off high bits, just in case */
  308.  
  309.                 /* end of line */
  310.                 if((c == KEY_CR) || (c == KEY_LF)) 
  311.                         {
  312.                         *bp = '\0';
  313.                         break;
  314.                         }
  315.  
  316.                 /* backspace or delete */
  317.                 else if(((c == KEY_BS) || (c == KEY_DEL)) && (bp != buffer))
  318.                         {
  319.                         --bp;
  320.                         auxput('\b');
  321.                         auxput(' ');
  322.                         auxput('\b');
  323.                         --i;
  324.                         }
  325.                 else if((c >= ' ') && (i < limit))
  326.                         {
  327.                         auxput(*bp++ = c);
  328.                         ++i;
  329.                         }
  330.                 }
  331.         return(buffer);
  332.         }
  333.  
  334.  
  335.  
  336. Configuring the serial port
  337. ---------------------------
  338.  
  339. In order to communicate with another machine over the serial port,
  340. speed and flow-control settings may need to be altered. Atari's XBIOS
  341. (Extended BIOS) provides a function that can configure the serial
  342. port. The function Rsconf sets the speed, flow-control and four
  343. bitmapped hardware registers. 
  344.  
  345. Speed (bits per second, often incorrectly called baud) can be set to
  346. any of 16 values:
  347.  
  348. #define BPS_19200       0 /* standard */
  349. #define BPS_9600        1 /* standard */
  350. #define BPS_4800        2 /* standard */
  351. #define BPS_3600        3
  352. #define BPS_2400        4 /* standard */
  353. #define BPS_2000        5
  354. #define BPS_1800        6
  355. #define BPS_1200        7 /* standard */
  356. #define BPS_600         8
  357. #define BPS_300         9 /* standard */
  358. #define BPS_200         10
  359. #define BPS_150         11
  360. #define BPS_134         12
  361. #define BPS_110         13
  362. #define BPS_75          14
  363. #define BPS_50          15
  364.  
  365. Only the values marked /* standard */ are commonly used in modem
  366. communications.
  367.  
  368. If you are using a slow- or medium-speed modem (under 9600bps) you
  369. probably will need to match the ST's serial port speed to that of the
  370. remote system.
  371.  
  372. If you are using a high-speed modem, you may be able to set the speed
  373. at 19,200bps and let the modem handle speed-matching chores. In this
  374. case, you probably will need to use RTS/CTS (hardware) flow-control
  375. to avoid overloading the ST's input buffer and the modem's output
  376. buffer. RTS/CTS is broken in most versions of TOS, and an /auto/
  377. folder program must be installed to fix it.
  378.  
  379. Flow control can be configured to any of four settings:
  380.  
  381. #define FLOW_NONE       0 /* default */
  382. #define FLOW_XONXOFF    1 /* control-s, control-q software */
  383. #define FLOW_RTSCTS     2 /* hardware */
  384. #define FLOW_BOTH       3 /* both hardware and software */
  385.  
  386. For binary file transfers, software flow control must not be used.
  387.  
  388. The hardware registers are not useful in ordinary modem-related
  389. programming, and altering their values incorrectly will have
  390. unpleasant side effects. Passing -1 to Rsconf will leave the
  391. corresponding setting unchanged.
  392.  
  393. Thus, to set bps rate to 2400 and flow control to none, the Rsconf
  394. function call would look like this:
  395.  
  396.         Rsconf(BPS_1200,FLOW_NONE,-1,-1,-1,-1)
  397.  
  398.  
  399. Parity and duplex
  400. -----------------
  401.  
  402. Parity is not commonly used in communicating with personal computers,
  403. but you may need to deal with it when communicating with a mainframe.
  404.  
  405. The idea behind parity is to provide at least a small degree of
  406. assurance that the transmitted character is the same as the received
  407. character. The way this is done is by fiddling with the high bit of
  408. each byte, which ordinarily would be zero for an ASCII character.
  409.  
  410. ``Even parity'' means that the high bit is either set or unset, as
  411. necessary, to ensure that the result is an even number. ``Odd
  412. parity'' means the opposite. You can test this with the modulus
  413. operator in C, then strip the high bit by ANDing the byte with 0x7F
  414. or set the high bit by ANDing with 0xFF.
  415.  
  416. TOS also can generate parity values automatically if the proper
  417. hardware register is set. However, setting the register requires that
  418. you also worry about start/stop bits, word length and the serial
  419. hardware frequency, which is a can of worms.
  420.  
  421. Duplex is a potentially confusing term that describes whether you or
  422. a remote system is responsible for putting a character on your
  423. screen.
  424.  
  425. Most bulletin board systems run ``full duplex,'' which means that a
  426. keystroke from a caller is sent to the BBS, which then echoes that
  427. character back to the caller. This relationship is not symmetrical.
  428. The caller doesn't echo anything back to the BBS, because the echoes
  429. would be interpreted as commands.
  430.  
  431. ``Half duplex'' means that each party is responsible for managing his
  432. or her own display. GEnie normally runs half duplex. Neither caller
  433. nor BBS echoes anything.
  434.  
  435. If you're writing a BBS or a terminal program with a ``chat mode,''
  436. you may want to include a ``both local and remote echo'' option. This
  437. would allow the remote to run full duplex while you manage both your
  438. screen and the caller's screen.
  439.  
  440.  
  441. Resizing the serial buffers
  442. ---------------------------
  443.  
  444. When a character arrives at the serial port, TOS interrupts what
  445. it's doing, grabs the character before it disappears, and
  446. records it in an input buffer. When your program wishes to write
  447. to the serial port, TOS buffers the output so that your program
  448. can proceed with other chores without having to wait for a slow
  449. modem to transmit the data.
  450.  
  451. TOS provides those buffers, but TOS also provides a facility for
  452. changing them. Why would you want to do so? Here's a scenario:
  453.  
  454. You're using Xmodem and you have a slow floppy drive. Xmodem
  455. ACKS a block, then proceeds to write it to disk while the sender
  456. sends the next block. So far, so good. But if you switch to
  457. Ymodem's 1K blocks and a very fast modem, the TOS default buffer
  458. might overflow while you're writing the previous block to that
  459. slow floppy drive. Solution: Use a bigger input buffer.
  460.  
  461. Another scenario: You're running a BBS. The caller is reading a
  462. long message and decides it's boring. The caller pushes the
  463. cancel key (control-C, or S, or whatever you've decided makes
  464. sense.) The BBS stops sending, but that big output buffer is
  465. full of data -- and it just keeps coming, and coming ....
  466. Solution: Use small i/o buffers for interacting with humans.
  467.  
  468. TOS provides an XBIOS function that returns a pointer to an
  469. input buffer record. It requires one argument: The BIOS number
  470. of a device (see above).
  471.  
  472. #define Iorec(dev) (void*) xbios(14, (short)(dev))
  473.  
  474. Assembly programmers: The xbios trap is #14, so push the device
  475. number followed by two 14's, then do the trap. The pointer will
  476. be found in d0.
  477.  
  478. The iorec structure is defined as follows:
  479.  
  480. struct iorec
  481.         {
  482.         char *buf;    /* pointer to an array: char buffer[bufsiz] */
  483.         short bufsiz; /* size of the array         */
  484.         short head;   /* index for writing         */
  485.         short tail;   /* index for reading         */
  486.         short low;    /* low water mark, for XON   */
  487.         short high;   /* high water mark, for XOFF */
  488.         }
  489.  
  490. For the AUX port, the output buffer record immediately follows
  491. the input buffer record.
  492.  
  493. Before you go poking around inside these records, keep in mind
  494. that they are internal to TOS, and they are shared by all
  495. processes that run on your system. You can change the buffers
  496. and even bypass GEMDOS and BIOS for reading and writing, if you
  497. so desire, but you must be prepared to live with the
  498. consequences.
  499.  
  500. In particular, be very careful to restore the original record
  501. when your program terminates (unless you're writing a TSR with
  502. the express purpose of enlarging a buffer).
  503.  
  504. Immediately after the serial port's input and output records are
  505. some internal TOS variables that may be useful. The following
  506. code has worked on all versions of TOS that I've encountered. I
  507. don't know whether Atari guarantees that the variables won't be
  508. changed on future versions of TOS.
  509.  
  510. With those cautions in mind, here is some code, originally
  511. lifted from an early ST communications program written by Dale
  512. Schumacher. I've been using the open_aux and close_aux functions
  513. for years, but I haven't had a need to deal with the rsr, tsr,
  514. and flow control variables.
  515.  
  516. typedef struct 
  517.         {
  518.         struct iorec in;        /*  0 Input buffer record  */
  519.         struct iorec out;       /* 14 Output buffer record */
  520.         char rsr_status;        /* 28 MFP(0x1C) Receiver status */
  521.         char tsr_status;        /* 29 MFP(0x1D) Transmit status */
  522.         char xoff_sent;         /* 30 TRUE if we sent XOFF      */
  523.         char xoff_received;     /* 31 TRUE if we got XOFF       */
  524.         char mode;              /* 32 Bit 0 XON, Bit 1 RTS mode */
  525.         char filler;            /* 33 Unused                    */
  526.         } IOREC;
  527.  
  528. #define IBUFSIZ 1200
  529. #define OBUFSIZ 2
  530.  
  531. char    st_ibuf[IBUFSIZ];       /* our own input buffer         */
  532. char    st_obuf[OBUFSIZ];       /* and our own output buffer    */
  533.  
  534. IOREC   *st_sysr;               /* ptr to system rs232 record   */
  535. IOREC   st_savr;                /* to save system rs232 record  */
  536.  
  537. IOREC   st_myiorec = 
  538.         {
  539.         /* first, an input record */
  540.         st_ibuf, IBUFSIZ, 0, 0, (IBUFSIZ/4), (3*(IBUFSIZ/4)),
  541.  
  542.         /* then an output record */
  543.         st_obuf, OBUFSIZ, 0, 0, 0, 1,
  544.  
  545.         /* and the rsr, tsr, flow control stuff */
  546.         0, 0, 0, 0, 0, 0
  547.         };
  548.  
  549. void openaux()          /* set up our own rs232 input and output buffers */
  550.         {
  551.         while(Bconstat(AUX))            /* flush existing buffer */
  552.                 Bconin(AUX);
  553.         st_sysr = (IOREC *)Iorec(0);
  554.         st_savr = *st_sysr;             /* Save system buffer   */
  555.         *st_sysr = st_myiorec;          /* Set my io buffer     */
  556.         }
  557.  
  558. void closeaux()         /* restore system i/o buffer */
  559.         {
  560.         *st_sysr = st_savr;
  561.         }
  562.  
  563.  
  564. Take a break
  565. ------------
  566.  
  567. Some remote systems, notably Unix sites, need a BREAK signal to
  568. wake them up and help them synchronize with a caller's bps rate.
  569. To generate a BREAK on the ST, you briefly toggle the transmit
  570. status register. Here is code that does so by directly accessing
  571. the hardware on the ST. I think Dale Schumacher is the author of
  572. this code.
  573.  
  574. #include <time.h>
  575. #include <osbind.h>
  576.  
  577. void nap(n)                     /* do nothing for n-100ths of a second */
  578.         register int n;
  579.         {
  580.         register clock_t t, dt;
  581.         dt = (CLK_TCK * ((clock_t) n)) / ((clock_t) 100);
  582.         t = clock() + dt;
  583.         while(clock() < t)
  584.                 ;
  585.         }
  586.  
  587. void snd_brk()          /* send a BREAK to the rs232 port */
  588.         {
  589.         register long ssp;
  590.         register char *tsr;
  591.         tsr = ((char *) 0xFFFFFA2DL);           /* address of tsr */
  592.         ssp = (long) Super(0L);                 /* enter supervisor mode */
  593.         *tsr = 0x09;                            /* turn break on */
  594.         Super(ssp);                             /* return to user mode */
  595.         nap(30);                                /* 0.3 second BREAK */
  596.         ssp = (long) Super(0L);                 /* enter supervisor mode */
  597.         *tsr = 0x01;                            /* turn break off */
  598.         Super(ssp);                             /* return to user mode */
  599.         }
  600.  
  601. The clock() function should be available with most C compilers.
  602. If you're writing in a language that doesn't have an equivalent,
  603. the trick is simply to peek at memory location 4BA (hex), which
  604. is the 200Hz system timer. You must be in supervisor mode to do
  605. so. The value is a 32-bit long.
  606.  
  607.  
  608. File transfers
  609. --------------
  610.  
  611. When transferring a binary file, the sender and the receiver
  612. have to agree on a method for signalling the end of the
  613. transmission. Ideally, they should have some method of
  614. determining whether the file was mangled by line noise. And if
  615. it was, there should be a way to retransmit the bad data.
  616.  
  617. The methods for solving these problems are called file-transfer
  618. protocols, and their name should be legion, for they are many.
  619. Xmodem, Ymodem, Zmodem, Quick B, WXmodem, Kermit, Punter, FAST,
  620. Telink, UUCP 'G' ... the list goes on.
  621.  
  622. Most of them follow the same basic strategy. A file is broken up
  623. into multiple packets. Each packet starts with a header that
  624. includes a packet number and a mathematical value, called a
  625. checksum, that can be used to determine whether the data in the
  626. packet has been corrupted.
  627.  
  628. XMODEM is a very simple implementation of this strategy.
  629.  
  630. It uses 128-byte data packets. It uses packet headers consisting
  631. of a a byte for the packet number, and (in the original version)
  632. a byte for a checksum. The checksum is calculated by adding the
  633. numeric value of each byte and throwing away the overflow. It's
  634. not foolproof, but it detects most errors.
  635.  
  636. Later versions of Xmodem use a 16-bit ``cyclical redundancy
  637. check'' calculated by using a fairly complex algorithm. It's
  638. much harder to fool.
  639.  
  640. If the Xmodem receiver adds up all the values and finds they
  641. don't match the total that was encoded in the packet header, it
  642. sends a NAK (Negative AcKnowledgement) signal to the sender,
  643. which then retransmits the corrupted packet. If they match, it
  644. the receiver sends an ACK.
  645.  
  646. Because the packages are small, and because the sender always waits
  647. for an ACK before proceeding, Xmodem is not particularly fast. When
  648. XMODEM runs over a network that does its own error-checking and
  649. packet operations internally (such as CompuServe or GEnie's data
  650. systems), Xmodem can get painfully slow. That lonely ACK signal
  651. can take a long time to wander through the network and get back
  652. to you.
  653.  
  654. There are two ways of dealing with Xmodem's speed problems. One is to
  655. use bigger packets, which is the approach taken by most alternatives,
  656. including Ymodem. The other is to use ``windowing,'' which is used by
  657. WXmodem, UUCP 'G' and Zmodem.
  658.  
  659. A windowing protocol doesn't wait for an ACK before sending.
  660. Instead, it transmits the next block. If a NAK arrives, it can
  661. back up and resend the bad block. The number of blocks that can
  662. be ``remembered'' is the size of the ``window.'' A
  663. sliding-window implementation is one in which the sender's
  664. window moves along as you move through the file and get ACKs
  665. from the receiver.
  666.  
  667. And now, the code.
  668.  
  669. This document isn't going to attempt to present a C
  670. implementation of a file-transfer protocol. But the question of
  671. ``how do I calculate a checksum'' comes up frequently, here is C
  672. code to create the values used by Xmodem, Ymodem and some
  673. related programs.
  674.  
  675. Some CRC functions index the CRC values out of an array of
  676. precalculated numbers. That approach probably is faster than the
  677. one below, but the ST has plenty of horsepower for this sort of
  678. thing.
  679.  
  680. Don't ask me to explain the math. I can't. I didn't write this;
  681. I just use it.
  682.  
  683.  /*
  684.   * This function calculates the CRC used by the XMODEM/CRC
  685.   * Protocol.
  686.   * The first argument is a pointer to the message block.
  687.   * The second argument is the number of bytes in the message block.
  688.   * The function returns a short integer that contains the CRC value.
  689.   */
  690.  short crc16(ptr, count)
  691.  register char *ptr;
  692.  register short count;
  693.     {
  694.     register short crc = 0, i;
  695.     while(--count >= 0) 
  696.        {
  697.  
  698.        crc = crc ^ (short)*ptr++ << 8;
  699.        for(i=0; i<8; ++i)
  700.           {
  701.           if(crc & 0x8000)
  702.              crc = crc << 1 ^ 0x1021;
  703.           else
  704.              crc = crc << 1;
  705.           }
  706.        }
  707.     return(crc & 0xFFFF);
  708.     }
  709.   
  710.  
  711. If you want to port this to another language, keep in mind that 
  712.  
  713.   a << b  evaluates to the bit pattern of a shifted left b bits
  714.  
  715.   * dereferences a pointer; *ptr++ evaluates to the pointed-to value
  716.     and then increments ptr by the size of the pointed-to value
  717.  
  718.   ^ is a bitwise exclusive OR operator; 11110000 (binary) ^ 00001111
  719.     evaluates to 11111111
  720.  
  721.   & in the context above is a bitwise AND operator; crc & 0xFFFF
  722.     basically strips anything above 16 bits (important if your
  723.     environment supports 32-bit ints).
  724.  
  725.  
  726. Acknowledgements
  727. ----------------
  728.  
  729. This document wouldn't have been possible without the assistance
  730. of the following people. Some provided instructive code samples;
  731. others caught errors in earlier versions of this document or
  732. raised important questions. Thanks to: Dale Schumacher, John
  733. Stanley, Robert Royar, Adam David, Rob McMullen, Michael
  734. Fischer.
  735.  
  736. Corrections
  737. -----------
  738.  
  739. Send corrections to steve@thelake.mn.org (Internet),
  740. S.YELVINGTO2 (GEnie), plains!umn-cs!thelake!steve (UUCP), or
  741. snail mail to P.O. Box 38, Marine on St. Croix, MN 55047 USA.
  742.  
  743. Permissions
  744. -----------
  745.  
  746. This file may be distributed only in electronic format, and only
  747. in its original, unaltered state. All other reproduction is
  748. prohibited without written permission of the author. This
  749. information is intended to be accurate, but the author makes no
  750. representation that is is free of errors. This information is
  751. presented without warranty of any sort, and the author is not
  752. responsible for any side effects. None of this information is
  753. extracted from any proprietary documents or trade secrets.
  754. PS: Shakespeare was right about the lawyers.
  755.