001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.commons.compress.compressors.lz4; 020 021import java.io.IOException; 022import java.io.InputStream; 023 024import org.apache.commons.compress.compressors.lz77support.AbstractLZ77CompressorInputStream; 025import org.apache.commons.compress.utils.ByteUtils; 026 027/** 028 * CompressorInputStream for the LZ4 block format. 029 * 030 * @see <a href="http://lz4.github.io/lz4/lz4_Block_format.html">LZ4 Block Format Description</a> 031 * @since 1.14 032 * @NotThreadSafe 033 */ 034public class BlockLZ4CompressorInputStream extends AbstractLZ77CompressorInputStream { 035 036 static final int WINDOW_SIZE = 1 << 16; 037 static final int SIZE_BITS = 4; 038 static final int BACK_REFERENCE_SIZE_MASK = (1 << SIZE_BITS) - 1; 039 static final int LITERAL_SIZE_MASK = BACK_REFERENCE_SIZE_MASK << SIZE_BITS; 040 041 /** Back-Reference-size part of the block starting byte. */ 042 private int nextBackReferenceSize; 043 044 /** Current state of the stream */ 045 private State state = State.NO_BLOCK; 046 047 /** 048 * Creates a new LZ4 input stream. 049 * 050 * @param is 051 * An InputStream to read compressed data from 052 * 053 * @throws IOException if reading fails 054 */ 055 public BlockLZ4CompressorInputStream(final InputStream is) throws IOException { 056 super(is, WINDOW_SIZE); 057 } 058 059 /** 060 * {@inheritDoc} 061 */ 062 @Override 063 public int read(final byte[] b, final int off, final int len) throws IOException { 064 if (len == 0) { 065 return 0; 066 } 067 switch (state) { 068 case EOF: 069 return -1; 070 case NO_BLOCK: // NOSONAR - fallthrough intended 071 readSizes(); 072 /*FALLTHROUGH*/ 073 case IN_LITERAL: 074 final int litLen = readLiteral(b, off, len); 075 if (!hasMoreDataInBlock()) { 076 state = State.LOOKING_FOR_BACK_REFERENCE; 077 } 078 return litLen > 0 ? litLen : read(b, off, len); 079 case LOOKING_FOR_BACK_REFERENCE: // NOSONAR - fallthrough intended 080 if (!initializeBackReference()) { 081 state = State.EOF; 082 return -1; 083 } 084 /*FALLTHROUGH*/ 085 case IN_BACK_REFERENCE: 086 final int backReferenceLen = readBackReference(b, off, len); 087 if (!hasMoreDataInBlock()) { 088 state = State.NO_BLOCK; 089 } 090 return backReferenceLen > 0 ? backReferenceLen : read(b, off, len); 091 default: 092 throw new IOException("Unknown stream state " + state); 093 } 094 } 095 096 private void readSizes() throws IOException { 097 final int nextBlock = readOneByte(); 098 if (nextBlock == -1) { 099 throw new IOException("Premature end of stream while looking for next block"); 100 } 101 nextBackReferenceSize = nextBlock & BACK_REFERENCE_SIZE_MASK; 102 long literalSizePart = (nextBlock & LITERAL_SIZE_MASK) >> SIZE_BITS; 103 if (literalSizePart == BACK_REFERENCE_SIZE_MASK) { 104 literalSizePart += readSizeBytes(); 105 } 106 if (literalSizePart < 0) { 107 throw new IOException("Illegal block with a negative literal size found"); 108 } 109 startLiteral(literalSizePart); 110 state = State.IN_LITERAL; 111 } 112 113 private long readSizeBytes() throws IOException { 114 long accum = 0; 115 int nextByte; 116 do { 117 nextByte = readOneByte(); 118 if (nextByte == -1) { 119 throw new IOException("Premature end of stream while parsing length"); 120 } 121 accum += nextByte; 122 } while (nextByte == 255); 123 return accum; 124 } 125 126 /** 127 * @return false if there is no more back-reference - this means this is the 128 * last block of the stream. 129 */ 130 private boolean initializeBackReference() throws IOException { 131 int backReferenceOffset = 0; 132 try { 133 backReferenceOffset = (int) ByteUtils.fromLittleEndian(supplier, 2); 134 } catch (final IOException ex) { 135 if (nextBackReferenceSize == 0) { // the last block has no back-reference 136 return false; 137 } 138 throw ex; 139 } 140 long backReferenceSize = nextBackReferenceSize; 141 if (nextBackReferenceSize == BACK_REFERENCE_SIZE_MASK) { 142 backReferenceSize += readSizeBytes(); 143 } 144 // minimal match length 4 is encoded as 0 145 if (backReferenceSize < 0) { 146 throw new IOException("Illegal block with a negative match length found"); 147 } 148 try { 149 startBackReference(backReferenceOffset, backReferenceSize + 4); 150 } catch (final IllegalArgumentException ex) { 151 throw new IOException("Illegal block with bad offset found", ex); 152 } 153 state = State.IN_BACK_REFERENCE; 154 return true; 155 } 156 157 private enum State { 158 NO_BLOCK, IN_LITERAL, LOOKING_FOR_BACK_REFERENCE, IN_BACK_REFERENCE, EOF 159 } 160}