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

org.refcodes.serial.StopAndWaitPacketOutputStream 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.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.refcodes.controlflow.RetryCounter;
import org.refcodes.data.IoTimeout;
import org.refcodes.io.SkipAvailableInputStream;
import org.refcodes.io.TimeoutInputStream;
import org.refcodes.mixin.ConcatenateMode;
import org.refcodes.numerical.ChecksumValidationMode;
import org.refcodes.numerical.CrcAlgorithm;
import org.refcodes.numerical.Endianess;
import org.refcodes.serial.InputReturnStreamAccessor.InputReturnStreamBuilder;
import org.refcodes.serial.SegmentPackager.DummySegmentPackager;

/**
 * The {@link StopAndWaitPacketOutputStream} wraps an {@link OutputStream} and
 * chunks any data to be written into packets with a sequence number, a block of
 * data and a CRC checksum. An according {@link StopAndWaitPacketInputStream}
 * then reverts the packetised data stream while performing CRC checksum
 * validation as well as sequence number validation.
 */
public class StopAndWaitPacketOutputStream extends PacketOutputStream implements AcknowledgeRetryNumberAccessor, AcknowledgeTimeoutMillisAccessor, AcknowledgeMagicBytesAccessor, AcknowledgeSegmentPackagerAccessor {

	// /////////////////////////////////////////////////////////////////////////
	// STATCIS:
	// /////////////////////////////////////////////////////////////////////////

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

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

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

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

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

	private StopAndWaitPacketOutputStream( Builder aBuilder ) {
		this( aBuilder.outputStream, 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 StopAndWaitPacketOutputStream} 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 aOutputStream The {@link OutputStream} to be wrapped.
	 * @param aReturnStream The {@link InputStream} for establishing a return
	 *        channel (handshake with the communication partner).
	 * @param aTransmissionMetrics The {@link TransmissionMetrics} to be used
	 *        for configuring this instance.
	 */
	public StopAndWaitPacketOutputStream( OutputStream aOutputStream, InputStream aReturnStream, TransmissionMetrics aTransmissionMetrics ) {
		this( aOutputStream, 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 StopAndWaitPacketOutputStream} instance
	 * wrapping the given {@link OutputStream}.
	 *
	 * @param aOutputStream The {@link OutputStream} to be wrapped.
	 * @param aBlockSize The block size of a data block for each packet.
	 * @param aPacketLengthWidth The width (bytes) for the number of bytes to be
	 *        truncated from the last block.
	 * @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 InputStream} 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 aAckRetryNumber The number of retries waiting for an ACK from the
	 *        return channel.
	 * @param aAckTimeoutInMs 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 StopAndWaitPacketOutputStream( OutputStream aOutputStream, int aBlockSize, int aPacketLengthWidth, byte[] aPacketMagicBytes, int aSequenceNumberInitValue, int aSequenceNumberWidth, ConcatenateMode aSequenceNumberConcatenateMode, SegmentPackager aPacketSegmentPackager, InputStream aReturnStream, byte[] aAcknowledgeMagicBytes, int aAckRetryNumber, long aAckTimeoutInMs, SegmentPackager aAckSegmentPackager, Endianess aEndianess ) {
		super( aOutputStream, aBlockSize, aPacketLengthWidth, aPacketMagicBytes, aSequenceNumberInitValue, aSequenceNumberWidth, aSequenceNumberConcatenateMode, aPacketSegmentPackager, aEndianess );
		_returnStream = aReturnStream;
		_acknowledgeMagicBytes = aAcknowledgeMagicBytes;
		_acknowledgeRetryNumber = aAckRetryNumber;
		_acknowledgeTimeoutInMs = aAckTimeoutInMs;
		_acknowledgeSegmentPackager = aAckSegmentPackager != null ? aAckSegmentPackager : new DummySegmentPackager();
		_acknowledgeSegment = _acknowledgeSegmentPackager.toPackaged( new SegmentComposite<>( _acknowledgeMagicBytesSegment = new MagicBytesSegment( _acknowledgeMagicBytes.length ), _acknowledgeSequenceNumberSegment = new NumberSegment( _sequenceNumberWidth, _endianess ) ) );
	}

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

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

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

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

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

	// /////////////////////////////////////////////////////////////////////////
	// HELPER:
	// /////////////////////////////////////////////////////////////////////////

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected void doTransmitPacket() throws IOException {
		if ( _returnStream == null ) {
			super.doTransmitPacket();
		}
		else {
			if ( _blockOffset != 0 ) {
				// Sequence number |-->
				_sequenceNumberSegment.setPayload( Long.valueOf( _sequenceNumber ) );
				// Sequence number <--|
				byte[] eAck;
				int eSequenceNumber;
				@SuppressWarnings("resource")
				final // Do not close me after done!
				SkipAvailableInputStream theSkipReturnStream = new SkipAvailableInputStream( _returnStream, _acknowledgeTimeoutInMs );
				Exception eException = null;
				final RetryCounter theRetries = new RetryCounter( _acknowledgeRetryNumber, _acknowledgeTimeoutInMs );
				final TimeoutInputStream theTimeoutReturnStream = SerialUtility.createTimeoutInputStream( _returnStream, _acknowledgeTimeoutInMs );
				while ( theRetries.nextRetry() ) {
					eException = null;
					try {
						_packetSegment.transmitTo( _outputStream );
						// Ack |--> 
						_acknowledgeSegment.receiveFrom( theTimeoutReturnStream );
						eAck = _acknowledgeMagicBytesSegment.getPayload();
						eSequenceNumber = _acknowledgeSequenceNumberSegment.getPayload().intValue();
						if ( Arrays.equals( eAck, _acknowledgeMagicBytes ) && eSequenceNumber == _sequenceNumber ) {
							if ( IS_DEBUG ) {
								LOGGER.log( Level.INFO, getClass().getSimpleName() + ": OK = " + _acknowledgeSegment );
							}
							// Transmission post-processing |-->
							_sequenceNumber++;
							_blockSequence.clear();
							_blockOffset = 0;
							// Transmission post-processing <--|
							return;
						}
						else {
							if ( IS_DEBUG ) {
								LOGGER.log( Level.WARNING, getClass().getSimpleName() + ": FAIL = " + _acknowledgeSegment );
							}
						}
						// Ack <--|
					}
					catch ( Exception e ) {
						eException = e;
					}
					if ( theRetries.hasNextRetry() ) {
						try {
							theSkipReturnStream.skipAvailableWithin( IoTimeout.toTimeoutSleepLoopTimeInMs( _acknowledgeTimeoutInMs ) );
						}
						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 final class Builder extends PacketOutputStream.Builder implements AcknowledgeMagicBytesBuilder, AcknowledgeTimeoutMillisBuilder, AcknowledgeRetryNumberBuilder, InputReturnStreamBuilder, 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;
		InputStream returnStream = null;

		private Builder() {}

		/**
		 * Returns the {@link StopAndWaitPacketInputStream} instance build
		 * according to the {@link Builder} configuration.
		 *
		 * @return The accordingly configured
		 *         {@link StopAndWaitPacketInputStream}.
		 */
		@Override
		public StopAndWaitPacketOutputStream build() {
			return new StopAndWaitPacketOutputStream( 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 aAckTimeoutInMs ) {
			acknowledgeTimeoutInMs = aAckTimeoutInMs;
			return this;
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public Builder withReturnStream( InputStream 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();
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy