@Override
  public int decompress(ByteBuffer compressedBuffer, ByteBuffer decompressedBuffer) throws CompressionException {
    Utils.checkNotNullOrEmpty(compressedBuffer, "compressedBuffer cannot be null or empty.");
    Utils.checkNotNullOrEmpty(decompressedBuffer, "decompressedBuffer cannot be null or empty.");

    // Check compressed buffer size - must be bigger than header size without algorithm name.
    if (compressedBuffer.remaining() < VERSION_AND_NAME_LENGTH_AND_ORIGINAL_SIZE_SIZE) {
      throw new IllegalArgumentException(
          "compressedBuffer size of " + compressedBuffer.remaining() + " bytes is too small.");
    }

    // Check compressed buffer size - must be bigger than header size with algorithm name.
    int compressedBufferPosition = compressedBuffer.position();
    int algorithmNameLength = compressedBuffer.get(compressedBufferPosition + VERSION_SIZE);
    int headerSize = VERSION_AND_NAME_LENGTH_AND_ORIGINAL_SIZE_SIZE + algorithmNameLength;
    if (compressedBuffer.remaining() < headerSize) {
      throw new IllegalArgumentException("compressedBuffer is too small and does not contain original data size.");
    }

    // Get the original size from the compressed buffer.  It also verifies compressed buffer parameters.
    int originalSize = compressedBuffer.getInt(
        compressedBufferPosition + VERSION_SIZE + ALGORITHM_NAME_LENGTH_SIZE + algorithmNameLength);
    int decompressedBufferSize = decompressedBuffer.capacity() - decompressedBuffer.position();
    if (decompressedBufferSize < originalSize) {
      throw new IllegalArgumentException(
          "decompressedBuffer size " + decompressedBufferSize + " is too small to hold decompressed data size "
              + originalSize);
    }

    // Save the position.  Use position instead of mark just in case decompressNative() uses mark().
    int decompressedBufferPosition = decompressedBuffer.position();
    try {
      // Invoke native decompress method and advance the index positions.
      int compressedBufferLimit = compressedBuffer.limit();
      int newDecompressionBufferPosition = decompressedBuffer.position() + originalSize;
      decompressNative(compressedBuffer, compressedBuffer.position() + headerSize,
          compressedBuffer.remaining() - headerSize, decompressedBuffer, decompressedBuffer.position(), originalSize);
      compressedBuffer.position(compressedBufferLimit);
      decompressedBuffer.position(newDecompressionBufferPosition);
      return originalSize;
    } catch (Exception ex) {
      // Exception thrown, restore the positions.
      compressedBuffer.position(compressedBufferPosition);
      decompressedBuffer.position(decompressedBufferPosition);
      throw ex;
    }
  }
Decompress the buffer specified in {@code compressedBuffer}.  Since compressedBuffer structure contains the
original data size, it reads only the portion required to decompress.
After calling, compressedBuffer position will be advanced to right after the compressed binary, and the
decompressedBuffer position will be advanced to the end of the decompressed binary.
If exception is thrown, the position index would not be updated.

@param compressedBuffer The buffer that contains compressed data generated by the compress() method.
@param decompressedBuffer The buffer to hold the decompressed/original data.  The size should be at least the
                          size returned by getDecompressBufferSize().
@return The original data size which is same as number of bytes written to sourceData buffer.
@throws CompressionException Throws this exception if decompression has internal failure.
ambry-commons/src/main/java/com/github/ambry/compression/BaseCompression.java