[net.sources] CompuServe's "B" Protocol source

benn@sphinx.UChicago.UUCP (T Cox) (03/18/86)

/* distribution to North America only    */
/* forward overseas yourself if you want */

Here are two files of C language source.  The first is a generic 
implementation of the protocol, in what they call "Dumb Terminal Emulator", 
in a file called "dte.c".  The second is a set of library routines for
the B Protocol, called bp.c .  The second for sure and the first possibly
are in Lattice C and are machine independent.

Enjoy, and please forward me your comments.  I'd be curious as to 
your opinions on 
	(1) Is this a worthwhile protocol?  Has it got
anything over Kermit or XMODEM?  CompuServe has been using it for
years now, with some large fraction of their 270,000 subscribers.
	(2) Is this good C?  Just in general, is this well written?
I don't really know C from a hole in the ground, and I want to see
if this is code I should look at for examples.  
	(3) Is there really any need for Yet Another File Transfer 
Protocol [YAFTP] ?  

Anyway, here they are.

Thomas Cox	...ihnp4!gargoyle!sphinx!benn


	====== D T E . C ======

/*
 * This program emulates a dump terminal with file transfer support using
 * CompuServe's B-Protocol.  This program is just a sample of how to interface
 * the BP module (BP.C) with the rest of the terminal emulator.
 */

#include "\lc\stdio.h"

extern int Transfer_File();     /* Transfer a file using the "B" protocol */
extern int Read_Keyboard();     /* Get a "raw" character from the keyboard */
extern Init_Comm();             /* Initialize the comm port */
extern int Read_Modem();        /* Read a character from the comm port */
extern int Write_Modem();       /* Send a character to the comm port */
extern Term_Comm();

#define True  1
#define False  0
#define Exit_Key  0x010F
#define Is_Function_Key(C)  ((C) & 0x0100)

#define ENQ  0x05
#define DLE  0x10
#define ESC  0x1B

static char VIDTEX_Response[] = "#IB1,PB,DT\015";

static int
    Old_Break_State,
    I,
    Ch,                         /* 16-bit "raw" character. If > 127 than we
                                   have a function key. */
    Want_7_Bit,
    ESC_Seq_State;              /* Escape sequence state variable */


int Wants_To_Abort()
{
    return Read_Keyboard() == ESC;
}

main()
{
    char *cp;

    Want_7_Bit = True;
    ESC_Seq_State = 0;
    Old_Break_State = Get_Break();
    Set_Break(0);
    Init_Comm();
    puts("[ Terminal Mode ]");
    Ch = Read_Keyboard();

    while (Ch != Exit_Key)
        {
        if (Ch > 0)
            {
            if (Is_Function_Key(Ch))
                {
                /* Here to process any local function keys. This sample
                 * program is real dumb. The only function key is the EXIT
                 * key (Alt-X).
                 */
                }
            else
                Write_Modem(Ch & 0x7F);
            }

        if ((Ch = Read_Modem()) >= 0)
            {
            if (Want_7_Bit) Ch &= 0x7F;

            switch (ESC_Seq_State)
                {
                case 0:
                    switch (Ch)
                        {
                        case ESC:
                            ESC_Seq_State = 1;
                            break;

                        case ENQ:
                            /* Enquiry -- send ACK for packet 0 */

                            Write_Modem(DLE);
                            Write_Modem('0');
                            break;

                        case DLE:
                            ESC_Seq_State = 2;
                            break;

                        default:
                            Put_Char(Ch);
                        }

                    break;

                case 1:
                    /* ESC -- process any escape sequences here */

                    switch (Ch)
                        {
                        case 'I':
                            /* Reply to the VIDTEX "ESC I" identify sequence */
                            cp = VIDTEX_Response;
                            while (*cp != 0) Put_Char(*cp++);
                            ESC_Seq_State = 0;
                            break;

                        default:
                            Put_Char(ESC);
                            Put_Char(Ch);
                            ESC_Seq_State = 0;
                        }

                    break;

                case 2:
                    /* DLE */

                    if (Ch == 'B')
                        {
                        /* Start of "B" protocol packet. Go into protocol
                         * mode and transfer the file as requested.
                         */

                        if (!Transfer_File()) puts("Transfer failed!");
                        }
                    else
                        {
                        Put_Char(DLE);
                        Put_Char(Ch);
                        }

                    ESC_Seq_State = 0;
                }
            }

        Ch = Read_Keyboard();
        }

    Term_Comm();
    Set_Break(Old_Break_State);
}


	====== B P . C ======

/**
 * Copyright (c) 1985 by Steve Wilhite, Worthington, Ohio
 *
 * Permission is granted to use or distribute this software without any
 * restrictions as long as this entire copyright notice is included intact.
 * You may include it in any software product that you sell for profit.
 *
 * This software is distributed as is, and is not guaranteed to work on any
 * given hardware/software configuration.  Furthermore, no liability is
 * granted with this software.
 *
 * ABSTRACT:
 *
 *      The function, Transfer_File, implements error-free file transfer using
 *      CompuServe's "B" protocol.
 *
 *      It has been assumed that the start-of-packet sequence, DLE "B", has
 *      been detected and the next byte not received yet is the packet
 *      sequence number (an ASCII digit).
 *
 * ENVIRONMENT: Lattice "C", machine independent.
 *
 * AUTHOR: Steve Wilhite, CREATION DATE: 21-Jul-85
 *
 * REVISION HISTORY:
 *
 **/

#include "\lc\stdio.h"
#include "\lc\fcntl.h"

/* External Functions */

extern Delay();                 /* Sleep for "n" milliseconds */
extern Start_Timer();           /* Enable the timer for the specified number
                                   seconds */
extern int Timer_Expired();     /* Returns "true" if the timer has expired,
                                   "false" otherwise */
extern int Wants_To_Abort();    /* Returns "true" if the user wants to abort
                                   the file transfer, "false" otherwise */
extern int Read_Modem();        /* Read a character from the comm port.
                                   Returns -1 if no character available */
extern int Write_Modem();       /* Send a character to the comm port. Returns
                                   "true" is successful, "false" otherwise */

extern int open(), creat();     /* standard I/O functions */
extern int read(), write();
extern int close();

#define ETX     0x03
#define ENQ     0x05
#define DLE     0x10
#define XON     0x11
#define XOFF    0x13
#define NAK     0x15

#define True            1
#define False           0
#define Success         -1
#define Failure         0
#define Packet_Size     512
#define Max_Errors      10
#define Max_Time        10
#define WACK            ';'             /* wait acknowledge */

/* Sender actions */

#define S_Send_Packet   0
#define S_Get_DLE       1
#define S_Get_Num       2
#define S_Get_Seq       3
#define S_Get_Data      4
#define S_Get_Checksum  5
#define S_Timed_Out     6
#define S_Send_NAK      7

/* Receiver actions */

#define R_Get_DLE       0
#define R_Get_B         1
#define R_Get_Seq       2
#define R_Get_Data      3
#define R_Get_Checksum  4
#define R_Send_NAK      5
#define R_Send_ACK      6

static int
    Ch,
    Checksum,
    Seq_Num,
    R_Size,                             /* Size of receiver buffer */
    XOFF_Flag,
    Seen_ETX;

static char
    S_Buffer[Packet_Size],              /* Sender buffer */
    R_Buffer[Packet_Size];              /* Receiver buffer */

static Put_Msg(Text)
    char *Text;
{
    while (*Text != 0) Put_Char(*Text++);
    Put_Char('\015');
    Put_Char('\012');
}

static Send_Byte(Ch)
    int Ch;
{
    int TCh;

    /* Listen for XOFF from the network */

    do
        {
        while ((TCh = Read_Modem()) >= 0)
            if (TCh == XON) XOFF_Flag = False;
            else if (TCh == XOFF) XOFF_Flag = True;
        }
    while (XOFF_Flag);

    while (!Write_Modem(Ch));
}

static Send_Masked_Byte(Ch)
    int Ch;
{
    /* Mask any protocol or flow characters */

    if (Ch == ETX || Ch == ENQ || Ch == DLE || Ch == NAK || Ch == XON || Ch == XOFF)
        {
        Send_Byte(DLE);
        Send_Byte(Ch + '@');
        }
    else Send_Byte(Ch);
}

static Send_ACK()
{
    Send_Byte(DLE);
    Send_Byte(Seq_Num + '0');
}

static Read_Byte()
{
    if ((Ch = Read_Modem()) < 0)
        {
        Start_Timer(Max_Time);

        do
            {
            if (Timer_Expired()) return Failure;
            }
        while ((Ch = Read_Modem()) < 0);
        }

    return Success;
}

static Read_Masked_Byte()
{
    Seen_ETX = False;
    if (Read_Byte() == Failure) return Failure;

    if (Ch == DLE)
        {
        if (Read_Byte() == Failure) return Failure;
        Ch &= 0x1F;
        }
    else if (Ch == ETX) Seen_ETX = True;

    return Success;
}


static Do_Checksum(Ch)
    int Ch;
{
    Checksum <<= 1;
    if (Checksum > 255) Checksum = (Checksum & 0xFF) + 1;
    Checksum += Ch;
    if (Checksum > 255) Checksum = (Checksum & 0xFF) + 1;
}

static int Read_Packet(Action)
/**
 * Function:
 *      Receive a packet from the host.
 *
 * Inputs:
 *      Action -- the starting action
 *
 * Outputs:
 *      R_Buffer -- contains the packet just received
 *      R_Size -- length of the packet
 *
 * Returns:
 *      success/failure
 **/
    int Action;
{
    int Errors;
    int Next_Seq;

    Errors = 0;

    while (Errors < Max_Errors)
        switch (Action)
            {
            case R_Get_DLE:
                if (Read_Byte() == Failure) Action = R_Send_NAK;
                else if (Ch == DLE) Action = R_Get_B;
                else if (Ch == ENQ) Action = R_Send_ACK;
                break;

            case R_Get_B:
                if (Read_Byte() == Failure) Action = R_Send_NAK;
                else if (Ch == 'B') Action = R_Get_Seq;
                else Action = R_Get_DLE;
                break;

            case R_Get_Seq:
                if (Read_Byte() == Failure) Action = R_Send_NAK;
                else
                    {
                    Checksum = 0;
                    Next_Seq = Ch - '0';
                    Do_Checksum(Ch);
                    R_Size = 0;
                    Action = R_Get_Data;
                    }

                break;

            case R_Get_Data:
                if (Read_Masked_Byte() == Failure) Action = R_Send_NAK;
                else if (Seen_ETX) Action = R_Get_Checksum;
                else if (R_Size == Packet_Size) Action = R_Send_NAK;
                else
                    {
                    R_Buffer[R_Size++] = Ch;
                    Do_Checksum(Ch);
                    }

                break;

            case R_Get_Checksum:
                Do_Checksum(ETX);

                if (Read_Masked_Byte() == Failure) Action = R_Send_NAK;
                else if (Checksum != Ch) Action = R_Send_NAK;
                else if (Next_Seq == Seq_Num)
                    Action = R_Send_ACK;        /* Ignore duplicate packet */
                else if (Next_Seq != (Seq_Num + 1) % 10) Action = R_Send_NAK;
                else
                    {
                    Seq_Num = Next_Seq;
                    return Success;
                    }

                break;

            case R_Send_NAK:
                Put_Char('-');
                Errors++;
                Send_Byte(NAK);
                Action = R_Get_DLE;
                break;

            case R_Send_ACK:
                Send_ACK();
                Action = R_Get_DLE;
                break;
            }

    return Failure;
}

static int Send_Packet(Size)
/**
 * Function:
 *      Send the specified packet to the host.
 *
 * Inputs:
 *      Size -- length of the packet
 *      S_Buffer -- the packet to send
 *
 * Outputs:
 *
 * Returns:
 *      success/failure
 **/
    int Size;                           /* size of packet to send */
{
    int Action;
    int Next_Seq;
    int RCV_Num;
    int I;
    int Errors;

    Next_Seq = (Seq_Num + 1) % 10;
    Errors = 0;
    Action = S_Send_Packet;

    while (Errors < Max_Errors)
        switch (Action)
            {
            case S_Send_Packet:
                Checksum = 0;
                Send_Byte(DLE);
                Send_Byte('B');
                Send_Byte(Next_Seq + '0');
                Do_Checksum(Next_Seq + '0');

                for (I = 0; I < Size; I++)
                    {
                    Send_Masked_Byte(S_Buffer[I]);
                    Do_Checksum(S_Buffer[I]);
                    }

                Send_Byte(ETX);
                Do_Checksum(ETX);
                Send_Masked_Byte(Checksum);
                Action = S_Get_DLE;
                break;

            case S_Get_DLE:
                if (Read_Byte() == Failure) Action = S_Timed_Out;
                else if (Ch == DLE) Action = S_Get_Num;
                else if (Ch == NAK)
                    {
                    Errors++;
                    Action = S_Send_Packet;
                    }

                break;

            case S_Get_Num:
                if (Read_Byte() == Failure) Action = S_Timed_Out;
                else if (Ch >= '0' && Ch <= '9')
                    {
                    if (Ch == Seq_Num + '0')
                        Action = S_Get_DLE;     /* Ignore duplicate ACK */
                    else if (Ch == Next_Seq + '0')
                        {
                        /* Correct sequence number */

                        Seq_Num = Next_Seq;
                        return Success;
                        }
                    else if (Errors == 0) Action = S_Send_Packet;
                    else Action = S_Get_DLE;
                    }
                else if (Ch == WACK)
                    {
                    Delay(5000);        /* Sleep for 5 seconds */
                    Action = S_Get_DLE;
                    }
                else if (Ch == 'B') Action = S_Get_Seq;
                else Action = S_Get_DLE;
                break;

            case S_Get_Seq:
                /**
                 * Start of a "B" protocol packet. The only packet that makes
                 * any sense here is a failure packet.
                 **/

                if (Read_Byte() == Failure) Action = S_Send_NAK;
                else
                    {
                    Checksum = 0;
                    RCV_Num = Ch - '0';
                    Do_Checksum(Ch);
                    I = 0;
                    Action = S_Get_Data;
                    }

                break;

            case S_Get_Data:
                if (Read_Masked_Byte() == Failure) Action = S_Send_NAK;
                else if (Seen_ETX) Action = S_Get_Checksum;
                else if (I == Packet_Size) Action = S_Send_NAK;
                else
                    {
                    R_Buffer[I++] = Ch;
                    Do_Checksum(Ch);
                    }

                break;

            case S_Get_Checksum:
                Do_Checksum(ETX);

                if (Read_Masked_Byte() == Failure) Action = S_Send_NAK;
                else if (Checksum != Ch) Action = S_Send_NAK;
                else if (RCV_Num != (Next_Seq + 1) % 10) Action = S_Send_NAK;
                else
                    {
                    /**
                     * Assume the packet is failure packet. It makes no
                     * difference since any other type of packet would be
                     * invalid anyway. Return failure to caller.
                     **/

                    Errors = Max_Errors;
                    }

                break;

            case S_Timed_Out:
                Errors++;
                Action = S_Get_DLE;
                break;

            case S_Send_NAK:
                Put_Char('-');
                Errors++;
                Send_Byte(NAK);
                Action = S_Get_DLE;
                break;
            }

    return Failure;
}

static Send_Failure(Code)
/**
 * Function:
 *      Send a failure packet to the host.
 *
 * Inputs:
 *      Code -- failure code
 *
 * Outputs:
 *
 * Returns:
 **/
    char Code;
{
    S_Buffer[0] = 'F';
    S_Buffer[1] = Code;
    Send_Packet(2);
}

static int Receive_File(Name)
/**
 * Function:
 *      Download the specified file from the host.
 *
 * Inputs:
 *      Name -- ptr to the file name string
 *
 * Outputs:
 *
 * Returns:
 *      success/failure
 **/
    char *Name;
{
    int Data_File;

    if ((Data_File = creat(Name, O_RAW)) == -1)
        {
        Put_Msg("Cannot create file");
        Send_Failure('E');
        return Failure;
        }

    Send_ACK();

    for (;;)
        if (Read_Packet(R_Get_DLE) == Success)
            switch (R_Buffer[0])
                {
                case 'N':               /* Data packet */

                    if (write(Data_File, &R_Buffer[1], R_Size - 1) != R_Size - 1)
                        {
                        /* Disk write error */

                        Put_Msg("Disk write error");
                        Send_Failure('E');
                        close(Data_File);
                        return Failure;
                        }

                    if (Wants_To_Abort())
                        {
                        /* The user wants to kill the transfer */

                        Send_Failure('A');
                        close(Data_File);
                        return Failure;
                        }

                    Send_ACK();
                    Put_Char('+');
                    break;

                case 'T':               /* Transfer packet */

                    if (R_Buffer[1] == 'C') /* Close file */
                        {
                        Send_ACK();
                        close(Data_File);
                        return Success;
                        }
                    else
                        {
                        /**
                         * Unexpected "T" packet. Something is rotten on the
                         * other end. Send a failure packet to kill the
                         * transfer cleanly.
                         **/

                        Put_Msg("Unexpected packet type");
                        Send_Failure('E');
                        close(Data_File);
                        return Failure;
                        }

                case 'F':               /* Failure packet */
                    Send_ACK();
                    close(Data_File);
                    return Failure;
                }
        else
            {
            close(Data_File);
            return Failure;
            }
}

static int Send_File(Name)
/**
 * Function:
 *      Send the specified file to the host.
 *
 * Inputs:
 *      Name -- ptr to the file name string
 *
 * Outputs:
 *
 * Returns:
 *      success/failure
 **/
    char *Name;
{
    int Data_File;
    int N;

    if ((Data_File = open(Name, O_RDONLY | O_RAW)) == -1)
        {
        Put_Msg("Cannot access that file");
        Send_Failure('E');
        return Failure;
        }

    do
        {
        S_Buffer[0] = 'N';
        N = read(Data_File, &S_Buffer[1], Packet_Size - 1);

        if (N > 0)
            {
            if (Send_Packet(N + 1) == Failure)
                {
                close(Data_File);
                return Failure;
                }

            if (Wants_To_Abort())
                {
                Send_Failure('A');
                close(Data_File);
                return Failure;
                }

            Put_Char('+');
            }
        }
    while (N > 0);

    if (N == 0)                         /* end of file */
        {
        close(Data_File);
        S_Buffer[0] = 'T';
        S_Buffer[1] = 'C';
        return Send_Packet(2);
        }
    else
        {
        Put_Msg("Disk read error");
        Send_Failure('E');
        return Failure;
        }
}

int Transfer_File()
/**
 * Function:
 *      Transfer a file from/to the macro to/from the host.
 *
 * Inputs:
 *
 * Outputs:
 *
 * Returns:
 *      success/failure
 **/
{
    int I, N;
    char Name[64];

    XOFF_Flag = False;
    Seq_Num = 0;

    if (Read_Packet(R_Get_Seq) == Success)
        {
        if (R_Buffer[0] == 'T')         /* transfer packet */
            {
            /* Check the direction */

            if (R_Buffer[1] != 'D' && R_Buffer[1] != 'U')
                {
                Send_Failure('N');      /* not implemented */
                return Failure;
                }

            /* Check the file type */

            if (R_Buffer[2] != 'A' && R_Buffer[2] != 'B')
                {
                Send_Failure('N');
                return Failure;
                }

            /* Collect the file name */

            if (R_Size - 3 > 63) N = 63;
            else N = R_Size - 3;

            for (I = 0; I < N; I++)
                Name[I] = R_Buffer[I + 3];

            Name[I] = 0;

            /* Do the transfer */

            if (R_Buffer[1] == 'U') return Send_File(Name);
            else return Receive_File(Name);
            }
        else
            {
            Send_Failure('E');          /* wrong type of packet */
            return Failure;
            }
        }
    else return Failure;
}

--