package com.tecspy.protocol;
import java.io.IOException;
import org.apache.log4j.Logger;
/**
* Is fed with bytes from an input stream and issues complete messages. The
* messages are bounded by a single start byte, "stx", and a single end byte,
* "etx".
*
* This class is coded to cope with the special case where stx and etx are the
* same. Bytes that are received "outside" the message stx and etx are
* considered to be erroneous and must be handled by the implementation.
*
* Test byte streams:
*
* normal : stx data etx | stx data etx | ...
*
* restart: stx data stx data etx - report discarded bytes
*
* quickrestart: stx stx data
*
* when stx == etx then this is ignored
*
* oob data: stx data etx data - report oob data
*
* random: ??????? - not a real test but may stress test
*
* join/part stream midway - quite possible at start of stream: data etx stx
* data etx - special handling?
*
* tests for maximum message length being exceeded
*
* @author Michael Erskine
*
*/
public abstract class StxEtxMessageByteSink implements ByteSink {
/** Standard log4j logger */
protected static Logger log = Logger.getLogger(StxEtxMessageByteSink.class);
/**
* Currently forming message buffer - should be constructed to the expected
* size of typical messages rather than the maximum
*/
protected StringBuilder msg;
/** Message start byte */
protected byte stx = (byte) 0x02;
/** Message end byte */
protected byte etx = (byte) 0x03;
/** Maximum message length */
protected int maxmsg = 1000;
/**
* State of read process: false = waiting for stx, true = waiting for data
* or etx.
*/
private boolean inmsg = false;
/**
* An indication of the number of bytes read. NB: not necessarily 100%
* correct.
*/
public long byteTotal = 0;
protected byte lastChar;
/**
* A subclass constructor must create the message buffer e.g.: -
*
* <code>
* super();
* stx = (byte) 0xFE;
* etx = (byte) 0xFE;
* maxmsg = 1000;
* msg = new StringBuilder(1000);
* </code>
*
*/
public StxEtxMessageByteSink() {
}
protected abstract void reportUnexpectedRestart();
protected abstract void reportUnexpectedEtx();
protected abstract void reportUnexpectedData(byte b);
protected abstract void handleMsg();
public int processBytes(byte[] buf, int len) {
// incrementing the byte total here rather than for each byte
byteTotal += len;
for (int i = 0; i < len; i++) { // foreach byte
if (!inmsg) { // !inmsg == waiting for stx
// we are waiting for stx to start the message
if (buf[i] == stx) {
// stx is the normal condition
inmsg = true;
} else if (buf[i] == etx) {
// we weren't expecting etx
// in the case where etx == stx, the byte will have been
// handled above
reportUnexpectedEtx();
} else {
// we got unexpected data whilst waiting for stx
reportUnexpectedData(buf[i]);
}
} else { // inmsg == true
// we are waiting for data or etx
if (buf[i] == etx) {
// etx is a normal condition - flush
handleMsg();
msg.setLength(0);
inmsg = false;
} else if (buf[i] == stx) {
// stx here is a message restart - report and reset
// in the case where etx == stx, the byte will have been
// handled above
reportUnexpectedRestart();
msg.setLength(0);
} else {
// normal condition add byte to message
char c = (char) buf[i];
if (msg.length() >= maxmsg) {
log.error("maximum message length exceeded (" + maxmsg
+ "): buffered data discarded");
msg.setLength(0);
}
msg.append(c);
}
}
lastChar = buf[i];
} // end of foreach byte
return len;
}
public abstract void processEof();
public abstract void processException(IOException e);
}