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

org.refcodes.serial.StopAndWaitPacketInputStream Maven / Gradle / Ivy

Go to download

Artifact providing generic (byte) serialization functionality including a TTY-/COM-Port implementation of the serial framework as well as a (local) loopback port.

The newest version!
// /////////////////////////////////////////////////////////////////////////////
// REFCODES.ORG
// /////////////////////////////////////////////////////////////////////////////
// This code is copyright (c) by Siegfried Steiner, Munich, Germany, distributed
// on an "AS IS" BASIS WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, and licen-
// sed under the following (see "http://en.wikipedia.org/wiki/Multi-licensing")
// licenses:
// -----------------------------------------------------------------------------
// GNU General Public License, v3.0 ("http://www.gnu.org/licenses/gpl-3.0.html")
// -----------------------------------------------------------------------------
// Apache License, v2.0 ("http://www.apache.org/licenses/TEXT-2.0")
// -----------------------------------------------------------------------------
// Please contact the copyright holding author(s) of the software artifacts in
// question for licensing issues not being covered by the above listed licenses,
// also regarding commercial licensing models or regarding the compatibility
// with other open source licenses.
// /////////////////////////////////////////////////////////////////////////////

package org.refcodes.serial;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.refcodes.controlflow.RetryCounter;
import org.refcodes.io.SkipAvailableInputStream;
import org.refcodes.mixin.ConcatenateMode;
import org.refcodes.numerical.ChecksumValidationMode;
import org.refcodes.numerical.CrcAlgorithm;
import org.refcodes.numerical.Endianess;
import org.refcodes.serial.OutputReturnStreamAccessor.OutputReturnStreamBuilder;
import org.refcodes.serial.SegmentPackager.DummySegmentPackager;

/**
 * The {@link StopAndWaitPacketInputStream} wraps an {@link InputStream} and
 * chunks any data to be written into packets with a sequence number and a block
 * of data. An according {@link StopAndWaitPacketInputStream} then reverts the
 * packetised data stream while performing sequence number validation. A
 * {@link SegmentPackager} can be used to add functionality such as CRC checksum
 * support.
 */
public class StopAndWaitPacketInputStream extends PacketInputStream implements AcknowledgeRetryNumberAccessor, AcknowledgeTimeoutMillisAccessor, AcknowledgeMagicBytesAccessor, AcknowledgeSegmentPackagerAccessor {

	// /////////////////////////////////////////////////////////////////////////
	// STATICS:
	// /////////////////////////////////////////////////////////////////////////

	private static final Logger LOGGER = Logger.getLogger( StopAndWaitPacketInputStream.class.getName() );

	// /////////////////////////////////////////////////////////////////////////
	// VARIABLES:
	// /////////////////////////////////////////////////////////////////////////

	private static final boolean IS_DEBUG = false;
	byte[] _acknowledgeMagicBytes;
	int _acknowledgeRetryNumber;
	Segment _acknowledgeSegment;
	SegmentPackager _acknowledgeSegmentPackager;
	NumberSegment _acknowledgeSequenceNumberSegment;
	long _acknowledgeTimeoutInMs;
	OutputStream _returnStream;

	// /////////////////////////////////////////////////////////////////////////
	// CONSTRUCTORS:
	// /////////////////////////////////////////////////////////////////////////

	/**
	 * Creates builder to build {@link StopAndWaitPacketInputStream}.
	 * 
	 * @return created builder
	 */
	public static Builder builder() {
		return new Builder();
	}

	private StopAndWaitPacketInputStream( Builder aBuilder ) {
		this( aBuilder.inputStream, aBuilder.blockSize, aBuilder.truncateLengthWidth, aBuilder.packetMagicBytes, aBuilder.sequenceNumberInitValue, aBuilder.sequenceNumberWidth, aBuilder.sequenceNumberConcatenateMode, aBuilder.toPacketSegmentPackager(), aBuilder.returnStream, aBuilder.acknowledgeMagicBytes, aBuilder.acknowledgeRetryNumber, aBuilder.acknowledgeTimeoutInMs, aBuilder.toAckSegmentPackager(), aBuilder.endianess );
	}

	// -------------------------------------------------------------------------

	/**
	 * Constructs an according {@link StopAndWaitPacketInputStream} instance
	 * wrapping the given {@link OutputStream}. The configuration attributes are
	 * taken from the {@link TransmissionMetrics} configuration object, though
	 * only those attributes are supported which are also supported by the other
	 * constructors!
	 *
	 * @param aInputStream The {@link InputStream} to be wrapped.
	 * @param aReturnStream The {@link OutputStream} for establishing a return
	 *        channel (handshake with the communication partner).
	 * @param aTransmissionMetrics The {@link TransmissionMetrics} to be used
	 *        for configuring this instance.
	 */
	public StopAndWaitPacketInputStream( InputStream aInputStream, OutputStream aReturnStream, TransmissionMetrics aTransmissionMetrics ) {
		this( aInputStream, aTransmissionMetrics.getBlockSize(), aTransmissionMetrics.getPacketLengthWidth(), aTransmissionMetrics.getPacketMagicBytes(), aTransmissionMetrics.getSequenceNumberInitValue(), aTransmissionMetrics.getSequenceNumberWidth(), aTransmissionMetrics.getSequenceNumberConcatenateMode(), aTransmissionMetrics.toPacketSegmentPackager(), aReturnStream, aTransmissionMetrics.getAcknowledgeMagicBytes(), aTransmissionMetrics.getAcknowledgeRetryNumber(), aTransmissionMetrics.getAcknowledgeTimeoutMillis(), aTransmissionMetrics.toAckSegmentPackager(), aTransmissionMetrics.getEndianess() );
	}

	// -------------------------------------------------------------------------

	/**
	 * Constructs an according {@link StopAndWaitPacketInputStream} instance
	 * wrapping the given {@link OutputStream}.
	 *
	 * @param aInputStream The {@link InputStream} to be wrapped.
	 * @param aBlockSize The block size of a data block for each packet.
	 * @param aPacketLengthWidth The width (bytes) for declaring the (max)
	 *        length of a package.
	 * @param aPacketMagicBytes The magic bytes identifying a packet and
	 *        distinguishing a packet from a last package.
	 * @param aSequenceNumberInitValue The initial sequence number from where to
	 *        start counting the blocks.
	 * @param aSequenceNumberWidth The width (in bytes) to be used for sequence
	 *        number values.
	 * @param aSequenceNumberConcatenateMode The mode of concatenation to use
	 *        when creating a {@link Sequence} from this {@link Transmission}
	 *        and the decorated {@link Transmission}.
	 * @param aPacketSegmentPackager An (optional) {@link SegmentPackager} used
	 *        to modify a packet's data e.g. with a CRC checksum.
	 * @param aReturnStream The {@link OutputStream} for establishing a return
	 *        channel (handshake with the communication partner).
	 * @param aAcknowledgeMagicBytes The ACK character(s) to be used by the
	 *        return channel to transmit an ACK (acknowledge) response after
	 *        successful receiving a transmission.
	 * @param aAcknowledgeRetryNumber The number of retries waiting for an ACK
	 *        from the return channel.
	 * @param aAcknowledgeTimeoutInMs The timeout in milliseconds to pend till
	 *        the next retry.
	 * @param aAckSegmentPackager An (optional) {@link SegmentPackager} used to
	 *        modify a ACK response data e.g. with a CRC checksum.
	 * @param aEndianess The {@link Endianess} to use for integer (double)
	 *        numbers and the like.
	 */
	public StopAndWaitPacketInputStream( InputStream aInputStream, int aBlockSize, int aPacketLengthWidth, byte[] aPacketMagicBytes, int aSequenceNumberInitValue, int aSequenceNumberWidth, ConcatenateMode aSequenceNumberConcatenateMode, SegmentPackager aPacketSegmentPackager, OutputStream aReturnStream, byte[] aAcknowledgeMagicBytes, int aAcknowledgeRetryNumber, long aAcknowledgeTimeoutInMs, SegmentPackager aAckSegmentPackager, Endianess aEndianess ) {
		super( aInputStream, aBlockSize, aPacketLengthWidth, aPacketMagicBytes, aSequenceNumberInitValue, aSequenceNumberWidth, aSequenceNumberConcatenateMode, aPacketSegmentPackager, aEndianess );
		_returnStream = aReturnStream;
		_acknowledgeMagicBytes = aAcknowledgeMagicBytes;
		_acknowledgeRetryNumber = aAcknowledgeRetryNumber;
		_acknowledgeTimeoutInMs = aAcknowledgeTimeoutInMs;
		_acknowledgeSegmentPackager = aAckSegmentPackager != null ? aAckSegmentPackager : new DummySegmentPackager();
		_acknowledgeSegment = _acknowledgeSegmentPackager.toPackaged( new SegmentComposite<>( new MagicBytesSegment( _acknowledgeMagicBytes ), _acknowledgeSequenceNumberSegment = new NumberSegment( aSequenceNumberWidth, aEndianess ) ) );
	}

	// /////////////////////////////////////////////////////////////////////
	// METHODS:
	// /////////////////////////////////////////////////////////////////////

	/**
	 * {@inheritDoc}
	 */
	@Override
	public byte[] getAcknowledgeMagicBytes() {
		return _acknowledgeMagicBytes;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int getAcknowledgeRetryNumber() {
		return _acknowledgeRetryNumber;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public SegmentPackager getAcknowledgeSegmentPackager() {
		return _acknowledgeSegmentPackager;
	}

	// /////////////////////////////////////////////////////////////////////////
	// HOOKS:
	// /////////////////////////////////////////////////////////////////////////

	/**
	 * {@inheritDoc}
	 */
	@Override
	public long getAcknowledgeTimeoutMillis() {
		return _acknowledgeTimeoutInMs;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected void doReceivePacket() throws IOException {
		if ( _returnStream == null ) {
			super.doReceivePacket();
			return;
		}
		else {
			final RetryCounter theRetries = new RetryCounter( _acknowledgeRetryNumber, _acknowledgeTimeoutInMs );
			Exception eException = null;
			long eSequenceNumber;
			@SuppressWarnings("resource")
			final // Do not close me after done!
			SkipAvailableInputStream theSkipInputStream = new SkipAvailableInputStream( _inputStream, _acknowledgeTimeoutInMs );
			while ( theRetries.nextRetry() ) {
				eException = null;
				try {
					_packetSegment.receiveFrom( _inputStream );
					eSequenceNumber = _sequenceNumberSegment.getPayload().intValue();
					if ( ( _sequenceNumberInitValue == -1 && _sequenceNumber == 0 ) || eSequenceNumber == _sequenceNumber ) {
						// Ack |-->
						_acknowledgeSequenceNumberSegment.setPayload( eSequenceNumber );
						_acknowledgeSegment.transmitTo( _returnStream );
						if ( IS_DEBUG ) {
							LOGGER.log( Level.INFO, getClass().getSimpleName() + ": OK = " + _acknowledgeSegment );
						}
						_sequenceNumber++;
						// Ack <--|
						// Transmission post-processing |-->
						_blockOffset = 0;
						// Transmission post-processing <--|
						return;
					}
					else {
						if ( IS_DEBUG ) {
							LOGGER.log( Level.WARNING, getClass().getSimpleName() + ": FAIL = " + _acknowledgeSegment );
						}
					}
				}
				catch ( Exception e ) {
					eException = e;
				}
				if ( theRetries.hasNextRetry() ) {
					try {
						// theSkipInputStream.skipAvailableFor( _acknowledgeTimeoutInMs );
						theSkipInputStream.skipAvailableTillSilenceFor( _acknowledgeTimeoutInMs * 8 / 10 );
					}
					catch ( IOException ignore ) {}
				}
			}
			if ( eException != null ) {
				throw new FlowControlRetryException( _acknowledgeRetryNumber, _acknowledgeTimeoutInMs, "Aborting after <" + _acknowledgeRetryNumber + "> retries with a timeout for each retry of <" + _acknowledgeTimeoutInMs + "> milliseconds: " + eException.getMessage(), eException );
			}
			else {
				throw new FlowControlRetryException( _acknowledgeRetryNumber, _acknowledgeTimeoutInMs, "Aborting after <" + _acknowledgeRetryNumber + "> retries with a timeout for each retry of <" + _acknowledgeTimeoutInMs + "> milliseconds." );
			}
		}
	}

	// /////////////////////////////////////////////////////////////////////////
	// INNER CLASSES:
	// /////////////////////////////////////////////////////////////////////////

	/**
	 * Builder to build {@link StopAndWaitPacketInputStream} instances.
	 */
	public static class Builder extends PacketInputStream.Builder implements AcknowledgeMagicBytesBuilder, AcknowledgeTimeoutMillisBuilder, AcknowledgeRetryNumberBuilder, OutputReturnStreamBuilder, AcknowledgeSegmentPackagerBuilder {

		byte[] acknowledgeMagicBytes = TransmissionMetrics.DEFAULT_ACKNOWLEDGE_MAGIC_BYTES;
		int acknowledgeRetryNumber = TransmissionMetrics.DEFAULT_ACKNOWLEDGE_RETRY_NUMBER;
		SegmentPackager acknowledgeSegmentPackager = null;
		long acknowledgeTimeoutInMs = TransmissionMetrics.DEFAULT_ACKNOWLEDGE_TIMEOUT_IN_MS;
		OutputStream returnStream = null;

		/**
		 * Instantiates a new builder.
		 */
		protected Builder() {}

		/**
		 * Returns the {@link StopAndWaitPacketInputStream} instance build
		 * according to the {@link Builder} configuration.
		 *
		 * @return The accordingly configured
		 *         {@link StopAndWaitPacketInputStream}.
		 */
		@Override
		public StopAndWaitPacketInputStream build() {
			return new StopAndWaitPacketInputStream( this );
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public Builder withAcknowledgeMagicBytes( byte[] aAcknowledgeMagicBytes ) {
			acknowledgeMagicBytes = aAcknowledgeMagicBytes;
			return this;
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public Builder withAcknowledgeRetryNumber( int aAcknowledgeRetryNumber ) {
			acknowledgeRetryNumber = aAcknowledgeRetryNumber;
			return this;
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public Builder withAcknowledgeSegmentPackager( SegmentPackager aAcknowledgeSegmentPackager ) {
			acknowledgeSegmentPackager = aAcknowledgeSegmentPackager;
			return this;
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public Builder withAcknowledgeTimeoutMillis( long aAcknowledgeTimeoutInMs ) {
			acknowledgeTimeoutInMs = aAcknowledgeTimeoutInMs;
			return this;
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public Builder withCrcAlgorithm( CrcAlgorithm aCrcAlgorithm ) {
			crcAlgorithm = aCrcAlgorithm;
			return this;
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public Builder withCrcChecksumConcatenateMode( ConcatenateMode aCrcChecksumConcatenateMode ) {
			crcChecksumConcatenateMode = aCrcChecksumConcatenateMode;
			return this;
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public Builder withChecksumValidationMode( ChecksumValidationMode aChecksumValidationMode ) {
			crcChecksumValidationMode = aChecksumValidationMode;
			return this;
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public Builder withPacketSegmentPackager( SegmentPackager aPacketSegmentPackager ) {
			packetSegmentPackager = aPacketSegmentPackager;
			return this;
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public Builder withReturnStream( OutputStream aReturnStream ) {
			returnStream = aReturnStream;
			return this;
		}

		/**
		 * Inferences the ACK {@link SegmentPackager}. In case one is available
		 * as of {@link #getAcknowledgeSegmentPackager()}, then that is
		 * returned. Else CRC settings are evaluated and if possible sufficient
		 * CRC settings are available, a {@link CrcSegmentPackager} is returned.
		 * If there are no sufficient CRC settings, then a
		 * {@link DummySegmentPackager} is returned.
		 * 
		 * @return An interferenced {@link SegmentPackager} as of the instance's
		 *         properties.
		 */
		SegmentPackager toAckSegmentPackager() {
			if ( acknowledgeSegmentPackager != null ) {
				return acknowledgeSegmentPackager;
			}
			if ( crcAlgorithm != null || crcChecksumValidationMode != null ) {
				final CrcAlgorithm theCrcAlgorithm = crcAlgorithm != null ? crcAlgorithm : TransmissionMetrics.DEFAULT_CRC_ALGORITHM;
				final ChecksumValidationMode theCrcChecksumValidationMode = crcChecksumValidationMode != null ? crcChecksumValidationMode : TransmissionMetrics.DEFAULT_CHECKSUM_VALIDATION_MODE;
				final ConcatenateMode theCrcChecksumConcatenateMode = crcChecksumConcatenateMode != null ? crcChecksumConcatenateMode : TransmissionMetrics.DEFAULT_CRC_CHECKSUM_CONCATENATE_MODE;
				final Endianess theEndianess = endianess != null ? endianess : TransmissionMetrics.DEFAULT_ENDIANESS;
				return new CrcSegmentPackager( theCrcAlgorithm, theCrcChecksumConcatenateMode, theCrcChecksumValidationMode, theEndianess );
			}
			return new DummySegmentPackager();
		}

		/**
		 * Inferences the packet {@link SegmentPackager}. In case one is
		 * available as of {@link #getPacketSegmentPackager()}, then that is
		 * returned. Else CRC settings are evaluated and if possible sufficient
		 * CRC settings are available, a {@link CrcSegmentPackager} is returned.
		 * If there are no sufficient CRC settings, then a
		 * {@link DummySegmentPackager} is returned.
		 * 
		 * @return An interferenced {@link SegmentPackager} as of the instance's
		 *         properties.
		 */
		@Override
		SegmentPackager toPacketSegmentPackager() {
			if ( packetSegmentPackager != null ) {
				return packetSegmentPackager;
			}
			if ( crcAlgorithm != null || crcChecksumValidationMode != null ) {
				final CrcAlgorithm theCrcAlgorithm = crcAlgorithm != null ? crcAlgorithm : TransmissionMetrics.DEFAULT_CRC_ALGORITHM;
				final ChecksumValidationMode theCrcChecksumValidationMode = crcChecksumValidationMode != null ? crcChecksumValidationMode : TransmissionMetrics.DEFAULT_CHECKSUM_VALIDATION_MODE;
				final ConcatenateMode theCrcChecksumConcatenateMode = crcChecksumConcatenateMode != null ? crcChecksumConcatenateMode : TransmissionMetrics.DEFAULT_CRC_CHECKSUM_CONCATENATE_MODE;
				final Endianess theEndianess = endianess != null ? endianess : TransmissionMetrics.DEFAULT_ENDIANESS;
				return new CrcSegmentPackager( theCrcAlgorithm, theCrcChecksumConcatenateMode, theCrcChecksumValidationMode, theEndianess );
			}
			return new DummySegmentPackager();
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy