| // SPDX-License-Identifier: LGPL-2.1-or-later |
| // Copyright (c) 2012-2014 Monty Program Ab |
| // Copyright (c) 2015-2021 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 byte[] buf; |
| |
| /** |
| * Constructor. When this handler is used, driver expect packet with 7 byte compression header |
| * |
| * @param in socket input stream |
| * @param compressionSequence compression sequence |
| */ |
| public CompressInputStream(InputStream in, MutableByte compressionSequence) { |
| this.in = in; |
| this.sequence = compressionSequence; |
| } |
| |
| /** |
| * Reads up to <code>len</code> bytes of data from the input stream into an array of bytes. An |
| * attempt is made to read as many as <code>len</code> bytes, but a smaller number may be read. |
| * The number of bytes actually read is returned as an integer. |
| * |
| * <p>This method blocks until input data is available, end of file is detected, or an exception |
| * is thrown. |
| * |
| * <p>If <code>len</code> is zero, then no bytes are read and <code>0</code> 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 <code>-1</code> is returned; otherwise, at least one byte |
| * is read and stored into <code>b</code>. |
| * |
| * <p>The first byte read is stored into element <code>b[off]</code>, the next one into <code> |
| * b[off+1]</code>, and so on. The number of bytes read is, at most, equal to <code>len</code>. |
| * Let <i>k</i> be the number of bytes actually read; these bytes will be stored in elements |
| * <code>b[off]</code> through <code>b[off+</code><i>k</i><code>-1]</code>, leaving elements |
| * <code>b[off+</code><i>k</i><code>]</code> through <code>b[off+len-1]</code> unaffected. |
| * |
| * <p>In every case, elements <code>b[0]</code> through <code>b[off]</code> and elements <code> |
| * b[off+len]</code> through <code>b[b.length-1]</code> are unaffected. |
| * |
| * <p>The <code>read(b,</code> <code>off,</code> <code>len)</code> method for class <code> |
| * InputStream</code> simply calls the method <code>read()</code> repeatedly. If the first such |
| * call results in an <code>IOException</code>, that exception is returned from the call to the |
| * <code>read(b,</code> <code>off,</code> <code>len)</code> method. If any subsequent call to |
| * <code>read()</code> results in a <code>IOException</code>, the exception is caught and treated |
| * as if it were end of file; the bytes read up to that point are stored into <code>b</code> 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 <code>len</code> 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 <code>b</code> 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 <code>-1</code> 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 <code>b</code> is <code>null</code>. |
| * @throws IndexOutOfBoundsException If <code>off</code> is negative, <code>len</code> is |
| * negative, or <code>len</code> is greater than <code>b.length - off</code> |
| * @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 <code>n</code> bytes of data from this input stream. The <code>skip |
| * </code> method may, for a variety of reasons, end up skipping over some smaller number of |
| * bytes, possibly <code>0</code>. This may result from any of a number of conditions; reaching |
| * end of file before <code>n</code> 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. |
| * |
| * <p>The <code>skip</code> method of this class creates a byte array and then repeatedly reads |
| * into it until <code>n</code> 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. |
| * |
| * <p>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. |
| * |
| * <p>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. |
| * |
| * <p>The {@code available} method for class {@code InputStream} always returns {@code 0}. |
| * |
| * <p>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. |
| * |
| * <p>The <code>close</code> method of <code>InputStream</code> 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 <code>reset</code> |
| * method repositions this stream at the last marked position so that subsequent reads re-read the |
| * same bytes. |
| * |
| * <p>The <code>readlimit</code> arguments tells this input stream to allow that many bytes to be |
| * read before the mark position gets invalidated. |
| * |
| * <p>The general contract of <code>mark</code> is that, if the method <code>markSupported</code> |
| * returns <code>true</code>, the stream somehow remembers all the bytes read after the call to |
| * <code>mark</code> and stands ready to supply those same bytes again if and whenever the method |
| * <code>reset</code> is called. However, the stream is not required to remember any data at all |
| * if more than <code>readlimit</code> bytes are read from the stream before <code>reset</code> is |
| * called. |
| * |
| * <p>Marking a closed stream should not have any effect on the stream. |
| * |
| * <p>The <code>mark</code> method of <code>InputStream</code> does nothing. |
| * |
| * @param readlimit the maximum limit of bytes that can be read before the mark position becomes |
| * invalid. |
| * @see InputStream#reset() |
| */ |
| @Override |
| public synchronized void mark(int readlimit) { |
| in.mark(readlimit); |
| } |
| |
| /** |
| * Repositions this stream to the position at the time the <code>mark</code> method was last |
| * called on this input stream. |
| * |
| * <p>The general contract of <code>reset</code> is: |
| * |
| * <ul> |
| * <li>If the method <code>markSupported</code> returns <code>true</code>, then: |
| * <ul> |
| * <li>If the method <code>mark</code> has not been called since the stream was created, |
| * or the number of bytes read from the stream since <code>mark</code> was last called |
| * is larger than the argument to <code>mark</code> at that last call, then an <code> |
| * IOException</code> might be thrown. |
| * <li>If such an <code>IOException</code> is not thrown, then the stream is reset to a |
| * state such that all the bytes read since the most recent call to <code>mark</code> |
| * (or since the start of the file, if <code>mark</code> has not been called) will be |
| * resupplied to subsequent callers of the <code>read</code> method, followed by any |
| * bytes that otherwise would have been the next input data as of the time of the call |
| * to <code>reset</code>. |
| * </ul> |
| * <li>If the method <code>markSupported</code> returns <code>false</code>, then: |
| * <ul> |
| * <li>The call to <code>reset</code> may throw an <code>IOException</code>. |
| * <li>If an <code>IOException</code> 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 <code>read |
| * </code> method depend on the particular type of the input stream. |
| * </ul> |
| * </ul> |
| * |
| * <p>The method <code>reset</code> for class <code>InputStream</code> does nothing except throw |
| * an <code>IOException</code>. |
| * |
| * @throws IOException if this stream has not been marked or if the mark has been invalidated. |
| * @see InputStream#mark(int) |
| * @see IOException |
| */ |
| @Override |
| public synchronized void reset() throws IOException { |
| in.reset(); |
| } |
| |
| /** |
| * Tests if this input stream supports the <code>mark</code> and <code>reset</code> methods. |
| * Whether <code>mark</code> and <code>reset</code> are supported is an invariant property of a |
| * particular input stream instance. The <code>markSupported</code> method of <code> |
| * InputStream</code> returns <code>false</code>. |
| * |
| * @return <code>true</code> if this stream instance supports the mark and reset methods; <code> |
| * false</code> 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 <code>int |
| * </code> in the range <code>0</code> to <code>255</code>. If no byte is available because the |
| * end of the stream has been reached, the value <code>-1</code> is returned. This method blocks |
| * until input data is available, the end of the stream is detected, or an exception is thrown. |
| * |
| * <p>A subclass must provide an implementation of this method. |
| * |
| * @return the next byte of data, or <code>-1</code> 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 !"); |
| } |
| } |