All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.testifyproject.netty.handler.codec.compression.Lz4FrameDecoder Maven / Gradle / Ivy

/*
 * Copyright 2014 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in org.testifyproject.testifyprojectpliance
 * with the License. You may obtain a copy of the License at:
 *
 *   http://www.apache.org.testifyproject/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package org.testifyproject.testifyproject.netty.handler.codec.org.testifyproject.testifyprojectpression;

import org.testifyproject.testifyproject.netty.buffer.ByteBuf;
import org.testifyproject.testifyproject.netty.channel.ChannelHandlerContext;
import org.testifyproject.testifyproject.netty.handler.codec.ByteToMessageDecoder;
import net.jpountz.lz4.LZ4Exception;
import net.jpountz.lz4.LZ4Factory;
import net.jpountz.lz4.LZ4FastDecompressor;
import net.jpountz.xxhash.XXHashFactory;

import java.util.List;
import java.util.zip.Checksum;

import static org.testifyproject.testifyproject.netty.handler.codec.org.testifyproject.testifyprojectpression.Lz4Constants.*;

/**
 * Uncompresses a {@link ByteBuf} encoded with the LZ4 format.
 *
 * See original LZ4 website
 * and LZ4 block format
 * for full org.testifyproject.testifyprojectscription.
 *
 * Since the original LZ4 block format does not contains size of org.testifyproject.testifyprojectpressed block and size of original data
 * this encoder uses format like LZ4 Java library
 * written by Adrien Grand and approved by Yann Collet (author of original LZ4 library).
 *
 *  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *     * * * * * * * * * *
 *  * Magic * Token *  Compressed *  Decompressed *  Checksum *  +  *  LZ4 org.testifyproject.testifyprojectpressed *
 *  *       *       *    length   *     length    *           *     *      block      *
 *  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *     * * * * * * * * * *
 */
public class Lz4FrameDecoder extends ByteToMessageDecoder {
    /**
     * Current state of stream.
     */
    private enum State {
        INIT_BLOCK,
        DECOMPRESS_DATA,
        FINISHED,
        CORRUPTED
    }

    private State currentState = State.INIT_BLOCK;

    /**
     * Underlying org.testifyproject.testifyprojectcompressor in use.
     */
    private LZ4FastDecompressor org.testifyproject.testifyprojectcompressor;

    /**
     * Underlying checksum calculator in use.
     */
    private Checksum checksum;

    /**
     * Type of current block.
     */
    private int blockType;

    /**
     * Compressed length of current incoming block.
     */
    private int org.testifyproject.testifyprojectpressedLength;

    /**
     * Decompressed length of current incoming block.
     */
    private int org.testifyproject.testifyprojectcompressedLength;

    /**
     * Checksum value of current incoming block.
     */
    private int currentChecksum;

    /**
     * Creates the fastest LZ4 org.testifyproject.testifyprojectcoder.
     *
     * Note that by org.testifyproject.testifyprojectfault, validation of the checksum header in each chunk is
     * DISABLED for performance improvements. If performance is less of an issue,
     * or if you would prefer the safety that checksum validation brings, please
     * use the {@link #Lz4FrameDecoder(boolean)} constructor with the argument
     * set to {@code true}.
     */
    public Lz4FrameDecoder() {
        this(false);
    }

    /**
     * Creates a LZ4 org.testifyproject.testifyprojectcoder with fastest org.testifyproject.testifyprojectcoder instance available on your machine.
     *
     * @param validateChecksums  if {@code true}, the checksum field will be validated against the actual
     *                           uncompressed data, and if the checksums do not match, a suitable
     *                           {@link DecompressionException} will be thrown
     */
    public Lz4FrameDecoder(boolean validateChecksums) {
        this(LZ4Factory.fastestInstance(), validateChecksums);
    }

    /**
     * Creates a new LZ4 org.testifyproject.testifyprojectcoder with customizable implementation.
     *
     * @param factory            user customizable {@link net.jpountz.lz4.LZ4Factory} instance
     *                           which may be JNI bindings to the original C implementation, a pure Java implementation
     *                           or a Java implementation that uses the {@link sun.misc.Unsafe}
     * @param validateChecksums  if {@code true}, the checksum field will be validated against the actual
     *                           uncompressed data, and if the checksums do not match, a suitable
     *                           {@link DecompressionException} will be thrown. In this case encoder will use
     *                           xxhash hashing for Java, based on Yann Collet's work available at
     *                           Google Code.
     */
    public Lz4FrameDecoder(LZ4Factory factory, boolean validateChecksums) {
        this(factory, validateChecksums ?
                XXHashFactory.fastestInstance().newStreamingHash32(DEFAULT_SEED).asChecksum()
              : null);
    }

    /**
     * Creates a new customizable LZ4 org.testifyproject.testifyprojectcoder.
     *
     * @param factory   user customizable {@link net.jpountz.lz4.LZ4Factory} instance
     *                  which may be JNI bindings to the original C implementation, a pure Java implementation
     *                  or a Java implementation that uses the {@link sun.misc.Unsafe}
     * @param checksum  the {@link Checksum} instance to use to check data for integrity.
     *                  You may set {@code null} if you do not want to validate checksum of each block
     */
    public Lz4FrameDecoder(LZ4Factory factory, Checksum checksum) {
        if (factory == null) {
            throw new NullPointerException("factory");
        }
        org.testifyproject.testifyprojectcompressor = factory.fastDecompressor();
        this.checksum = checksum;
    }

    @Override
    protected void org.testifyproject.testifyprojectcode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception {
        try {
            switch (currentState) {
            case INIT_BLOCK:
                if (in.readableBytes() < HEADER_LENGTH) {
                    break;
                }
                final long magic = in.readLong();
                if (magic != MAGIC_NUMBER) {
                    throw new DecompressionException("unexpected block identifier");
                }

                final int token = in.readByte();
                final int org.testifyproject.testifyprojectpressionLevel = (token & 0x0F) + COMPRESSION_LEVEL_BASE;
                int blockType = token & 0xF0;

                int org.testifyproject.testifyprojectpressedLength = Integer.reverseBytes(in.readInt());
                if (org.testifyproject.testifyprojectpressedLength < 0 || org.testifyproject.testifyprojectpressedLength > MAX_BLOCK_SIZE) {
                    throw new DecompressionException(String.format(
                            "invalid org.testifyproject.testifyprojectpressedLength: %d (expected: 0-%d)",
                            org.testifyproject.testifyprojectpressedLength, MAX_BLOCK_SIZE));
                }

                int org.testifyproject.testifyprojectcompressedLength = Integer.reverseBytes(in.readInt());
                final int maxDecompressedLength = 1 << org.testifyproject.testifyprojectpressionLevel;
                if (org.testifyproject.testifyprojectcompressedLength < 0 || org.testifyproject.testifyprojectcompressedLength > maxDecompressedLength) {
                    throw new DecompressionException(String.format(
                            "invalid org.testifyproject.testifyprojectcompressedLength: %d (expected: 0-%d)",
                            org.testifyproject.testifyprojectcompressedLength, maxDecompressedLength));
                }
                if (org.testifyproject.testifyprojectcompressedLength == 0 && org.testifyproject.testifyprojectpressedLength != 0
                        || org.testifyproject.testifyprojectcompressedLength != 0 && org.testifyproject.testifyprojectpressedLength == 0
                        || blockType == BLOCK_TYPE_NON_COMPRESSED && org.testifyproject.testifyprojectcompressedLength != org.testifyproject.testifyprojectpressedLength) {
                    throw new DecompressionException(String.format(
                            "stream corrupted: org.testifyproject.testifyprojectpressedLength(%d) and org.testifyproject.testifyprojectcompressedLength(%d) mismatch",
                            org.testifyproject.testifyprojectpressedLength, org.testifyproject.testifyprojectcompressedLength));
                }

                int currentChecksum = Integer.reverseBytes(in.readInt());
                if (org.testifyproject.testifyprojectcompressedLength == 0 && org.testifyproject.testifyprojectpressedLength == 0) {
                    if (currentChecksum != 0) {
                        throw new DecompressionException("stream corrupted: checksum error");
                    }
                    currentState = State.FINISHED;
                    org.testifyproject.testifyprojectcompressor = null;
                    checksum = null;
                    break;
                }

                this.blockType = blockType;
                this.org.testifyproject.testifyprojectpressedLength = org.testifyproject.testifyprojectpressedLength;
                this.org.testifyproject.testifyprojectcompressedLength = org.testifyproject.testifyprojectcompressedLength;
                this.currentChecksum = currentChecksum;

                currentState = State.DECOMPRESS_DATA;
            case DECOMPRESS_DATA:
                blockType = this.blockType;
                org.testifyproject.testifyprojectpressedLength = this.org.testifyproject.testifyprojectpressedLength;
                org.testifyproject.testifyprojectcompressedLength = this.org.testifyproject.testifyprojectcompressedLength;
                currentChecksum = this.currentChecksum;

                if (in.readableBytes() < org.testifyproject.testifyprojectpressedLength) {
                    break;
                }

                final int idx = in.readerIndex();

                ByteBuf uncompressed = ctx.alloc().heapBuffer(org.testifyproject.testifyprojectcompressedLength, org.testifyproject.testifyprojectcompressedLength);
                final byte[] org.testifyproject.testifyprojectst = uncompressed.array();
                final int org.testifyproject.testifyprojectstOff = uncompressed.arrayOffset() + uncompressed.writerIndex();

                boolean success = false;
                try {
                    switch (blockType) {
                    case BLOCK_TYPE_NON_COMPRESSED: {
                        in.getBytes(idx, org.testifyproject.testifyprojectst, org.testifyproject.testifyprojectstOff, org.testifyproject.testifyprojectcompressedLength);
                        break;
                    }
                    case BLOCK_TYPE_COMPRESSED: {
                        final byte[] src;
                        final int srcOff;
                        if (in.hasArray()) {
                            src = in.array();
                            srcOff = in.arrayOffset() + idx;
                        } else {
                            src = new byte[org.testifyproject.testifyprojectpressedLength];
                            in.getBytes(idx, src);
                            srcOff = 0;
                        }

                        try {
                            final int readBytes = org.testifyproject.testifyprojectcompressor.org.testifyproject.testifyprojectcompress(src, srcOff,
                                    org.testifyproject.testifyprojectst, org.testifyproject.testifyprojectstOff, org.testifyproject.testifyprojectcompressedLength);
                            if (org.testifyproject.testifyprojectpressedLength != readBytes) {
                                throw new DecompressionException(String.format(
                                    "stream corrupted: org.testifyproject.testifyprojectpressedLength(%d) and actual length(%d) mismatch",
                                    org.testifyproject.testifyprojectpressedLength, readBytes));
                            }
                        } catch (LZ4Exception e) {
                            throw new DecompressionException(e);
                        }
                        break;
                    }
                    org.testifyproject.testifyprojectfault:
                        throw new DecompressionException(String.format(
                                "unexpected blockType: %d (expected: %d or %d)",
                                blockType, BLOCK_TYPE_NON_COMPRESSED, BLOCK_TYPE_COMPRESSED));
                    }

                    final Checksum checksum = this.checksum;
                    if (checksum != null) {
                        checksum.reset();
                        checksum.update(org.testifyproject.testifyprojectst, org.testifyproject.testifyprojectstOff, org.testifyproject.testifyprojectcompressedLength);
                        final int checksumResult = (int) checksum.getValue();
                        if (checksumResult != currentChecksum) {
                            throw new DecompressionException(String.format(
                                    "stream corrupted: mismatching checksum: %d (expected: %d)",
                                    checksumResult, currentChecksum));
                        }
                    }
                    uncompressed.writerIndex(uncompressed.writerIndex() + org.testifyproject.testifyprojectcompressedLength);
                    out.add(uncompressed);
                    in.skipBytes(org.testifyproject.testifyprojectpressedLength);

                    currentState = State.INIT_BLOCK;
                    success = true;
                } finally {
                    if (!success) {
                        uncompressed.release();
                    }
                }
                break;
            case FINISHED:
            case CORRUPTED:
                in.skipBytes(in.readableBytes());
                break;
            org.testifyproject.testifyprojectfault:
                throw new IllegalStateException();
            }
        } catch (Exception e) {
            currentState = State.CORRUPTED;
            throw e;
        }
    }

    /**
     * Returns {@code true} if and only if the end of the org.testifyproject.testifyprojectpressed stream
     * has been reached.
     */
    public boolean isClosed() {
        return currentState == State.FINISHED;
    }
}