All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.smpp.ReceiverBase Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 1996-2001
 * Logica Mobile Networks Limited
 * All rights reserved.
 *
 * This software is distributed under Logica Open Source License Version 1.0
 * ("Licence Agreement"). You shall use it and distribute only in accordance
 * with the terms of the License Agreement.
 *
 */
package org.smpp;

import java.io.IOException;

import org.smpp.pdu.HeaderIncompleteException;
import org.smpp.pdu.MessageIncompleteException;
import org.smpp.pdu.PDU;
import org.smpp.pdu.PDUException;
import org.smpp.pdu.UnknownCommandIdException;
import org.smpp.util.ByteBuffer;
import org.smpp.util.NotEnoughDataInByteBufferException;
import org.smpp.util.ProcessingThread;
import org.smpp.util.Unprocessed;

/**
 * Abstract base class for classes which can receive PDUs from connection.
 * The receiving of PDUs can be be performed on background within a separate
 * thread using the ProcessingThread class.
 * 
 * @author Logica Mobile Networks SMPP Open Source Team
 * @version $Revision: 1.4 $
 * @see Receiver
 * @see OutbindReceiver
 * @see Connection
 * @see Transmitter
 * @see Session
 */
public abstract class ReceiverBase extends ProcessingThread {

	/**
	 * Timeout for receiving the rest of PDU from the connection.
	 * If the rest of PDU isn't receive in time, TimeoutException
	 * is thrown.
	 *
	 * @see TimeoutException
	 */
	private long receiveTimeout = Data.RECEIVER_TIMEOUT;


	private byte messageIncompleteRetryCount = 0;


	/**
	 * Method repeatedly called from process method.
	 * It's expected that derived classes implement atomic receive of
	 * one PDU in this method using ReceiverBase's
	 * tryReceivePDUWithTimeout and 
	 * receivePDUFromConnection methods.
	 *
	 * @see #tryReceivePDUWithTimeout(Connection,PDU,long)
	 * @see ProcessingThread#process()
	 * @see ProcessingThread#run()
	 */
	protected abstract void receiveAsync();

	/**
	 * This method should try to receive one PDU from the connection.
	 * It is called in cycle from tryReceivePDUWithTimeout until
	 * timeout expires. The method should check if the actualy received
	 * PDU is equal to the expectedPDU.
	 *
	 * @param connection  the connection from which the PDU should be received
	 * @param expectedPDU the command id and sequence id of the received PDU
	 *                    should be equal to those of expectedPDU
	 * @return the received PDU if any or null if none received
	 *
	 * @exception IOException exception during communication
	 * @exception PDUException incorrect format of PDU
	 * @exception TimeoutException rest of data not received for too long time
	 * @exception UnknownCommandIdException PDU with unknown id was received
	 * @see #tryReceivePDUWithTimeout(Connection,PDU,long)
	 * @see #receiveAsync()
	 * @see #run()
	 * @see PDU#equals(Object)
	 */
	protected abstract PDU tryReceivePDU(Connection connection, PDU expectedPDU)
		throws UnknownCommandIdException, TimeoutException, PDUException, IOException;

	/**
	 * This is an implementation of ProcessingThread's 
	 * process method, which is method called in loop from
	 * the run method.
* This simply calls receiveAsync. */ public void process() { receiveAsync(); } /** * Calls tryReceivePDUWithTimeout(Connection,PDU,long) with * timeout set by setReceiveTimeout. * * @param connection the connection from which the PDU should be received * @param expectedPDU the command id and sequence id of the received PDU * should be equal to those of expectedPDU * @return the received PDU if any or null if none received * * @exception IOException exception during communication * @exception PDUException incorrect format of PDU * @exception TimeoutException rest of data not received for too long time * @exception UnknownCommandIdException PDU with unknown id was received * @see #tryReceivePDUWithTimeout(Connection,PDU,long) */ final protected PDU tryReceivePDUWithTimeout(Connection connection, PDU expectedPDU) throws UnknownCommandIdException, TimeoutException, PDUException, IOException { return tryReceivePDUWithTimeout(connection, expectedPDU, getReceiveTimeout()); } /** * For specified time tries to receive a PDU from given connection by * calling method tryReceivePDU. * The method tryReceivePDU must be implemented in the derived * class. *

* The timeout can be either value > 0, then it means for * how many milliseconds will be repeatedly tried to receive a PDU. * If the timeout is = 0 then there is only one attempt to receive a PDU. * If the timeout is equal to Data.RECEIVE_BLOCKING, then the this method * tries receive a PDU until it is received. * * @param connection the connection from which the PDU should be received * @param expectedPDU the command id and sequence id of the received PDU * should be equal to those of expectedPDU * @param timeout the timeout indication * @return the received PDU if any or null if none received * * @exception IOException exception during communication * @exception PDUException incorrect format of PDU * @exception TimeoutException rest of data not received for too long time * @exception UnknownCommandIdException PDU with unknown id was received * @see #tryReceivePDU(Connection,PDU) * @see PDU#equals(Object) */ final protected PDU tryReceivePDUWithTimeout(Connection connection, PDU expectedPDU, long timeout) throws UnknownCommandIdException, TimeoutException, PDUException, IOException { debug.write(DRXTX, "receivePDU: Going to receive response."); long startTime = Data.getCurrentTime(); PDU pdu = null; if (timeout == 0) { // with no timeout try just once pdu = tryReceivePDU(connection, expectedPDU); } else { // with timeout keep trying until get some or timeout expires while ((pdu == null) && canContinueReceiving(startTime, timeout)) { pdu = tryReceivePDU(connection, expectedPDU); } } if (pdu != null) { debug.write(DRXTX, "Got pdu " + pdu.debugString()); } return pdu; } /** * Elementary method receiving data from connection, trying to create * PDU from them and buffering data in case the PDU * isn't still complete. It has timeout checking for incomplete * messages: if the message isn't received completly for certain time * and no new data are received for this time, then exception is thrown * as this could indicate communication problem. * * @param connection the connection to receive the data from * @return either PDU, if complete received or null * * @exception IOException exception during communication * @exception PDUException incorrect format of PDU * @throws TimeoutException rest of data not received for too long time * @throws UnknownCommandIdException PDU with unknown id was received * @see Connection * @see Unprocessed */ final protected PDU receivePDUFromConnection(Connection connection, Unprocessed unprocessed) throws UnknownCommandIdException, TimeoutException, PDUException, IOException { debug.write(DRXTXD2, "ReceiverBase.receivePDUFromConnection start"); PDU pdu = null; ByteBuffer buffer; ByteBuffer unprocBuffer; try { // first check if there is something left from the last time if (unprocessed.getHasUnprocessed()) { unprocBuffer = unprocessed.getUnprocessed(); debug.write(DRXTX, "have unprocessed " + unprocBuffer.length() + " bytes from previous try"); pdu = tryGetUnprocessedPDU(unprocessed); } if (pdu == null) { // only if we didn't manage to get pdu from unproc buffer = connection.receive(); unprocBuffer = unprocessed.getUnprocessed(); // if received something now or have something from the last receive if (buffer.length() != 0) { unprocBuffer.appendBuffer(buffer); unprocessed.setLastTimeReceived(); pdu = tryGetUnprocessedPDU(unprocessed); } else { debug.write(DRXTXD2, "no data received this time."); // check if it's not too long since we received any data long timeout = getReceiveTimeout(); if ((unprocBuffer.length() > 0) && ((unprocessed.getLastTimeReceived() + timeout) < Data.getCurrentTime())) { debug.write(DRXTX, "and it's been very long time."); unprocessed.reset(); throw new TimeoutException(timeout, unprocessed.getExpected(), unprocBuffer.length()); } } } } catch (UnknownCommandIdException e) { // [email protected]: if we got an UnknownCommandIdException here, the // chances are excellent that some trailing garbage is hanging around in // the unprocessed buffer. Given that it's unlikely that it contained a // valid PDU, we don't rethrow the error - nothing to respond to with a // gnack. Was originally just checking this if the unprocessed buffer had // content, but that's not enough. event.write(e,"There is _probably_ garbage in the unprocessed buffer - flushing unprocessed buffer now."); unprocessed.reset(); } debug.write(DRXTXD2, "ReceiverBase.receivePDUFromConnection finished"); return pdu; } /** * Tries to create a PDU from the buffer provided. * Returns the PDU if successfull or null if not or an exception * if the PDU is incorrect. */ private final PDU tryGetUnprocessedPDU(Unprocessed unprocessed) throws UnknownCommandIdException, PDUException { debug.write(DRXTX, "trying to create pdu from unprocessed buffer"); PDU pdu = null; ByteBuffer unprocBuffer = unprocessed.getUnprocessed(); try { pdu = PDU.createPDU(unprocBuffer); unprocessed.check(); // Reset counter after successful createPDU (as per bug #2138444): messageIncompleteRetryCount = 0; } catch (HeaderIncompleteException e) { // the header wasn't received completly, we will try to // receive the rest next time debug.write(DRXTXD, "incomplete message header, will wait for the rest."); unprocessed.setHasUnprocessed(false); // as it's incomplete - wait for new data unprocessed.setExpected(Data.PDU_HEADER_SIZE); } catch (MessageIncompleteException e) { // [email protected] - this number (5) is somewhat arbitrary. Too low // a figure could trigger a false positive with fast data rates, // busy servers and PDUs split across TCP packets. if (messageIncompleteRetryCount > 5) { messageIncompleteRetryCount = 0; event.write("Giving up on incomplete messages - probably garbage in unprocessed buffer. Flushing unprocessed buffer."); unprocessed.reset(); } // the message wasn't received completly, less bytes than command // length has been received, will try to receive the rest next time debug.write(DRXTXD, "incomplete message, will wait for the rest."); unprocessed.setHasUnprocessed(false); // as it's incomplete - wait for new data unprocessed.setExpected(Data.PDU_HEADER_SIZE); messageIncompleteRetryCount++; } catch (UnknownCommandIdException e) { // message with invalid id was received, should send generic_nack debug.write(DRXTX, "unknown pdu, might remove from unprocessed buffer. CommandId=" + e.getCommandId()); if (e.getCommandLength() <= unprocBuffer.length()) { // have already enough to remove try { unprocBuffer.removeBytes(e.getCommandLength()); } catch (NotEnoughDataInByteBufferException e1) { // can't happen, we've checked it above throw new Error("Not enough data in buffer even if previously checked that there was enough."); } unprocessed.check(); throw e; // caller will decide what to do } // [email protected]: added this: see why in caller. Advantage: lets // us trap garbage PDUs that break things by being trapped in the // unprocessed buffer eternally. Disadvantage: if this was a valid // (well-formed) PDU with an unknown command id AND it was not fully // read into the buffer in one go, then: // 1) we will respond to the part _was_ already read (which we know // contains at least a header - see code in PDU) with an error, which // is fine. // 2) when we receive the second part of the incomplete PDU, it will // effectively seem to us to be an invalid PDU itself. It could be // processed as follows: // - as an unknown command_id again, which is fine; or // - as an incomplete message, which is a problem, because it will // then have the subsequent PDU tacked to the end of it, and // that PDU will then be discarded as well (and almost certainly // discarded via an UnknownCommandIdException again). throw e; } catch (PDUException e) { // [email protected]: safer to catch all other PDU exceptions and force // force a check() here - some exception in parsing should not be allowed // to leave ghost data in the Unprocessed buffer (even though this is now // less likely after improvements to PDU.createPDU()): unprocessed.check(); throw e; } /* [email protected]: concerned that this is too broad, and will stop useful exceptions from being passed back up the call stack, so disabling for now: } catch (Exception e) { debug.write(DRXTX, "Exception catched: " + e.toString()); StringWriter stringWriter = new StringWriter(); PrintWriter printWriter = new PrintWriter(stringWriter); e.printStackTrace(printWriter); debug.write(DRXTX, stringWriter.toString()); } */ if (pdu != null) { debug.write(DRXTX, "received complete pdu" + pdu.debugString()); debug.write(DRXTX, "there is " + unprocBuffer.length() + " bytes left in unprocessed buffer"); } // unprocessed.check(); return pdu; } /** * Sets the timeout for receiving the complete message. * If no data are received for time longer then this timeout and there * is still not completly received PDU in the internal buffer, * TimeoutException is thrown. * * @param timeout the new timeout value * * @see #receivePDUFromConnection(Connection,Unprocessed) * @see TimeoutException */ public void setReceiveTimeout(long timeout) { receiveTimeout = timeout; } /** * Returns the current setting of the receiving timeout. * * @return the current timeout value */ public long getReceiveTimeout() { return receiveTimeout; } /** * Depending on value of timeout and on startTime * returns if it's still possible to continue in receiving of message. * timeout can indicate either timeout in milliseconds * (if > 0), or that there has to be only one attempt to receive * a message (if = 0) or that the the receiving shuld continue until * a PDU is received (if = Data.RECEIVE_BLOCKING). * * @param startTime when the receiving started * @param timeout timeout indication * @return if it's possible to continue receiving */ private boolean canContinueReceiving(long startTime, long timeout) { return timeout == Data.RECEIVE_BLOCKING ? true : Data.getCurrentTime() <= (startTime + timeout); } } /* * $Log: not supported by cvs2svn $ * Revision 1.3 2004/09/04 09:34:50 paoloc * no message * * Revision 1.2 2004/09/04 09:00:27 paoloc * Various changes which deal better with invalid ((non-spec or garbage) data being received. Previously, such data could get a Receiver object stuck in a state in which it stops passing incoming data to the application. * * Revision 1.1 2003/07/23 00:28:39 sverkera * Imported * * * Old changelog: * 13-07-01 [email protected] start(), stop(), setReceiveTimeout(), * getReceiveTimeout(), setTermException(), * getTermException() made not synchronized; * receive(long) & receive(PDU) made synchronized * so the receiver no longer locks up * 13-07-01 [email protected] the call to receiveAsync() in run() now enclosed * in try-finally and the status RCV_FINISHED * is set in finally block so the finished status * is now reported correctly even in case of exception * 13-07-01 [email protected] some debug lines corrected; some added * 08-08-01 [email protected] added support for Session's asynchronous processing capability * 23-08-01 [email protected] added async capability exhibited that if more than * one pdu is read from connection at once (in one * connection.receive(), the additional ones aren't * processed. this behaviour was corrected. * 23-08-01 [email protected] added yield() to run() to give chance to other * threads * 26-09-01 [email protected] debug code categorized to groups * 01-10-01 [email protected] now derived from new ProcessingThread which implements * the thread related issues; this change allows to focus * only on the receiving related in the ReceiverBase * and on the thread management in the ProcessingThread; * function covered by ProcessingThread removed */





© 2015 - 2024 Weber Informatics LLC | Privacy Policy