_C PROGRAMMING COLUMN_
by Al Stevens


[LISTING ONE]

/* ------- the hook to the phone directory ---------- */
extern void phdirectory(void);
static void (*phone_directory)(void) = phdirectory;
/* ------- the hook to script processors ---------- */
void (*script_processor)(void);
/* ------- hooks to file transfer protocols --------- */
extern int select_protocol(void);
static int (*select_transfer_protocol)(void) = select_protocol;
/* ----- up to five upload function pointers ----- */
void upload_xmodem(FILE *);
void upload_kermit(FILE *);
static void (*up_protocol[5])(FILE *file_pointer) = {
    upload_ASCII, upload_xmodem, upload_kermit,NULL,NULL
};
/* ----- up to five download function pointers ----- */
void download_xmodem(FILE *);
void download_kermit(FILE *);
static void (*down_protocol[5])(FILE *file_pointer) = {
    download_ASCII, download_xmodem, download_kermit,NULL,NULL
};


[LISTING TWO]

/* --------- phonedir.c ---------- */
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <string.h>
#include <mem.h>
#include <ctype.h>
#include "window.h"
#include "entry.h"
#include "help.h"
#include "modem.h"

#define DIRECTORY "phone.dir"
#define MAX_ENTRIES 50
#define WRITEDIR F2
#define MODIFYDIR F3

void phdirectory(void);
char scriptfile[13];
static void get_directory(void);
static void put_directory(void);
static int dirproc(int, int);
static void bld_default(int);
static void build_dir(int);
static int enter_directory(int);
static void select_directory(int);
char *prompt_line(char *, int, char *);
void reset_prompt(char *, int);
static int edit_directory(void);
static int direrror(int);
static void field_terminate(FIELD *fld, int termchar);

extern int PARITY,STOPBITS,WORDLEN,BAUD;
extern char PHONENO[];
extern char spaces[];
extern struct wn wkw;   /* the directory window structure */
extern void (*phone_directory)() = phdirectory;
/* -------- phone directory file record ------------ */
struct {
    char ol_name[21];    /* callee's name             */
    char ol_phone[24];   /* phone number              */
    char ol_parity[8];   /* none/odd/even             */
    char ol_stopbits[4]; /* 1 or 2                    */
    char ol_wordlen[3];  /* 7 or 8                    */
    char ol_baud[6];     /* 110,150,300,600,1200,2400 */
    char ol_script[9];   /* name of script file       */
} pd;
static char hdr[] =
    " Name                 "
    "Phone Number           "
    "Parity Stop Len Baud  Script";
static char select_prompt[] =
    "\030\031\021\304\331:Select Esc:Return "
    "F2:Write Directory F3:Modify "
    "Ins:Insert Del:Delete";
static char enter_prompt[] =
    " F2:Write Changes to Directory    "
    " Esc:Ignore Entry   F1:Help";
static char *pds[MAX_ENTRIES+1];
static int pct;
static FILE *fp;
static char nmmask[] = "____________________";
static char phmask[] = "____________________";
static char prmask[] = "____";
static char sbmask[] = "_";
static char wlmask[] = "_";
static char bdmask[] = "____";
static char scmask[] = "________";
/* ------- data entry template for the directory ------- */
FIELD directory_template[] = {
    {3, 16, 1, pd.ol_name,     nmmask, "name"},
    {4, 16, 1, pd.ol_phone,    phmask, "phone"},
    {5, 16, 1, pd.ol_parity,   prmask, "parity"},
    {6, 16, 1, pd.ol_stopbits, sbmask, "stopbits"},
    {7, 16, 1, pd.ol_wordlen,  wlmask, "wordlen"},
    {8, 16, 1, pd.ol_baud,     bdmask, "baud"},
    {9, 16, 1, pd.ol_script,   scmask, "script"},
    {0}
};
/* -------- data entry error messages --------- */
static char *ermsgs[] = {
    "Parity must be None, Odd, or Even",
    "Stop Bits must 1 or 2",
    "Word Length must be 7 or 8",
    "Baud Rate must be 110,150,300,600,1200,2400"
};
/* ------ manage the telephone directory ------ */
void phdirectory(void)
{
    int s = 1;
    char *ttl, *sel;
    set_help("directry");
    ttl = prompt_line(hdr, 1, NULL);
    sel = prompt_line(select_prompt, 25, NULL);
    establish_window(1,2,80,24,TEXTFG,TEXTBG,TRUE);
    get_directory();
    text_window(pds, 1);
    while (pct &&
          (s=select_window(s,SELECTFG,SELECTBG,dirproc))!=0)
        if (pct && pds[s-1] != spaces+1)    {
            select_directory(s-1);
            break;
        }
    delete_window();
    reset_prompt(sel, 25);
    reset_prompt(ttl, 1);
}
/* -------- select the directory entry for the dialer ------- */
static void select_directory(int n)
{
    char *cp = scriptfile;
    movmem(pds[n], &pd, sizeof pd);
    strncpy(PHONENO, pd.ol_phone, 20);
    BAUD     = atoi(pd.ol_baud);
    STOPBITS = *pd.ol_stopbits - '0';
    WORDLEN  = *pd.ol_wordlen  - '0';
    PARITY   = (*pd.ol_wordlen == 'N' ? 0 :
                *pd.ol_wordlen == 'O' ? 1 : 2);
    establish_window(30,11,50,13,HELPFG,HELPBG,TRUE);
    gotoxy(2,2);
    cputs("Initializing Modem");
    initmodem();
    delete_window();
    setmem(scriptfile, sizeof scriptfile, '\0');
    strncpy(scriptfile, pd.ol_script, 8);
    while (*cp && *cp != ' ')
        cp++;
    strcpy(cp, ".scr");
}
/* ------ read the phone directory ----------- */
static void get_directory(void)
{
    if (pct == 0 && (fp = fopen(DIRECTORY, "r")) != NULL)   {
        while (fread(&pd, sizeof pd, 1, fp) != 0)   {
            build_dir(pct++);
            if (pct == MAX_ENTRIES)
                break;
        }
        pds[pct++] = spaces+1;
        pds[pct] = NULL;
        fclose(fp);
    }
    if (pct == 0)
        dirproc(INS, 1);
}
/* ------- build a default phone directory entry -------- */
static void bld_default(int n)
{
    static char *prs[] = {"None", "Odd ", "Even"};
    setmem(&pd, sizeof pd-1, ' ');
    strncpy(pd.ol_parity, prs[PARITY], 4);
    *pd.ol_stopbits = STOPBITS + '0';
    *pd.ol_wordlen  = WORDLEN + '0';
    sprintf(pd.ol_baud, "%4d", BAUD);
    pd.ol_baud[4] = ' ';
    build_dir(n);
}
/* --------- build a directory entry for display ----------- */
static void build_dir(int n)
{
    if ((pds[n] = malloc(sizeof pd)) != NULL)
        movmem(&pd, pds[n], sizeof pd);
}
/* ------- write the phone directory ---------- */
static void put_directory(void)
{
    int i;
    fp = fopen(DIRECTORY, "w");
    for (i = 0; i < pct; i++)
        if (pds[i] != spaces+1)
            fwrite(pds[i], sizeof pd, 1, fp);
    fclose(fp);
}
/* ---------- process a directory entry ------------- */
static int dirproc(int c, int lineno)
{
    int i, j;
    switch (c)  {
        case DEL:
            if (pds[lineno-1] != spaces+1)  {
                free(pds[lineno-1]);
                for (j = lineno-1; j < pct; j++)
                    pds[j] = pds[j+1];
                if (--pct)  {
                    text_window(pds, wkw.wtop);
                    for (i = pct+2; i <= wkw.wtop+wkw.ht; i++)
                        writeline(2, i, spaces+1);
                    if (lineno-1 == pct)
                        --lineno;
                }
                else
                    clear_window();
            }
            break;
        case INS:
            if (pct == MAX_ENTRIES)
                break;
            i = pct;
            if (i)
                while (i >= lineno) {
                    pds[i] = pds[i-1];
                    --i;
                }
            bld_default(i);
            pct++;
        case MODIFYDIR:
            if (pds[lineno-1] != spaces+1)  {
                movmem(pds[lineno-1], &pd, sizeof pd);
                enter_directory(lineno-1);
            }
            break;
        case WRITEDIR:
            put_directory();
            break;
    }
    wkw.wy = lineno - wkw.wtop + 1;
    return (pct == 0);
}
/* ------- data entry for a directory record ---------- */
static int enter_directory(int lineno)
{
    int s = 1;
    char *p = prompt_line(enter_prompt, 25, NULL);
    establish_window(20,5,56,15,ENTRYFG,ENTRYBG,TRUE);
    window_title(" Telephone Directory Entry ");
    gotoxy(3,3), cputs("Name:");
    gotoxy(3,4), cputs("Phone:");
    gotoxy(3,5), cputs("Parity:");
    gotoxy(3,6), cputs("Stop Bits:");
    gotoxy(3,7), cputs("Word Length:");
    gotoxy(3,8), cputs("Baud Rate:");
    gotoxy(3,9), cputs("Script:");
    field_terminate(directory_template, '\0');
    while (s != WRITEDIR && s != ESC)   {
        s = data_entry(directory_template, FALSE, s);
        if (s == WRITEDIR)
            s = edit_directory();
    }
    field_terminate(directory_template, ' ');
    *(((char *)(&pd)) + sizeof pd - 1) = '\0';
    delete_window();
    reset_prompt(p, 25);
    if (s == WRITEDIR)  {
        movmem(&pd, pds[lineno], sizeof pd);
        put_directory();
    }
    text_window(pds,wkw.wtop ? wkw.wtop : 1);
    return (s != ESC);
}
/* -------- validate the directory entry -------- */
static int edit_directory(void)
{
    int i;
    static int bds[] = {110,150,300,600,1200,2400};
    *pd.ol_parity = toupper(*pd.ol_parity);
    if (*pd.ol_parity != 'N' &&
            *pd.ol_parity != 'O' &&
            *pd.ol_parity != 'E')
        return direrror(3);
    if (*pd.ol_stopbits != '1' && *pd.ol_stopbits != '2')
        return direrror(4);
    if (*pd.ol_wordlen != '7' && *pd.ol_wordlen != '8')
        return direrror(5);
    for (i = 0; i < 6; i++)
        if (atoi(pd.ol_baud) == bds[i])
            break;
    if (i == 6)
        return direrror(6);
    return WRITEDIR;
}
/* ------- post a directory entry error ---------- */
static int direrror(int n)
{
    error_message(ermsgs[n-3]);
    return n;
}
/* -------- set field terminators to null or space ------- */
static void field_terminate(FIELD *fld, int termchar)
{
    for (;fld->frow;fld++)
        *(fld->fbuff+strlen(fld->fmask)) = termchar;
}


[LISTING THREE]

/* ------------- protocol.c --------------- */

#include <stdio.h>
#include <conio.h>
#include <ctype.h>
#include "window.h"
#include "help.h"
#include "menu.h"

static char *prots[] = {
    " ASCII",
    " Xmodem",
    " Kermit",
    NULL
};

/* ----- translate A,X,K keystrokes for protocol menu ----- */
static int protkey(int ky, int lnno)
{
    ky = tolower(ky);
    return ky=='a' ? 1 : ky=='x' ? 2 : ky=='k' ? 3 : ERROR;
}

/* --- file transfer protocol for uploads and downloads --- */
int select_protocol(void)
{
    extern MENU *mn;
    MENU *holdmn;
    static int rtn = 0;
    holdmn = mn;
    mn = NULL;
    set_help("protocol");
    establish_window(25,7,55,11,MENUFG,MENUBG,TRUE);
    window_title(" Select Transfer Protocol ");
    text_window(prots, 1);
    rtn = select_window(rtn?rtn:1,SELECTFG,SELECTBG,protkey);
    delete_window();
    mn = holdmn;
    return rtn ? rtn-1 : 0;
}

/* ---- These are stubs, to be replaced later ---- */
void upload_kermit(FILE *fd)
{
    error_message("Upload KERMIT not implemented");
}

void download_kermit(FILE *fd)
{
    error_message("Download KERMIT not implemented");
}


[LISTING FOUR]

/* -------------- xmodem.c --------------- */
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <mem.h>
#include "window.h"
#include "serial.h"

#define RETRIES  12
#define CRCTRIES 2
#define PADCHAR  0x1a
#define SOH      1
#define EOT      4
#define ACK      6
#define NAK      0x15
#define CAN      0x18
#define CRC      'C'
/* -------- external data ---------- */
extern int TIMEOUT;
extern int WORDLEN;
extern int xonxoff_enabled;
/* --------- local data ------------ */
static int tries;   /* retry counter */
static char bf [130]; /* i/o buffer  */
/* -------- prototypes ------------- */
extern int keyhit(void);
static void receive_error(int, int);
static void xmodem_msg(char *);
static void test_wordlen(void);
unsigned compute_crc(char *, int);
/* --------- error messages ----------- */
static char *errs[] = {
    "Timed Out         ",
    "Invalid SOH       ",
    "Invalid block #   ",
    "Invalid chksum/crc"
};
/* ---------- upload with xmodem protocol ------------- */
void upload_xmodem(FILE *fd)
{
    int i, chksum, eof = FALSE, ans = 0, ln, crcout = 0;
    unsigned crc;
    char bno = 1;
    xonxoff_enabled = FALSE;
    establish_window(20,10,52,14,MENUFG,MENUBG,TRUE);
    window_title("XMODEM Upload (CHKSUM)");
    tries = 0;
    test_wordlen();
    /* ----- wait for the go-ahead from the receiver ------ */
    TIMEOUT = 6;
    while (tries++ < RETRIES && crcout != NAK && crcout != CRC)
        crcout = readcomm();
    if (crcout == CRC)
        window_title(" XMODEM Upload (CRC)  ");
    TIMEOUT = 10;
    /* -------- send the file to the receiver ----------- */
    while (tries < RETRIES &&
            !eof && ans != CAN && !timed_out())     {
        /* ---- read the next data block ----- */
        setmem(bf, 128, PADCHAR);
        if ((ln = fread(bf, 1, 128, fd)) < 128)
            eof = TRUE;
        if (ln == 0)
            break;
        gotoxy(2, 2);
        cprintf("Block %d  ",bno);
        chksum = 0;
        if (keyhit())
            if (getch() == ESC) {
                writecomm(CAN);
                break;
            }
        writecomm(SOH);      /* SOH           */
        writecomm(bno);      /* block number  */
        writecomm(~bno);     /* 1s complement */
        /* ------- send the data block ------ */
        for (i = 0; i < 128; i++)   {
            writecomm(bf[i]);
            chksum += bf[i];        /* checksum calculation */
        }
        /* -- send error-correcting value (chksum or crc) -- */
        if (crcout == NAK)
            writecomm(chksum & 255);
        else    {
            crc = compute_crc(bf, 130);
            writecomm((crc >> 8) & 255);
            writecomm(crc & 255);
        }
        /* ----- read ACK, NAK, or CAN from receiver ----- */
        ans = readcomm();
        if (ans == ACK) {
            bno++;
            tries = 0;
            gotoxy(2, 4);
            cprintf("        ");
        }
        if (ans == NAK) {
            eof = FALSE;
            gotoxy(2, 4);
            cprintf("%2d tries", ++tries);
            /* ---- position to previous block ----- */
            if (fseek(fd, -128L, 1) == -1)
                fseek(fd, 0L, 0);
        }
    }
    if (eof)    {
        writecomm(EOT);                 /* send the EOT    */
        readcomm();                     /* wait for an ACK */
        xmodem_msg("Transfer Completed");
    }
    else
        xmodem_msg("Transfer Aborted");
    xonxoff_enabled = TRUE;
}
/* ---------- download with xmodem protocol ------------- */
void download_xmodem(FILE *fd)
{
    int blk=0, soh= 0, bn, nbn, i, crcin = TRUE, fst = TRUE;
    unsigned chksum, cs, cs1;
    xonxoff_enabled = FALSE;
    establish_window(20,10,52,14,MENUFG,MENUBG,TRUE);
    window_title("XMODEM Download (CHKSUM)");
    /* - send Cs then NAKs until the sender starts sending - */
    tries = 0;
    test_wordlen();
    TIMEOUT = 6;
    while (soh != SOH && tries < RETRIES)   {
        crcin = (tries++ < CRCTRIES);
        writecomm(crcin ? CRC : NAK);
        soh = readcomm();
        if (!timed_out() && soh != SOH)
            sleep(6);
    }
    if (crcin)
        window_title(" XMODEM Download (CRC)  ");
    while (tries < RETRIES) {
        if (timed_out())
            receive_error(0, NAK);
        /* -- Receive the data and build the file -- */
        gotoxy(2,2);
        cprintf("Block %d   ", blk + 1);
        if (!fst)   {
            TIMEOUT = 10;
            soh = readcomm();
            if (timed_out())
                continue;
            if (soh == CAN)
                break;
            if (soh == EOT) {
                writecomm(ACK);
                break;
            }
        }
        fst = FALSE;
        TIMEOUT = 1;
        bn  = readcomm();       /* block number */
        nbn = readcomm();       /* 1's complement */
        chksum = 0;
        /* ---- data block ----- */
        for (i = 0; i < 128; i++)   {
            *(bf + i) = readcomm();
            if (timed_out())
                break;
            chksum = (chksum + (*(bf + i)) & 255) & 255;
        }
        if (timed_out())
            continue;
        /* ---- checksum or crc from sender ---- */
        cs = readcomm() & 255;
        if (crcin)  {
            cs1 = readcomm() & 255;
            cs = (cs << 8) + cs1;
        }
        if (timed_out())
            continue;
        if (soh != SOH) {       /* check the SOH */
            receive_error(1, NAK);
            continue;
        }
        /* --- same as previous block number? --- */
        if (bn == blk)
            fseek(fd, -128L, 1);
        /* --- no, next sequential block number? --- */
        else if (bn != blk + 1) {
            receive_error(2, CAN);
            break;
        }
        blk = bn;
        /* --- test the block # 1s complement --- */
        if ((nbn & 255) != (~blk & 255))    {
            receive_error(2, NAK);
            continue;
        }
        if (crcin)
            chksum = compute_crc(bf, 130);
        /* --- test chksum or crc vs one sent --- */
        if (cs != chksum)   {
            receive_error(6, NAK);
            continue;
        }
        soh = bn = nbn = cs = 0;
        tries = 0;
        /* --- write the block to disk --- */
        fwrite(bf, 128, 1, fd);
        if (keyhit())
            if (getch() == ESC) {
                writecomm(CAN);
                break;
            }
        writecomm(ACK);
    }
    if (soh == EOT)
        xmodem_msg("Transfer Complete");
    else
        xmodem_msg("Transfer Aborted");
    TIMEOUT = 10;
    xonxoff_enabled = TRUE;
}
/* ------------- send a nak ------------ */
static void receive_error(erno, rtn)
{
    ++tries;
    if (TIMEOUT == 1)   {
        gotoxy(2,4);
        cprintf("%s (%d tries)", errs[erno], tries);
    }
    writecomm(rtn);
}
/* ------ test for valid word length -------- */
static void test_wordlen(void)
{
    if (WORDLEN != 8)   {
        gotoxy(2,4);
        cprintf("Must be 8 Data Bits");
        tries = RETRIES;
    }
}
/* --------- final message about xmodem transfer -------- */
static void xmodem_msg(char *s)
{
    gotoxy(2,3);
    cprintf(s);
    putch(BELL);
    sleep(3);
    delete_window();
}
/* --------- compute the crc ------------ */
unsigned compute_crc(char *bf, int len)
{
    int i;
    long crc = 0;
    while (len--)   {
        crc |= (*bf++) & 255;
        for (i = 0; i < 8; i++) {
            crc <<= 1;
            if (crc & 0x1000000L)
                crc ^= 0x102100L;
        }
    }
    return (unsigned) (crc >> 8);
}

