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, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 package org.apache.commons.compress.compressors.lz4; 20 21 import java.io.IOException; 22 import java.io.InputStream; 23 24 import org.apache.commons.compress.compressors.lz77support.AbstractLZ77CompressorInputStream; 25 import org.apache.commons.compress.utils.ByteUtils; 26 27 /** 28 * CompressorInputStream for the LZ4 block format. 29 * 30 * @see <a href="http://lz4.github.io/lz4/lz4_Block_format.html">LZ4 Block Format Description</a> 31 * @since 1.14 32 * @NotThreadSafe 33 */ 34 public class BlockLZ4CompressorInputStream extends AbstractLZ77CompressorInputStream { 35 36 static final int WINDOW_SIZE = 1 << 16; 37 static final int SIZE_BITS = 4; 38 static final int BACK_REFERENCE_SIZE_MASK = (1 << SIZE_BITS) - 1; 39 static final int LITERAL_SIZE_MASK = BACK_REFERENCE_SIZE_MASK << SIZE_BITS; 40 41 /** Back-Reference-size part of the block starting byte. */ 42 private int nextBackReferenceSize; 43 44 /** Current state of the stream */ 45 private State state = State.NO_BLOCK; 46 47 /** 48 * Creates a new LZ4 input stream. 49 * 50 * @param is 51 * An InputStream to read compressed data from 52 * 53 * @throws IOException if reading fails 54 */ BlockLZ4CompressorInputStream(final InputStream is)55 public BlockLZ4CompressorInputStream(final InputStream is) throws IOException { 56 super(is, WINDOW_SIZE); 57 } 58 59 /** 60 * {@inheritDoc} 61 */ 62 @Override read(final byte[] b, final int off, final int len)63 public int read(final byte[] b, final int off, final int len) throws IOException { 64 switch (state) { 65 case EOF: 66 return -1; 67 case NO_BLOCK: // NOSONAR - fallthrough intended 68 readSizes(); 69 /*FALLTHROUGH*/ 70 case IN_LITERAL: 71 int litLen = readLiteral(b, off, len); 72 if (!hasMoreDataInBlock()) { 73 state = State.LOOKING_FOR_BACK_REFERENCE; 74 } 75 return litLen > 0 ? litLen : read(b, off, len); 76 case LOOKING_FOR_BACK_REFERENCE: // NOSONAR - fallthrough intended 77 if (!initializeBackReference()) { 78 state = State.EOF; 79 return -1; 80 } 81 /*FALLTHROUGH*/ 82 case IN_BACK_REFERENCE: 83 int backReferenceLen = readBackReference(b, off, len); 84 if (!hasMoreDataInBlock()) { 85 state = State.NO_BLOCK; 86 } 87 return backReferenceLen > 0 ? backReferenceLen : read(b, off, len); 88 default: 89 throw new IOException("Unknown stream state " + state); 90 } 91 } 92 readSizes()93 private void readSizes() throws IOException { 94 int nextBlock = readOneByte(); 95 if (nextBlock == -1) { 96 throw new IOException("Premature end of stream while looking for next block"); 97 } 98 nextBackReferenceSize = nextBlock & BACK_REFERENCE_SIZE_MASK; 99 long literalSizePart = (nextBlock & LITERAL_SIZE_MASK) >> SIZE_BITS; 100 if (literalSizePart == BACK_REFERENCE_SIZE_MASK) { 101 literalSizePart += readSizeBytes(); 102 } 103 startLiteral(literalSizePart); 104 state = State.IN_LITERAL; 105 } 106 readSizeBytes()107 private long readSizeBytes() throws IOException { 108 long accum = 0; 109 int nextByte; 110 do { 111 nextByte = readOneByte(); 112 if (nextByte == -1) { 113 throw new IOException("Premature end of stream while parsing length"); 114 } 115 accum += nextByte; 116 } while (nextByte == 255); 117 return accum; 118 } 119 120 /** 121 * @return false if there is no more back-reference - this means this is the 122 * last block of the stream. 123 */ initializeBackReference()124 private boolean initializeBackReference() throws IOException { 125 int backReferenceOffset = 0; 126 try { 127 backReferenceOffset = (int) ByteUtils.fromLittleEndian(supplier, 2); 128 } catch (IOException ex) { 129 if (nextBackReferenceSize == 0) { // the last block has no back-reference 130 return false; 131 } 132 throw ex; 133 } 134 long backReferenceSize = nextBackReferenceSize; 135 if (nextBackReferenceSize == BACK_REFERENCE_SIZE_MASK) { 136 backReferenceSize += readSizeBytes(); 137 } 138 // minimal match length 4 is encoded as 0 139 startBackReference(backReferenceOffset, backReferenceSize + 4); 140 state = State.IN_BACK_REFERENCE; 141 return true; 142 } 143 144 private enum State { 145 NO_BLOCK, IN_LITERAL, LOOKING_FOR_BACK_REFERENCE, IN_BACK_REFERENCE, EOF 146 } 147 } 148