/************************************************************************* ** File: bprot.c ** contains routines to perform CompuServe's B-protocol file transfers. ** ** Code originally downloaded from Compuserve -- See following ** 'original' remarks. Modified heavily by Bob Rakosky to fit in ** with the rest of TermPlus -- June, 1986 ** ** Functions: ** Send_Byte() - send character to remote, honoring XON/XOFF from host ** Send_Masked_Byte() - send character to remote, translating control ** characters to DLE sequences ** Send_ACK() - send positive response to remote, including current ** sequence number ** Read_Byte() - timed read of single character from remote ** Read_Masked_Byte() - read of character from remote, translating DLE ** sequence to original control character ** Do_Checksum() = calculate/update checksum based on single character, ** conforming with B-protocol algorithm ** Read_Packet() - receive a packet from remote host ** Send_Packet() - send a packet to remote host ** Send_Failure() - send a Failure packet to remote host ** Receive_File() - do download of file from remote host ** Send_File() - do upload of file to remote host ** B_prot_xfer() - do file transfer (either way, as directed by host) ** *************************************************************************/ #include "term.h" /** * BP.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: * * Steve Wilhite, 17-Jan-86 * - included a virtual file interface. **/ /* External Functions */ extern int emit(); /* Write a character to the display */ extern int start_timer(); /* Enable the timer for the specified number seconds */ extern int test_timer(); /* Returns "true" if the timer has expired, "false" otherwise */ extern int test_kbd_abort(); /* Returns "true" if the user wants to abort the file transfer, "false" otherwise */ extern int sendchar(); /* Send a character to the comm port. Returns "true" is successful, "false" otherwise */ extern int timeout; extern char rs_in[]; extern struct IOExtSer *Read_Request; extern struct timerequest *timer; #define NUL 0x00 #define ETX 0x03 #define XON 0x11 #define XOFF 0x13 #define True 1 #define False 0 #define Success -1 #define Failure 0 #define Packet_Size 512 #define Max_Errors 5 #define Max_Time 10 #define Max_Xoff_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 #define S_Send_ACK 8 /* 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, Seen_ENQ; static char S_Buffer[Packet_Size], /* Sender buffer */ R_Buffer[Packet_Size]; /* Receiver buffer */ static Send_Byte(Ch) int Ch; { int TCh; /* Listen for XOFF from the network */ start_timer(Max_Xoff_Time); do { while ((TCh = cond_read()) >= 0) if (TCh == XON) XOFF_Flag = False; else if (TCh == XOFF) { XOFF_Flag = True; kill_timer(); start_timer(Max_Xoff_Time); } } while (XOFF_Flag && !test_timer()); if (!timeout) kill_timer(); sendchar(Ch); } static Send_Masked_Byte(Ch) int Ch; { /* Mask any protocol or flow characters */ if (Ch == NUL || 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 = cond_read()) < 0) { start_timer(Max_Time); do { if (test_timer()) { return Failure; } } while ((Ch = cond_read()) < 0); kill_timer(); } return Success; } static Read_Masked_Byte() { Seen_ETX = False; Seen_ENQ = False; if (Read_Byte() == Failure) return Failure; if (Ch == DLE) { if (Read_Byte() == Failure) return Failure; Ch -= '@'; } else if (Ch == ETX) Seen_ETX = True; else if (Ch == ENQ) Seen_ENQ = True; return Success; } static Do_Checksum(Ch) int Ch; { Checksum <<= 1; if (Checksum > 255) Checksum = (Checksum & 0xFF) + 1; Checksum += Ch & 0xFF; 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, Next_Seq; Errors = 0; while (Errors < Max_Errors) switch (Action) { case R_Get_DLE: if (Read_Byte() == Failure) { emits("Timeout-get_dle\n"); 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) { emits("Timeout-get_B\n"); Action = R_Send_NAK; } else if (Ch == 'B') Action = R_Get_Seq; else { emits("Not xfer packet\n"); Action = R_Get_DLE; } break; case R_Get_Seq: if (Read_Byte() == Failure) { emits("Timeout-get_seq\n"); 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) { emits("Timeout-get_data\n"); Action = R_Send_NAK; } else if (Seen_ETX) Action = R_Get_Checksum; else if (Seen_ENQ) Action = R_Send_ACK; 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) { emits("Timeout-get_checksum\n"); Action = R_Send_NAK; } else if (Checksum != Ch) { emits("--Checksum Error\n"); 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) { emits("--Sequence error\n"); Action = R_Send_NAK; } else { Seq_Num = Next_Seq; return Success; } break; case R_Send_NAK: 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; { int Action, Next_Seq, RCV_Num, I, 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 == ENQ) Action = S_Send_ACK; 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(5); /* 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 (Seen_ENQ) Action = S_Send_ACK; 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: Errors++; Send_Byte(NAK); Action = S_Get_DLE; break; case S_Send_ACK: Send_ACK(); 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; /* file descriptor */ char line[80]; int Real_Seq = 0; if ((Data_File = creat(Name, 0)) == -1) { emits("Cannot create file\n"); Send_Failure('E'); close_time(); return Failure; } sprintf(line,"\nReceiving File: %s\n\n",Name); emits(line); Send_ACK(); for (;;) { sprintf(line,"\rReceiving Packet %d ",Real_Seq); emits(line); if (Read_Packet(R_Get_DLE) == Success) { Real_Seq++; switch (R_Buffer[0]) { case 'N': /* Data packet */ if (write(Data_File, &R_Buffer[1], R_Size - 1) != R_Size - 1) { /* Disk write error */ emits("Disk write error\n"); Send_Failure('E'); close(Data_File); close_time(); return Failure; } if (test_kbd_abort()) { /* The user wants to kill the transfer */ Send_Failure('A'); close(Data_File); close_time(); return Failure; } Send_ACK(); emit('+'); break; case 'T': /* Transfer packet */ if (R_Buffer[1] == 'C') /* Close file */ { Send_ACK(); close(Data_File); close_time(); return Success; } else { /** * Unexpected "T" packet. Something is rotten on the * other end. Send a failure packet to kill the * transfer cleanly. **/ emits("Unexpected packet type\n"); Send_Failure('E'); close(Data_File); close_time(); return Failure; } case 'F': /* Failure packet */ Send_ACK(); close(Data_File); close_time(); return Failure; } } else { Send_Failure('E'); close(Data_File); close_time(); 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 N; int Data_File; /* file descriptor */ char line[80]; int Real_Seq = 0; if ((Data_File = open(Name,O_RDONLY)) == -1) { emits("Cannot access that file\n"); Send_Failure('E'); close_time(); return Failure; } sprintf(line,"\nSending File: %s\n\n",Name); emits(line); do { S_Buffer[0] = 'N'; N = read(Data_File, &S_Buffer[1], Packet_Size - 1); if (N > 0) { sprintf(line,"\rSending Packet %d ",Real_Seq); emits(line); if (Send_Packet(N + 1) == Failure) { close(Data_File); close_time(); return Failure; } Real_Seq++; if (test_kbd_abort()) { Send_Failure('A'); close(Data_File); close_time(); return Failure; } } } while (N > 0); if (N == 0) /* end of file */ { close(Data_File); S_Buffer[0] = 'T'; S_Buffer[1] = 'C'; N = Send_Packet(2); close_time(); emits("\n\nFile Sent\n\n"); return(N); } else { emits("Disk read error\n"); Send_Failure('E'); close_time(); return Failure; } } int B_prot_xfer() /** * Function: * Transfer a file from/to the micro to/from the host. * * Inputs: * * Outputs: * * Returns: * success/failure **/ { int I, N; char Name[64]; /* holds the file name */ if (!open_time()) { /* open timer device */ return Failure; } 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 */ close_time(); return Failure; } /* Check the file type */ if (R_Buffer[2] != 'A' && R_Buffer[2] != 'B') { Send_Failure('N'); close_time(); return Failure; } /* Collect the file name */ N = R_Size - 3 > 63 ? 63 : 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 */ close_time(); return Failure; } } else { return Failure; } }