• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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