/* ========================================================================== */
/*! \file
 * \brief Data compression and decompression
 *
 * Copyright (c) 2015 by the developers. See the LICENSE file for details.
 *
 * If nothing else is specified, function return zero to indicate success
 * and a negative value to indicate an error.
 */


/* ========================================================================== */
/* Include headers */

#include "posix.h"  /* Include this first because of feature test macros */

#include "config.h"

#  if CFG_USE_ZLIB  /* This requires data from "config.h" */
#     include <zlib.h>
#  endif  /* CFG_USE_ZLIB */
#  include <string.h>

#  include "compression.h"
#  include "main.h"


/* ========================================================================== */
/*! \addtogroup COMPRESSION
 *
 * This module provides optional support for data compression and decompression.
 *
 * Currently the following algorithms are supported:
 * <br>
 * - DEFLATE according to RFC1951
 *
 * \note
 * This module currently works only if the zlib library is available.
 */
/* @{ */


#if !CFG_CMPR_DISABLE


/* ========================================================================== */
/* Data types */

struct cmpr_stream
{
   unsigned int  alg;  /* Compression algorithm (\c CMPR_ALG_xxx constant ) */
   int  sd;      /* Socket descriptor or stream handle of undelaying protocol */
   ssize_t  (*tx_send)(int, const void*, size_t, int);  /* Send function */
   ssize_t  (*rx_recv)(int, void*, size_t, int);        /* Receive function */
   char*  tx_buf_in;   /* TX data buffer (uncompressed data) */
   char*  tx_buf_out;  /* TX data buffer (compressed data) */
   char*  rx_buf_in;   /* RX data buffer (compressed data) */
   char*  rx_buf_out;  /* RX data buffer (uncompressed data) */
   void*  tx;          /* Stream object for TX direction (compression) */
   void*  rx;          /* Stream object for RX direction (decompression) */
};


/* ========================================================================== */
/* Constants */

/*! \brief Print debug messages to stdout if nonzero */
#  define CMPR_DEBUG  0

/*! \name Flush options for TX direction of compressed data streams
 *
 * The flags can be bitwise ORed together.
 */
/* @{ */
#  define CMPR_FLUSH_NO   0
#  define CMPR_FLUSH_YES  1
#  define CMPR_FLUSH_END  2
/* @} */


/*! \brief Size of internal data buffers
 *
 * \attention
 * The zlib data type \c uInt must be capable of holding this value.
 */
#  define CMPR_BUFSIZE  (size_t) 32768


#  if CFG_USE_ZLIB


/*! \brief Window size for DEFLATE algorithm
 *
 * Compression ratio vs. memory usage tradeoff (integer value from -8 to -15).
 * Negative base two logarithm of the window size (size of the history buffer)
 * for zlib. The negative sign selects raw data without container.
 * <br>
 *  -8: Worst compression and minimum memory usage (256 Byte)
 * <br>
 * -15: Best compression and maximum memory usage (32 KiByte)
 *
 * \attention
 * Don't change this value until you know what you are doing. It must match the
 * setup of the peer.
 */
#     define CMPR_ZLIB_WINDOW_BITS  -15

/*! \brief Memory usage for DEFLATE algorithm
 *
 * Speed vs. memory usage tradeoff for zlib (integer value from 1 to 9)
 * <br>
 * 1: Minimum memory usage for minimum speed
 * <br>
 * 9: Maximum memory usage for maximum speed
 */
#     define CMPR_ZLIB_MEM_LEVEL  9


#  endif  /* CFG_USE_ZLIB */


/* ========================================================================== */
/* Variables */

#  if CMPR_DEBUG
static size_t  tx_u = 0;
static size_t  tx_c = 0;
static size_t  rx_u = 0;
static size_t  rx_c = 0;
#  endif  /* CMPR_DEBUG */


#  if CFG_USE_ZLIB


/* ========================================================================== */
/* Memory manager for zlib (allocation)
 *
 * \param[in] opaque  Ignored
 * \param[in] items   Number of items
 * \param[in] size    Size of item
 *
 * This function allocates \e items * \e size bytes of memory.
 *
 * \return
 * - Pointer to allocated data block on success
 * - \c Z_NULL on error
 */

static voidpf  cmpr_zlib_alloc(voidpf opaque, uInt items, uInt size)
{
   voidpf  res = Z_NULL;
   void*  rv;
   size_t  len = (size_t) items * (size_t) size;

   rv = posix_malloc(len);
   if(NULL != rv)  { res = (voidpf) rv; }

   return(res);
}


/* ========================================================================== */
/* Memory manager for zlib (release)
 *
 * \param[in] opaque   Ignored
 * \param[in] address  Address of memory block to release
 */

static void  cmpr_zlib_free(voidpf  opaque, voidpf  address)
{
   posix_free((void*) address);

   return;
}


#  endif  /* CFG_USE_ZLIB */


/* ========================================================================== */
/*! \brief Send data
 *
 * \param[in] stream  Pointer to stream object pointer
 * \param[in] buf     Pointer to data buffer
 * \param[in] len     Number of octets to send
 * \param[in] flush   Flush TX direction (use \c COMPR_FLUSH_xxx constants)
 *
 * \return
 * - Number of bytes sent
 * - -1 on error
 */

static posix_ssize_t  cmpr_send_i(void*  stream, const void*  buf, size_t  len,
                                  int  flush)
{
   struct cmpr_stream*  s = (struct cmpr_stream*) stream;
   posix_ssize_t  res = -1;
#  if CFG_USE_ZLIB
   z_streamp  p;
   int  rv;
   posix_ssize_t  rv2;
   size_t  i;
   size_t  len2;
   int  zflush;
#  endif  /* CFG_USE_ZLIB */

   /* Check stream */
   if(NULL == stream)
   {
      print_error("CMPR: cmpr_send(): Invalid pointer to stream object");
   }
   /* Check size */
   else if(INT_MAX < len)
   {
      print_error("CMPR: cmpr_send(): Data length too large");
   }
   else if(!len && CMPR_FLUSH_NO == flush)
   {
      /* NOP and no error */
      res = 0;
   }
   else
   {
      res = 0;
      /* Clamp data length to buffer size */
      if(CMPR_BUFSIZE < len)  { len = CMPR_BUFSIZE; }
      /* Compress data */
      switch(s->alg)
      {
#  if CFG_USE_ZLIB
         case CMPR_ALG_DEFLATE:
         {
            p = (z_streamp) s->tx;
            while(!res)
            {
               /* Check whether TX input buffer is empty */
               if(!p->avail_in)
               {
                  /* Yes => Feed TX buffer with uncompressed data */
                  memcpy((void*) s->tx_buf_in, buf, len);
                  p->next_in = (Bytef*) s->tx_buf_in;
                  p->avail_in = (uInt) len;
                  res = (posix_ssize_t) len;
               }
               /* Check whether TX output buffer is full */
               if(p->avail_out)
               {
                  /* No => Drive compression engine */
                  zflush = Z_NO_FLUSH;
                  if(CMPR_FLUSH_NO != flush)
                  {
                     if(CMPR_FLUSH_END != flush)  { zflush = Z_PARTIAL_FLUSH; }
                     else  { zflush = Z_FINISH; }
                  }
                  rv = deflate(p, zflush);
                  switch(rv)
                  {
                     case Z_OK:
                     case Z_BUF_ERROR:
                     {
                        break;
                     }
                     case  Z_STREAM_END:
                     {
                        if(CMPR_FLUSH_END != flush)  { res = -1; }
                        break;
                     }
                     case Z_STREAM_ERROR:
                     {
                        print_error("CMPR: cmpr_send(): "
                                    "Stream object corrupted");
                        /* No break here is intended */
                     }
                     default:
                     {
                        /* Fatal error */
                        res = -1;
                        break;
                     }
                  }
               }
               /* Write TX output buffer to underlaying protocol */
               if(!res && (uInt) CMPR_BUFSIZE > p->avail_out)
               {
                  len2 = CMPR_BUFSIZE - (size_t) p->avail_out;
#     if CMPR_DEBUG
                  printf("CMPR: Send %u compressed bytes\n",
                         (unsigned int) len2);
                  tx_c += len2;
#     endif  /* CMPR_DEBUG */
                  i = 0;
                  while(!res && len2 > i)
                  {
                     do
                     {
                        /* Use function registered by stream constructor */
                        rv2 = (*s->tx_send)(s->sd,
                                            &s->tx_buf_out[i], len2 - i, 0);
                     }
                     while( (posix_ssize_t) -1 == rv2
                            && POSIX_EINTR == posix_errno );
                     if((posix_ssize_t) -1 == rv2)
                     {
                        print_error("CMPR: cmpr_send(): Writing data "
                                    "to underlaying protocol failed");
                        res = -1;
                        break;
                     }
                     i += (size_t) rv2;
                  }
                  p->next_out = (Bytef*) s->tx_buf_out;
                  p->avail_out = (uInt) CMPR_BUFSIZE;
               }
               /* Check whether flush is finished */
               if(CMPR_FLUSH_NO != flush)
               {
                  if(!p->avail_in && (uInt) CMPR_BUFSIZE == p->avail_out)
                  {
                     /* No more data to compress and all data flushed */
                     break;
                  }
               }
            }
            break;
         }
#  endif  /* CFG_USE_ZLIB */
         default:
         {
            /* Compression algorithm not supported */
            print_error("CMPR: cmpr_send(): "
                        "Compression algorithm not supported");
            break;
         }
      }
   }

   return(res);
}


/* ========================================================================== */
/*! \brief Send data
 *
 * \param[in] stream  Pointer to stream object pointer
 * \param[in] buf     Pointer to data buffer
 * \param[in] len     Number of octets to send
 *
 * \return
 * - Number of bytes sent
 * - -1 on error
 */

posix_ssize_t  cmpr_send(void*  stream, const void*  buf, size_t  len)
{
#  if CMPR_DEBUG
   printf("CMPR: --- TX ------------------------\n");
   printf("CMPR: Accepted %u uncompressed bytes\n", (unsigned int) len);
   tx_u += len;
#  endif  /* CMPR_DEBUG */
   return(cmpr_send_i(stream, buf, len, CMPR_FLUSH_NO));
}


/* ========================================================================== */
/*! \brief Flush TX direction
 *
 * \param[in] stream  Pointer to stream object pointer
 *
 * \return
 * - 0 on success
 * - -1 on error
 */

int  cmpr_flush(void*  stream)
{
   int  res = -1;
   char  buf = 0;
   posix_ssize_t  rv;

#  if CMPR_DEBUG
   printf("CMPR: Flush requested\n");
#  endif  /* CMPR_DEBUG */
   rv = cmpr_send_i(stream, (void*) &buf, (size_t) 0, CMPR_FLUSH_YES);
   if(0 <= rv)  { res = 0; }

   return(res);
}


/* ========================================================================== */
/*! \brief Flush TX direction and terminate data stream
 *
 * \param[in] stream  Pointer to stream object pointer
 *
 * \return
 * - 0 on success
 * - -1 on error
 */

int  cmpr_terminate(void*  stream)
{
   int  res = -1;
   char  buf = 0;
   posix_ssize_t  rv;

#  if CMPR_DEBUG
   printf("CMPR: Terminate requested\n");
#  endif  /* CMPR_DEBUG */
   rv = cmpr_send_i(stream, (void*) &buf, (size_t) 0, CMPR_FLUSH_END);
   if(0 <= rv)  { res = 0; }

   return(res);
}


/* ========================================================================== */
/*! \brief Receive data
 *
 * \param[in] stream  Pointer to stream object pointer
 * \param[out] buf    Pointer to data buffer
 * \param[in]  len    Number of octets to receive
 * \param[in]  peek   Data should stay available for reading if nonzero
 *
 * \attention
 * A nonzero value must be used for the \e len parameter!
 *
 * \return
 * - Number of bytes received
 * - 0 if underlaying protocol lost connection
 * - -1 on error
 */

posix_ssize_t  cmpr_recv(void*  stream, void*  buf, size_t  len, int  peek)
{
   struct cmpr_stream*  s = (struct cmpr_stream*) stream;
   posix_ssize_t  res = -1;
#  if CFG_USE_ZLIB
   z_streamp  p;
   int  rv;
   posix_ssize_t  rv2;
   size_t  len2;
   size_t  remain;
#  endif  /* CFG_USE_ZLIB */

   /* Check stream */
   if(NULL == stream)
   {
      print_error("CMPR: cmpr_recv(): Invalid pointer to stream object");
   }
   /* Check size */
   else if(INT_MAX < len)
   {
      print_error("CMPR: cmpr_recv(): Data length too large");
   }
   else if(!len)
   {
      print_error("CMPR: cmpr_recv(): Zero data length not supported");
   }
   else
   {
      res = 0;
      if(len)
      {
         /* Compress data */
         switch(s->alg)
         {
#  if CFG_USE_ZLIB
            case CMPR_ALG_DEFLATE:
            {
               p = (z_streamp) s->rx;
               while(!res)
               {
                  /* Check whether RX output buffer is full */
                  if(p->avail_out)
                  {
                     /* No => Drive decompression engine */
                     rv = inflate(p, Z_SYNC_FLUSH);
                     switch(rv)
                     {
                        case Z_OK:
                        case Z_BUF_ERROR:
                        {
                           break;
                        }
                        case Z_DATA_ERROR:
                        case Z_STREAM_ERROR:
                        {
                           print_error("CMPR: cmpr_recv(): Data or"
                                       "stream object corrupted");
                           /* No break here is intended */
                        }
                        case Z_NEED_DICT:
                        case Z_MEM_ERROR:
                        default:
                        {
                           /* Fatal error */
                           res = -1;
                           break;
                        }
                     }
                  }
                  /* Check whether RX output buffer contains data */
                  if(!res && (uInt) CMPR_BUFSIZE > p->avail_out)
                  {
                     /* Yes => Decompressed data in RX output buffer present */
                     len2 = CMPR_BUFSIZE - (size_t) p->avail_out;
                     if(len < len2)
                     {
                        remain = len2 - len;
                        len2 = len;
                     }
                     else  { remain = 0; }
                     memcpy(buf, (void*) s->rx_buf_out, len2);
                     if(!peek)
                     {
#     if CMPR_DEBUG
                        printf("CMPR: Providing %u uncompressed bytes\n",
                               (unsigned int) len);
                        rx_u += len2;
#     endif  /* CMPR_DEBUG */
                        /* Consume data (remove it from RX output buffer) */
                        if(remain)
                        {
                           memmove((void*) s->rx_buf_out,
                                   (void*) &s->rx_buf_out[len2], remain);
                           p->next_out = (Bytef*) &s->rx_buf_out[remain];
                           p->avail_out = (uInt) CMPR_BUFSIZE - (uInt) remain;
                        }
                        else
                        {
                           p->next_out = (Bytef*) s->rx_buf_out;
                           p->avail_out = (uInt) CMPR_BUFSIZE;
                        }
                     }
                     res = (posix_ssize_t) len2;
                  }
                  /* Check whether RX input buffer is empty */
                  if(!res && !p->avail_in)
                  {
                     /* Yes => Read from underlaying proto to RX input buffer */
                     /* Do this last to avoid blocking */
                     len2 = CMPR_BUFSIZE;
                     do
                     {
                        /* Use function registered by stream constructor */
                        rv2 = (*s->rx_recv)(s->sd, s->rx_buf_in, len2, 0);
                     }
                     while( (posix_ssize_t) -1 == rv2
                            && POSIX_EINTR == posix_errno );
                     if((posix_ssize_t) -1 == rv2)
                     {
                        print_error("CMPR: cmpr_recv(): Reading data "
                                    "from underlaying protocol failed");
                        res = -1;
                        break;
                     }
#     if CMPR_DEBUG
                     printf("CMPR: --- RX ------------------------\n");
                     printf("CMPR: Received %d compressed bytes\n", (int) rv2);
                     rx_c += (size_t) rv2;
#     endif  /* CMPR_DEBUG */
                     p->next_in = (Bytef*) s->rx_buf_in;
                     p->avail_in = (uInt) rv2;
                     /* Check for terminated connection */
                     if(!rv2)  { break; }
                  }
               }
               break;
            }
#  endif  /* CFG_USE_ZLIB */
            default:
            {
               /* Compression algorithm not supported */
               print_error("CMPR: cmpr_recv(): "
                           "Compression algorithm not supported");
               break;
            }
         }
      }
   }

   return(res);
}


/* ========================================================================== */
/*! \brief Compressed data stream object constructor
 *
 * \param[in] alg      Compression algorithm (use \c CMPR_ALG_xxx constants)
 * \param[in] sd       Socket descriptor or stream handle of undelaying protocol
 * \param[in] tx_send  Send function to underlaying protocol
 * \param[in] rx_recv  Receive function for underlaying protocol
 *
 * \attention
 * The functions to access the undelaying protocol \e tx_send() and \e rx_recv()
 * are expected to behave like \c send() and \c recv() defined by POSIX.1-2001
 * with compatibility to the 4.2BSD implementation regarding flags).
 *
 * \return
 * - Pointer to constructed object on success
 * - \c NULL on error
 */

void*  cmpr_stream_constructor( unsigned int  alg, int  sd,
                             ssize_t  (*tx_send)(int, const void*, size_t, int),
                             ssize_t  (*rx_recv)(int, void*, size_t, int) )
{
   struct cmpr_stream*  res = NULL;
   int  error = 0;
#  if CFG_USE_ZLIB
   int  rv;
   z_streamp  p;
#  endif  /* CFG_USE_ZLIB */

   /* Allocate and initialize data stream object */
   res = (struct cmpr_stream*) posix_malloc(sizeof(struct cmpr_stream));
   if(NULL == res)  { error = 1; }
   else
   {
      res->alg = alg;
      res->sd = sd;
      res->tx_send = tx_send;
      res->rx_recv = rx_recv;
      res->tx_buf_in = NULL;
      res->tx_buf_out = NULL;
      res->rx_buf_in = NULL;
      res->rx_buf_out = NULL;
      res->tx = NULL;
      res->rx = NULL;
   }

   /* Check parameters to access underlaying protocol */
   if(!error)
   {
      if(0 > sd || NULL == res->tx_send || NULL == res->rx_recv)
      {
         print_error("CMPR: Invalid parameters to access underlaying protocol");
         error = 1;
      }
   }

   /* Allocate data buffers */
   if(!error)
   {
      res->tx_buf_in = (char*) posix_malloc(CMPR_BUFSIZE);
      res->tx_buf_out = (char*) posix_malloc(CMPR_BUFSIZE);
      res->rx_buf_in = (char*) posix_malloc(CMPR_BUFSIZE);
      res->rx_buf_out = (char*) posix_malloc(CMPR_BUFSIZE);
      if(NULL == res->tx_buf_in || NULL == res->tx_buf_out
         || NULL == res->rx_buf_in || NULL == res->rx_buf_out)
      {
         print_error("CMPR: Memory allocation for data buffers failed");
         error = 1;
      }
   }

   /* Allocate and initialize algorithm specific objects */
   if(!error)
   {
      switch(alg)
      {
#  if CFG_USE_ZLIB
         case CMPR_ALG_DEFLATE:
         {
            res->tx = posix_malloc(sizeof(z_stream));
            res->rx = posix_malloc(sizeof(z_stream));
            if(NULL == res->tx || NULL == res->rx)
            {
               print_error("CMPR: Memory allocation for zlib streams failed");
               error = 1;
            }
            else
            {
               /* Init zlib stream for TX direction (compression) */
               p = (z_streamp) res->tx;
               p->zalloc = cmpr_zlib_alloc;
               p->zfree = cmpr_zlib_free;
               p->opaque = NULL;
               p->next_in = (Bytef*) res->tx_buf_in;
               p->avail_in = (uInt) 0;
               rv = deflateInit2(p, Z_BEST_COMPRESSION, Z_DEFLATED,
                                 CMPR_ZLIB_WINDOW_BITS, CMPR_ZLIB_MEM_LEVEL,
                                 Z_DEFAULT_STRATEGY);
               if(Z_OK != rv)  { error = 1; }
               else
               {
                  p->next_out = (Bytef*) res->tx_buf_out;
                  p->avail_out = (uInt) CMPR_BUFSIZE;

                  /* Init zlib stream for RX direction (decompression) */
                  p = (z_streamp) res->rx;
                  p->zalloc = cmpr_zlib_alloc;
                  p->zfree = cmpr_zlib_free;
                  p->opaque = NULL;
                  p->next_in = (Bytef*) res->rx_buf_in;
                  p->avail_in = (uInt) 0;
                  rv = inflateInit2(p, CMPR_ZLIB_WINDOW_BITS);
                  if(Z_OK != rv)
                  {
                     deflateEnd(p);  /* Destroy zlib stream for TX */
                     error = 1;
                  }
                  else
                  {
                     p->next_out = (Bytef*) res->rx_buf_out;
                     p->avail_out = (uInt) CMPR_BUFSIZE;
                  }
               }
            }
            if(error)
            {
               print_error("CMPR: Initialization of zlib streams failed");
            }
            break;
         }
#  endif  /* CFG_USE_ZLIB */
         default:
         {
            /* Compression algorithm not supported */
            print_error("CMPR: Compression algorithm not supported");
            error = 1;
            break;
         }
      }
   }

   /* Check for error */
   if(error)
   {
      if(NULL != res)
      {
         posix_free((void*) res->tx);
         posix_free((void*) res->rx);
         posix_free((void*) res->tx_buf_in);
         posix_free((void*) res->tx_buf_out);
         posix_free((void*) res->rx_buf_in);
         posix_free((void*) res->rx_buf_out);
         posix_free((void*) res);
         res = NULL;
      }
   }

   return((void*) res);
}


/* ========================================================================== */
/*! \brief Destroy compressed data stream object allocated by compression module
 *
 * \param[in] stream  Pointer to object
 *
 * \note
 * The pointer \e p is allowed to be \c NULL and no operation is performed in
 * this case.
 */

void  cmpr_stream_destructor(void*  stream)
{
   struct cmpr_stream*  s = (struct cmpr_stream*) stream;
#  if CFG_USE_ZLIB
   z_streamp  p;
   int  error = 0;
   int  rv;
#  endif  /* CFG_USE_ZLIB */

   if(NULL != s)
   {
      /* Destroy algorithm specific objects */
      switch(s->alg)
      {
#  if CFG_USE_ZLIB
         case CMPR_ALG_DEFLATE:
         {
            /* Destory zlib stream objects */
            p = (z_streamp) s->rx;
            rv = inflateEnd(p);
            if(Z_OK != rv)  { error = 1; }
            p = (z_streamp) s->tx;
            rv = deflateEnd(p);
            if(Z_OK != rv && Z_DATA_ERROR != rv)  { error = 1; }
            if(error)  { print_error("CMPR: Destroying zlib streams failed"); }
            break;
         }
#  endif  /* CFG_USE_ZLIB */
         default:
         {
            /* Compression algorithm not supported */
            print_error("CMPR: Compression algorithm not supported");
            break;
         }
      }
      posix_free((void*) s->rx);
      posix_free((void*) s->tx);

      /* Free data buffers */
      posix_free((void*) s->rx_buf_out);
      posix_free((void*) s->rx_buf_in);
      posix_free((void*) s->tx_buf_out);
      posix_free((void*) s->tx_buf_in);

      /* Free stream object */
      posix_free(stream);
   }
}


/* ========================================================================== */
/*! \brief Initialize compression module */

int  cmpr_init(void)
{
   int  res = 0;
#  if CFG_USE_ZLIB
   const char*  ver;
   int  minor_c = 0;
   int  minor_r = 0;
   int  rv1, rv2;

   /* Check version of zlib library */
   ver = zlibVersion();
   printf("%s: CMPR: zlib library version: %s\n", CFG_NAME, ver);
   if(ZLIB_VERSION[0] != ver[0])
   {
      print_error("CMPR: zlib library has incompatible ABI");
      res = -1;
   }
   else
   {
      rv1 = sscanf(ZLIB_VERSION, "%*d.%d", &minor_c);
      rv2 = sscanf(ver, "%*d.%d", &minor_r);
      if(1 != rv1 || 1 != rv2)
      {
         print_error("CMPR: Parsing zlib version string failed");
      }
      else if(minor_r < minor_c)
      {
         printf("%s: CMPR: Warning: Compiled for a newer zlib minor version "
                "(%s) \n", CFG_NAME, ZLIB_VERSION);
      }
   }

   /* Print RX window size */
   if(!res)
   {
      printf("%s: CMPR: DEFLATE: Using RX window size: %d bytes\n", CFG_NAME,
             1 << -CMPR_ZLIB_WINDOW_BITS);
   }
#  else  /* CFG_USE_ZLIB */
   print_error("CMPR: Compiled without zlib support");
#  endif  /* CFG_USE_ZLIB */

   return(res);
}


/* ========================================================================== */
/*! \brief Shutdown compress module */

void  cmpr_exit(void)
{
#  if CMPR_DEBUG
   unsigned long int  tx_ul = (unsigned long int) tx_u;
   unsigned long int  tx_cl = (unsigned long int) tx_c;
   unsigned long int  rx_cl = (unsigned long int) rx_c;
   unsigned long int  rx_ul = (unsigned long int) rx_u;
   double  tx_r = (double) tx_c / (double) tx_u;
   double  rx_r = (double) rx_c / (double) rx_u;

   printf("CMPR: --- Statistics ----------------\n");
   printf("CMPR: Uncompressed TX bytes accepted: %lu\n", tx_ul);
   printf("CMPR: Compressed TX bytes sent      : %lu\n", tx_cl);
   printf("CMPR: Compressed RX bytes received  : %lu\n", rx_cl);
   printf("CMPR: Uncompressed RX bytes provided: %lu\n", rx_ul);
   if(tx_u)  { printf("CMPR: TX compression ratio: %.2f\n", tx_r); }
   if(rx_u)  { printf("CMPR: RX compression ratio: %.2f\n", rx_r); }
#  endif  /* CMPR_DEBUG */

   return;
}


#endif  /* !CFG_CMPR_DISABLE */


/* @} */

/* EOF */
