001/*
002 * This file is part of lzo-java, an implementation of LZO in Java.
003 * https://github.com/shevek/lzo-java
004 *
005 * The Java portion of this library is:
006 * Copyright (C) 2011 Shevek <shevek@anarres.org>
007 * All Rights Reserved.
008 *
009 * The preprocessed C portion of this library is:
010 * Copyright (C) 2006-2011 Markus Franz Xaver Johannes Oberhumer
011 * All Rights Reserved.
012 *
013 * This library is free software; you can redistribute it and/or
014 * modify it under the terms of the GNU General Public License
015 * as published by the Free Software Foundation; either version
016 * 2 of the License, or (at your option) any later version.
017 *
018 * This library is distributed in the hope that it will be useful,
019 * but WITHOUT ANY WARRANTY; without even the implied warranty
020 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
021 * See the GNU General Public License for more details.
022 *
023 * You should have received a copy of the GNU General Public
024 * License along with the LZO library; see the file COPYING.
025 * If not, see <http://www.gnu.org/licenses/> or write to the
026 * Free Software Foundation, Inc., 51 Franklin Street, Fifth
027 * Floor, Boston, MA 02110-1301, USA.
028
029 * As a special exception, the copyright holders of this file
030 * give you permission to link this file with independent
031 * modules to produce an executable, regardless of the license
032 * terms of these independent modules, and to copy and distribute
033 * the resulting executable under terms of your choice, provided
034 * that you also meet, for each linked independent module,
035 * the terms and conditions of the license of that module. An
036 * independent module is a module which is not derived from or
037 * based on this library or file. If you modify this file, you may
038 * extend this exception to your version of the file, but
039 * you are not obligated to do so. If you do not wish to do so,
040 * delete this exception statement from your version.
041 */
042package org.anarres.lzo;
043
044import java.io.IOException;
045import java.io.InputStream;
046import java.util.Arrays;
047import java.util.zip.Adler32;
048import java.util.zip.CRC32;
049import java.util.zip.Checksum;
050import javax.annotation.CheckForNull;
051import javax.annotation.Nonnegative;
052import javax.annotation.Nonnull;
053import org.apache.commons.logging.Log;
054import org.apache.commons.logging.LogFactory;
055
056/**
057 *
058 * @author shevek
059 */
060public class LzopInputStream extends LzoInputStream {
061
062    private static final Log LOG = LogFactory.getLog(LzopInputStream.class);
063    private final int flags;
064    private final CRC32 c_crc32_c;
065    private final CRC32 c_crc32_d;
066    private final Adler32 c_adler32_c;
067    private final Adler32 c_adler32_d;
068    private boolean eof;
069
070    public LzopInputStream(@Nonnull InputStream in) throws IOException {
071        super(in, new LzoDecompressor1x());
072        this.flags = readHeader();
073        this.c_crc32_c = ((flags & LzopConstants.F_CRC32_C) == 0) ? null : new CRC32();
074        this.c_crc32_d = ((flags & LzopConstants.F_CRC32_D) == 0) ? null : new CRC32();
075        this.c_adler32_c = ((flags & LzopConstants.F_ADLER32_C) == 0) ? null : new Adler32();
076        this.c_adler32_d = ((flags & LzopConstants.F_ADLER32_D) == 0) ? null : new Adler32();
077        this.eof = false;
078        // logState();
079    }
080
081    public int getFlags() {
082        return flags;
083    }
084
085    @Nonnegative
086    public int getCompressedChecksumCount() {
087        int out = 0;
088        if (c_crc32_c != null)
089            out++;
090        if (c_adler32_c != null)
091            out++;
092        return out;
093    }
094
095    @Nonnegative
096    public int getUncompressedChecksumCount() {
097        int out = 0;
098        if (c_crc32_d != null)
099            out++;
100        if (c_adler32_d != null)
101            out++;
102        return out;
103    }
104
105    @Override
106    protected void logState(@Nonnull String when) {
107        super.logState(when);
108        LOG.info(when + " Flags = " + Integer.toHexString(flags));
109        // LOG.info(when + " CRC32C = " + c_crc32_c);
110        // LOG.info(when + " CRC32D = " + c_crc32_d);
111        // LOG.info(when + " Adler32C = " + c_adler32_c);
112        // LOG.info(when + " Adler32D = " + c_adler32_d);
113    }
114
115    /**
116     * Read len bytes into buf, st LSB of int returned is the last byte of the
117     * first word read.
118     */
119    // @Nonnegative ?
120    private int readInt(@Nonnull byte[] buf, @Nonnegative int len)
121            throws IOException {
122        readBytes(buf, 0, len);
123        int ret = (0xFF & buf[0]) << 24;
124        ret |= (0xFF & buf[1]) << 16;
125        ret |= (0xFF & buf[2]) << 8;
126        ret |= (0xFF & buf[3]);
127        return (len > 3) ? ret : (ret >>> (8 * (4 - len)));
128    }
129
130    /**
131     * Read bytes, update checksums, return first four bytes as an int, first
132     * byte read in the MSB.
133     */
134    // @Nonnegative ?
135    private int readHeaderItem(@Nonnull byte[] buf, @Nonnegative int len, @Nonnull Adler32 adler, @Nonnull CRC32 crc32) throws IOException {
136        int ret = readInt(buf, len);
137        adler.update(buf, 0, len);
138        crc32.update(buf, 0, len);
139        Arrays.fill(buf, (byte) 0);
140        return ret;
141    }
142
143    /**
144     * Read and verify an lzo header, setting relevant block checksum options
145     * and ignoring most everything else.
146     */
147    protected int readHeader() throws IOException {
148        byte[] buf = new byte[9];
149        readBytes(buf, 0, 9);
150        if (!Arrays.equals(buf, LzopConstants.LZOP_MAGIC))
151            throw new IOException("Invalid LZO header");
152        Arrays.fill(buf, (byte) 0);
153        Adler32 adler = new Adler32();
154        CRC32 crc32 = new CRC32();
155        int hitem = readHeaderItem(buf, 2, adler, crc32); // lzop version
156        if (hitem > LzopConstants.LZOP_VERSION) {
157            LOG.debug("Compressed with later version of lzop: "
158                    + Integer.toHexString(hitem) + " (expected 0x"
159                    + Integer.toHexString(LzopConstants.LZOP_VERSION) + ")");
160        }
161        hitem = readHeaderItem(buf, 2, adler, crc32); // lzo library version
162        if (hitem > LzoVersion.LZO_LIBRARY_VERSION) {
163            throw new IOException("Compressed with incompatible lzo version: 0x"
164                    + Integer.toHexString(hitem) + " (expected 0x"
165                    + Integer.toHexString(LzoVersion.LZO_LIBRARY_VERSION) + ")");
166        }
167        hitem = readHeaderItem(buf, 2, adler, crc32); // lzop extract version
168        if (hitem > LzopConstants.LZOP_VERSION) {
169            throw new IOException("Compressed with incompatible lzop version: 0x"
170                    + Integer.toHexString(hitem) + " (expected 0x"
171                    + Integer.toHexString(LzopConstants.LZOP_VERSION) + ")");
172        }
173        hitem = readHeaderItem(buf, 1, adler, crc32); // method
174        switch (hitem) {
175            case LzopConstants.M_LZO1X_1:
176            case LzopConstants.M_LZO1X_1_15:
177            case LzopConstants.M_LZO1X_999:
178                break;
179            default:
180                throw new IOException("Invalid strategy " + Integer.toHexString(hitem));
181        }
182        readHeaderItem(buf, 1, adler, crc32); // ignore level
183
184        // flags
185        int flags = readHeaderItem(buf, 4, adler, crc32);
186        boolean useCRC32 = (flags & LzopConstants.F_H_CRC32) != 0;
187        boolean extraField = (flags & LzopConstants.F_H_EXTRA_FIELD) != 0;
188        if ((flags & LzopConstants.F_MULTIPART) != 0)
189            throw new IOException("Multipart lzop not supported");
190        if ((flags & LzopConstants.F_H_FILTER) != 0)
191            throw new IOException("lzop filter not supported");
192        if ((flags & LzopConstants.F_RESERVED) != 0)
193            throw new IOException("Unknown flags in header");
194        // known !F_H_FILTER, so no optional block
195
196        readHeaderItem(buf, 4, adler, crc32); // ignore mode
197        readHeaderItem(buf, 4, adler, crc32); // ignore mtime
198        readHeaderItem(buf, 4, adler, crc32); // ignore gmtdiff
199        hitem = readHeaderItem(buf, 1, adler, crc32); // fn len
200        if (hitem > 0) {
201            byte[] tmp = (hitem > buf.length) ? new byte[hitem] : buf;
202            readHeaderItem(tmp, hitem, adler, crc32); // skip filename
203        }
204        int checksum = (int) (useCRC32 ? crc32.getValue() : adler.getValue());
205        hitem = readHeaderItem(buf, 4, adler, crc32); // read checksum
206        if (hitem != checksum) {
207            throw new IOException("Invalid header checksum: "
208                    + Long.toHexString(checksum) + " (expected 0x"
209                    + Integer.toHexString(hitem) + ")");
210        }
211        if (extraField) { // lzop 1.08 ultimately ignores this
212            LOG.debug("Extra header field not processed");
213            adler.reset();
214            crc32.reset();
215            hitem = readHeaderItem(buf, 4, adler, crc32);
216            readHeaderItem(new byte[hitem], hitem, adler, crc32);
217            checksum = (int) (useCRC32 ? crc32.getValue() : adler.getValue());
218            if (checksum != readHeaderItem(buf, 4, adler, crc32)) {
219                throw new IOException("Invalid checksum for extra header field");
220            }
221        }
222
223        return flags;
224    }
225
226    private int readChecksum(@CheckForNull Checksum csum) throws IOException {
227        if (csum == null)
228            return 0;
229        // LOG.info("Reading checksum " + csum);
230        return readInt(false);
231    }
232
233    private void testChecksum(@CheckForNull Checksum csum, int value, @Nonnull byte[] data, @Nonnegative int off, @Nonnegative int len) throws IOException {
234        if (csum == null)
235            return;
236        csum.reset();
237        csum.update(data, off, len);
238        if (value != (int) csum.getValue())
239            throw new IOException("Checksum failure: "
240                    + "Expected " + Integer.toHexString(value)
241                    + "; got " + Long.toHexString(csum.getValue()));
242    }
243
244    @Override
245    protected boolean readBlock() throws IOException {
246        // logState("Before readBlock");
247        if (eof)
248            return false;
249        int outputBufferLength = readInt(false);
250        if (outputBufferLength == 0) {
251            // logState("After empty readBlock");
252            eof = true;
253            return false;
254        }
255        setOutputBufferSize(outputBufferLength);
256        int inputBufferLength = readInt(false);
257        setInputBufferSize(inputBufferLength);
258        int v_adler32_d = readChecksum(c_adler32_d);
259        int v_crc32_d = readChecksum(c_crc32_d);
260        // LOG.info("outputBufferLength=" + outputBufferLength + "; inputBufferLength=" + inputBufferLength);
261        if (outputBufferLength == inputBufferLength) {
262            outputBufferPos = 0;
263            outputBufferLen.value = outputBufferLength;
264            readBytes(outputBuffer, 0, outputBufferLength);
265            testChecksum(c_adler32_d, v_adler32_d, outputBuffer, 0, outputBufferLength);
266            testChecksum(c_crc32_d, v_crc32_d, outputBuffer, 0, outputBufferLength);
267            // logState("After uncompressed readBlock");
268            return true;
269        }
270        int v_adler32_c = readChecksum(c_adler32_c);
271        int v_crc32_c = readChecksum(c_crc32_c);
272        readBytes(inputBuffer, 0, inputBufferLength);
273        testChecksum(c_adler32_c, v_adler32_c, inputBuffer, 0, inputBufferLength);
274        testChecksum(c_crc32_c, v_crc32_c, inputBuffer, 0, inputBufferLength);
275        decompress(outputBufferLength, inputBufferLength);
276        testChecksum(c_adler32_d, v_adler32_d, outputBuffer, 0, outputBufferLength);
277        testChecksum(c_crc32_d, v_crc32_d, outputBuffer, 0, outputBufferLength);
278        // logState("After compressed readBlock");
279        return true;
280    }
281}