Это ошибка в Java Inflater или как? - PullRequest
2 голосов
/ 20 сентября 2019

Я был укушен этим в некоторых модульных тестах.

Я хочу распаковать некоторые сжатые ZLIB данные, используя Inflater , где длина необработанных данных известна заранее.

Это (прямолинейно) работает как положено

    /*  
     * Decompresses a zlib compressed buffer, with given size of raw data.
     * All data is fed and inflated in full (one step) 
     */
    public static byte[] decompressFull(byte[] comp, int len) throws Exception {
        byte[] res = new byte[len]; // result (uncompressed)
        Inflater inf = new Inflater();
        inf.setInput(comp);
        int n = inf.inflate(res, 0, len);
        if (n != len)
            throw new RuntimeException("didn't inflate all data");
        System.out.println("Data done (full). bytes in :"  + inf.getBytesRead() 
                + " out=" + inf.getBytesWritten()
                + " finished: " + inf.finished());
        // done - the next is not needed, just for checking... 
        //try a final inflate just in case (might trigger ZLIB crc check)
        byte[] buf2 = new byte[6];
        int nx = inf.inflate(buf2);//should give 0
        if (nx != 0)
            throw new RuntimeException("nx=" + nx + " " + Arrays.toString(buf2));
        if (!inf.finished())
            throw new RuntimeException("not finished?");
        inf.end();
        return res;
    }

Теперь сжатый ввод может входить в порции произвольного размера.Следующий код эмулирует случай, когда сжатый вход подается полностью, за исключением последних 4 байтов, а затем остальные байты подаются по одному.(Как я понимаю, последние 4 или 5 байтов потока zlib не нужны для распаковки полных данных, но они необходимы для проверки целостности - Adler-32 CRC).

    public static byte[] decompressBytexByte(byte[] comp, int len) throws Exception {
            byte[] res = new byte[len]; // result (uncompressed)
            Inflater inf = new Inflater();
            inf.setInput(comp, 0, comp.length - 4);
            int n = inf.inflate(res, 0, len);
            if (n != len)
                throw new RuntimeException("didn't inflate all data");
            // inf.setInput(comp, comp.length-4,4); 
            // !!! works if I uncomment the line befor and comment the next for 
            for (int p = comp.length - 4; p < comp.length; p++)
                inf.setInput(comp, p, 1);
            System.out.println("Data done (decompressBytexByte). bytes in :" + inf.getBytesRead() 
                    + " out=" + inf.getBytesWritten() + " finished: " + inf.finished());
            // all data fed... try a final inflate (might -should?- trigger ZLIB crc check)
            byte[] buf2 = new byte[6];
            int nx = inf.inflate(buf2);//should give 0
            if (nx != 0)
                throw new RuntimeException("nx=" + nx + " " + Arrays.toString(buf2));
            if (!inf.finished())
                throw new RuntimeException("not finished?");
            inf.end();
            return res;
        }

Ну, это не работает для меня (Java 1.8.0_181).Воздуходувка не закончена, проверка Adler CRC не выполнена, кажется;более того: кажется, что байты не передаются в инфлятор.

Еще более странно: это работает, если завершающие 4 байта передаются за один вызов.

Вы можете попробовать это здесь: https://repl.it/@HernanJJ/Inflater-Test

Даже странные вещи случаются, когда я подаю весь ввод по одному байту за раз: иногда строка int nx= inf.inflate(buf2);//should give 0 возвращает ненулевое значение (когда все данные уже заполнены).

Это ожидаемое поведение?Я что-то упустил?

1 Ответ

0 голосов
/ 20 сентября 2019

Как уже заметил @SeanBright, вы должны подавать на него новый ввод только тогда, когда Inflater.needsInput() возвращает true.

Последовательный вызов setInput отменяет ранее переданный вами ввод.

Javadoc of Inflater.needsInput():

Возвращает true, если во входном буфере не осталось данных.Это можно использовать, чтобы определить, следует ли вызывать #setInput для обеспечения большего ввода.

Пока вы передаете его побайтно, это всегда так, поэтому вы, вероятно, можете пропустить проверкусам по себе.

Вы можете заменить часть настройки ввода метода decompressBytexByte следующим (для полной байтовой подачи):

byte[] res = new byte[len];
Inflater inf = new Inflater();

int a = 0; // number of bytes that have already been obtained
for (int p = 0; p < comp.length; p++) {         
    inf.setInput(comp, p, 1);
    a += inf.inflate(res, a, len - a);
}
...