Class TailStrippingInputStream

java.lang.Object
java.io.InputStream
zeroecho.core.io.TailStrippingInputStream
All Implemented Interfaces:
Closeable, AutoCloseable
Direct Known Subclasses:
SignatureTrailerInputStream

public abstract class TailStrippingInputStream extends InputStream
InputStream that withholds the last N bytes from the upstream stream and emits everything before that tail, delivering the withheld tail to a callback at EOF.

Overview

This stream is useful when the final bytes carry out-of-band metadata such as checksums, MAC tags, or trailers that must not appear in the payload. During normal reads it buffers enough data to ensure the last tailLen bytes are never exposed. When the upstream reaches EOF, the accumulated tail is passed to processTail(byte[]) exactly once.

How it works

  • A sliding window of size tailLen + ioBuffer is maintained.
  • While reading, any overflow beyond tailLen is immediately emitted to the caller; the final tailLen bytes are withheld in the window.
  • On EOF, the remaining bytes in the window (length <= tailLen) are delivered to processTail(byte[]) and never returned from read().

Behavioral notes

  • If the entire stream is shorter than tailLen, no payload bytes are ever emitted; the complete content is provided to processTail(byte[]).
  • close() drains the upstream to ensure tail processing happens even if the caller stops reading early.
  • Mark/reset is not supported.

Typical use

InputStream raw = ...; // e.g., file or network
int tagBytes = 16;     // withhold a 16-byte authentication tag at the end
TailStrippingInputStream in = new TailStrippingInputStream(raw, tagBytes, 4096) {
    &#64;Override
    protected void processTail(byte[] tail) throws IOException {
        // verify MAC, parse footer, etc.
    }
};

// Read payload only (tag withheld and never exposed here)
byte[] buf = in.readAllBytes();
in.close(); // ensures processTail(...) is invoked if not already

Thread-safety

Instances are not thread-safe. Use a separate instance per stream.
Since:
1.0
  • Constructor Details

    • TailStrippingInputStream

      public TailStrippingInputStream(InputStream upstream, int tailLen, int ioBuffer)
      Creates a new tail-stripping stream around an upstream source.

      The internal sliding window is allocated with capacity tailLen + max(1024, ioBuffer). The parameter ioBuffer controls incremental emission granularity; tailLen controls how many bytes are withheld until EOF.

      Parameters:
      upstream - non-null source stream
      tailLen - number of bytes to strip and deliver to processTail(byte[]); must be >= 1
      ioBuffer - additional working buffer size; must be >= 1 (actual allocation is max(1024, ioBuffer))
      Throws:
      NullPointerException - if upstream is null
      IllegalArgumentException - if tailLen < 1 or ioBuffer < 1
  • Method Details

    • processTail

      protected abstract void processTail(byte[] tail) throws IOException
      Hook invoked exactly once at end of stream with the withheld tail bytes.

      The provided array contains the last min(totalBytesRead, tailLen) bytes of the upstream stream. For short streams, this may be smaller than tailLen and can even contain the entire stream contents. Implementations typically verify or parse the trailer here.

      This method is called from the reading thread either upon the first read that observes upstream EOF or during close() if the stream was not fully consumed. It is never called more than once.

      Parameters:
      tail - the withheld tail bytes (never null; length in [0, tailLen])
      Throws:
      IOException - if processing the tail fails
    • read

      public int read() throws IOException
      Reads and returns a single byte of payload data.

      This method never returns bytes belonging to the tail. On end of stream, -1 is returned and processTail(byte[]) is invoked if not already processed.

      Specified by:
      read in class InputStream
      Returns:
      the next payload byte (0..255) or -1 if no more payload is available
      Throws:
      IOException - if an I/O error occurs, if the stream is closed, or if tail processing fails
    • read

      public int read(byte[] b, int off, int len) throws IOException
      Reads up to len bytes of payload into the provided buffer.

      The bytes returned exclude the last tailLen bytes of the upstream stream. When EOF is reached, this method returns -1 and ensures processTail(byte[]) is invoked exactly once with the withheld tail. If the stream is shorter than tailLen, this method returns -1 immediately once EOF is known and the entire content is reported via processTail.

      Overrides:
      read in class InputStream
      Parameters:
      b - destination buffer; must not be null
      off - start offset in b
      len - maximum number of bytes to read
      Returns:
      the number of payload bytes read, or -1 if the payload is finished
      Throws:
      NullPointerException - if b is null
      IndexOutOfBoundsException - if off or len are invalid for b
      IOException - if an I/O error occurs or if the stream is closed
    • close

      public void close() throws IOException
      Closes this stream and the underlying upstream stream.

      If the upstream has not yet reached EOF, this method drains it to force tail detection and ensures processTail(byte[]) is invoked exactly once. Subsequent calls are ignored.

      Specified by:
      close in interface AutoCloseable
      Specified by:
      close in interface Closeable
      Overrides:
      close in class InputStream
      Throws:
      IOException - if an I/O error occurs while draining or closing
    • available

      public int available() throws IOException
      Returns an estimate of the number of bytes that can be read without blocking.

      This includes any previously prepared payload bytes and the upstream's reported availability. Note that upstream availability may over-report relative to payload because the last tailLen bytes are withheld.

      Overrides:
      available in class InputStream
      Returns:
      the number of bytes that can be read without blocking
      Throws:
      IOException - if an I/O error occurs in the upstream
    • markSupported

      public boolean markSupported()
      Indicates that mark/reset is not supported.
      Overrides:
      markSupported in class InputStream
      Returns:
      always false
    • reset

      public void reset() throws IOException
      Unsupported operation; always throws.
      Overrides:
      reset in class InputStream
      Throws:
      IOException - always thrown because mark/reset is not supported
    • tailLength

      public int tailLength()
      Returns the number of bytes stripped from the end of the stream.
      Returns:
      the configured tail length in bytes