/*
* Copyright (c) 2006-2007 Graz University of Technology. All rights reserved.
*
* 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.
*
* 3. The names "Graz University of Technology" and "IAIK of Graz University of
* Technology" must not be used to endorse or promote products derived from
* this software without prior written permission.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED 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 LICENSOR 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.
*/
package ejip.jtcpip;
import java.io.IOException;
import java.io.OutputStream;
import ejip.jtcpip.util.Debug;
/**
* Output stream passed to the user if he opens a TCP connection over CLDC
* Also the data for retransmission is stored in the stream's buffer. Because
* of this the buffer can be full also if all written data was read out using
* {@link #read()}. With {@link #ackData(int)} this retransmission data can
* be released.
*
* @author Ulrich Feichter
* @author Tobias Kellner
* @author Christof Rath
* @version $Rev: 989 $ $Date: 2007/01/24 19:37:07 $
*/
public class TCPOutputStream extends OutputStream
{
/**
* Size of the circular buffer in bytes. BEWARE: DON'T SET THIS BIGGER THAN
* MAX_INT!
*/
private int BUFSIZE;
/**
* The circular buffer
*/
private byte[] buffer;
/**
* Points at the next element to be written
*/
private int writePtr;
/**
* points at the next element to be read
*/
private int readPtr;
/**
* points at the next that was not already acked (conn.sndUnack si the
* sequence number for this byte)
*/
private int ackWaitPtr;
/**
* Whether the buffer is blocked (if {@link #writePtr} == {@link #readPtr})
*/
private boolean isBufferBlocked;
/**
* Whether the buffer is full (if {@link #writePtr} == {@link #ackWaitPtr})
*/
private boolean isBufferFull;
/**
* Whether the stream is closed
*/
private boolean closed;
/**
* Exception that gets thrown when trying to write although the stream has
* been closed
*/
private static IOException streamClosedException;
/**
* Exception that gets thrown when trying to write although the buffer is
* full
*/
private static IOException bufferFullException;
public static void init(){
streamClosedException = new IOException(
"TCPOutputStream: Stream closed");
bufferFullException = new IOException(
"TCPOutputStream: Buffer is full");
}
/**
* Returns the number of free bytes in the buffer.
*
* @return int containing the number of free bytes in the buffer
*/
protected int getFreeBufferSpace()
{
if (isBufferFull)
return 0;
else if (writePtr == ackWaitPtr)
return BUFSIZE;
else if (writePtr > ackWaitPtr)
return BUFSIZE - (writePtr - ackWaitPtr);
else
return ackWaitPtr - writePtr;
}
/**
* Constructor which takes the size of the circular buffer in bytes
*
* @param size
* size of the circular buffer
*/
protected TCPOutputStream(int size)
{
closed = false;
BUFSIZE = size;
writePtr = 0;
readPtr = 0;
ackWaitPtr = 0;
isBufferFull = false;
isBufferBlocked = false;
buffer = new byte[BUFSIZE];
}
/**
* Writes the 8 least significant bits of b into the stream. If the streams
* buffer is full or the stream is closed a IOException is thrown.
*
* @param b
* byte to be written. (is passed as integer from which the 24
* most significant bits are discarded)
* @throws IOException
* thrown if there is no more buffer space or the stream is
* closed
*/
synchronized public void write(int b) throws IOException
{
if (closed)
throw streamClosedException;
if (isBufferFull || isBufferBlocked)
throw bufferFullException;
buffer[writePtr] = (byte) (b & 0xFF);
writePtr = ++writePtr % BUFSIZE;
if (writePtr == readPtr)
isBufferBlocked = true; // don't write over read Pointer
if (writePtr == ackWaitPtr)
isBufferFull = true;
}
/**
* Reads a byte out from the buffer but returns it as an integer, so just
* the last 8 least significant bits should be observed. If the buffer is
* empty the function will return -1!
*
* @return The read byte in the LSByte of the int, or -1 if empty
*/
synchronized protected int read()
{
if (isNoMoreDataToRead())
return -1;
byte out = buffer[readPtr];
readPtr = ++readPtr % BUFSIZE;
isBufferBlocked = false;
return out & 0xFF;
}
/**
* Get the maximum number of octets that can be acknowledged.
*
* @return the number of bytes that can be ACKed
*/
protected int getNumAckableBytes()
{
return BUFSIZE - getFreeBufferSpace();
}
/**
* If some data was acknowledged this method will free num_bytes of memory
* of the already sent data. <b>Note:</b> only the data bytes are count!
* dont include flags!
*
* @param num_bytes
* number of DATA-bytes
*/
synchronized protected void ackData(int num_bytes)
{
if (num_bytes == 0)
return;
// if (Debug.enabled)
// Debug.println("num ackable bytes: + getNumAckableBytes()", Debug.DBG_TCP);
// - no assert, report error
// int oldAckWaitPtr = ackWaitPtr;
ackWaitPtr = (ackWaitPtr + num_bytes) % BUFSIZE;
// set readPtr to AckWaitPtr if newer data ACKed than readPtr points at
// (retransmission)
/*
* if (NumFunctions.relGt(ackWaitPtr, readPtr, oldAckWaitPtr) == 1 ||
* oldAckWaitPtr == ackWaitPtr)
* {
* if (Debug.enabled)
* Debug.println("Advancing read pointer because of ACKed data! ackableBytes: "
* + getNumAckableBytes() + ", acked bytes " + num_bytes, Debug.DBG_TCP);
* //readPtr = ackWaitPtr;
* //what if this happens during retransmission and we get new data at readpointer?
* }
*/
isBufferFull = false;
}
/**
* Checks if there is more data available for read out. The data for
* retransmit is not counted <b>Note:</b> if also data for retransmission
* should be counted use is buffer empty!
*
* @return true if empty, else false
*/
protected boolean isNoMoreDataToRead()
{
return (writePtr == readPtr && !isBufferBlocked); // because the read
// pointer bumped into the write pointer, not vice versa
}
/**
* Checks if the buffer of the stream is empty the data for retransmit is
* not counted
*
* @return true if empty, else false
*/
protected boolean isBufferEmpty()
{
return (writePtr == ackWaitPtr && !isBufferFull);
}
/**
* Checks if the stream's buffer is full
*
* @return true if full, else false
*/
protected boolean isBufferFull()
{
return isBufferFull;
}
/**
* closes the stream. ATENTION: dont't invoke protected methods on a closed
* stream!
*/
synchronized public void close()
{
closed = true;
//notifyAll();
}
/**
* re opens the stream ATTENTION: if a user keeps the handle after reopening
* the stream is re activated
*/
protected void reOpen()
{
if (!closed)
return;
closed = false;
writePtr = 0;
readPtr = 0;
ackWaitPtr = 0;
isBufferFull = false;
isBufferBlocked = false;
}
/**
* if a retransmission is needed, this method sets the read Pointer so that
* in the following read() calls the not acknowledged data will be read out.
* ATTENTION: dont forget setting the sequence numbers in the Connection to the
* right values! (sndNext = sndUnack)
*/
synchronized protected void setPtrForRetransmit()
{
readPtr = ackWaitPtr;
if (isBufferFull)
isBufferBlocked = true;
}
/**
* Clones the instance.
*
* @return the cloned stream
*/
synchronized protected TCPOutputStream cloneInstance()
{
TCPOutputStream clone = new TCPOutputStream(BUFSIZE);
for (int i = 0; i < buffer.length; i++)
clone.buffer[i] = buffer[i];
clone.writePtr = writePtr;
clone.readPtr = readPtr;
clone.ackWaitPtr = ackWaitPtr;
clone.isBufferFull = isBufferFull;
clone.isBufferBlocked = isBufferBlocked;
clone.closed = closed;
return clone;
}
}