TModem.java Source code

Java tutorial

Introduction

Here is the source code for TModem.java

Source

/*
 * 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 < SECSIZE)
                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));
    }
}