Network Protocol Java

/*
 * Copyright (c) Ian F. Darwin, http://www.darwinsys.com/, 1996-2002.
 * All rights reserved. Software written by Ian F. Darwin and others.
 * $Id: LICENSE,v 1.8 2004/02/09 03:33:38 ian Exp $
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 * 
 * Java, the Duke mascot, and all variants of Sun's Java "steaming coffee
 * cup" logo are trademarks of Sun Microsystems. Sun's, and James Gosling's,
 * pioneering role in inventing and promulgating (and standardizing) the Java 
 * language and environment is gratefully acknowledged.
 * 
 * The pioneering role of Dennis Ritchie and Bjarne Stroustrup, of AT&T, for
 * inventing predecessor languages C and C++ is also gratefully acknowledged.
 */
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
/**
 * a tiny version of Ward Christensen's MODEM program for UNIX. 
 * Written ~ 1980 by Andrew Scott Beals. Last revised 1982.
 * A.D. 2000 - dragged from the archives for use in Java Cookbook.
 *
 * @author C version by Andrew Scott Beals, sjobrg.andy%mit-oz@mit-mc.arpa.
 * @author Java version by Ian F. Darwin, http://www.darwinsys.com/
 * $Id: TModem.java,v 1.10 2004/04/11 23:50:40 ian Exp $
 */
public class TModem {
  protected final byte CPMEOF = 26;    /* control/z */
  protected final int MAXERRORS = 10;    /* max times to retry one block */
  protected final int SECSIZE  = 128;    /* cpm sector, transmission block */
  protected final int SENTIMOUT = 30;    /* timeout time in send */
  protected final int  SLEEP  = 30;    /* timeout time in recv */
  /* Protocol characters used */
  protected final byte  SOH  = 1;  /* Start Of Header */
  protected final byte  EOT  = 4;  /* End Of Transmission */
  protected final byte  ACK  = 6;  /* ACKnowlege */
  protected final byte  NAK  = 0x15;  /* Negative AcKnowlege */
  protected InputStream inStream;
  protected OutputStream outStream;
  protected PrintWriter errStream;
  /** Construct a TModem */
  public TModem(InputStream is, OutputStream os, PrintWriter errs) {
    inStream = is;
    outStream = os;
    errStream = errs;
  }
  /** Construct a TModem with default files (stdin and stdout). */
  public TModem() {
    inStream = System.in;
    outStream = System.out;
    errStream = new PrintWriter(System.err);
  }
  /** A main program, for direct invocation. */
  public static void main(String[] argv) throws 
    IOException, InterruptedException {
    /* argc must == 2, i.e., `java TModem -s filename' */
    if (argv.length != 2) 
      usage();
    if (argv[0].charAt(0) != '-')
      usage();
    TModem tm = new TModem();
    tm.setStandalone(true);
    boolean OK = false;
    switch (argv[0].charAt(1)){
    case 'r': 
      OK = tm.receive(argv[1]); 
      break;
    case 's': 
      OK = tm.send(argv[1]); 
      break;
    default: 
      usage();
    }
    System.out.print(OK?"Done OK":"Failed");
    System.exit(0);
  }
  /* give user minimal usage message */
  protected static void usage()
  {
    System.err.println("usage: TModem -r/-s file");
    // not errStream, not die(), since this is static.
    System.exit(1);
  }
  /** If we're in a standalone app it is OK to System.exit() */
  protected boolean standalone = false;
  public void setStandalone(boolean is) {
    standalone = is;
  }
  public boolean isStandalone() {
    return standalone;
  }
  /** A flag used to communicate with inner class IOTimer */
  protected boolean gotChar;
  /** An inner class to provide a read timeout for alarms. */
  class IOTimer extends Thread {
    String message;
    long milliseconds;
    /** Construct an IO Timer */
    IOTimer(long sec, String mesg) {
      milliseconds = 1000 * sec;
      message = mesg;
    }
    
    public void run() {
        try {
        Thread.sleep(milliseconds);
      } catch (InterruptedException e) {
        // can't happen
      }
      /** Implement the timer */
      if (!gotChar)
      errStream.println("Timed out waiting for " + message);
      try {
        die(1);
      } catch (Exception e1) {
        // TODO Auto-generated catch block
        e1.printStackTrace();
      }
    }
  }
  /*
   * send a file to the remote
   */
  public boolean send(String tfile) throws IOException, InterruptedException
  {
    char checksum, index, blocknumber, errorcount;
    byte character;
    byte[] sector = new byte[SECSIZE];
    int nbytes;
    DataInputStream foo;
    foo = new DataInputStream(new FileInputStream(tfile));
    errStream.println( "file open, ready to send");
    errorcount = 0;
    blocknumber = 1;
    // The C version uses "alarm()", a UNIX-only system call,
    // to detect if the read times out. Here we do detect it
    // by using a Thread, the IOTimer class defined above.
    gotChar = false;
    new IOTimer(SENTIMOUT, "NAK to start send").start();
    do {
      character = getchar();
      gotChar = true;
      if (character != NAK && errorcount < MAXERRORS)
        ++errorcount;
    } while (character != NAK && errorcount < MAXERRORS);
    errStream.println( "transmission beginning");
    if (errorcount == MAXERRORS) {
      xerror();
    }
    while ((nbytes=inStream.read(sector))!=0) {
      if (nbytes        sector[nbytes]=CPMEOF;
      errorcount = 0;
      while (errorcount < MAXERRORS) {
        errStream.println( "{" + blocknumber + "} ");
        putchar(SOH);  /* here is our header */
        putchar(blocknumber);  /* the block number */
        putchar(~blocknumber);  /* & its complement */
        checksum = 0;
        for (index = 0; index < SECSIZE; index++) {
          putchar(sector[index]);
          checksum += sector[index];
        }
        putchar(checksum);  /* tell our checksum */
        if (getchar() != ACK)
          ++errorcount;
        else
          break;
      }
      if (errorcount == MAXERRORS)
        xerror();
      ++blocknumber;
    }
    boolean isAck = false;
    while (!isAck) {
      putchar(EOT);
      isAck = getchar() == ACK;
    }
    errStream.println( "Transmission complete.");
    return true;
  }
  /*
   * receive a file from the remote
   */
  public boolean receive(String tfile) throws IOException, InterruptedException
  {
    char checksum, index, blocknumber, errorcount;
    byte character;
    byte[] sector = new byte[SECSIZE];
    DataOutputStream foo;
    foo = new DataOutputStream(new FileOutputStream(tfile));
    System.out.println("you have " + SLEEP + " seconds...");
    /* wait for the user or remote to get his act together */
    gotChar = false;
    new IOTimer(SLEEP, "receive from remote").start(); 
    errStream.println("Starting receive...");
    putchar(NAK);
    errorcount = 0;
    blocknumber = 1;
    rxLoop:
    do { 
      character = getchar();
      gotChar = true;
      if (character != EOT) {
        try {
          byte not_ch;
          if (character != SOH) {
            errStream.println( "Not SOH");
            if (++errorcount < MAXERRORS)
              continue rxLoop;
            else
              xerror();
          }
          character = getchar();
          not_ch = (byte)(~getchar());
          errStream.println( "[" +  character + "] ");
          if (character != not_ch) {
            errStream.println( "Blockcounts not ~");
            ++errorcount;
            continue rxLoop;
          }
          if (character != blocknumber) {
            errStream.println( "Wrong blocknumber");
            ++errorcount;
            continue rxLoop;
          }
          checksum = 0;
          for (index = 0; index < SECSIZE; index++) {
            sector[index] = getchar();
            checksum += sector[index];
          }
          if (checksum != getchar()) {
            errStream.println( "Bad checksum");
            errorcount++;
            continue rxLoop;
          }
          putchar(ACK);
          blocknumber++;
          try {
            foo.write(sector);
          } catch (IOException e) {
            errStream.println("write failed, blocknumber " + blocknumber);
          }
        } finally {
        if (errorcount != 0)
          putchar(NAK);
      }
    }
    } while (character != EOT);
    foo.close();
    putchar(ACK);  /* tell the other end we accepted his EOT   */
    putchar(ACK);
    putchar(ACK);
    errStream.println("Receive Completed.");
    return true;
  }
  protected byte getchar() throws IOException {
    return (byte)inStream.read();
  }
  protected void putchar(int c) throws IOException {
    outStream.write(c);
  }
  protected void xerror()
  {
    errStream.println("too many errors...aborting");
    try {
      die(1);
    } catch (Exception e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
  protected void die(int how) throws Exception
  {
    if (standalone)
      System.exit(how);
    else throw new Exception("Error code " + Integer.toString(how));
  }
}