xref: /reactos/sdk/lib/rtl/compress.c (revision 4561998a)
1 /*
2  * COPYRIGHT:       See COPYING in the top level directory
3  * PROJECT:         ReactOS system libraries
4  * PURPOSE:         Compression and decompression functions
5  * FILE:            lib/rtl/compress.c
6  * PROGRAMER:       Eric Kohl
7                     Sebastian Lackner
8                     Michael Müller
9  */
10 
11 /* INCLUDES *****************************************************************/
12 
13 #include <rtl.h>
14 
15 #define NDEBUG
16 #include <debug.h>
17 
18 /* MACROS *******************************************************************/
19 
20 #define COMPRESSION_FORMAT_MASK  0x00FF
21 #define COMPRESSION_ENGINE_MASK  0xFF00
22 
23 
24 
25 
26 /* FUNCTIONS ****************************************************************/
27 
28 /* Based on Wine Staging */
29 
30 /* decompress a single LZNT1 chunk */
31 static PUCHAR lznt1_decompress_chunk(UCHAR *dst, ULONG dst_size, UCHAR *src, ULONG src_size)
32 {
33     UCHAR *src_cur, *src_end, *dst_cur, *dst_end;
34     ULONG displacement_bits, length_bits;
35     ULONG code_displacement, code_length;
36     WORD flags, code;
37 
38     src_cur = src;
39     src_end = src + src_size;
40     dst_cur = dst;
41     dst_end = dst + dst_size;
42 
43     /* Partial decompression is no error on Windows. */
44     while (src_cur < src_end && dst_cur < dst_end)
45     {
46         /* read flags header */
47         flags = 0x8000 | *src_cur++;
48 
49         /* parse following 8 entities, either uncompressed data or backwards reference */
50         while ((flags & 0xFF00) && src_cur < src_end)
51         {
52             if (flags & 1)
53             {
54                 /* backwards reference */
55                 if (src_cur + sizeof(WORD) > src_end)
56                     return NULL;
57                 code = *(WORD *)src_cur;
58                 src_cur += sizeof(WORD);
59 
60                 /* find length / displacement bits */
61                 for (displacement_bits = 12; displacement_bits > 4; displacement_bits--)
62                     if ((1 << (displacement_bits - 1)) < dst_cur - dst) break;
63                 length_bits       = 16 - displacement_bits;
64                 code_length       = (code & ((1 << length_bits) - 1)) + 3;
65                 code_displacement = (code >> length_bits) + 1;
66 
67                 /* ensure reference is valid */
68                 if (dst_cur < dst + code_displacement)
69                     return NULL;
70 
71                 /* copy bytes of chunk - we can't use memcpy()
72                  * since source and dest can be overlapping */
73                 while (code_length--)
74                 {
75                     if (dst_cur >= dst_end) return dst_cur;
76                     *dst_cur = *(dst_cur - code_displacement);
77                     dst_cur++;
78                 }
79             }
80             else
81             {
82                 /* uncompressed data */
83                 if (dst_cur >= dst_end) return dst_cur;
84                 *dst_cur++ = *src_cur++;
85             }
86             flags >>= 1;
87         }
88 
89     }
90 
91     return dst_cur;
92 }
93 
94 /* decompress data encoded with LZNT1 */
95 static NTSTATUS lznt1_decompress(UCHAR *dst, ULONG dst_size, UCHAR *src, ULONG src_size,
96                                  ULONG offset, ULONG *final_size, UCHAR *workspace)
97 {
98     UCHAR *src_cur = src, *src_end = src + src_size;
99     UCHAR *dst_cur = dst, *dst_end = dst + dst_size;
100     ULONG chunk_size, block_size;
101     WORD chunk_header;
102     UCHAR *ptr;
103 
104     if (src_cur + sizeof(WORD) > src_end)
105         return STATUS_BAD_COMPRESSION_BUFFER;
106 
107     /* skip over chunks which have a big distance (>= 0x1000) to the destination offset */
108     while (offset >= 0x1000 && src_cur + sizeof(WORD) <= src_end)
109     {
110         /* read chunk header and extract size */
111         chunk_header = *(WORD *)src_cur;
112         src_cur += sizeof(WORD);
113         if (!chunk_header) goto out;
114         chunk_size = (chunk_header & 0xFFF) + 1;
115 
116         /* ensure we have enough buffer to process chunk */
117         if (src_cur + chunk_size > src_end)
118             return STATUS_BAD_COMPRESSION_BUFFER;
119 
120         src_cur += chunk_size;
121         offset  -= 0x1000;
122     }
123 
124     /* this chunk is can be included partially */
125     if (offset && src_cur + sizeof(WORD) <= src_end)
126     {
127         /* read chunk header and extract size */
128         chunk_header = *(WORD *)src_cur;
129         src_cur += sizeof(WORD);
130         if (!chunk_header) goto out;
131         chunk_size = (chunk_header & 0xFFF) + 1;
132 
133         /* ensure we have enough buffer to process chunk */
134         if (src_cur + chunk_size > src_end)
135             return STATUS_BAD_COMPRESSION_BUFFER;
136 
137         if (dst_cur >= dst_end)
138             goto out;
139 
140         if (chunk_header & 0x8000)
141         {
142             /* compressed chunk */
143             if (!workspace) return STATUS_ACCESS_VIOLATION;
144             ptr = lznt1_decompress_chunk(workspace, 0x1000, src_cur, chunk_size);
145             if (!ptr) return STATUS_BAD_COMPRESSION_BUFFER;
146             if (ptr - workspace > offset)
147             {
148                 block_size = min((ptr - workspace) - offset, dst_end - dst_cur);
149                 memcpy(dst_cur, workspace + offset, block_size);
150                 dst_cur += block_size;
151             }
152         }
153         else
154         {
155             /* uncompressed chunk */
156             if (chunk_size > offset)
157             {
158                 block_size = min(chunk_size - offset, dst_end - dst_cur);
159                 memcpy(dst_cur, src_cur + offset, block_size);
160                 dst_cur += block_size;
161             }
162         }
163 
164         src_cur += chunk_size;
165     }
166 
167     /* handle remaining chunks */
168     while (src_cur + sizeof(WORD) <= src_end)
169     {
170         /* read chunk header and extract size */
171         chunk_header = *(WORD *)src_cur;
172         src_cur += sizeof(WORD);
173         if (!chunk_header) goto out;
174         chunk_size = (chunk_header & 0xFFF) + 1;
175 
176         if (src_cur + chunk_size > src_end)
177             return STATUS_BAD_COMPRESSION_BUFFER;
178 
179         /* add padding if required */
180         block_size = ((dst_cur - dst) + offset) & 0xFFF;
181         if (block_size)
182         {
183             block_size = 0x1000 - block_size;
184             if (dst_cur + block_size >= dst_end)
185                 goto out;
186             memset(dst_cur, 0, block_size);
187             dst_cur += block_size;
188         }
189 
190         if (dst_cur >= dst_end)
191             goto out;
192 
193         if (chunk_header & 0x8000)
194         {
195             /* compressed chunk */
196             dst_cur = lznt1_decompress_chunk(dst_cur, dst_end - dst_cur, src_cur, chunk_size);
197             if (!dst_cur) return STATUS_BAD_COMPRESSION_BUFFER;
198         }
199         else
200         {
201             /* uncompressed chunk */
202             block_size = min(chunk_size, dst_end - dst_cur);
203             memcpy(dst_cur, src_cur, block_size);
204             dst_cur += block_size;
205         }
206 
207         src_cur += chunk_size;
208     }
209 
210 out:
211     if (final_size)
212         *final_size = dst_cur - dst;
213 
214     return STATUS_SUCCESS;
215 
216 }
217 
218 
219 static NTSTATUS
220 RtlpCompressBufferLZNT1(UCHAR *src, ULONG src_size, UCHAR *dst, ULONG dst_size,
221                         ULONG chunk_size, ULONG *final_size, UCHAR *workspace)
222 {
223         UCHAR *src_cur = src, *src_end = src + src_size;
224         UCHAR *dst_cur = dst, *dst_end = dst + dst_size;
225         ULONG block_size;
226 
227         while (src_cur < src_end)
228         {
229             /* determine size of current chunk */
230             block_size = min(0x1000, src_end - src_cur);
231             if (dst_cur + sizeof(WORD) + block_size > dst_end)
232                 return STATUS_BUFFER_TOO_SMALL;
233 
234             /* write (uncompressed) chunk header */
235             *(WORD *)dst_cur = 0x3000 | (block_size - 1);
236             dst_cur += sizeof(WORD);
237 
238             /* write chunk content */
239             memcpy(dst_cur, src_cur, block_size);
240             dst_cur += block_size;
241             src_cur += block_size;
242         }
243 
244         if (final_size)
245             *final_size = dst_cur - dst;
246 
247         return STATUS_SUCCESS;
248 }
249 
250 
251 static NTSTATUS
252 RtlpWorkSpaceSizeLZNT1(USHORT Engine,
253                        PULONG BufferAndWorkSpaceSize,
254                        PULONG FragmentWorkSpaceSize)
255 {
256    if (Engine == COMPRESSION_ENGINE_STANDARD)
257    {
258       *BufferAndWorkSpaceSize = 0x8010;
259       *FragmentWorkSpaceSize = 0x1000;
260       return(STATUS_SUCCESS);
261    }
262    else if (Engine == COMPRESSION_ENGINE_MAXIMUM)
263    {
264       *BufferAndWorkSpaceSize = 0x10;
265       *FragmentWorkSpaceSize = 0x1000;
266       return(STATUS_SUCCESS);
267    }
268 
269    return(STATUS_NOT_SUPPORTED);
270 }
271 
272 
273 /*
274  * @implemented
275  */
276 NTSTATUS NTAPI
277 RtlCompressBuffer(IN USHORT CompressionFormatAndEngine,
278                   IN PUCHAR UncompressedBuffer,
279                   IN ULONG UncompressedBufferSize,
280                   OUT PUCHAR CompressedBuffer,
281                   IN ULONG CompressedBufferSize,
282                   IN ULONG UncompressedChunkSize,
283                   OUT PULONG FinalCompressedSize,
284                   IN PVOID WorkSpace)
285 {
286    USHORT Format = CompressionFormatAndEngine & COMPRESSION_FORMAT_MASK;
287    /* USHORT Engine = CompressionFormatAndEngine & COMPRESSION_ENGINE_MASK; */
288 
289    if ((Format == COMPRESSION_FORMAT_NONE) ||
290          (Format == COMPRESSION_FORMAT_DEFAULT))
291       return(STATUS_INVALID_PARAMETER);
292 
293    if (Format == COMPRESSION_FORMAT_LZNT1)
294       return(RtlpCompressBufferLZNT1(UncompressedBuffer,
295                                      UncompressedBufferSize,
296                                      CompressedBuffer,
297                                      CompressedBufferSize,
298                                      UncompressedChunkSize,
299                                      FinalCompressedSize,
300                                      WorkSpace));
301 
302    return(STATUS_UNSUPPORTED_COMPRESSION);
303 }
304 
305 
306 /*
307  * @unimplemented
308  */
309 NTSTATUS NTAPI
310 RtlCompressChunks(IN PUCHAR UncompressedBuffer,
311                   IN ULONG UncompressedBufferSize,
312                   OUT PUCHAR CompressedBuffer,
313                   IN ULONG CompressedBufferSize,
314                   IN OUT PCOMPRESSED_DATA_INFO CompressedDataInfo,
315                   IN ULONG CompressedDataInfoLength,
316                   IN PVOID WorkSpace)
317 {
318     UNIMPLEMENTED;
319     return STATUS_NOT_IMPLEMENTED;
320 }
321 
322 /*
323  * @unimplemented
324  */
325 NTSTATUS NTAPI
326 RtlDecompressChunks(OUT PUCHAR UncompressedBuffer,
327                     IN ULONG UncompressedBufferSize,
328                     IN PUCHAR CompressedBuffer,
329                     IN ULONG CompressedBufferSize,
330                     IN PUCHAR CompressedTail,
331                     IN ULONG CompressedTailSize,
332                     IN PCOMPRESSED_DATA_INFO CompressedDataInfo)
333 {
334     UNIMPLEMENTED;
335     return STATUS_NOT_IMPLEMENTED;
336 }
337 
338 /*
339  * @implemented
340  */
341 NTSTATUS NTAPI
342 RtlDecompressFragment(IN USHORT format,
343                       OUT PUCHAR uncompressed,
344                       IN ULONG uncompressed_size,
345                       IN PUCHAR compressed,
346                       IN ULONG compressed_size,
347                       IN ULONG offset,
348                       OUT PULONG final_size,
349                       IN PVOID workspace)
350 {
351     DPRINT("0x%04x, %p, %u, %p, %u, %u, %p, %p :stub\n", format, uncompressed,
352            uncompressed_size, compressed, compressed_size, offset, final_size, workspace);
353 
354     switch (format & ~COMPRESSION_ENGINE_MAXIMUM)
355     {
356         case COMPRESSION_FORMAT_LZNT1:
357             return lznt1_decompress(uncompressed, uncompressed_size, compressed,
358                                     compressed_size, offset, final_size, workspace);
359 
360         case COMPRESSION_FORMAT_NONE:
361         case COMPRESSION_FORMAT_DEFAULT:
362             return STATUS_INVALID_PARAMETER;
363 
364         default:
365             DPRINT1("format %d not implemented\n", format);
366             return STATUS_UNSUPPORTED_COMPRESSION;
367     }
368 }
369 
370 /*
371  * @implemented
372  */
373 NTSTATUS NTAPI
374 RtlDecompressBuffer(IN USHORT CompressionFormat,
375                     OUT PUCHAR UncompressedBuffer,
376                     IN ULONG UncompressedBufferSize,
377                     IN PUCHAR CompressedBuffer,
378                     IN ULONG CompressedBufferSize,
379                     OUT PULONG FinalUncompressedSize)
380 {
381     return RtlDecompressFragment(CompressionFormat, UncompressedBuffer, UncompressedBufferSize,
382                                  CompressedBuffer, CompressedBufferSize, 0, FinalUncompressedSize, NULL);
383 }
384 
385 /*
386  * @unimplemented
387  */
388 NTSTATUS NTAPI
389 RtlDescribeChunk(IN USHORT CompressionFormat,
390                  IN OUT PUCHAR *CompressedBuffer,
391                  IN PUCHAR EndOfCompressedBufferPlus1,
392                  OUT PUCHAR *ChunkBuffer,
393                  OUT PULONG ChunkSize)
394 {
395     UNIMPLEMENTED;
396     return STATUS_NOT_IMPLEMENTED;
397 }
398 
399 
400 /*
401  * @unimplemented
402  */
403 NTSTATUS NTAPI
404 RtlGetCompressionWorkSpaceSize(IN USHORT CompressionFormatAndEngine,
405                                OUT PULONG CompressBufferAndWorkSpaceSize,
406                                OUT PULONG CompressFragmentWorkSpaceSize)
407 {
408    USHORT Format = CompressionFormatAndEngine & COMPRESSION_FORMAT_MASK;
409    USHORT Engine = CompressionFormatAndEngine & COMPRESSION_ENGINE_MASK;
410 
411    if ((Format == COMPRESSION_FORMAT_NONE) ||
412          (Format == COMPRESSION_FORMAT_DEFAULT))
413       return(STATUS_INVALID_PARAMETER);
414 
415    if (Format == COMPRESSION_FORMAT_LZNT1)
416       return(RtlpWorkSpaceSizeLZNT1(Engine,
417                                     CompressBufferAndWorkSpaceSize,
418                                     CompressFragmentWorkSpaceSize));
419 
420    return(STATUS_UNSUPPORTED_COMPRESSION);
421 }
422 
423 
424 
425 /*
426  * @unimplemented
427  */
428 NTSTATUS NTAPI
429 RtlReserveChunk(IN USHORT CompressionFormat,
430                 IN OUT PUCHAR *CompressedBuffer,
431                 IN PUCHAR EndOfCompressedBufferPlus1,
432                 OUT PUCHAR *ChunkBuffer,
433                 IN ULONG ChunkSize)
434 {
435     UNIMPLEMENTED;
436     return STATUS_NOT_IMPLEMENTED;
437 }
438 
439 /* EOF */
440