0b33d40c24293ec826aee017153d41c61c04549b
[metze/samba/wb-ndr.git] / source / librpc / ndr / ndr_compression.c
1 /* 
2    Unix SMB/CIFS implementation.
3
4    libndr compression support
5
6    Copyright (C) Stefan Metzmacher 2005
7    Copyright (C) Matthieu Suiche 2008
8    
9    This program is free software; you can redistribute it and/or modify
10    it under the terms of the GNU General Public License as published by
11    the Free Software Foundation; either version 3 of the License, or
12    (at your option) any later version.
13    
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License for more details.
18    
19    You should have received a copy of the GNU General Public License
20    along with this program.  If not, see <http://www.gnu.org/licenses/>.
21 */
22
23 #include "includes.h"
24 #include "lib/compression/lzxpress.h"
25 #include "librpc/ndr/libndr.h"
26 #include "librpc/ndr/ndr_compression.h"
27 #include <zlib.h>
28
29 static voidpf ndr_zlib_alloc(voidpf opaque, uInt items, uInt size)
30 {
31         return talloc_zero_size(opaque, items * size);
32 }
33
34 static void  ndr_zlib_free(voidpf opaque, voidpf address)
35 {
36         talloc_free(address);
37 }
38
39 static enum ndr_err_code ndr_pull_compression_mszip_chunk(struct ndr_pull *ndrpull,
40                                                  struct ndr_push *ndrpush,
41                                                  z_stream *z,
42                                                  bool *last)
43 {
44         DATA_BLOB comp_chunk;
45         uint32_t comp_chunk_offset;
46         uint32_t comp_chunk_size;
47         DATA_BLOB plain_chunk;
48         uint32_t plain_chunk_offset;
49         uint32_t plain_chunk_size;
50         int z_ret;
51
52         NDR_CHECK(ndr_pull_uint32(ndrpull, NDR_SCALARS, &plain_chunk_size));
53         if (plain_chunk_size > 0x00008000) {
54                 return ndr_pull_error(ndrpull, NDR_ERR_COMPRESSION, "Bad MSZIP plain chunk size %08X > 0x00008000 (PULL)", 
55                                       plain_chunk_size);
56         }
57
58         NDR_CHECK(ndr_pull_uint32(ndrpull, NDR_SCALARS, &comp_chunk_size));
59
60         DEBUG(10,("MSZIP plain_chunk_size: %08X (%u) comp_chunk_size: %08X (%u)\n",
61                   plain_chunk_size, plain_chunk_size, comp_chunk_size, comp_chunk_size));
62
63         comp_chunk_offset = ndrpull->offset;
64         NDR_CHECK(ndr_pull_advance(ndrpull, comp_chunk_size));
65         comp_chunk.length = comp_chunk_size;
66         comp_chunk.data = ndrpull->data + comp_chunk_offset;
67
68         plain_chunk_offset = ndrpush->offset;
69         NDR_CHECK(ndr_push_zero(ndrpush, plain_chunk_size));
70         plain_chunk.length = plain_chunk_size;
71         plain_chunk.data = ndrpush->data + plain_chunk_offset;
72
73         if (comp_chunk.length < 2) {
74                 return ndr_pull_error(ndrpull, NDR_ERR_COMPRESSION,
75                                       "Bad MSZIP comp chunk size %u < 2 (PULL)",
76                                       (unsigned int)comp_chunk.length);
77         }
78         /* CK = Chris Kirmse, official Microsoft purloiner */
79         if (comp_chunk.data[0] != 'C' ||
80             comp_chunk.data[1] != 'K') {
81                 return ndr_pull_error(ndrpull, NDR_ERR_COMPRESSION,
82                                       "Bad MSZIP invalid prefix [%c%c] != [CK]",
83                                       comp_chunk.data[0], comp_chunk.data[1]);
84         }
85
86         z->next_in      = comp_chunk.data + 2;
87         z->avail_in     = comp_chunk.length -2;
88         z->total_in     = 0;
89
90         z->next_out     = plain_chunk.data;
91         z->avail_out    = plain_chunk.length;
92         z->total_out    = 0;
93
94         if (!z->opaque) {
95                 /* the first time we need to intialize completely */
96                 z->zalloc       = ndr_zlib_alloc;
97                 z->zfree        = ndr_zlib_free;
98                 z->opaque       = ndrpull;
99
100                 z_ret = inflateInit2(z, -15);
101                 if (z_ret != Z_OK) {
102                         return ndr_pull_error(ndrpull, NDR_ERR_COMPRESSION,
103                                               "Bad inflateInit2 error %s(%d) (PULL)",
104                                               zError(z_ret), z_ret);
105
106                 }
107         }
108
109         /* call inflate untill we get Z_STREAM_END or an error */
110         while (true) {
111                 z_ret = inflate(z, Z_BLOCK);
112                 if (z_ret != Z_OK) break;
113         }
114
115         if (z_ret != Z_STREAM_END) {
116                 return ndr_pull_error(ndrpull, NDR_ERR_COMPRESSION,
117                                       "Bad inflate(Z_BLOCK) error %s(%d) (PULL)",
118                                       zError(z_ret), z_ret);
119         }
120
121         if (z->avail_in) {
122                 return ndr_pull_error(ndrpull, NDR_ERR_COMPRESSION,
123                                       "MSZIP not all avail_in[%u] bytes consumed (PULL)",
124                                       z->avail_in);
125         }
126
127         if (z->avail_out) {
128                 return ndr_pull_error(ndrpull, NDR_ERR_COMPRESSION,
129                                       "MSZIP not all avail_out[%u] bytes consumed (PULL)",
130                                       z->avail_out);
131         }
132
133         if ((plain_chunk_size < 0x00008000) || (ndrpull->offset+4 >= ndrpull->data_size)) {
134                 /* this is the last chunk */
135                 *last = true;
136         }
137
138         z_ret = inflateReset(z);
139         if (z_ret != Z_OK) {
140                 return ndr_pull_error(ndrpull, NDR_ERR_COMPRESSION,
141                                       "Bad inflateReset error %s(%d) (PULL)",
142                                       zError(z_ret), z_ret);
143         }
144
145         z_ret = inflateSetDictionary(z, plain_chunk.data, plain_chunk.length);
146         if (z_ret != Z_OK) {
147                 return ndr_pull_error(ndrpull, NDR_ERR_COMPRESSION,
148                                       "Bad inflateSetDictionary error %s(%d) (PULL)",
149                                       zError(z_ret), z_ret);
150         }
151
152         return NDR_ERR_SUCCESS;
153 }
154
155 static enum ndr_err_code ndr_push_compression_mszip_chunk(struct ndr_push *ndrpush,
156                                                           struct ndr_pull *ndrpull,
157                                                           z_stream *z,
158                                                           bool *last)
159 {
160         DATA_BLOB comp_chunk;
161         uint32_t comp_chunk_size;
162         uint32_t comp_chunk_size_offset;
163         DATA_BLOB plain_chunk;
164         uint32_t plain_chunk_size;
165         uint32_t plain_chunk_offset;
166         uint32_t max_plain_size = 0x00008000;
167         uint32_t max_comp_size = 0x00008000 + 2 + 12 /*TODO: what value do we really need here?*/;
168         uint32_t tmp_offset;
169         int z_ret;
170
171         plain_chunk_size = MIN(max_plain_size, ndrpull->data_size - ndrpull->offset);
172         plain_chunk_offset = ndrpull->offset;
173         NDR_CHECK(ndr_pull_advance(ndrpull, plain_chunk_size));
174
175         plain_chunk.data = ndrpull->data + plain_chunk_offset;
176         plain_chunk.length = plain_chunk_size;
177
178         if (plain_chunk_size < max_plain_size) {
179                 *last = true;
180         }
181
182         NDR_CHECK(ndr_push_uint32(ndrpush, NDR_SCALARS, plain_chunk_size));
183         comp_chunk_size_offset = ndrpush->offset;
184         NDR_CHECK(ndr_push_uint32(ndrpush, NDR_SCALARS, 0xFEFEFEFE));
185
186         NDR_CHECK(ndr_push_expand(ndrpush, max_comp_size));
187
188         comp_chunk.data = ndrpush->data + ndrpush->offset;
189         comp_chunk.length = max_comp_size;
190
191         /* CK = Chris Kirmse, official Microsoft purloiner */
192         comp_chunk.data[0] = 'C';
193         comp_chunk.data[1] = 'K';
194
195         z->next_in      = plain_chunk.data;
196         z->avail_in     = plain_chunk.length;
197         z->total_in     = 0;
198
199         z->next_out     = comp_chunk.data + 2;
200         z->avail_out    = comp_chunk.length - 2;
201         z->total_out    = 0;
202
203         if (!z->opaque) {
204                 /* the first time we need to intialize completely */
205                 z->zalloc       = ndr_zlib_alloc;
206                 z->zfree        = ndr_zlib_free;
207                 z->opaque       = ndrpull;
208
209                 /* TODO: find how to trigger the same parameters windows uses */
210                 z_ret = deflateInit2(z,
211                                      Z_DEFAULT_COMPRESSION,
212                                      Z_DEFLATED,
213                                      -15,
214                                      9,
215                                      Z_DEFAULT_STRATEGY);
216                 if (z_ret != Z_OK) {
217                         return ndr_push_error(ndrpush, NDR_ERR_COMPRESSION,
218                                               "Bad deflateInit2 error %s(%d) (PUSH)",
219                                               zError(z_ret), z_ret);
220
221                 }
222         } else {
223                 /* TODO: keep the window */
224                 z_ret = deflateReset(z);
225                 if (z_ret != Z_OK) {
226                         return ndr_push_error(ndrpush, NDR_ERR_COMPRESSION,
227                                               "Bad delateReset2 error %s(%d) (PUSH)",
228                                               zError(z_ret), z_ret);
229                 }
230         }
231
232         /* call deflate untill we get Z_STREAM_END or an error */
233         while (true) {
234                 z_ret = deflate(z, Z_FINISH);
235                 if (z_ret != Z_OK) break;
236         }
237         if (z_ret != Z_STREAM_END) {
238                 return ndr_push_error(ndrpush, NDR_ERR_COMPRESSION,
239                                       "Bad delate(Z_BLOCK) error %s(%d) (PUSH)",
240                                       zError(z_ret), z_ret);
241         }
242
243         if (z->avail_in) {
244                 return ndr_push_error(ndrpush, NDR_ERR_COMPRESSION,
245                                       "MSZIP not all avail_in[%u] bytes consumed (PUSH)",
246                                       z->avail_in);
247         }
248
249         comp_chunk_size = 2 + z->total_out;
250
251         tmp_offset = ndrpush->offset;
252         ndrpush->offset = comp_chunk_size_offset;
253         NDR_CHECK(ndr_push_uint32(ndrpush, NDR_SCALARS, comp_chunk_size));
254         ndrpush->offset = tmp_offset;
255
256         ndrpush->offset += comp_chunk_size;
257         return NDR_ERR_SUCCESS;
258 }
259
260 static enum ndr_err_code ndr_pull_compression_xpress_chunk(struct ndr_pull *ndrpull,
261                                                   struct ndr_push *ndrpush,
262                                                   bool *last)
263 {
264         DATA_BLOB comp_chunk;
265         DATA_BLOB plain_chunk;
266         uint32_t comp_chunk_offset;
267         uint32_t plain_chunk_offset;
268         uint32_t comp_chunk_size;
269         uint32_t plain_chunk_size;
270
271         NDR_CHECK(ndr_pull_uint32(ndrpull, NDR_SCALARS, &plain_chunk_size));
272         if (plain_chunk_size > 0x00010000) {
273                 return ndr_pull_error(ndrpull, NDR_ERR_COMPRESSION, "Bad XPRESS plain chunk size %08X > 0x00010000 (PULL)", 
274                                       plain_chunk_size);
275         }
276
277         NDR_CHECK(ndr_pull_uint32(ndrpull, NDR_SCALARS, &comp_chunk_size));
278
279         comp_chunk_offset = ndrpull->offset;
280         NDR_CHECK(ndr_pull_advance(ndrpull, comp_chunk_size));
281         comp_chunk.length = comp_chunk_size;
282         comp_chunk.data = ndrpull->data + comp_chunk_offset;
283
284         plain_chunk_offset = ndrpush->offset;
285         NDR_CHECK(ndr_push_zero(ndrpush, plain_chunk_size));
286         plain_chunk.length = plain_chunk_size;
287         plain_chunk.data = ndrpush->data + plain_chunk_offset;
288
289         DEBUG(10,("XPRESS plain_chunk_size: %08X (%u) comp_chunk_size: %08X (%u)\n",
290                   plain_chunk_size, plain_chunk_size, comp_chunk_size, comp_chunk_size));
291
292         /* Uncompressing the buffer using LZ Xpress algorithm */
293         lzxpress_decompress(&comp_chunk, &plain_chunk);
294
295         if ((plain_chunk_size < 0x00010000) || (ndrpull->offset+4 >= ndrpull->data_size)) {
296                 /* this is the last chunk */
297                 *last = true;
298         }
299
300         return NDR_ERR_SUCCESS;
301 }
302
303 static enum ndr_err_code ndr_push_compression_xpress_chunk(struct ndr_push *ndrpush,
304                                                            struct ndr_pull *ndrpull,
305                                                            bool *last)
306 {
307         return ndr_push_error(ndrpush, NDR_ERR_COMPRESSION, "XPRESS compression is not supported yet (PUSH)");
308 }
309
310 /*
311   handle compressed subcontext buffers, which in midl land are user-marshalled, but
312   we use magic in pidl to make them easier to cope with
313 */
314 enum ndr_err_code ndr_pull_compression_start(struct ndr_pull *subndr,
315                                     struct ndr_pull **_comndr,
316                                     enum ndr_compression_alg compression_alg,
317                                     ssize_t decompressed_len)
318 {
319         struct ndr_push *ndrpush;
320         struct ndr_pull *comndr;
321         DATA_BLOB uncompressed;
322         bool last = false;
323         z_stream z;
324
325         ndrpush = ndr_push_init_ctx(subndr, subndr->iconv_convenience);
326         NDR_ERR_HAVE_NO_MEMORY(ndrpush);
327
328         switch (compression_alg) {
329         case NDR_COMPRESSION_MSZIP:
330                 ZERO_STRUCT(z);
331                 while (!last) {
332                         NDR_CHECK(ndr_pull_compression_mszip_chunk(subndr, ndrpush, &z, &last));
333                 }
334                 break;
335
336         case NDR_COMPRESSION_XPRESS:
337                 while (!last) {
338                         NDR_CHECK(ndr_pull_compression_xpress_chunk(subndr, ndrpush, &last));
339                 }
340                 break;
341
342         default:
343                 return ndr_pull_error(subndr, NDR_ERR_COMPRESSION, "Bad compression algorithm %d (PULL)",
344                                       compression_alg);
345         }
346
347         uncompressed = ndr_push_blob(ndrpush);
348         if (uncompressed.length != decompressed_len) {
349                 return ndr_pull_error(subndr, NDR_ERR_COMPRESSION,
350                                       "Bad uncompressed_len [%u] != [%u](0x%08X) (PULL)",
351                                       (int)uncompressed.length,
352                                       (int)decompressed_len,
353                                       (int)decompressed_len);
354         }
355
356         comndr = talloc_zero(subndr, struct ndr_pull);
357         NDR_ERR_HAVE_NO_MEMORY(comndr);
358         comndr->flags           = subndr->flags;
359         comndr->current_mem_ctx = subndr->current_mem_ctx;
360
361         comndr->data            = uncompressed.data;
362         comndr->data_size       = uncompressed.length;
363         comndr->offset          = 0;
364
365         comndr->iconv_convenience = talloc_reference(comndr, subndr->iconv_convenience);
366
367         *_comndr = comndr;
368         return NDR_ERR_SUCCESS;
369 }
370
371 enum ndr_err_code ndr_pull_compression_end(struct ndr_pull *subndr,
372                                   struct ndr_pull *comndr,
373                                   enum ndr_compression_alg compression_alg,
374                                   ssize_t decompressed_len)
375 {
376         return NDR_ERR_SUCCESS;
377 }
378
379 /*
380   push a compressed subcontext
381 */
382 enum ndr_err_code ndr_push_compression_start(struct ndr_push *subndr,
383                                     struct ndr_push **_uncomndr,
384                                     enum ndr_compression_alg compression_alg,
385                                     ssize_t decompressed_len)
386 {
387         struct ndr_push *uncomndr;
388
389         switch (compression_alg) {
390         case NDR_COMPRESSION_MSZIP:
391         case NDR_COMPRESSION_XPRESS:
392                 break;
393         default:
394                 return ndr_push_error(subndr, NDR_ERR_COMPRESSION,
395                                       "Bad compression algorithm %d (PUSH)",
396                                       compression_alg);
397         }
398
399         uncomndr = ndr_push_init_ctx(subndr, subndr->iconv_convenience);
400         NDR_ERR_HAVE_NO_MEMORY(uncomndr);
401         uncomndr->flags = subndr->flags;
402
403         *_uncomndr = uncomndr;
404         return NDR_ERR_SUCCESS;
405 }
406
407 /*
408   push a compressed subcontext
409 */
410 enum ndr_err_code ndr_push_compression_end(struct ndr_push *subndr,
411                                   struct ndr_push *uncomndr,
412                                   enum ndr_compression_alg compression_alg,
413                                   ssize_t decompressed_len)
414 {
415         struct ndr_pull *ndrpull;
416         bool last = false;
417         z_stream z;
418
419         ndrpull = talloc_zero(uncomndr, struct ndr_pull);
420         NDR_ERR_HAVE_NO_MEMORY(ndrpull);
421         ndrpull->flags          = uncomndr->flags;
422         ndrpull->data           = uncomndr->data;
423         ndrpull->data_size      = uncomndr->offset;
424         ndrpull->offset         = 0;
425
426         ndrpull->iconv_convenience = talloc_reference(ndrpull, subndr->iconv_convenience);
427
428         switch (compression_alg) {
429         case NDR_COMPRESSION_MSZIP:
430                 ZERO_STRUCT(z);
431                 while (!last) {
432                         NDR_CHECK(ndr_push_compression_mszip_chunk(subndr, ndrpull, &z, &last));
433                 }
434                 break;
435
436         case NDR_COMPRESSION_XPRESS:
437                 while (!last) {
438                         NDR_CHECK(ndr_push_compression_xpress_chunk(subndr, ndrpull, &last));
439                 }
440                 break;
441
442         default:
443                 return ndr_push_error(subndr, NDR_ERR_COMPRESSION, "Bad compression algorithm %d (PUSH)", 
444                                       compression_alg);
445         }
446
447         talloc_free(uncomndr);
448         return NDR_ERR_SUCCESS;
449 }