CPPMyth
Library to interoperate with MythTV server
compressor.cpp
1 /*
2  * Copyright (C) 2016 Jean-Luc Barriere
3  *
4  * This library is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU Lesser General Public License as published
6  * by the Free Software Foundation; either version 3, or (at your option)
7  * any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public License
15  * along with this library; see the file COPYING. If not, write to
16  * the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston,
17  * MA 02110-1301 USA
18  * http://www.gnu.org/copyleft/gpl.html
19  *
20  */
21 
22 #include "compressor.h"
23 #include "cppdef.h"
24 
25 #if HAVE_ZLIB
26 #include <zlib.h>
27 #else
28 #define Z_NO_FLUSH 0
29 #define Z_OK 0
30 #define Z_STREAM_END 1
31 #define Z_STREAM_ERROR (-2)
32 #define Z_BUF_ERROR (-5)
33 #endif
34 
35 #define GZIP_WINDOWS_BIT 15 + 16
36 #define GZIP_CHUNK_SIZE 16384
37 #define MIN(a,b) (a > b ? b : a)
38 #define MAX(a,b) (a > b ? a : b)
39 
40 using namespace NSROOT;
41 
42 Compressor::Compressor(const char *input, size_t len, int level /*= -1*/)
43 : m_status(Z_STREAM_ERROR)
44 , m_flush(Z_NO_FLUSH)
45 , m_stop(true)
46 , m_chunk_size(GZIP_CHUNK_SIZE)
47 , m_type_in(MEM_BUFFER)
48 , m_input_len(len)
49 , m_input(input)
50 , m_rstream(0)
51 , m_rstream_hdl(0)
52 , m_rstream_buf(0)
53 , m_output(0)
54 , m_output_pos(0)
55 , m_output_len(0)
56 , _opaque(0)
57 {
58 #if HAVE_ZLIB
59  m_output = new char[m_chunk_size];
60  _opaque = new z_stream;
61  m_status = _init(_opaque, m_output, m_chunk_size, level);
62  m_stop = (m_status != Z_OK);
63 #endif
64 }
65 
66 Compressor::Compressor(STREAM_READER reader, void *handle, int level /*= -1*/)
67 : m_status(Z_STREAM_ERROR)
68 , m_flush(Z_NO_FLUSH)
69 , m_stop(true)
70 , m_chunk_size(GZIP_CHUNK_SIZE)
71 , m_type_in(FCB_READER)
72 , m_input_len(0)
73 , m_input(0)
74 , m_rstream(reader)
75 , m_rstream_hdl(handle)
76 , m_rstream_buf(0)
77 , m_output(0)
78 , m_output_pos(0)
79 , m_output_len(0)
80 , _opaque(0)
81 {
82 #if HAVE_ZLIB
83  m_rstream_buf = new char[m_chunk_size];
84  m_output = new char[m_chunk_size];
85  _opaque = new z_stream;
86  m_status = _init(_opaque, m_output, m_chunk_size, level);
87  m_stop = (m_status != Z_OK);
88 #endif
89 }
90 
91 Compressor::~Compressor()
92 {
93 #if HAVE_ZLIB
94  z_stream *strm = static_cast<z_stream*>(_opaque);
95  deflateEnd(strm);
96  SAFE_DELETE(strm);
97  SAFE_DELETE_ARRAY(m_output);
98  SAFE_DELETE_ARRAY(m_rstream_buf);
99 #endif
100 }
101 
102 bool Compressor::IsCompleted()
103 {
104  return (m_status == Z_STREAM_END);
105 }
106 
107 bool Compressor::HasBufferError()
108 {
109  return (m_status == Z_BUF_ERROR);
110 }
111 
112 bool Compressor::HasStreamError()
113 {
114  switch(m_status)
115  {
116  case Z_OK:
117  case Z_STREAM_END:
118  case Z_BUF_ERROR:
119  return false;
120  default:
121  return true;
122  }
123 }
124 
125 size_t Compressor::ReadOutput(char *buf, size_t len)
126 {
127  size_t out = 0;
128 #if HAVE_ZLIB
129  while (len)
130  {
131  if (m_output_len)
132  {
133  size_t sz = MIN(m_output_len, len);
134  memcpy(buf, m_output + m_output_pos, sz);
135  out += sz;
136  buf += sz;
137  len -= sz;
138  m_output_pos += sz;
139  m_output_len -= sz;
140  }
141  else if (m_status != Z_STREAM_END)
142  {
143  z_stream *strm = static_cast<z_stream*>(_opaque);
144  if (!strm->avail_in)
145  NextChunk();
146  if (!strm->avail_out)
147  {
148  strm->next_out = (unsigned char*)m_output;
149  strm->avail_out = m_chunk_size;
150  m_output_pos = 0;
151  }
152  // Try to deflate chunk
153  m_status = deflate(strm, m_flush);
154  if (m_status < 0)
155  {
156  m_stop = true;
157  return 0;
158  }
159  m_output_len = m_chunk_size - m_output_pos - strm->avail_out;
160  m_stop = false;
161  }
162  else
163  {
164  m_stop = true;
165  break;
166  }
167  }
168 #endif
169  return out;
170 }
171 
172 size_t Compressor::FetchOutput(const char **data)
173 {
174  *data = 0;
175 #if HAVE_ZLIB
176  size_t len = 0;
177  while (!m_stop)
178  {
179  if (m_output_len)
180  {
181  *data = m_output + m_output_pos;
182  len = m_output_len;
183  m_output_pos += m_output_len;
184  m_output_len = 0;
185  return len;
186  }
187  else if (m_status != Z_STREAM_END)
188  {
189  z_stream *strm = static_cast<z_stream*>(_opaque);
190  if (!strm->avail_in)
191  NextChunk();
192  if (!strm->avail_out)
193  {
194  strm->next_out = (unsigned char*)m_output;
195  strm->avail_out = m_chunk_size;
196  m_output_pos = 0;
197  }
198  // Try to inflate chunk
199  m_status = deflate(strm, m_flush);
200  if (m_status < 0)
201  {
202  m_stop = true;
203  return 0;
204  }
205  m_output_len = m_chunk_size - m_output_pos - strm->avail_out;
206  m_stop = false;
207  }
208  else
209  {
210  m_stop = true;
211  break;
212  }
213  }
214 #endif
215  return 0;
216 }
217 
218 int Compressor::_init(void *zp, void *out, size_t len, int level)
219 {
220 #if HAVE_ZLIB
221  z_stream *strm = static_cast<z_stream*>(zp);
222  // Prepare inflater status
223  strm->zalloc = Z_NULL;
224  strm->zfree = Z_NULL;
225  strm->opaque = Z_NULL;
226  strm->avail_in = 0;
227  strm->next_in = Z_NULL;
228  strm->avail_out = len;
229  strm->next_out = (unsigned char*)out;
230  return deflateInit2(strm, MAX(-1, MIN(level, 9)), Z_DEFLATED, GZIP_WINDOWS_BIT, 8, Z_DEFAULT_STRATEGY);
231 #else
232  return Z_STREAM_ERROR;
233 #endif
234 }
235 
236 size_t Compressor::NextChunk()
237 {
238  size_t sz = 0;
239 #if HAVE_ZLIB
240  if (m_flush != Z_FINISH)
241  {
242  z_stream *strm = static_cast<z_stream*>(_opaque);
243  switch(m_type_in)
244  {
245  case MEM_BUFFER:
246  // Determine current chunk size
247  sz = MIN(m_chunk_size, m_input_len);
248  // Check for end of data
249  if (sz)
250  {
251  // Set stream
252  strm->next_in = (unsigned char*)m_input;
253  strm->avail_in = (unsigned)sz;
254  // Update next chunk
255  m_input += sz;
256  m_input_len -= sz;
257  // flush on last chunk
258  m_flush = (m_input_len > 0 ? Z_NO_FLUSH : Z_FINISH);
259  }
260  break;
261  case FCB_READER:
262  {
263  int ret = (*m_rstream)(m_rstream_hdl, (void*)m_rstream_buf, m_chunk_size);
264  if (ret < 0)
265  sz = 0;
266  else
267  {
268  m_flush = (ret > 0 ? Z_NO_FLUSH : Z_FINISH);
269  sz = ret;
270  }
271  // Set stream
272  strm->next_in = (unsigned char*)m_rstream_buf;
273  strm->avail_in = (unsigned)sz;
274  }
275  break;
276  default:
277  break;
278  }
279  }
280 #endif
281  return sz;
282 }
283 
284 Decompressor::Decompressor(const char *input, size_t len)
285 : m_status(Z_STREAM_ERROR)
286 , m_stop(true)
287 , m_chunk_size(GZIP_CHUNK_SIZE)
288 , m_type_in(MEM_BUFFER)
289 , m_input_len(len)
290 , m_input(input)
291 , m_rstream(0)
292 , m_rstream_hdl(0)
293 , m_rstream_buf(0)
294 , m_output(0)
295 , m_output_pos(0)
296 , m_output_len(0)
297 , _opaque(0)
298 {
299 #if HAVE_ZLIB
300  m_output = new char[m_chunk_size];
301  _opaque = new z_stream;
302  m_status = _init(_opaque, m_output, m_chunk_size);
303  m_stop = (m_status != Z_OK);
304 #endif
305 }
306 
307 Decompressor::Decompressor(STREAM_READER reader, void *handle)
308 : m_status(Z_STREAM_ERROR)
309 , m_stop(true)
310 , m_chunk_size(GZIP_CHUNK_SIZE)
311 , m_type_in(FCB_READER)
312 , m_input_len(0)
313 , m_input(0)
314 , m_rstream(reader)
315 , m_rstream_hdl(handle)
316 , m_rstream_buf(0)
317 , m_output(0)
318 , m_output_pos(0)
319 , m_output_len(0)
320 , _opaque(0)
321 {
322 #if HAVE_ZLIB
323  m_rstream_buf = new char[m_chunk_size];
324  m_output = new char[m_chunk_size];
325  _opaque = new z_stream;
326  m_status = _init(_opaque, m_output, m_chunk_size);
327  m_stop = (m_status != Z_OK);
328 #endif
329 }
330 
331 Decompressor::~Decompressor()
332 {
333 #if HAVE_ZLIB
334  z_stream *strm = static_cast<z_stream*>(_opaque);
335  inflateEnd(strm);
336  SAFE_DELETE(strm);
337  SAFE_DELETE_ARRAY(m_output);
338  SAFE_DELETE_ARRAY(m_rstream_buf);
339 #endif
340 }
341 
342 bool Decompressor::IsCompleted()
343 {
344  return (m_status == Z_STREAM_END);
345 }
346 
347 bool Decompressor::HasBufferError()
348 {
349  return (m_status == Z_BUF_ERROR);
350 }
351 
352 bool Decompressor::HasStreamError()
353 {
354  switch(m_status)
355  {
356  case Z_OK:
357  case Z_STREAM_END:
358  case Z_BUF_ERROR:
359  return false;
360  default:
361  return true;
362  }
363 }
364 
365 size_t Decompressor::ReadOutput(char *buf, size_t len)
366 {
367  size_t out = 0;
368 #if HAVE_ZLIB
369  while (len)
370  {
371  if (m_output_len)
372  {
373  size_t sz = MIN(m_output_len, len);
374  memcpy(buf, m_output + m_output_pos, sz);
375  out += sz;
376  buf += sz;
377  len -= sz;
378  m_output_pos += sz;
379  m_output_len -= sz;
380  }
381  else if (m_status != Z_STREAM_END)
382  {
383  z_stream *strm = static_cast<z_stream*>(_opaque);
384  if (!strm->avail_in)
385  NextChunk();
386  if (!strm->avail_out)
387  {
388  strm->next_out = (unsigned char*)m_output;
389  strm->avail_out = m_chunk_size;
390  m_output_pos = 0;
391  }
392  // Try to inflate chunk
393  m_status = inflate(strm, Z_NO_FLUSH);
394  if (m_status < 0)
395  {
396  m_stop = true;
397  return 0;
398  }
399  m_output_len = m_chunk_size - m_output_pos - strm->avail_out;
400  m_stop = false;
401  }
402  else
403  {
404  m_stop = true;
405  break;
406  }
407  }
408 #endif
409  return out;
410 }
411 
412 size_t Decompressor::FetchOutput(const char **data)
413 {
414  *data = 0;
415 #if HAVE_ZLIB
416  size_t len = 0;
417  do
418  {
419  if (m_output_len)
420  {
421  *data = m_output + m_output_pos;
422  len = m_output_len;
423  m_output_pos += m_output_len;
424  m_output_len = 0;
425  return len;
426  }
427  else if (m_status != Z_STREAM_END)
428  {
429  z_stream *strm = static_cast<z_stream*>(_opaque);
430  if (!strm->avail_in)
431  NextChunk();
432  if (!strm->avail_out)
433  {
434  strm->next_out = (unsigned char*)m_output;
435  strm->avail_out = m_chunk_size;
436  m_output_pos = 0;
437  }
438  // Try to inflate chunk
439  m_status = inflate(strm, Z_NO_FLUSH);
440  if (m_status < 0)
441  {
442  m_stop = true;
443  return 0;
444  }
445  m_output_len = m_chunk_size - m_output_pos - strm->avail_out;
446  m_stop = false;
447  }
448  else
449  {
450  m_stop = true;
451  break;
452  }
453  } while (!m_stop);
454 #endif
455  return 0;
456 }
457 
458 int Decompressor::_init(void *zp, void *out, size_t len)
459 {
460 #if HAVE_ZLIB
461  z_stream *strm = static_cast<z_stream*>(zp);
462  // Prepare inflater status
463  strm->zalloc = Z_NULL;
464  strm->zfree = Z_NULL;
465  strm->opaque = Z_NULL;
466  strm->avail_in = 0;
467  strm->next_in = Z_NULL;
468  strm->avail_out = len;
469  strm->next_out = (unsigned char*)out;
470  return inflateInit2(strm, GZIP_WINDOWS_BIT);
471 #else
472  return Z_STREAM_ERROR;
473 #endif
474 }
475 
476 size_t Decompressor::NextChunk()
477 {
478  size_t sz = 0;
479 #if HAVE_ZLIB
480  z_stream *strm = static_cast<z_stream*>(_opaque);
481  switch(m_type_in)
482  {
483  case MEM_BUFFER:
484  // Determine current chunk size
485  sz = MIN(m_chunk_size, m_input_len);
486  // Check for end of data
487  if (sz)
488  {
489  // Set stream
490  strm->next_in = (unsigned char*)m_input;
491  strm->avail_in = (unsigned)sz;
492  // Update next chunk
493  m_input += sz;
494  m_input_len -= sz;
495  }
496  break;
497  case FCB_READER:
498  {
499  int ret = (*m_rstream)(m_rstream_hdl, (void*)m_rstream_buf, m_chunk_size);
500  if (ret > 0)
501  sz = ret;
502  else
503  sz = 0;
504  // Set stream
505  strm->next_in = (unsigned char*)m_rstream_buf;
506  strm->avail_in = (unsigned)sz;
507  }
508  break;
509  default:
510  break;
511  }
512 #endif
513  return sz;
514 }