| // SPDX-License-Identifier: GPL-2.0-or-later | |
| /* | |
| * xpress_decompress.c - A decompressor for the XPRESS compression format | |
| * (Huffman variant), which can be used in "System Compressed" (WOF) files. | |
| * | |
| * This is a port of the upstream wimlib "xpress_decompress.c" which uses a | |
| * subtable-based Huffman decode table format. The decode table and the | |
| * codeword-length array share a union since the lengths are fully consumed | |
| * before the table is written. | |
| * | |
| * Copyright (C) 2012-2016 Eric Biggers | |
| */ | |
| #include <linux/array_size.h> | |
| #include "decompress_common.h" | |
| #include "lib.h" | |
| #include "../ntfs_codec.h" | |
| #define XPRESS_NUM_CHARS 256 | |
| #define XPRESS_NUM_SYMBOLS 512 | |
| #define XPRESS_MAX_CODEWORD_LEN 15 | |
| #define XPRESS_MIN_MATCH_LEN 3 | |
| /* This value is chosen for fast decompression. */ | |
| #define XPRESS_TABLEBITS 11 | |
| /* Reusable heap-allocated memory for XPRESS decompression. The decode table | |
| * and the codeword-length array alias each other in a union: all lengths are | |
| * consumed into the working space before any decode-table entry is written. | |
| */ | |
| struct xpress_decompressor { | |
| union { | |
| DECODE_TABLE(decode_table, XPRESS_NUM_SYMBOLS, XPRESS_TABLEBITS, | |
| XPRESS_MAX_CODEWORD_LEN); | |
| u8 lens[XPRESS_NUM_SYMBOLS]; | |
| }; | |
| DECODE_TABLE_WORKING_SPACE(working_space, XPRESS_NUM_SYMBOLS, | |
| XPRESS_MAX_CODEWORD_LEN); | |
| } __aligned(DECODE_TABLE_ALIGNMENT); | |
| int xpress_decompress(struct xpress_decompressor *d, | |
| const void *compressed_data, size_t compressed_size, | |
| void *uncompressed_data, size_t uncompressed_size) | |
| { | |
| const u8 *const in_begin = compressed_data; | |
| u8 *const out_begin = uncompressed_data; | |
| u8 *out_next = out_begin; | |
| u8 *const out_end = out_begin + uncompressed_size; | |
| struct input_bitstream is; | |
| u32 i; | |
| /* Read the Huffman codeword lengths (512 4-bit values packed into 256 | |
| * bytes). | |
| */ | |
| if (compressed_size < XPRESS_NUM_SYMBOLS / 2) | |
| return -1; | |
| for (i = 0; i < XPRESS_NUM_SYMBOLS / 2; i++) { | |
| d->lens[2 * i + 0] = in_begin[i] & 0xf; | |
| d->lens[2 * i + 1] = in_begin[i] >> 4; | |
| } | |
| /* Build a decoding table for the Huffman code. */ | |
| if (make_huffman_decode_table(d->decode_table, XPRESS_NUM_SYMBOLS, | |
| XPRESS_TABLEBITS, d->lens, | |
| XPRESS_MAX_CODEWORD_LEN, | |
| d->working_space, | |
| ARRAY_SIZE(d->decode_table))) | |
| return -1; | |
| /* Decode the matches and literals. */ | |
| init_input_bitstream(&is, in_begin + XPRESS_NUM_SYMBOLS / 2, | |
| compressed_size - XPRESS_NUM_SYMBOLS / 2); | |
| while (out_next != out_end) { | |
| u32 sym; | |
| u32 log2_offset; | |
| u32 length; | |
| u32 offset; | |
| sym = read_huffsym(&is, d->decode_table, XPRESS_TABLEBITS, | |
| XPRESS_MAX_CODEWORD_LEN); | |
| if (sym < XPRESS_NUM_CHARS) { | |
| /* Literal */ | |
| *out_next++ = sym; | |
| } else { | |
| /* Match */ | |
| length = sym & 0xf; | |
| log2_offset = (sym >> 4) & 0xf; | |
| bitstream_ensure_bits(&is, 16); | |
| offset = ((u32)1 << log2_offset) | | |
| bitstream_pop_bits(&is, log2_offset); | |
| if (length == 0xf) { | |
| length += bitstream_read_byte(&is); | |
| if (length == 0xf + 0xff) | |
| length = bitstream_read_u16(&is); | |
| } | |
| length += XPRESS_MIN_MATCH_LEN; | |
| if (unlikely(lz_copy(length, offset, out_begin, out_next, | |
| out_end, XPRESS_MIN_MATCH_LEN))) | |
| return -1; | |
| out_next += length; | |
| } | |
| } | |
| return 0; | |
| } | |
| struct xpress_decompressor *xpress_allocate_decompressor(void) | |
| { | |
| return kmalloc_obj(struct xpress_decompressor, GFP_NOFS); | |
| } | |
| void xpress_free_decompressor(struct xpress_decompressor *d) | |
| { | |
| kfree(d); | |
| } | |
| static size_t xpress_scratch_size(u32 chunk_size) | |
| { | |
| return sizeof(struct xpress_decompressor); | |
| } | |
| static int xpress_decompress_chunk(void *scratch, const void *src, | |
| size_t src_len, void *dst, size_t dst_len, | |
| u32 chunk_size) | |
| { | |
| return xpress_decompress(scratch, src, src_len, dst, dst_len); | |
| } | |
| const struct ntfs_codec_ops ntfs_xpress4k_codec_ops = { | |
| .id = NTFS_CODEC_XPRESS4K, | |
| .name = "xpress4k", | |
| .scratch_size = xpress_scratch_size, | |
| .decompress_chunk = xpress_decompress_chunk, | |
| }; | |
| const struct ntfs_codec_ops ntfs_xpress8k_codec_ops = { | |
| .id = NTFS_CODEC_XPRESS8K, | |
| .name = "xpress8k", | |
| .scratch_size = xpress_scratch_size, | |
| .decompress_chunk = xpress_decompress_chunk, | |
| }; | |
| const struct ntfs_codec_ops ntfs_xpress16k_codec_ops = { | |
| .id = NTFS_CODEC_XPRESS16K, | |
| .name = "xpress16k", | |
| .scratch_size = xpress_scratch_size, | |
| .decompress_chunk = xpress_decompress_chunk, | |
| }; |