CtrCryptoInputStream.java

  1.  /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one
  3.  * or more contributor license agreements.  See the NOTICE file
  4.  * distributed with this work for additional information
  5.  * regarding copyright ownership.  The ASF licenses this file
  6.  * to you under the Apache License, Version 2.0 (the
  7.  * "License"); you may not use this file except in compliance
  8.  * with the License.  You may obtain a copy of the License at
  9.  *
  10.  *     http://www.apache.org/licenses/LICENSE-2.0
  11.  *
  12.  * Unless required by applicable law or agreed to in writing, software
  13.  * distributed under the License is distributed on an "AS IS" BASIS,
  14.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15.  * See the License for the specific language governing permissions and
  16.  * limitations under the License.
  17.  */
  18. package org.apache.commons.crypto.stream;

  19. import java.io.IOException;
  20. import java.io.InputStream;
  21. import java.nio.ByteBuffer;
  22. import java.nio.channels.ReadableByteChannel;
  23. import java.security.GeneralSecurityException;
  24. import java.util.Properties;

  25. import javax.crypto.Cipher;
  26. import javax.crypto.spec.IvParameterSpec;

  27. import org.apache.commons.crypto.cipher.CryptoCipher;
  28. import org.apache.commons.crypto.cipher.CryptoCipherFactory;
  29. import org.apache.commons.crypto.stream.input.ChannelInput;
  30. import org.apache.commons.crypto.stream.input.Input;
  31. import org.apache.commons.crypto.stream.input.StreamInput;
  32. import org.apache.commons.crypto.utils.AES;
  33. import org.apache.commons.crypto.utils.Utils;

  34. /**
  35.  * <p>
  36.  * CtrCryptoInputStream decrypts data. AES CTR mode is required in order to
  37.  * ensure that the plain text and cipher text have a 1:1 mapping. CTR crypto
  38.  * stream has stream characteristic which is useful for implement features like
  39.  * random seek. The decryption is buffer based. The key points of the decryption
  40.  * are (1) calculating the counter and (2) padding through stream position:
  41.  * </p>
  42.  * <p>
  43.  * counter = base + pos/(algorithm blocksize); padding = pos%(algorithm
  44.  * blocksize);
  45.  * </p>
  46.  * The underlying stream offset is maintained as state. It is not thread-safe.
  47.  */
  48. public class CtrCryptoInputStream extends CryptoInputStream {
  49.     /**
  50.      * <p>
  51.      * This method is only for Counter (CTR) mode. Generally the CryptoCipher
  52.      * calculates the IV and maintain encryption context internally.For example
  53.      * a Cipher will maintain its encryption context internally when we do
  54.      * encryption/decryption using the CryptoCipher#update interface.
  55.      * </p>
  56.      * <p>
  57.      * Encryption/Decryption is not always on the entire file. For example, in
  58.      * Hadoop, a node may only decrypt a portion of a file (i.e. a split). In
  59.      * these situations, the counter is derived from the file position.
  60.      * </p>
  61.      * The IV can be calculated by combining the initial IV and the counter with
  62.      * a lossless operation (concatenation, addition, or XOR).
  63.      *
  64.      * @see <a
  65.      *      href="http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_.28CTR.29">
  66.      *      http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_.28CTR.29</a>
  67.      *
  68.      * @param initIV initial IV
  69.      * @param counter counter for input stream position
  70.      * @param IV the IV for input stream position
  71.      */
  72.     static void calculateIV(final byte[] initIV, long counter, final byte[] IV) {
  73.         int i = IV.length; // IV length

  74.         Utils.checkArgument(initIV.length == CryptoCipherFactory.AES_BLOCK_SIZE);
  75.         Utils.checkArgument(i == CryptoCipherFactory.AES_BLOCK_SIZE);

  76.         int j = 0; // counter bytes index
  77.         int sum = 0;
  78.         while (i-- > 0) {
  79.             // (sum >>> Byte.SIZE) is the carry for addition
  80.             sum = (initIV[i] & 0xff) + (sum >>> Byte.SIZE); // NOPMD
  81.             if (j++ < 8) { // Big-endian, and long is 8 bytes length
  82.                 sum += (byte) counter & 0xff;
  83.                 counter >>>= 8;
  84.             }
  85.             IV[i] = (byte) sum;
  86.         }
  87.     }

  88.     /**
  89.      * Underlying stream offset
  90.      */
  91.     private long streamOffset;

  92.     /**
  93.      * The initial IV.
  94.      */
  95.     private final byte[] initIV;

  96.     /**
  97.      * Initialization vector for the cipher.
  98.      */
  99.     private final byte[] iv;

  100.     /**
  101.      * Padding = pos%(algorithm blocksize); Padding is put into
  102.      * {@link #inBuffer} before any other data goes in. The purpose of padding
  103.      * is to put the input data at proper position.
  104.      */
  105.     private byte padding;

  106.     /**
  107.      * Flag to mark whether the cipher has been reset
  108.      */
  109.     private boolean cipherReset;

  110.     /**
  111.      * Constructs a {@link CtrCryptoInputStream}.
  112.      *
  113.      * @param input the input data.
  114.      * @param cipher the CryptoCipher instance.
  115.      * @param bufferSize the bufferSize.
  116.      * @param key crypto key for the cipher.
  117.      * @param iv Initialization vector for the cipher.
  118.      * @throws IOException if an I/O error occurs.
  119.      */
  120.     protected CtrCryptoInputStream(final Input input, final CryptoCipher cipher,
  121.             final int bufferSize, final byte[] key, final byte[] iv) throws IOException {
  122.         this(input, cipher, bufferSize, key, iv, 0);
  123.     }

  124.     /**
  125.      * Constructs a {@link CtrCryptoInputStream}.
  126.      *
  127.      * @param input the input data.
  128.      * @param cipher the CryptoCipher instance.
  129.      * @param bufferSize the bufferSize.
  130.      * @param key crypto key for the cipher.
  131.      * @param iv Initialization vector for the cipher.
  132.      * @param streamOffset the start offset in the stream.
  133.      * @throws IOException if an I/O error occurs.
  134.      */
  135.     protected CtrCryptoInputStream(final Input input, final CryptoCipher cipher,
  136.             final int bufferSize, final byte[] key, final byte[] iv, final long streamOffset)
  137.             throws IOException {
  138.         super(input, cipher, bufferSize, AES.newSecretKeySpec(key),
  139.                 new IvParameterSpec(iv));

  140.         this.initIV = iv.clone();
  141.         this.iv = iv.clone();

  142.         CryptoInputStream.checkStreamCipher(cipher);

  143.         resetStreamOffset(streamOffset);
  144.     }

  145.     /**
  146.      * Constructs a {@link CtrCryptoInputStream}.
  147.      *
  148.      * @param inputStream the input stream.
  149.      * @param cipher the CryptoCipher instance.
  150.      * @param bufferSize the bufferSize.
  151.      * @param key crypto key for the cipher.
  152.      * @param iv Initialization vector for the cipher.
  153.      * @throws IOException if an I/O error occurs.
  154.      */
  155.     protected CtrCryptoInputStream(final InputStream inputStream, final CryptoCipher cipher,
  156.             final int bufferSize, final byte[] key, final byte[] iv) throws IOException {
  157.         this(inputStream, cipher, bufferSize, key, iv, 0);
  158.     }

  159.     /**
  160.      * Constructs a {@link CtrCryptoInputStream}.
  161.      *
  162.      * @param inputStream the InputStream instance.
  163.      * @param cipher the CryptoCipher instance.
  164.      * @param bufferSize the bufferSize.
  165.      * @param key crypto key for the cipher.
  166.      * @param iv Initialization vector for the cipher.
  167.      * @param streamOffset the start offset in the stream.
  168.      * @throws IOException if an I/O error occurs.
  169.      */
  170.     @SuppressWarnings("resource") // Closing the instance closes the StreamInput
  171.     protected CtrCryptoInputStream(final InputStream inputStream, final CryptoCipher cipher,
  172.             final int bufferSize, final byte[] key, final byte[] iv, final long streamOffset)
  173.             throws IOException {
  174.         this(new StreamInput(inputStream, bufferSize), cipher, bufferSize, key, iv,
  175.                 streamOffset);
  176.     }

  177.     /**
  178.      * Constructs a {@link CtrCryptoInputStream}.
  179.      *
  180.      * @param properties The {@code Properties} class represents a set of
  181.      *        properties.
  182.      * @param inputStream the input stream.
  183.      * @param key crypto key for the cipher.
  184.      * @param iv Initialization vector for the cipher.
  185.      * @throws IOException if an I/O error occurs.
  186.      */
  187.     public CtrCryptoInputStream(final Properties properties, final InputStream inputStream, final byte[] key,
  188.             final byte[] iv) throws IOException {
  189.         this(properties, inputStream, key, iv, 0);
  190.     }

  191.     /**
  192.      * Constructs a {@link CtrCryptoInputStream}.
  193.      *
  194.      * @param properties The {@code Properties} class represents a set of
  195.      *        properties.
  196.      * @param inputStream the InputStream instance.
  197.      * @param key crypto key for the cipher.
  198.      * @param iv Initialization vector for the cipher.
  199.      * @param streamOffset the start offset in the stream.
  200.      * @throws IOException if an I/O error occurs.
  201.      */
  202.     @SuppressWarnings("resource") // The CryptoCipher returned by getCipherInstance() is closed by CtrCryptoInputStream.
  203.     public CtrCryptoInputStream(final Properties properties, final InputStream inputStream, final byte[] key,
  204.             final byte[] iv, final long streamOffset) throws IOException {
  205.         this(inputStream, Utils.getCipherInstance(
  206.                 AES.CTR_NO_PADDING, properties),
  207.                 CryptoInputStream.getBufferSize(properties), key, iv, streamOffset);
  208.     }

  209.     /**
  210.      * Constructs a {@link CtrCryptoInputStream}.
  211.      *
  212.      * @param properties The {@code Properties} class represents a set of
  213.      *        properties.
  214.      * @param channel the ReadableByteChannel instance.
  215.      * @param key crypto key for the cipher.
  216.      * @param iv Initialization vector for the cipher.
  217.      * @throws IOException if an I/O error occurs.
  218.      */
  219.     public CtrCryptoInputStream(final Properties properties, final ReadableByteChannel channel,
  220.             final byte[] key, final byte[] iv) throws IOException {
  221.         this(properties, channel, key, iv, 0);
  222.     }

  223.     /**
  224.      * Constructs a {@link CtrCryptoInputStream}.
  225.      *
  226.      * @param properties The {@code Properties} class represents a set of
  227.      *        properties.
  228.      * @param in the ReadableByteChannel instance.
  229.      * @param key crypto key for the cipher.
  230.      * @param iv Initialization vector for the cipher.
  231.      * @param streamOffset the start offset in the stream.
  232.      * @throws IOException if an I/O error occurs.
  233.      */
  234.     @SuppressWarnings("resource") // The CryptoCipher returned by getCipherInstance() is closed by CtrCryptoInputStream.
  235.     public CtrCryptoInputStream(final Properties properties, final ReadableByteChannel in,
  236.             final byte[] key, final byte[] iv, final long streamOffset) throws IOException {
  237.         this(in, Utils.getCipherInstance(
  238.                 AES.CTR_NO_PADDING, properties),
  239.                 CryptoInputStream.getBufferSize(properties), key, iv, streamOffset);
  240.     }

  241.     /**
  242.      * Constructs a {@link CtrCryptoInputStream}.
  243.      *
  244.      * @param channel the ReadableByteChannel instance.
  245.      * @param cipher the cipher instance.
  246.      * @param bufferSize the bufferSize.
  247.      * @param key crypto key for the cipher.
  248.      * @param iv Initialization vector for the cipher.
  249.      * @throws IOException if an I/O error occurs.
  250.      */
  251.     protected CtrCryptoInputStream(final ReadableByteChannel channel, final CryptoCipher cipher,
  252.             final int bufferSize, final byte[] key, final byte[] iv) throws IOException {
  253.         this(channel, cipher, bufferSize, key, iv, 0);
  254.     }

  255.     /**
  256.      * Constructs a {@link CtrCryptoInputStream}.
  257.      *
  258.      * @param channel the ReadableByteChannel instance.
  259.      * @param cipher the CryptoCipher instance.
  260.      * @param bufferSize the bufferSize.
  261.      * @param key crypto key for the cipher.
  262.      * @param iv Initialization vector for the cipher.
  263.      * @param streamOffset the start offset in the stream.
  264.      * @throws IOException if an I/O error occurs.
  265.      */
  266.     @SuppressWarnings("resource") // Closing the instance closes the ChannelInput
  267.     protected CtrCryptoInputStream(final ReadableByteChannel channel, final CryptoCipher cipher,
  268.             final int bufferSize, final byte[] key, final byte[] iv, final long streamOffset)
  269.             throws IOException {
  270.         this(new ChannelInput(channel), cipher, bufferSize, key, iv, streamOffset);
  271.     }

  272.     /**
  273.      * Does the decryption using inBuffer as input and outBuffer as output. Upon
  274.      * return, inBuffer is cleared; the decrypted data starts at
  275.      * outBuffer.position() and ends at outBuffer.limit().
  276.      *
  277.      * @throws IOException if an I/O error occurs.
  278.      */
  279.     @Override
  280.     protected void decrypt() throws IOException {
  281.         Utils.checkState(inBuffer.position() >= padding);
  282.         if (inBuffer.position() == padding) {
  283.             // There is no real data in inBuffer.
  284.             return;
  285.         }

  286.         inBuffer.flip();
  287.         outBuffer.clear();
  288.         decryptBuffer(outBuffer);
  289.         inBuffer.clear();
  290.         outBuffer.flip();

  291.         if (padding > 0) {
  292.             /*
  293.              * The plain text and cipher text have a 1:1 mapping, they start at
  294.              * the same position.
  295.              */
  296.             outBuffer.position(padding);
  297.         }
  298.     }

  299.     /**
  300.      * Decrypts all data in buf: total n bytes from given start position. Output
  301.      * is also buf and same start position. buf.position() and buf.limit()
  302.      * should be unchanged after decryption.
  303.      *
  304.      * @param buf The buffer into which bytes are to be transferred.
  305.      * @param offset the start offset in the data.
  306.      * @param len the maximum number of decrypted data bytes to read.
  307.      * @throws IOException if an I/O error occurs.
  308.      */
  309.     protected void decrypt(final ByteBuffer buf, final int offset, final int len)
  310.             throws IOException {
  311.         final int pos = buf.position();
  312.         final int limit = buf.limit();
  313.         int n = 0;
  314.         while (n < len) {
  315.             buf.position(offset + n);
  316.             buf.limit(offset + n + Math.min(len - n, inBuffer.remaining()));
  317.             inBuffer.put(buf);
  318.             // Do decryption
  319.             try {
  320.                 decrypt();
  321.                 buf.position(offset + n);
  322.                 buf.limit(limit);
  323.                 n += outBuffer.remaining();
  324.                 buf.put(outBuffer);
  325.             } finally {
  326.                 padding = postDecryption(streamOffset - (len - n));
  327.             }
  328.         }
  329.         buf.position(pos);
  330.     }

  331.     /**
  332.      * Does the decryption using out as output.
  333.      *
  334.      * @param out the output ByteBuffer.
  335.      * @throws IOException if an I/O error occurs.
  336.      */
  337.     protected void decryptBuffer(final ByteBuffer out) throws IOException {
  338.         final int inputSize = inBuffer.remaining();
  339.         try {
  340.             final int n = cipher.update(inBuffer, out);
  341.             if (n < inputSize) {
  342.                 /**
  343.                  * Typically code will not get here. CryptoCipher#update will
  344.                  * consume all input data and put result in outBuffer.
  345.                  * CryptoCipher#doFinal will reset the cipher context.
  346.                  */
  347.                 cipher.doFinal(inBuffer, out);
  348.                 cipherReset = true;
  349.             }
  350.         } catch (final GeneralSecurityException e) {
  351.             throw new IOException(e);
  352.         }
  353.     }

  354.     /**
  355.      * Does the decryption using inBuffer as input and buf as output. Upon
  356.      * return, inBuffer is cleared; the buf's position will be equal to
  357.      * <i>p</i>&nbsp;{@code +}&nbsp;<i>n</i> where <i>p</i> is the position
  358.      * before decryption, <i>n</i> is the number of bytes decrypted. The buf's
  359.      * limit will not have changed.
  360.      *
  361.      * @param buf The buffer into which bytes are to be transferred.
  362.      * @throws IOException if an I/O error occurs.
  363.      */
  364.     protected void decryptInPlace(final ByteBuffer buf) throws IOException {
  365.         Utils.checkState(inBuffer.position() >= padding);
  366.         Utils.checkState(buf.isDirect());
  367.         Utils.checkState(buf.remaining() >= inBuffer.position());
  368.         Utils.checkState(padding == 0);

  369.         if (inBuffer.position() == padding) {
  370.             // There is no real data in inBuffer.
  371.             return;
  372.         }
  373.         inBuffer.flip();
  374.         decryptBuffer(buf);
  375.         inBuffer.clear();
  376.     }

  377.     /**
  378.      * Decrypts more data by reading the under layer stream. The decrypted data
  379.      * will be put in the output buffer.
  380.      *
  381.      * @return The number of decrypted data. -1 if end of the decrypted stream.
  382.      * @throws IOException if an I/O error occurs.
  383.      */
  384.     @Override
  385.     protected int decryptMore() throws IOException {
  386.         final int n = input.read(inBuffer);
  387.         if (n <= 0) {
  388.             return n;
  389.         }

  390.         streamOffset += n; // Read n bytes
  391.         decrypt();
  392.         padding = postDecryption(streamOffset);
  393.         return outBuffer.remaining();
  394.     }

  395.     /**
  396.      * Gets the counter for input stream position.
  397.      *
  398.      * @param position the given position in the data.
  399.      * @return the counter for input stream position.
  400.      */
  401.     protected long getCounter(final long position) {
  402.         return position / cipher.getBlockSize();
  403.     }

  404.     /**
  405.      * Gets the initialization vector.
  406.      *
  407.      * @return the initIV.
  408.      */
  409.     protected byte[] getInitIV() {
  410.         return initIV;
  411.     }

  412.     /**
  413.      * Gets the padding for input stream position.
  414.      *
  415.      * @param position the given position in the data.
  416.      * @return the padding for input stream position.
  417.      */
  418.     protected byte getPadding(final long position) {
  419.         return (byte) (position % cipher.getBlockSize());
  420.     }

  421.     /**
  422.      * Gets the offset of the stream.
  423.      *
  424.      * @return the stream offset.
  425.      */
  426.     protected long getStreamOffset() {
  427.         return streamOffset;
  428.     }

  429.     /**
  430.      * Gets the position of the stream.
  431.      *
  432.      * @return the position of the stream.
  433.      */
  434.     protected long getStreamPosition() {
  435.         return streamOffset - outBuffer.remaining();
  436.     }

  437.     /**
  438.      * Overrides the {@link CtrCryptoInputStream#initCipher()}. Initializes the
  439.      * cipher.
  440.      */
  441.     @Override
  442.     protected void initCipher() {
  443.         // Do nothing for initCipher
  444.         // Will reset the cipher when reset the stream offset
  445.     }

  446.     /**
  447.      * This method is executed immediately after decryption. Checks whether
  448.      * cipher should be updated and recalculate padding if needed.
  449.      *
  450.      * @param position the given position in the data..
  451.      * @return the byte.
  452.      * @throws IOException if an I/O error occurs.
  453.      */
  454.     protected byte postDecryption(final long position) throws IOException {
  455.         byte padding = 0;
  456.         if (cipherReset) {
  457.             /*
  458.              * This code is generally not executed since the cipher usually
  459.              * maintains cipher context (e.g. the counter) internally. However,
  460.              * some implementations can't maintain context so a re-init is
  461.              * necessary after each decryption call.
  462.              */
  463.             resetCipher(position);
  464.             padding = getPadding(position);
  465.             inBuffer.position(padding);
  466.         }
  467.         return padding;
  468.     }

  469.     /**
  470.      * Overrides the {@link CtrCryptoInputStream#read(ByteBuffer)}. Reads a
  471.      * sequence of bytes from this channel into the given buffer.
  472.      *
  473.      * @param buf The buffer into which bytes are to be transferred.
  474.      * @return The number of bytes read, possibly zero, or {@code -1} if the
  475.      *         channel has reached end-of-stream.
  476.      * @throws IOException if an I/O error occurs.
  477.      */
  478.     @Override
  479.     public int read(final ByteBuffer buf) throws IOException {
  480.         checkStream();
  481.         int unread = outBuffer.remaining();
  482.         if (unread <= 0) { // Fill the unread decrypted data buffer firstly
  483.             final int n = input.read(inBuffer);
  484.             if (n <= 0) {
  485.                 return n;
  486.             }

  487.             streamOffset += n; // Read n bytes
  488.             if (buf.isDirect() && buf.remaining() >= inBuffer.position()
  489.                     && padding == 0) {
  490.                 // Use buf as the output buffer directly
  491.                 decryptInPlace(buf);
  492.                 padding = postDecryption(streamOffset);
  493.                 return n;
  494.             }
  495.             // Use outBuffer as the output buffer
  496.             decrypt();
  497.             padding = postDecryption(streamOffset);
  498.         }

  499.         // Copy decrypted data from outBuffer to buf
  500.         unread = outBuffer.remaining();
  501.         final int toRead = buf.remaining();
  502.         if (toRead <= unread) {
  503.             final int limit = outBuffer.limit();
  504.             outBuffer.limit(outBuffer.position() + toRead);
  505.             buf.put(outBuffer);
  506.             outBuffer.limit(limit);
  507.             return toRead;
  508.         }
  509.         buf.put(outBuffer);
  510.         return unread;
  511.     }

  512.     /**
  513.      * Calculates the counter and iv, resets the cipher.
  514.      *
  515.      * @param position the given position in the data.
  516.      * @throws IOException if an I/O error occurs.
  517.      */
  518.     protected void resetCipher(final long position) throws IOException {
  519.         final long counter = getCounter(position);
  520.         CtrCryptoInputStream.calculateIV(initIV, counter, iv);
  521.         try {
  522.             cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
  523.         } catch (final GeneralSecurityException e) {
  524.             throw new IOException(e);
  525.         }
  526.         cipherReset = false;
  527.     }

  528.     /**
  529.      * Resets the underlying stream offset; clear {@link #inBuffer} and
  530.      * {@link #outBuffer}. This Typically happens during {@link #skip(long)}.
  531.      *
  532.      * @param offset the offset of the stream.
  533.      * @throws IOException if an I/O error occurs.
  534.      */
  535.     protected void resetStreamOffset(final long offset) throws IOException {
  536.         streamOffset = offset;
  537.         inBuffer.clear();
  538.         outBuffer.clear();
  539.         outBuffer.limit(0);
  540.         resetCipher(offset);
  541.         padding = getPadding(offset);
  542.         inBuffer.position(padding); // Set proper position for input data.
  543.     }

  544.     /**
  545.      * Seeks the stream to a specific position relative to start of the under
  546.      * layer stream.
  547.      *
  548.      * @param position the given position in the data.
  549.      * @throws IOException if an I/O error occurs.
  550.      */
  551.     public void seek(final long position) throws IOException {
  552.         Utils.checkArgument(position >= 0, "Cannot seek to negative offset.");
  553.         checkStream();
  554.         /*
  555.          * If data of target pos in the underlying stream has already been read
  556.          * and decrypted in outBuffer, we just need to re-position outBuffer.
  557.          */
  558.         if (position >= getStreamPosition() && position <= getStreamOffset()) {
  559.             final int forward = (int) (position - getStreamPosition());
  560.             if (forward > 0) {
  561.                 outBuffer.position(outBuffer.position() + forward);
  562.             }
  563.         } else {
  564.             input.seek(position);
  565.             resetStreamOffset(position);
  566.         }
  567.     }

  568.     /**
  569.      * Sets the offset of stream.
  570.      *
  571.      * @param streamOffset the stream offset.
  572.      */
  573.     protected void setStreamOffset(final long streamOffset) {
  574.         this.streamOffset = streamOffset;
  575.     }

  576.     /**
  577.      * Overrides the {@link CryptoInputStream#skip(long)}. Skips over and
  578.      * discards {@code n} bytes of data from this input stream.
  579.      *
  580.      * @param n the number of bytes to be skipped.
  581.      * @return the actual number of bytes skipped.
  582.      * @throws IOException if an I/O error occurs.
  583.      */
  584.     @Override
  585.     public long skip(long n) throws IOException {
  586.         Utils.checkArgument(n >= 0, "Negative skip length.");
  587.         checkStream();

  588.         if (n == 0) {
  589.             return 0;
  590.         }
  591.         if (n <= outBuffer.remaining()) {
  592.             final int pos = outBuffer.position() + (int) n;
  593.             outBuffer.position(pos);
  594.             return n;
  595.         }
  596.         /*
  597.          * Subtract outBuffer.remaining() to see how many bytes we need to
  598.          * skip in the underlying stream. Add outBuffer.remaining() to the
  599.          * actual number of skipped bytes in the underlying stream to get
  600.          * the number of skipped bytes from the user's point of view.
  601.          */
  602.         n -= outBuffer.remaining();
  603.         long skipped = input.skip(n);
  604.         if (skipped < 0) {
  605.             skipped = 0;
  606.         }
  607.         final long pos = streamOffset + skipped;
  608.         skipped += outBuffer.remaining();
  609.         resetStreamOffset(pos);
  610.         return skipped;
  611.     }
  612. }