org.mariadb.jdbc.client.socket.impl.CompressInputStream Maven / Gradle / Ivy
Show all versions of mariadb-java-client Show documentation
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (c) 2012-2014 Monty Program Ab
// Copyright (c) 2015-2024 MariaDB Corporation Ab
package org.mariadb.jdbc.client.socket.impl;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;
import org.mariadb.jdbc.client.util.MutableByte;
/**
* Compression handler, permitting decompression of mysql packet if needed. When compression is set,
* using a 7 byte header to identify is packet is compressed or not.
*/
public class CompressInputStream extends InputStream {
private final InputStream in;
private final MutableByte sequence;
private final byte[] header = new byte[7];
private int end;
private int pos;
private volatile byte[] buf;
/**
* Constructor. When this handler is used, driver expect packet with 7 byte compression header
*
* Implementation doesn't use synchronized/semaphore because all used are already locked by
* Statement/PreparedStatement Reentrant lock
*
* @param in socket input stream
* @param compressionSequence compression sequence
*/
public CompressInputStream(InputStream in, MutableByte compressionSequence) {
this.in = in;
this.sequence = compressionSequence;
}
/**
* Reads up to len
bytes of data from the input stream into an array of bytes. An
* attempt is made to read as many as len
bytes, but a smaller number may be read.
* The number of bytes actually read is returned as an integer.
*
*
This method blocks until input data is available, end of file is detected, or an exception
* is thrown.
*
*
If len
is zero, then no bytes are read and 0
is returned;
* otherwise, there is an attempt to read at least one byte. If no byte is available because the
* stream is at end of file, the value -1
is returned; otherwise, at least one byte
* is read and stored into b
.
*
*
The first byte read is stored into element b[off]
, the next one into
* b[off+1]
, and so on. The number of bytes read is, at most, equal to len
.
* Let k be the number of bytes actually read; these bytes will be stored in elements
* b[off]
through b[off+
k-1]
, leaving elements
* b[off+
k]
through b[off+len-1]
unaffected.
*
*
In every case, elements b[0]
through b[off]
and elements
* b[off+len]
through b[b.length-1]
are unaffected.
*
*
The read(b,
off,
len)
method for class
* InputStream
simply calls the method read()
repeatedly. If the first such
* call results in an IOException
, that exception is returned from the call to the
* read(b,
off,
len)
method. If any subsequent call to
* read()
results in a IOException
, the exception is caught and treated
* as if it were end of file; the bytes read up to that point are stored into b
and
* the number of bytes read before the exception occurred is returned. The default implementation
* of this method blocks until the requested amount of input data len
has been read,
* end of file is detected, or an exception is thrown. Subclasses are encouraged to provide a more
* efficient implementation of this method.
*
* @param b the buffer into which the data is read.
* @param off the start offset in array b
at which the data is written.
* @param len the maximum number of bytes to read.
* @return the total number of bytes read into the buffer, or -1
if there is no more
* data because the end of the stream has been reached.
* @throws IOException If the first byte cannot be read for any reason other than end of file, or
* if the input stream has been closed, or if some other I/O error occurs.
* @throws NullPointerException If b
is null
.
* @throws IndexOutOfBoundsException If off
is negative, len
is
* negative, or len
is greater than b.length - off
* @see InputStream#read()
*/
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (len == 0) {
return 0;
}
int totalReads = 0;
do {
if (end - pos <= 0) {
retrieveBuffer();
}
// copy internal value to buf.
int copyLength = Math.min(len - totalReads, end - pos);
System.arraycopy(buf, pos, b, off + totalReads, copyLength);
pos += copyLength;
totalReads += copyLength;
} while (totalReads < len && super.available() > 0);
return totalReads;
}
private void retrieveBuffer() throws IOException {
// ***************************************************
// Read header
// ***************************************************
int remaining = 7;
int readOffset = 0;
do {
int count = in.read(header, readOffset, remaining);
if (count < 0) {
throw new EOFException(
"unexpected end of stream, read "
+ readOffset
+ " bytes from 7 (socket was closed by server)");
}
remaining -= count;
readOffset += count;
} while (remaining > 0);
int compressedPacketLength =
(header[0] & 0xff) + ((header[1] & 0xff) << 8) + ((header[2] & 0xff) << 16);
sequence.set(header[3]);
int packetLength = (header[4] & 0xff) + ((header[5] & 0xff) << 8) + ((header[6] & 0xff) << 16);
boolean compressed = (packetLength != 0);
remaining = compressedPacketLength;
byte[] intermediaryBuf = new byte[remaining];
// ***************************************************
// Read content
// ***************************************************
readOffset = 0;
do {
int count = in.read(intermediaryBuf, readOffset, remaining);
if (count < 0) {
throw new EOFException(
"unexpected end of stream, read "
+ ((compressed ? compressedPacketLength : packetLength) - remaining)
+ " bytes from "
+ (compressed ? compressedPacketLength : packetLength)
+ " (socket was closed by server)");
}
remaining -= count;
readOffset += count;
} while (remaining > 0);
if (compressed) {
buf = new byte[packetLength];
Inflater inflater = new Inflater();
inflater.setInput(intermediaryBuf);
try {
int actualUncompressBytes = inflater.inflate(buf);
if (actualUncompressBytes != packetLength) {
throw new IOException(
"Invalid exception length after decompression "
+ actualUncompressBytes
+ ",expected "
+ packetLength);
}
} catch (DataFormatException dfe) {
throw new IOException(dfe);
}
inflater.end();
end = packetLength;
} else {
buf = intermediaryBuf;
end = compressedPacketLength;
}
pos = 0;
}
/**
* Skips over and discards n
bytes of data from this input stream. The skip
*
method may, for a variety of reasons, end up skipping over some smaller number of
* bytes, possibly 0
. This may result from any of a number of conditions; reaching
* end of file before n
bytes have been skipped is only one possibility. The actual
* number of bytes skipped is returned. If {@code n} is negative, the {@code skip} method for
* class {@code InputStream} always returns 0, and no bytes are skipped. Subclasses may handle the
* negative value differently.
*
*
The skip
method of this class creates a byte array and then repeatedly reads
* into it until n
bytes have been read or the end of the stream has been reached.
* Subclasses are encouraged to provide a more efficient implementation of this method. For
* instance, the implementation may depend on the ability to seek.
*
* @param n the number of bytes to be skipped.
* @return the actual number of bytes skipped.
* @throws IOException if the stream does not support seek, or if some other I/O error occurs.
*/
@Override
public long skip(long n) throws IOException {
return read(new byte[(int) n], 0, (int) n);
}
/**
* Returns an estimate of the number of bytes that can be read (or skipped over) from this input
* stream without blocking by the next invocation of a method for this input stream. The next
* invocation might be the same thread or another thread. A single read or skip of this many bytes
* will not block, but may read or skip fewer bytes.
*
*
Note that while some implementations of {@code InputStream} will return the total number of
* bytes in the stream, many will not. It is never correct to use the return value of this method
* to allocate a buffer intended to hold all data in this stream.
*
*
A subclass' implementation of this method may choose to throw an {@link IOException} if this
* input stream has been closed by invoking the {@link #close()} method.
*
*
The {@code available} method for class {@code InputStream} always returns {@code 0}.
*
*
This method should be overridden by subclasses.
*
* @return an estimate of the number of bytes that can be read (or skipped over) from this input
* stream without blocking or {@code 0} when it reaches the end of the input stream.
* @throws IOException if an I/O error occurs.
*/
@Override
public int available() throws IOException {
return in.available();
}
/**
* Closes this input stream and releases any system resources associated with the stream.
*
*
The close
method of InputStream
does nothing.
*
* @throws IOException if an I/O error occurs.
*/
@Override
public void close() throws IOException {
in.close();
}
/**
* Marks the current position in this input stream. A subsequent call to the reset
* method repositions this stream at the last marked position so that subsequent reads re-read the
* same bytes.
*
*
The readlimit
arguments tells this input stream to allow that many bytes to be
* read before the mark position gets invalidated.
*
*
The general contract of mark
is that, if the method markSupported
* returns true
, the stream somehow remembers all the bytes read after the call to
* mark
and stands ready to supply those same bytes again if and whenever the method
* reset
is called. However, the stream is not required to remember any data at all
* if more than readlimit
bytes are read from the stream before reset
is
* called.
*
*
Marking a closed stream should not have any effect on the stream.
*
*
The mark
method of InputStream
does nothing.
*
* @param readlimit the maximum limit of bytes that can be read before the mark position becomes
* invalid.
* @see InputStream#reset()
*/
@Override
public void mark(int readlimit) {
in.mark(readlimit);
}
/**
* Repositions this stream to the position at the time the mark
method was last
* called on this input stream.
*
*
The general contract of reset
is:
*
*
* - If the method
markSupported
returns true
, then:
*
* - If the method
mark
has not been called since the stream was created,
* or the number of bytes read from the stream since mark
was last called
* is larger than the argument to mark
at that last call, then an
* IOException
might be thrown.
* - If such an
IOException
is not thrown, then the stream is reset to a
* state such that all the bytes read since the most recent call to mark
* (or since the start of the file, if mark
has not been called) will be
* resupplied to subsequent callers of the read
method, followed by any
* bytes that otherwise would have been the next input data as of the time of the call
* to reset
.
*
* - If the method
markSupported
returns false
, then:
*
* - The call to
reset
may throw an IOException
.
* - If an
IOException
is not thrown, then the stream is reset to a fixed
* state that depends on the particular type of the input stream and how it was
* created. The bytes that will be supplied to subsequent callers of the read
*
method depend on the particular type of the input stream.
*
*
*
* The method reset
for class InputStream
does nothing except throw
* an IOException
.
*
* @throws IOException if this stream has not been marked or if the mark has been invalidated.
* @see InputStream#mark(int)
* @see IOException
*/
@Override
public void reset() throws IOException {
in.reset();
}
/**
* Tests if this input stream supports the mark
and reset
methods.
* Whether mark
and reset
are supported is an invariant property of a
* particular input stream instance. The markSupported
method of
* InputStream
returns false
.
*
* @return true
if this stream instance supports the mark and reset methods;
* false
otherwise.
* @see InputStream#mark(int)
* @see InputStream#reset()
*/
@Override
public boolean markSupported() {
return in.markSupported();
}
/**
* Reads the next byte of data from the input stream. The value byte is returned as an int
*
in the range 0
to 255
. If no byte is available because the
* end of the stream has been reached, the value -1
is returned. This method blocks
* until input data is available, the end of the stream is detected, or an exception is thrown.
*
*
A subclass must provide an implementation of this method.
*
* @return the next byte of data, or -1
if the end of the stream is reached.
* @throws IOException if an I/O error occurs.
*/
@Override
public int read() throws IOException {
throw new IOException("NOT IMPLEMENTED !");
}
}