Skip to Main Content

Java Programming

Announcement

For appeals, questions and feedback about Oracle Forums, please email oracle-forums-moderators_us@oracle.com. Technical questions should be asked in the appropriate category. Thank you!

XModem via TCP for Java

807603Aug 10 2007 — edited Dec 18 2007
I am sure that many of you experienced developers have read requests in the past concerning implmentation of Ward Christenen's XModem protocol over a TCP socket. If not, well... you are about to...

This is a major hack... but it is starting to come together... thanks to Fred Potter for his source code to start this project...

Objective:

Basically, I want to create a console application which accepts an incoming connection and starts the receive mode for a XModem file transfer. I am using CGTerm (for Commodore retrocomputing) but can test with HyperTerminal as well...

The user who connects to the server selects SEND and the FILE to send for a XModem file transfer... and the transfer begins...

The incoming blocks of 128 bytes are written to a file

After the transfer is over the server disconnects the client terminal.

Here is what I have so far:

import java.net.*;
import java.lang.*;
import java.io.*;

// X-Modem Server implementation via TCP/IP socket

public class XServer {


public static FileWriter fw;

public static void main(String[] args) throws IOException {


// define the file
try {

fw = new FileWriter("filename.txt");

} catch (Exception e) {
System.out.println(e);
System.exit(0);
}


int port = Integer.parseInt(args[0]);
ServerSocket server = new ServerSocket(port);
System.out.println("X-Server v1.0 - waiting for connection");
Socket client = server.accept();

// Handle a connection and exit.
try {

InputStream inputStream = client.getInputStream();
OutputStream outputStream = client.getOutputStream();

new PrintStream(outputStream).println("Go to send file mode!"); // sent to client
System.out.println("Ready to receive file via X-Modem...");


/**
* BEGIN TRANSFER HERE!
*/

// set the debug flag
XModem.debug = true;

/**
* Here we are instantiating a new InputStream that represents the remote
* file that we are receiving. In this single line we are attempting to
* start the flow.
*
* Behind The Scenes: We're sending a NAK across the serial line repeatedly
* until we finaly start seeing the data flow. If we don't see the data
* flow, then we throw an exception.
*/

System.out.println("Sending NAK to start receive mode...");

InputStream incomingFile;

try {
incomingFile = new XModemRXStream(inputStream, outputStream);
} catch (IOException e) {
System.out.println("ERROR! Unable to start file transfer!");
e.printStackTrace();
return;
}

System.out.println("Starting file transfer...");

/**
* Here we are reading from the incoming file, byte by byte, and printing out.
*
* Behind The Scenes: Internally, the read() method is handling the task of
* asking for the next data block from the remote computer, processing it (i.e.
* parsing, running checksums), and then putting it in an internal buffer. Not
* all calls to read() will request a new data block as each block contains at
* least 128 bytes of data. Sometimes you will only hit the buffer.
*/
try {
for (;;) {
int c = incomingFile.read();

if (c==-1)
break; // End of File

// print character / byte
System.out.print(c+",");

// write to file
try {

//System.out.print(".");
fw.write(c);

} catch (Exception e) {
System.out.println(e);
System.exit(0);
}

}
} catch (IOException e) {
System.out.println("error while reading the incoming file.");
e.printStackTrace();
return;
}

// done

System.out.println("File sent.");
new PrintStream(outputStream).println("");
new PrintStream(outputStream).println("transfer successful!");


} finally {

//client.close();

// save the file
try {

fw.close();
System.out.println("file saved.");
} catch (Exception e) {
System.out.println(e);
System.exit(0);
}


}
}
}



/**
* XModem keeps track of settings that the Receive and Transmit Stream classes will
* reference.
* <p>Copyright: Copyright (c) 2004</p>
* @author Fred Potter
* @version 0.1
*/



class XModem {

public static boolean debug = false;


}





/**
* XModemRXStream is an easy to use class for receiving files via the XModem protocol.
* @author Fred Potter
* @version 0.1
*/

class XModemRXStream
extends InputStream {

// CONSTANTS
private static final int SOH = 0x01;
private static final int EOT = 0x04;
private static final int ACK = 0x06;
private static final int NAK = 0x15;
private static final int CAN = 0x18;
private static final int CR = 0x0d;
private static final int LF = 0x0a;
private static final int EOF = 0x1a;

// block size - DON'T CHANGE - I toyed with the idea of adding 1K support but the code is NOT there yet.
private static final int bs = 128;

// PRIVATE STUFF
private int ebn; // expected incoming block #

private byte[] data; // our data buffer
private int dataPos; // our position with the data buffer

private InputStream in;
private OutputStream out;

/**
* Creates a new InputStream allowing you to read the incoming file. All of the XModem
* protocol functions are handled transparently.
*
* As soon as this class is instantiated, it will attempt to iniatate the transfer
* with the remote computer - if unsuccessful, an IOException will be thrown. If it
* is successful, reading may commense.
*
* NOTE: It is important not to wait too long in between calls to read() - the remote
* computer will resend a data block if too much time has passed or even just give up
* on the transfer altogether.
*
* @param in InputStream from Serial Line
* @param out OutputStream from Serial Line
*/
public XModemRXStream(InputStream in, OutputStream out) throws
IOException {
this.in = in;
this.out = out;

//
// Initiate the receive sequence - basically, we send a NAK until the data
// starts flowing.
//

init:for (int t = 0; t < 10; t++) {

if (XModem.debug) {
System.out.println("Waiting for response [ try #" + t + " ]");
}

long mark = System.currentTimeMillis();

out.write(NAK);

// Frequently check to see if the data is flowing, give up after a couple seconds.
for (; ; ) {
if (in.available() > 0) {
break init;
}
try {
Thread.sleep(10);
}
catch (Exception e) {}
if (System.currentTimeMillis() - mark > 2000) {
break;
}
}

}

// We have either successfully negotiated the transfer, OR, it was
// a failure and timed out. Check in.available() to see if we have incoming
// bytes and that will be our sign.

if (in.available() == 0) {
throw new IOException();
}

//
// Initialize some stuff
//

ebn = 1; // the first block we see should be #1
data = new byte[bs];
dataPos = bs;

}

/**
* Reads the next block of data from the remote computer. Most of the real XModem protocol
* is encapsulated within this method.
* @throws IOException
*/
private synchronized void getNextBlock() throws IOException {

if (XModem.debug) {
//System.out.println("Getting block #" + ebn);
}

//
// Read block into buffer. There is a 1 sec timeout for each character,
// otherwise we NAK and start over.
//

byte[] buffer;

recv:for (; ; ) {
buffer = new byte[bs + 4];
for (int t = 0; t < 10; t++) {
System.out.println("\nReceiving block [ #" + ebn + " ]");
// Read in block
buffer = new byte[buffer.length];
for (int i = 0; i < buffer.length; i++) {
int b = readTimed(1);

// if EOT - don't worry about the rest of the block.
if ( (i == 0) && (b == EOT)) {
buffer[0] = (byte) (b & 0xff);
break;
}

// if CAN - the other side has cancelled the transfer
if (b == CAN) {
throw new IOException("cancelled");
}

if (b < 0) {
if (XModem.debug) {
System.out.println("Time out... NAK'ing");
}
out.write(NAK);
continue recv;
}
else {
buffer[i] = (byte) (b & 0xFF);
}

}
break;
}

int type = buffer[0] & 0xff; // either SOH or EOT

if (type == EOT) {
if (XModem.debug) {
System.out.println("EOT!");
}
out.write(ACK);
break;
}

int bn = buffer[1] & 0xff; // block number
int bnc = buffer[2] & 0xff; // one's complement to block #

if (
(bn != ebn) && (bn != (ebn - 1)) ||
(bn + bnc != 255)) {
if (XModem.debug) {
System.out.println("NAK'ing type = " + type + " bn = " + bn +
" ebn = " +
ebn + " bnc = " + bnc);
}
out.write(NAK);
continue recv;
}

byte chksum = buffer[ (buffer.length - 1)];

byte echksum = 0;
for (int i = 3; i < (buffer.length - 1); i++) {
echksum = (byte) ( ( (echksum & 0xff) + (buffer[i] & 0xff)) & 0xff);

}
if (chksum != echksum) {
out.write(NAK);
continue recv;
}

out.write(ACK);

if (ebn == 255) {
ebn = 0;
}
else {
ebn++;

}

break;

}

// We got our block, now save it in our data buffer.
data = new byte[bs];
for (int i = 3; i < (buffer.length - 1); i++) {
data[(i - 3)] = buffer;
}

dataPos = 0;

}

public synchronized int read() throws IOException {


// If at the end of our buffer, refill it.
if (dataPos == bs) {
try {
getNextBlock();
}
catch (IOException e) {
throw new IOException();
}
}

// If we're still at end of buffer, say so.
if ( dataPos == bs) {
return -1;
}

int d = data[dataPos];

if (d == EOF)
return -1;

dataPos++;

return d;

}

/**
* A wrapper around the native read() call that provides the ability
* to timeout if no data is available within the specified timeout value.
* @param timeout timeout value in seconds
* @throws IOException
* @return int an integer representing the byte value read.
*/
private int readTimed(int timeout) throws IOException {
long start = System.currentTimeMillis();

for (; ; ) {
if (in.available() > 0) {
return (in.read());
}
try {
Thread.sleep(10);
}
catch (InterruptedException ex) {
}
//if (System.currentTimeMillis() - start > timeout * 1000) {
if (System.currentTimeMillis() - start > timeout * 5000) {
return -1;
}
}
}

}


Here was the output...

Original file:
(Commodore CBM SEQ file exported to PC using DirMaster)

��

� �
� ��� �� �� ��� ��
� �� �� ���� �� ��� ��
� ��� ����������������������������������������������
�� ����� ������� ����� �� ����� ������ ����� ���
� �� ������ ������ ��� ��� �� ��� ���� �� ������
� � ���
����
� � ��OWERED BY �OLOR 64 ��� V8
�UNNING �ETWORK64 V1.26A

�UPPORTING 38400 �AUD �ATES
�����/����/�������

�ESTING �CHO-�ET V1 BETA

�EATURING �ESSAGES, �ILES,
�ET�AIL, AND �NLINE �AMES!

�YS�P: � � � � � � � � �

�RESS ANY KEY TO LOGIN\C�

The result when the file was uploaded and received by my XServer:

??

? ?
? ??? ?? ?? ??? ??
? ?? ?? ???? ?? ??? ??
? ??? ??????????????????????????????????????????????
?? ????? ??????? ????? ?? ????? ?????? ????? ???
? ?? ?????? ?????? ??? ??? ?? ??? ???? ?? ??????
? ? ???
????
? ? ??OWERED BY ?OLOR 64 ??? V8
?UNNING ?ETWORK64 V1.26A
?
?UPPORTING 38400 ?AUD ?ATES
?????/????/???????
?
?ESTING ?CHO-?ET V1 BETA
?
?EATURING ?ESSAGES, ?ILES,
?ET?AIL, AND ?NLINE ?AMES!

?YS?P: ? ? ? ? ? ? ? ? ?
?
?RESS ANY KEY TO LOGIN\C?


The result is different!

Can someone help me along here... I have been trying to figure out how to do this for approx. a year or so... it has been a very slow process.

I could use a guru to help me out so I can write the upload and download routines for my Commodore BBS PETSCII Emulation Server.

Visit http://www.retrogradebbs.com for details.

Thanks.

Please help out a dedicated developer who is in over his head...

-Dave
Comments
Locked Post
New comments cannot be posted to this locked post.
Post Details
Locked on Jan 15 2008
Added on Aug 10 2007
7 comments
1,640 views