xref: /openbmc/linux/fs/ntfs3/lznt.c (revision 5f8b7d4b2e9604d03ae06f1a2dd5a1f34c33e533)
1522e010bSKonstantin Komarov // SPDX-License-Identifier: GPL-2.0
2522e010bSKonstantin Komarov /*
3522e010bSKonstantin Komarov  *
4522e010bSKonstantin Komarov  * Copyright (C) 2019-2021 Paragon Software GmbH, All rights reserved.
5522e010bSKonstantin Komarov  *
6522e010bSKonstantin Komarov  */
7e8b8e97fSKari Argillander 
8977d0558SKari Argillander #include <linux/kernel.h>
9977d0558SKari Argillander #include <linux/slab.h>
10977d0558SKari Argillander #include <linux/stddef.h>
11977d0558SKari Argillander #include <linux/string.h>
12977d0558SKari Argillander #include <linux/types.h>
13522e010bSKonstantin Komarov 
14522e010bSKonstantin Komarov #include "debug.h"
15522e010bSKonstantin Komarov #include "ntfs_fs.h"
16522e010bSKonstantin Komarov 
17522e010bSKonstantin Komarov // clang-format off
18e8b8e97fSKari Argillander /* Src buffer is zero. */
19522e010bSKonstantin Komarov #define LZNT_ERROR_ALL_ZEROS	1
20522e010bSKonstantin Komarov #define LZNT_CHUNK_SIZE		0x1000
21522e010bSKonstantin Komarov // clang-format on
22522e010bSKonstantin Komarov 
23522e010bSKonstantin Komarov struct lznt_hash {
24522e010bSKonstantin Komarov 	const u8 *p1;
25522e010bSKonstantin Komarov 	const u8 *p2;
26522e010bSKonstantin Komarov };
27522e010bSKonstantin Komarov 
28522e010bSKonstantin Komarov struct lznt {
29522e010bSKonstantin Komarov 	const u8 *unc;
30522e010bSKonstantin Komarov 	const u8 *unc_end;
31522e010bSKonstantin Komarov 	const u8 *best_match;
32522e010bSKonstantin Komarov 	size_t max_len;
33522e010bSKonstantin Komarov 	bool std;
34522e010bSKonstantin Komarov 
35522e010bSKonstantin Komarov 	struct lznt_hash hash[LZNT_CHUNK_SIZE];
36522e010bSKonstantin Komarov };
37522e010bSKonstantin Komarov 
get_match_len(const u8 * ptr,const u8 * end,const u8 * prev,size_t max_len)38522e010bSKonstantin Komarov static inline size_t get_match_len(const u8 *ptr, const u8 *end, const u8 *prev,
39522e010bSKonstantin Komarov 				   size_t max_len)
40522e010bSKonstantin Komarov {
41522e010bSKonstantin Komarov 	size_t len = 0;
42522e010bSKonstantin Komarov 
43522e010bSKonstantin Komarov 	while (ptr + len < end && ptr[len] == prev[len] && ++len < max_len)
44522e010bSKonstantin Komarov 		;
45522e010bSKonstantin Komarov 	return len;
46522e010bSKonstantin Komarov }
47522e010bSKonstantin Komarov 
longest_match_std(const u8 * src,struct lznt * ctx)48522e010bSKonstantin Komarov static size_t longest_match_std(const u8 *src, struct lznt *ctx)
49522e010bSKonstantin Komarov {
50522e010bSKonstantin Komarov 	size_t hash_index;
51522e010bSKonstantin Komarov 	size_t len1 = 0, len2 = 0;
52522e010bSKonstantin Komarov 	const u8 **hash;
53522e010bSKonstantin Komarov 
54522e010bSKonstantin Komarov 	hash_index =
55522e010bSKonstantin Komarov 		((40543U * ((((src[0] << 4) ^ src[1]) << 4) ^ src[2])) >> 4) &
56522e010bSKonstantin Komarov 		(LZNT_CHUNK_SIZE - 1);
57522e010bSKonstantin Komarov 
58522e010bSKonstantin Komarov 	hash = &(ctx->hash[hash_index].p1);
59522e010bSKonstantin Komarov 
60522e010bSKonstantin Komarov 	if (hash[0] >= ctx->unc && hash[0] < src && hash[0][0] == src[0] &&
61522e010bSKonstantin Komarov 	    hash[0][1] == src[1] && hash[0][2] == src[2]) {
62522e010bSKonstantin Komarov 		len1 = 3;
63522e010bSKonstantin Komarov 		if (ctx->max_len > 3)
64522e010bSKonstantin Komarov 			len1 += get_match_len(src + 3, ctx->unc_end,
65522e010bSKonstantin Komarov 					      hash[0] + 3, ctx->max_len - 3);
66522e010bSKonstantin Komarov 	}
67522e010bSKonstantin Komarov 
68522e010bSKonstantin Komarov 	if (hash[1] >= ctx->unc && hash[1] < src && hash[1][0] == src[0] &&
69522e010bSKonstantin Komarov 	    hash[1][1] == src[1] && hash[1][2] == src[2]) {
70522e010bSKonstantin Komarov 		len2 = 3;
71522e010bSKonstantin Komarov 		if (ctx->max_len > 3)
72522e010bSKonstantin Komarov 			len2 += get_match_len(src + 3, ctx->unc_end,
73522e010bSKonstantin Komarov 					      hash[1] + 3, ctx->max_len - 3);
74522e010bSKonstantin Komarov 	}
75522e010bSKonstantin Komarov 
76e8b8e97fSKari Argillander 	/* Compare two matches and select the best one. */
77522e010bSKonstantin Komarov 	if (len1 < len2) {
78522e010bSKonstantin Komarov 		ctx->best_match = hash[1];
79522e010bSKonstantin Komarov 		len1 = len2;
80522e010bSKonstantin Komarov 	} else {
81522e010bSKonstantin Komarov 		ctx->best_match = hash[0];
82522e010bSKonstantin Komarov 	}
83522e010bSKonstantin Komarov 
84522e010bSKonstantin Komarov 	hash[1] = hash[0];
85522e010bSKonstantin Komarov 	hash[0] = src;
86522e010bSKonstantin Komarov 	return len1;
87522e010bSKonstantin Komarov }
88522e010bSKonstantin Komarov 
longest_match_best(const u8 * src,struct lznt * ctx)89522e010bSKonstantin Komarov static size_t longest_match_best(const u8 *src, struct lznt *ctx)
90522e010bSKonstantin Komarov {
91522e010bSKonstantin Komarov 	size_t max_len;
92522e010bSKonstantin Komarov 	const u8 *ptr;
93522e010bSKonstantin Komarov 
94522e010bSKonstantin Komarov 	if (ctx->unc >= src || !ctx->max_len)
95522e010bSKonstantin Komarov 		return 0;
96522e010bSKonstantin Komarov 
97522e010bSKonstantin Komarov 	max_len = 0;
98522e010bSKonstantin Komarov 	for (ptr = ctx->unc; ptr < src; ++ptr) {
99522e010bSKonstantin Komarov 		size_t len =
100522e010bSKonstantin Komarov 			get_match_len(src, ctx->unc_end, ptr, ctx->max_len);
101522e010bSKonstantin Komarov 		if (len >= max_len) {
102522e010bSKonstantin Komarov 			max_len = len;
103522e010bSKonstantin Komarov 			ctx->best_match = ptr;
104522e010bSKonstantin Komarov 		}
105522e010bSKonstantin Komarov 	}
106522e010bSKonstantin Komarov 
107522e010bSKonstantin Komarov 	return max_len >= 3 ? max_len : 0;
108522e010bSKonstantin Komarov }
109522e010bSKonstantin Komarov 
110522e010bSKonstantin Komarov static const size_t s_max_len[] = {
111522e010bSKonstantin Komarov 	0x1002, 0x802, 0x402, 0x202, 0x102, 0x82, 0x42, 0x22, 0x12,
112522e010bSKonstantin Komarov };
113522e010bSKonstantin Komarov 
114522e010bSKonstantin Komarov static const size_t s_max_off[] = {
115522e010bSKonstantin Komarov 	0x10, 0x20, 0x40, 0x80, 0x100, 0x200, 0x400, 0x800, 0x1000,
116522e010bSKonstantin Komarov };
117522e010bSKonstantin Komarov 
make_pair(size_t offset,size_t len,size_t index)118522e010bSKonstantin Komarov static inline u16 make_pair(size_t offset, size_t len, size_t index)
119522e010bSKonstantin Komarov {
120522e010bSKonstantin Komarov 	return ((offset - 1) << (12 - index)) |
121522e010bSKonstantin Komarov 	       ((len - 3) & (((1 << (12 - index)) - 1)));
122522e010bSKonstantin Komarov }
123522e010bSKonstantin Komarov 
parse_pair(u16 pair,size_t * offset,size_t index)124522e010bSKonstantin Komarov static inline size_t parse_pair(u16 pair, size_t *offset, size_t index)
125522e010bSKonstantin Komarov {
126522e010bSKonstantin Komarov 	*offset = 1 + (pair >> (12 - index));
127522e010bSKonstantin Komarov 	return 3 + (pair & ((1 << (12 - index)) - 1));
128522e010bSKonstantin Komarov }
129522e010bSKonstantin Komarov 
130522e010bSKonstantin Komarov /*
131522e010bSKonstantin Komarov  * compress_chunk
132522e010bSKonstantin Komarov  *
133e8b8e97fSKari Argillander  * Return:
134e8b8e97fSKari Argillander  * * 0	- Ok, @cmpr contains @cmpr_chunk_size bytes of compressed data.
135e8b8e97fSKari Argillander  * * 1	- Input buffer is full zero.
136e8b8e97fSKari Argillander  * * -2 - The compressed buffer is too small to hold the compressed data.
137522e010bSKonstantin Komarov  */
compress_chunk(size_t (* match)(const u8 *,struct lznt *),const u8 * unc,const u8 * unc_end,u8 * cmpr,u8 * cmpr_end,size_t * cmpr_chunk_size,struct lznt * ctx)138522e010bSKonstantin Komarov static inline int compress_chunk(size_t (*match)(const u8 *, struct lznt *),
139522e010bSKonstantin Komarov 				 const u8 *unc, const u8 *unc_end, u8 *cmpr,
140522e010bSKonstantin Komarov 				 u8 *cmpr_end, size_t *cmpr_chunk_size,
141522e010bSKonstantin Komarov 				 struct lznt *ctx)
142522e010bSKonstantin Komarov {
143522e010bSKonstantin Komarov 	size_t cnt = 0;
144522e010bSKonstantin Komarov 	size_t idx = 0;
145522e010bSKonstantin Komarov 	const u8 *up = unc;
146522e010bSKonstantin Komarov 	u8 *cp = cmpr + 3;
147522e010bSKonstantin Komarov 	u8 *cp2 = cmpr + 2;
148522e010bSKonstantin Komarov 	u8 not_zero = 0;
149e8b8e97fSKari Argillander 	/* Control byte of 8-bit values: ( 0 - means byte as is, 1 - short pair ). */
150522e010bSKonstantin Komarov 	u8 ohdr = 0;
151522e010bSKonstantin Komarov 	u8 *last;
152522e010bSKonstantin Komarov 	u16 t16;
153522e010bSKonstantin Komarov 
154522e010bSKonstantin Komarov 	if (unc + LZNT_CHUNK_SIZE < unc_end)
155522e010bSKonstantin Komarov 		unc_end = unc + LZNT_CHUNK_SIZE;
156522e010bSKonstantin Komarov 
157522e010bSKonstantin Komarov 	last = min(cmpr + LZNT_CHUNK_SIZE + sizeof(short), cmpr_end);
158522e010bSKonstantin Komarov 
159522e010bSKonstantin Komarov 	ctx->unc = unc;
160522e010bSKonstantin Komarov 	ctx->unc_end = unc_end;
161522e010bSKonstantin Komarov 	ctx->max_len = s_max_len[0];
162522e010bSKonstantin Komarov 
163522e010bSKonstantin Komarov 	while (up < unc_end) {
164522e010bSKonstantin Komarov 		size_t max_len;
165522e010bSKonstantin Komarov 
166522e010bSKonstantin Komarov 		while (unc + s_max_off[idx] < up)
167522e010bSKonstantin Komarov 			ctx->max_len = s_max_len[++idx];
168522e010bSKonstantin Komarov 
169e8b8e97fSKari Argillander 		/* Find match. */
170522e010bSKonstantin Komarov 		max_len = up + 3 <= unc_end ? (*match)(up, ctx) : 0;
171522e010bSKonstantin Komarov 
172522e010bSKonstantin Komarov 		if (!max_len) {
173522e010bSKonstantin Komarov 			if (cp >= last)
174522e010bSKonstantin Komarov 				goto NotCompressed;
175522e010bSKonstantin Komarov 			not_zero |= *cp++ = *up++;
176522e010bSKonstantin Komarov 		} else if (cp + 1 >= last) {
177522e010bSKonstantin Komarov 			goto NotCompressed;
178522e010bSKonstantin Komarov 		} else {
179522e010bSKonstantin Komarov 			t16 = make_pair(up - ctx->best_match, max_len, idx);
180522e010bSKonstantin Komarov 			*cp++ = t16;
181522e010bSKonstantin Komarov 			*cp++ = t16 >> 8;
182522e010bSKonstantin Komarov 
183522e010bSKonstantin Komarov 			ohdr |= 1 << cnt;
184522e010bSKonstantin Komarov 			up += max_len;
185522e010bSKonstantin Komarov 		}
186522e010bSKonstantin Komarov 
187522e010bSKonstantin Komarov 		cnt = (cnt + 1) & 7;
188522e010bSKonstantin Komarov 		if (!cnt) {
189522e010bSKonstantin Komarov 			*cp2 = ohdr;
190522e010bSKonstantin Komarov 			ohdr = 0;
191522e010bSKonstantin Komarov 			cp2 = cp;
192522e010bSKonstantin Komarov 			cp += 1;
193522e010bSKonstantin Komarov 		}
194522e010bSKonstantin Komarov 	}
195522e010bSKonstantin Komarov 
196522e010bSKonstantin Komarov 	if (cp2 < last)
197522e010bSKonstantin Komarov 		*cp2 = ohdr;
198522e010bSKonstantin Komarov 	else
199522e010bSKonstantin Komarov 		cp -= 1;
200522e010bSKonstantin Komarov 
201522e010bSKonstantin Komarov 	*cmpr_chunk_size = cp - cmpr;
202522e010bSKonstantin Komarov 
203522e010bSKonstantin Komarov 	t16 = (*cmpr_chunk_size - 3) | 0xB000;
204522e010bSKonstantin Komarov 	cmpr[0] = t16;
205522e010bSKonstantin Komarov 	cmpr[1] = t16 >> 8;
206522e010bSKonstantin Komarov 
207522e010bSKonstantin Komarov 	return not_zero ? 0 : LZNT_ERROR_ALL_ZEROS;
208522e010bSKonstantin Komarov 
209522e010bSKonstantin Komarov NotCompressed:
210522e010bSKonstantin Komarov 
211522e010bSKonstantin Komarov 	if ((cmpr + LZNT_CHUNK_SIZE + sizeof(short)) > last)
212522e010bSKonstantin Komarov 		return -2;
213522e010bSKonstantin Komarov 
214522e010bSKonstantin Komarov 	/*
215e8b8e97fSKari Argillander 	 * Copy non cmpr data.
216522e010bSKonstantin Komarov 	 * 0x3FFF == ((LZNT_CHUNK_SIZE + 2 - 3) | 0x3000)
217522e010bSKonstantin Komarov 	 */
218522e010bSKonstantin Komarov 	cmpr[0] = 0xff;
219522e010bSKonstantin Komarov 	cmpr[1] = 0x3f;
220522e010bSKonstantin Komarov 
221522e010bSKonstantin Komarov 	memcpy(cmpr + sizeof(short), unc, LZNT_CHUNK_SIZE);
222522e010bSKonstantin Komarov 	*cmpr_chunk_size = LZNT_CHUNK_SIZE + sizeof(short);
223522e010bSKonstantin Komarov 
224522e010bSKonstantin Komarov 	return 0;
225522e010bSKonstantin Komarov }
226522e010bSKonstantin Komarov 
decompress_chunk(u8 * unc,u8 * unc_end,const u8 * cmpr,const u8 * cmpr_end)227522e010bSKonstantin Komarov static inline ssize_t decompress_chunk(u8 *unc, u8 *unc_end, const u8 *cmpr,
228522e010bSKonstantin Komarov 				       const u8 *cmpr_end)
229522e010bSKonstantin Komarov {
230522e010bSKonstantin Komarov 	u8 *up = unc;
231522e010bSKonstantin Komarov 	u8 ch = *cmpr++;
232522e010bSKonstantin Komarov 	size_t bit = 0;
233522e010bSKonstantin Komarov 	size_t index = 0;
234522e010bSKonstantin Komarov 	u16 pair;
235522e010bSKonstantin Komarov 	size_t offset, length;
236522e010bSKonstantin Komarov 
237e8b8e97fSKari Argillander 	/* Do decompression until pointers are inside range. */
238522e010bSKonstantin Komarov 	while (up < unc_end && cmpr < cmpr_end) {
239*5f21e3e6SAndrew Ballance 		// return err if more than LZNT_CHUNK_SIZE bytes are written
240*5f21e3e6SAndrew Ballance 		if (up - unc > LZNT_CHUNK_SIZE)
241*5f21e3e6SAndrew Ballance 			return -EINVAL;
242522e010bSKonstantin Komarov 		/* Correct index */
243522e010bSKonstantin Komarov 		while (unc + s_max_off[index] < up)
244522e010bSKonstantin Komarov 			index += 1;
245522e010bSKonstantin Komarov 
246e8b8e97fSKari Argillander 		/* Check the current flag for zero. */
247522e010bSKonstantin Komarov 		if (!(ch & (1 << bit))) {
248e8b8e97fSKari Argillander 			/* Just copy byte. */
249522e010bSKonstantin Komarov 			*up++ = *cmpr++;
250522e010bSKonstantin Komarov 			goto next;
251522e010bSKonstantin Komarov 		}
252522e010bSKonstantin Komarov 
253e8b8e97fSKari Argillander 		/* Check for boundary. */
254522e010bSKonstantin Komarov 		if (cmpr + 1 >= cmpr_end)
255522e010bSKonstantin Komarov 			return -EINVAL;
256522e010bSKonstantin Komarov 
257e8b8e97fSKari Argillander 		/* Read a short from little endian stream. */
258522e010bSKonstantin Komarov 		pair = cmpr[1];
259522e010bSKonstantin Komarov 		pair <<= 8;
260522e010bSKonstantin Komarov 		pair |= cmpr[0];
261522e010bSKonstantin Komarov 
262522e010bSKonstantin Komarov 		cmpr += 2;
263522e010bSKonstantin Komarov 
264e8b8e97fSKari Argillander 		/* Translate packed information into offset and length. */
265522e010bSKonstantin Komarov 		length = parse_pair(pair, &offset, index);
266522e010bSKonstantin Komarov 
267e8b8e97fSKari Argillander 		/* Check offset for boundary. */
268522e010bSKonstantin Komarov 		if (unc + offset > up)
269522e010bSKonstantin Komarov 			return -EINVAL;
270522e010bSKonstantin Komarov 
271e8b8e97fSKari Argillander 		/* Truncate the length if necessary. */
272522e010bSKonstantin Komarov 		if (up + length >= unc_end)
273522e010bSKonstantin Komarov 			length = unc_end - up;
274522e010bSKonstantin Komarov 
275522e010bSKonstantin Komarov 		/* Now we copy bytes. This is the heart of LZ algorithm. */
276522e010bSKonstantin Komarov 		for (; length > 0; length--, up++)
277522e010bSKonstantin Komarov 			*up = *(up - offset);
278522e010bSKonstantin Komarov 
279522e010bSKonstantin Komarov next:
280e8b8e97fSKari Argillander 		/* Advance flag bit value. */
281522e010bSKonstantin Komarov 		bit = (bit + 1) & 7;
282522e010bSKonstantin Komarov 
283522e010bSKonstantin Komarov 		if (!bit) {
284522e010bSKonstantin Komarov 			if (cmpr >= cmpr_end)
285522e010bSKonstantin Komarov 				break;
286522e010bSKonstantin Komarov 
287522e010bSKonstantin Komarov 			ch = *cmpr++;
288522e010bSKonstantin Komarov 		}
289522e010bSKonstantin Komarov 	}
290522e010bSKonstantin Komarov 
291e8b8e97fSKari Argillander 	/* Return the size of uncompressed data. */
292522e010bSKonstantin Komarov 	return up - unc;
293522e010bSKonstantin Komarov }
294522e010bSKonstantin Komarov 
295522e010bSKonstantin Komarov /*
296e8b8e97fSKari Argillander  * get_lznt_ctx
297e8b8e97fSKari Argillander  * @level: 0 - Standard compression.
298e8b8e97fSKari Argillander  *	   !0 - Best compression, requires a lot of cpu.
299522e010bSKonstantin Komarov  */
get_lznt_ctx(int level)300522e010bSKonstantin Komarov struct lznt *get_lznt_ctx(int level)
301522e010bSKonstantin Komarov {
30296de65a9SKonstantin Komarov 	struct lznt *r = kzalloc(level ? offsetof(struct lznt, hash) :
30396de65a9SKonstantin Komarov 					 sizeof(struct lznt),
304d3624466SKonstantin Komarov 				 GFP_NOFS);
305522e010bSKonstantin Komarov 
306522e010bSKonstantin Komarov 	if (r)
307522e010bSKonstantin Komarov 		r->std = !level;
308522e010bSKonstantin Komarov 	return r;
309522e010bSKonstantin Komarov }
310522e010bSKonstantin Komarov 
311522e010bSKonstantin Komarov /*
312e8b8e97fSKari Argillander  * compress_lznt - Compresses @unc into @cmpr
313522e010bSKonstantin Komarov  *
314e8b8e97fSKari Argillander  * Return:
315e8b8e97fSKari Argillander  * * +x - Ok, @cmpr contains 'final_compressed_size' bytes of compressed data.
316e8b8e97fSKari Argillander  * * 0 - Input buffer is full zero.
317522e010bSKonstantin Komarov  */
compress_lznt(const void * unc,size_t unc_size,void * cmpr,size_t cmpr_size,struct lznt * ctx)318522e010bSKonstantin Komarov size_t compress_lznt(const void *unc, size_t unc_size, void *cmpr,
319522e010bSKonstantin Komarov 		     size_t cmpr_size, struct lznt *ctx)
320522e010bSKonstantin Komarov {
321522e010bSKonstantin Komarov 	int err;
322522e010bSKonstantin Komarov 	size_t (*match)(const u8 *src, struct lznt *ctx);
323522e010bSKonstantin Komarov 	u8 *p = cmpr;
324522e010bSKonstantin Komarov 	u8 *end = p + cmpr_size;
325522e010bSKonstantin Komarov 	const u8 *unc_chunk = unc;
326522e010bSKonstantin Komarov 	const u8 *unc_end = unc_chunk + unc_size;
327522e010bSKonstantin Komarov 	bool is_zero = true;
328522e010bSKonstantin Komarov 
329522e010bSKonstantin Komarov 	if (ctx->std) {
330522e010bSKonstantin Komarov 		match = &longest_match_std;
331522e010bSKonstantin Komarov 		memset(ctx->hash, 0, sizeof(ctx->hash));
332522e010bSKonstantin Komarov 	} else {
333522e010bSKonstantin Komarov 		match = &longest_match_best;
334522e010bSKonstantin Komarov 	}
335522e010bSKonstantin Komarov 
336e8b8e97fSKari Argillander 	/* Compression cycle. */
337522e010bSKonstantin Komarov 	for (; unc_chunk < unc_end; unc_chunk += LZNT_CHUNK_SIZE) {
338522e010bSKonstantin Komarov 		cmpr_size = 0;
339522e010bSKonstantin Komarov 		err = compress_chunk(match, unc_chunk, unc_end, p, end,
340522e010bSKonstantin Komarov 				     &cmpr_size, ctx);
341522e010bSKonstantin Komarov 		if (err < 0)
342522e010bSKonstantin Komarov 			return unc_size;
343522e010bSKonstantin Komarov 
344522e010bSKonstantin Komarov 		if (is_zero && err != LZNT_ERROR_ALL_ZEROS)
345522e010bSKonstantin Komarov 			is_zero = false;
346522e010bSKonstantin Komarov 
347522e010bSKonstantin Komarov 		p += cmpr_size;
348522e010bSKonstantin Komarov 	}
349522e010bSKonstantin Komarov 
350522e010bSKonstantin Komarov 	if (p <= end - 2)
351522e010bSKonstantin Komarov 		p[0] = p[1] = 0;
352522e010bSKonstantin Komarov 
353522e010bSKonstantin Komarov 	return is_zero ? 0 : PtrOffset(cmpr, p);
354522e010bSKonstantin Komarov }
355522e010bSKonstantin Komarov 
356522e010bSKonstantin Komarov /*
357e8b8e97fSKari Argillander  * decompress_lznt - Decompress @cmpr into @unc.
358522e010bSKonstantin Komarov  */
decompress_lznt(const void * cmpr,size_t cmpr_size,void * unc,size_t unc_size)359522e010bSKonstantin Komarov ssize_t decompress_lznt(const void *cmpr, size_t cmpr_size, void *unc,
360522e010bSKonstantin Komarov 			size_t unc_size)
361522e010bSKonstantin Komarov {
362522e010bSKonstantin Komarov 	const u8 *cmpr_chunk = cmpr;
363522e010bSKonstantin Komarov 	const u8 *cmpr_end = cmpr_chunk + cmpr_size;
364522e010bSKonstantin Komarov 	u8 *unc_chunk = unc;
365522e010bSKonstantin Komarov 	u8 *unc_end = unc_chunk + unc_size;
366522e010bSKonstantin Komarov 	u16 chunk_hdr;
367522e010bSKonstantin Komarov 
368522e010bSKonstantin Komarov 	if (cmpr_size < sizeof(short))
369522e010bSKonstantin Komarov 		return -EINVAL;
370522e010bSKonstantin Komarov 
371e8b8e97fSKari Argillander 	/* Read chunk header. */
372522e010bSKonstantin Komarov 	chunk_hdr = cmpr_chunk[1];
373522e010bSKonstantin Komarov 	chunk_hdr <<= 8;
374522e010bSKonstantin Komarov 	chunk_hdr |= cmpr_chunk[0];
375522e010bSKonstantin Komarov 
376e8b8e97fSKari Argillander 	/* Loop through decompressing chunks. */
377522e010bSKonstantin Komarov 	for (;;) {
378522e010bSKonstantin Komarov 		size_t chunk_size_saved;
379522e010bSKonstantin Komarov 		size_t unc_use;
380522e010bSKonstantin Komarov 		size_t cmpr_use = 3 + (chunk_hdr & (LZNT_CHUNK_SIZE - 1));
381522e010bSKonstantin Komarov 
382e8b8e97fSKari Argillander 		/* Check that the chunk actually fits the supplied buffer. */
383522e010bSKonstantin Komarov 		if (cmpr_chunk + cmpr_use > cmpr_end)
384522e010bSKonstantin Komarov 			return -EINVAL;
385522e010bSKonstantin Komarov 
386e8b8e97fSKari Argillander 		/* First make sure the chunk contains compressed data. */
387522e010bSKonstantin Komarov 		if (chunk_hdr & 0x8000) {
388e8b8e97fSKari Argillander 			/* Decompress a chunk and return if we get an error. */
389522e010bSKonstantin Komarov 			ssize_t err =
390522e010bSKonstantin Komarov 				decompress_chunk(unc_chunk, unc_end,
391522e010bSKonstantin Komarov 						 cmpr_chunk + sizeof(chunk_hdr),
392522e010bSKonstantin Komarov 						 cmpr_chunk + cmpr_use);
393522e010bSKonstantin Komarov 			if (err < 0)
394522e010bSKonstantin Komarov 				return err;
395522e010bSKonstantin Komarov 			unc_use = err;
396522e010bSKonstantin Komarov 		} else {
397e8b8e97fSKari Argillander 			/* This chunk does not contain compressed data. */
39896de65a9SKonstantin Komarov 			unc_use = unc_chunk + LZNT_CHUNK_SIZE > unc_end ?
39996de65a9SKonstantin Komarov 					  unc_end - unc_chunk :
40096de65a9SKonstantin Komarov 					  LZNT_CHUNK_SIZE;
401522e010bSKonstantin Komarov 
402522e010bSKonstantin Komarov 			if (cmpr_chunk + sizeof(chunk_hdr) + unc_use >
403522e010bSKonstantin Komarov 			    cmpr_end) {
404522e010bSKonstantin Komarov 				return -EINVAL;
405522e010bSKonstantin Komarov 			}
406522e010bSKonstantin Komarov 
407522e010bSKonstantin Komarov 			memcpy(unc_chunk, cmpr_chunk + sizeof(chunk_hdr),
408522e010bSKonstantin Komarov 			       unc_use);
409522e010bSKonstantin Komarov 		}
410522e010bSKonstantin Komarov 
411e8b8e97fSKari Argillander 		/* Advance pointers. */
412522e010bSKonstantin Komarov 		cmpr_chunk += cmpr_use;
413522e010bSKonstantin Komarov 		unc_chunk += unc_use;
414522e010bSKonstantin Komarov 
415e8b8e97fSKari Argillander 		/* Check for the end of unc buffer. */
416522e010bSKonstantin Komarov 		if (unc_chunk >= unc_end)
417522e010bSKonstantin Komarov 			break;
418522e010bSKonstantin Komarov 
419e8b8e97fSKari Argillander 		/* Proceed the next chunk. */
420522e010bSKonstantin Komarov 		if (cmpr_chunk > cmpr_end - 2)
421522e010bSKonstantin Komarov 			break;
422522e010bSKonstantin Komarov 
423522e010bSKonstantin Komarov 		chunk_size_saved = LZNT_CHUNK_SIZE;
424522e010bSKonstantin Komarov 
425e8b8e97fSKari Argillander 		/* Read chunk header. */
426522e010bSKonstantin Komarov 		chunk_hdr = cmpr_chunk[1];
427522e010bSKonstantin Komarov 		chunk_hdr <<= 8;
428522e010bSKonstantin Komarov 		chunk_hdr |= cmpr_chunk[0];
429522e010bSKonstantin Komarov 
430522e010bSKonstantin Komarov 		if (!chunk_hdr)
431522e010bSKonstantin Komarov 			break;
432522e010bSKonstantin Komarov 
433e8b8e97fSKari Argillander 		/* Check the size of unc buffer. */
434522e010bSKonstantin Komarov 		if (unc_use < chunk_size_saved) {
435522e010bSKonstantin Komarov 			size_t t1 = chunk_size_saved - unc_use;
436522e010bSKonstantin Komarov 			u8 *t2 = unc_chunk + t1;
437522e010bSKonstantin Komarov 
438e8b8e97fSKari Argillander 			/* 'Zero' memory. */
439522e010bSKonstantin Komarov 			if (t2 >= unc_end)
440522e010bSKonstantin Komarov 				break;
441522e010bSKonstantin Komarov 
442522e010bSKonstantin Komarov 			memset(unc_chunk, 0, t1);
443522e010bSKonstantin Komarov 			unc_chunk = t2;
444522e010bSKonstantin Komarov 		}
445522e010bSKonstantin Komarov 	}
446522e010bSKonstantin Komarov 
447e8b8e97fSKari Argillander 	/* Check compression boundary. */
448522e010bSKonstantin Komarov 	if (cmpr_chunk > cmpr_end)
449522e010bSKonstantin Komarov 		return -EINVAL;
450522e010bSKonstantin Komarov 
451522e010bSKonstantin Komarov 	/*
452522e010bSKonstantin Komarov 	 * The unc size is just a difference between current
453e8b8e97fSKari Argillander 	 * pointer and original one.
454522e010bSKonstantin Komarov 	 */
455522e010bSKonstantin Komarov 	return PtrOffset(unc, unc_chunk);
456522e010bSKonstantin Komarov }
457