//
//  File:   hzCodec.cpp
//
//  Legal Notice:   This file is part of the HadronZoo C++ Class Library. Copyright 2025 HadronZoo Project (http://www.hadronzoo.com)
//
//  The HadronZoo C++ Class Library is free software: You can redistribute it, and/or modify it under the terms of the GNU Lesser General Public License, as published by the Free
//  Software Foundation, either version 3 of the License, or any later version.
//
//  The HadronZoo C++ Class Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
//  A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public License along with the HadronZoo C++ Class Library. If not, see http://www.gnu.org/licenses.
//
#include <iostream>
#include <fstream>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <crypt.h>
#include <zlib.h>
#include <sys/stat.h>
#include <openssl/sha.h>
#include <openssl/hmac.h>
#include "hzBasedefs.h"
#include "hzChars.h"
#include "hzErrcode.h"
#include "hzDirectory.h"
#include "hzCodec.h"
#include "hzProcess.h"
using namespace std ;
/*
**  Variables
*/
global  const hzMD5 _hz_null_hzMD5 ;
/*
**  Endian Conversion Functions
*/
//  Import an internal uint16_t from char array of 2 bytes
void    _endian_import2 (uint16_t& v, const uchar* i)
{
    v=0;
    if (i)
        v=(i[0]<<8)+i[1];
}
//  Import an internal uint32_t from char array of 2 to 8 bytes
void    _endian_import2 (uint32_t& v, const uchar* i)
{
    v=0;
    if (i)
        v=(i[0]<<8)+i[1];
}
void    _endian_import3 (uint32_t& v, const uchar* i)
{
    v=0;
    if (i)
        v=(i[0]<<16)+(i[1]<<8)+i[2];
}
void    _endian_import4 (uint32_t& v, const uchar* i)
{
    v=0;
    if (i)
        v=(i[0]<<24)+(i[1]<<16)+(i[2]<<8)+i[3];
}
//  Import an internal uint64_t from char array of 2 to 8 bytes
void    _endian_import2 (uint64_t& v, const uchar* i)
{
    v = 0 ;
    if (i)
        v = (i[0]<<8) + i[1] ;
}
void    _endian_import3 (uint64_t& v, const uchar* i)
{
    v = 0 ;
    if (i)
        v = (i[0]<<16) + (i[1]<<8) + i[2] ;
}
void    _endian_import4 (uint64_t& v, const uchar* i)
{
    v = 0 ;
    if (i)
        v = (i[0]<<24) + (i[1]<<16) + (i[2]<<8) + i[3] ;
}
void    _endian_import5 (uint64_t& v, const uchar* i)
{
    v = 0 ;
    if (i)
    {
        v = i[0] ;
        v <<= 32 ;
        v |= ((i[1]<<24) + (i[2]<<16) + (i[3]<<8) + i[4]) ;
    }
}
void    _endian_import6 (uint64_t& v, const uchar* i)
{
    v = 0 ;
    if (i)
    {
        v = (i[0]<<8) + i[1] ;
        v <<= 32 ;
        v |= ((i[2]<<24) + (i[3]<<16) + (i[4]<<8) + i[5]) ;
    }
}
void    _endian_import7 (uint64_t& v, const uchar* i)
{
    v = 0 ;
    if (i)
    {
        v = (i[0]<<16) + (i[1]<<8) + i[2] ;
        v <<= 32 ;
        v |= ((i[3]<<24) + (i[4]<<16) + (i[5]<<8) + i[6]);
    }
}
void    _endian_import8 (uint64_t& v, const uchar* i)
{
    v = 0 ;
    if (i)
    {
        v = (i[0]<<24) + (i[1]<<16) + (i[2]<<8) + i[3];
        v <<= 32;
        v |= ((i[4]<<24) + (i[5]<<16) + (i[6]<<8) + i[7]);
    }
}
//  Export an internal uint16_t to char array of 2 bytes
void    _endian_export2 (uchar* s, uint16_t n)
{
    if (!s)
        return ;
    s[0]=(n&0xff00)>>8;
    s[1]=n&0xff;
}
//  Export an internal uint32_t to char array of 2 to 4 bytes
void    _endian_export2 (uchar* s, uint32_t n)
{
    if (!s)
        return ;
    s[0]=(n&0xff00)>>8;
    s[1]=n&0xff;
}
void    _endian_export3 (uchar* s, uint32_t n)
{
    if (!s)
        return ;
    s[0]=(n&0xff0000)>>16;
    s[1]=(n&0xff00)>>8;
    s[2]=n&0xff;
}
void    _endian_export4 (uchar* s, uint32_t n)
{
    if (!s)
        return ;
    s[0]=(n&0xff000000)>>24;
    s[1]=(n&0xff0000)>>16;
    s[2]=(n&0xff00)>>8;
    s[3]=n&0xff;
}
//  Export an internal uint64_t to char array of 2 to 8 bytes
void    _endian_export2 (uchar* s, uint64_t n)
{
    if (!s)
        return ;
    s[0]=(n&0xff00)>>8;
    s[1]=n&0xff;
}
void    _endian_export3 (uchar* s, uint64_t n)
{
    if (!s)
        return ;
    s[0]=(n&0xff0000)>>16;
    s[1]=(n&0xff00)>>8;
    s[2]=n&0xff;
}
void    _endian_export4 (uchar* s, uint64_t n)
{
    if (!s)
        return ;
    s[0]=(n&0xff000000)>>24;
    s[1]=(n&0xff0000)>>16;
    s[2]=(n&0xff00)>>8;
    s[3]=n&0xff;
}
void    _endian_export5 (uchar* s, uint64_t n)
{
    if (!s)
        return ;
    s[0]=(n&0xff00000000)>>32;
    s[1]=(n&0xff000000)>>24;
    s[2]=(n&0xff0000)>>16;
    s[3]=(n&0xff00)>>8;
    s[4]=n&0xff;
}
void    _endian_export6 (uchar* s, uint64_t n)
{
    if (!s)
        return ;
    s[0]=(n&0xff0000000000)>>40;
    s[1]=(n&0xff00000000)>>32;
    s[2]=(n&0xff000000)>>24;
    s[3]=(n&0xff0000)>>16;
    s[4]=(n&0xff00)>>8;
    s[5]=n&0xff;
}
void    _endian_export7 (uchar* s, uint64_t n)
{
    if (!s)
        return ;
    s[0]=(n&0xff000000000000)>>48;
    s[1]=(n&0xff0000000000)>>40;
    s[2]=(n&0xff00000000)>>32;
    s[3]=(n&0xff000000)>>24;
    s[4]=(n&0xff0000)>>16;
    s[5]=(n&0xff00)>>8;
    s[6]=n&0xff;
}
void    _endian_export8 (uchar* s, uint64_t n)
{
    if (!s)
        return ;
    s[0]=(n&0xff00000000000000)>>56;
    s[1]=(n&0xff000000000000)>>48;
    s[2]=(n&0xff0000000000)>>40;
    s[3]=(n&0xff00000000)>>32;
    s[4]=(n&0xff000000)>>24;
    s[5]=(n&0xff0000)>>16;
    s[6]=(n&0xff00)>>8;
    s[7]=n&0xff;
}
/*
**  Serialized Integer Read Functions: Restores to uint64, int64, uint32 and int32 from char* input
*/
hzEcode ReadSerialUINT64    (uint64_t& nValue, uint32_t& nLen, const uchar* ptr)
{
    //  Convert a serialized integer into a 64-bit unsigned integer.
    //
    //  Arguments:  1)  nValue  Indicated length or value
    //              2)  nLen    The size of the indicator itself
    //              3)  ptr     Pointer into a uchar buffer (start of indicator)
    //
    //  Returns:    E_ARGUMENT  If no ptr is supplied
    //              E_OK        Operation successful
    uint64_t    inc ;       //  Increament value
    if (!ptr)
        return hzerr(E_ARGUMENT, "No length indicator supplied") ;
    if (*ptr & 0x80)
    {
        nLen = (*ptr & 0x78) >> 3 ;
        nValue = *ptr & 0x07 ;
        switch (nLen)
        {
        case 0:     nValue <<= 8 ;  nValue += ptr[1] ;                          break ;
        case 1:     nValue <<= 16 ; _endian_import2(inc, ptr+1) ; nValue += inc ;   break ;
        case 2:     nValue <<= 24 ; _endian_import3(inc, ptr+1) ; nValue += inc ;   break ;
        case 3:     nValue <<= 32 ; _endian_import4(inc, ptr+1) ; nValue += inc ;   break ;
        case 4:     nValue <<= 40 ; _endian_import5(inc, ptr+1) ; nValue += inc ;   break ;
        case 5:     nValue <<= 48 ; _endian_import6(inc, ptr+1) ; nValue += inc ;   break ;
        case 6:     nValue <<= 56 ; _endian_import7(inc, ptr+1) ; nValue += inc ;   break ;
        default:
            _endian_import8(nValue, ptr+1) ;
            break ;
        }
    }
    else
    {
        nLen = 1 ;
        nValue = *ptr & 0x7f ;
    }
    return E_OK ;
}
hzEcode ReadSerialSINT64    (int64_t& nValue, uint32_t& nLen, const uchar* ptr)
{
    //  Convert a serialized integer into a 64-bit unsigned integer.
    //
    //  Arguments:  1)  nValue  Indicated length or value
    //              2)  nLen    The size of the indicator itself
    //              3)  ptr     Pointer into a uchar buffer (start of indicator)
    //
    //  Returns:    E_ARGUMENT  If no ptr is supplied
    //              E_OK        Operation successful
    uint64_t    inc ;       //  Increament value
    bool        bNeg ;      //  Set if negative
    if (!ptr)
        return hzerr(E_ARGUMENT, "No length indicator supplied") ;
    if (*ptr & 0x80)
    {
        nLen = (*ptr & 0x70) >> 4 ;
        bNeg = *ptr & 0x0c ;
        nValue = *ptr & 0x03 ;
        switch (nLen)
        {
        case 0:     nValue <<= 8 ;  nValue += ptr[1] ;                          break ;
        case 1:     nValue <<= 16 ; _endian_import2(inc, ptr+1) ; nValue += inc ;   break ;
        case 2:     nValue <<= 24 ; _endian_import3(inc, ptr+1) ; nValue += inc ;   break ;
        case 3:     nValue <<= 32 ; _endian_import4(inc, ptr+1) ; nValue += inc ;   break ;
        case 4:     nValue <<= 40 ; _endian_import5(inc, ptr+1) ; nValue += inc ;   break ;
        case 5:     nValue <<= 48 ; _endian_import6(inc, ptr+1) ; nValue += inc ;   break ;
        case 6:     nValue <<= 56 ; _endian_import7(inc, ptr+1) ; nValue += inc ;   break ;
        default:
            _endian_import8(inc, ptr+1) ;
            nValue = (int64_t) inc ;
            break ;
        }
        if (bNeg && nLen < 7)
            nValue *= -1 ;
    }
    else
    {
        nLen = 1 ;
        nValue = *ptr & 0x7f ;
    }
    return E_OK ;
}
hzEcode ReadSerialUINT32    (uint32_t& nValue, uint32_t& nLen, const uchar* ptr)
{
    //  Convert a serialized integer into a 32-bit unsigned integer.
    //
    //  Arguments:  1)  nValue  Indicated length or value
    //              2)  nLen    The size of the indicator itself
    //              3)  ptr     Pointer into a uchar buffer (start of indicator)
    //
    //  Returns:    E_ARGUMENT  If no ptr is supplied
    //              E_OK        Operation successful
    if (!ptr)
        return hzerr(E_ARGUMENT, "No length indicator supplied") ;
    if (*ptr & 0x80)
    {
        //  Top bit set so indicator length will be ...
        switch  (*ptr & 0xE0)
        {
        case 0x80:  //  100xxxxx:   2 byte serial
                    nValue = ((ptr[0] & 0x1f) << 8) ;
                    nValue += ptr[1] ;
                    nLen = 2 ;
                    break ;
        case 0xA0:  //  101xxxxx:   3 byte serial
                    nValue += ((ptr[1] << 8) + ptr[2]) ;
                    nLen = 3 ;
                    break ;
        case 0xC0:  //  110xxxxx:   4 byte serial
                    nValue += ((ptr[1] << 16) + (ptr[2] << 8) + ptr[3]) ;
                    nLen = 4 ;
                    break ;
        case 0xE0:  //  11100000:   Next 4 bytes
                    _endian_import4(nValue, ptr + 1) ;
                    nLen = 5 ;
                    break ;
        }
    }
    else
    {
        nLen = 1 ;
        nValue = *ptr & 0x7f ;
    }
    return E_OK ;
}
hzEcode ReadSerialSINT32    (int32_t& nValue, uint32_t& nLen, const uchar* ptr)
{
    //  Convert a serialized integer into a 32-bit signed integer.
    //
    //  Arguments:  1)  nValue  Indicated length or value
    //              2)  nLen    The size of the indicator itself
    //              3)  ptr     Pointer into a uchar buffer (start of indicator)
    //
    //  Returns:    E_ARGUMENT  If no ptr is supplied
    //              E_OK        Operation successful
    uint32_t    val ;   //  Value
    bool        bNeg ;  //  Negative
    if (!ptr)
        { nValue = 0 ; nLen = 0 ; return E_ARGUMENT ; }
    if (!(*ptr & 0x80))
    {
        bNeg = *ptr & 0x40 ;
        val = *ptr & 0x3f ;
    }
    else
    {
        //  Top bit set so indicator length will be ...
        bNeg = *ptr & 0x10 ;
        nLen = (*ptr & 0x60) >> 5 ;
        nLen += 2 ;
        switch  (nLen)
        {
        case 2:     val = ((ptr[0] & 0x0f) << 8) ;
                    val += ptr[1] ;
                    break ;
        case 3:     val = ((ptr[0] & 0x0f) << 16) ;
                    val += ((ptr[1] << 8) + ptr[2]) ;
                    break ;
        case 4:     val = ((ptr[0] & 0x0f) << 24) ;
                    val += ((ptr[1] << 16) + (ptr[2] << 8) + ptr[3]) ;
                    break ;
        case 5:     _endian_import4(val, ptr) ;
                    break ;
        }
    }
    nValue = bNeg ? -val : val ;
    return E_OK ;
}
/*
**  Serialized Integer Read Functions: Restores to uint64, int64, uint32 and int32 from hzChain input
*/
hzEcode ReadSerialUINT64    (uint64_t& nValue, hzChain::Iter& zi)
{
    //  Convert a serialized integer into a 64-bit unsigned integer.
    //
    //  Arguments:  1)  nValue  Indicated length or value
    //              2)  zi      Chain iterator (start of indicator)
    //
    //  Returns:    E_ARGUMENT  If no ptr is supplied
    //              E_OK        Operation successful
    uint64_t    val ;   //  Value
    uint32_t    nLen ;  //  Length of serial integer
    uint32_t    n ;     //  Counter
    if (!(*zi & 0x80))
    {
        val = *zi & 0x7f ;
        zi++ ;
    }
    else
    {
        //  Top bit set so indicator length will be ...
        nLen = (*zi & 0x70) >> 4 ;
        nLen += 2 ;
        val = *zi & 0x0f ;
        if (nLen == 9)
        {
            //  Max value, next 8 bytes are the number
            val = 0 ;
        }
        for (n = nLen == 9 ? 1 : 2 ; n < nLen ; n++)
        {
            zi++ ;
            val <<= 8 ;
            val += *zi ;
        }
    }
    nValue = val ;
    return E_OK ;
}
hzEcode ReadSerialSINT64    (int64_t& nValue, hzChain::Iter& zi)
{
    //  Convert a serialized integer into a 64-bit signed integer.
    //
    //  Arguments:  1)  nValue  Indicated length or value
    //              2)  zi      Chain iterator (start of indicator)
    //
    //  Returns:    E_ARGUMENT  If no ptr is supplied
    //              E_OK        Operation successful
    uint64_t    val ;   //  Value
    uint32_t    n ;     //  Counter
    uint32_t    nLen ;  //  Length of serial integer
    bool        bNeg ;  //  Negative
    bNeg = *zi & 0x40 ;
    if (!(*zi & 0x80))
    {
        val = *zi & 0x3f ;
        zi++ ;
    }
    else
    {
        //  Top bit set so indicator length will be ...
        nLen = (*zi & 0x70) >> 4 ;
        nLen += 2 ;
        val = *zi & 0x0f ;
        if (nLen == 9)
        {
            //  Max value, next 8 bytes are the number
            val = 0 ;
        }
        for (n = nLen == 9 ? 1 : 2 ; n < nLen ; n++)
        {
            zi++ ;
            val <<= 8 ;
            val += *zi ;
        }
    }
    nValue = bNeg ? -val : val ;
    return E_OK ;
}
hzEcode ReadSerialUINT32    (uint32_t& nValue, hzChain::Iter& zi)
{
    //  Convert a serialized integer into a 32-bit unsigned integer.
    //
    //  Arguments:  1)  nValue  Indicated length or value
    //              2)  zi      Chain iterator (start of indicator)
    //
    //  Returns:    E_ARGUMENT  If no ptr is supplied
    //              E_OK        Operation successful
    uint32_t    val ;   //  Value
    if (!(*zi & 0x80))
    {
        nValue = *zi & 0x7f ;
        zi++ ;
        return E_OK ;
    }
    switch  (*zi & 0xE0)
    {
    case 0x80:  //  100xxxxx:   2 byte value
                val = *zi & 0x1f ;
                zi++ ;  val <<= 8 ; val += (uchar) *zi ;
                break ;
    case 0xA0:  //  101xxxxx:   3 byte value
                val = *zi & 0x1f ;
                zi++ ;  val <<= 8 ; val += (uchar) *zi ;
                zi++ ;  val <<= 8 ; val += (uchar) *zi ;
                break ;
    case 0xC0:  //  110xxxxx:   4 byte value
                val = *zi & 0x1f ;
                zi++ ;  val <<= 8 ; val += (uchar) *zi ;
                zi++ ;  val <<= 8 ; val += (uchar) *zi ;
                zi++ ;  val <<= 8 ; val += (uchar) *zi ;
                break ;
    case 0xE0:  //  11100000:   Next 4 bytes are the value
                zi++ ;  val = (uchar) *zi ;
                zi++ ;  val <<= 8 ; val += (uchar) *zi ;
                zi++ ;  val <<= 8 ; val += (uchar) *zi ;
                zi++ ;  val <<= 8 ; val += (uchar) *zi ;
                break ;
    }
    zi++ ;
    nValue = val ;
    return E_OK ;
}
hzEcode ReadSerialSINT32    (int32_t& nValue, hzChain::Iter& zi)
{
    //  Convert a serialized integer into a 32-bit signed integer.
    //
    //  Arguments:  1)  nValue  Indicated length or value
    //              2)  zi      Chain iterator (start of indicator)
    //
    //  Returns:    E_ARGUMENT  If no ptr is supplied
    //              E_OK        Operation successful
    uint32_t    val ;   //  Value
    bool        bNeg ;  //  Negative
    bNeg = *zi & 0x40 ;
    if (!(*zi & 0x80))
    {
        val = *zi & 0x3f ;
        zi++ ;
    }
    else
    {
        switch  (*zi & 0x30)
        {
        case 0x00:  //  1n00xxxx:   2 byte value
                    val = *zi & 0x0f ;
                    zi++ ;  val <<= 8 ; val+= (uchar) *zi ;
                    break ;
        case 0x10:  //  1n01xxxx:   3 byte value
                    val = *zi & 0x0f ;
                    zi++ ;  val <<= 8 ; val+= (uchar) *zi ;
                    zi++ ;  val <<= 8 ; val+= (uchar) *zi ;
                    break ;
        case 0x20:  //  1n10xxxx:   4 byte value
                    val = *zi & 0x0f ;
                    zi++ ;  val <<= 8 ; val+= (uchar) *zi ;
                    zi++ ;  val <<= 8 ; val+= (uchar) *zi ;
                    zi++ ;  val <<= 8 ; val+= (uchar) *zi ;
                    break ;
        case 0x30:  //  1n11xxxx:   Next 4 bytes are the value
                    zi++ ;  val = (uchar) *zi ;
                    zi++ ;  val <<= 8 ; val+= (uchar) *zi ;
                    zi++ ;  val <<= 8 ; val+= (uchar) *zi ;
                    zi++ ;  val <<= 8 ; val+= (uchar) *zi ;
                    break ;
        }
    }
    nValue = bNeg ? -val : val ;
    return E_OK ;
}
/*
**  Serialized Integer Read Functions: Restores to uint64 and uint32 from hzXbuf input. Note that there are currently no signed variants and no Write functions to hzXbuf.
*/
hzEcode ReadSerialUINT64    (uint64_t& nValue, xbufIter& zi)
{
    //  Convert a serialized integer into a 64-bit unsigned integer.
    //
    //  Arguments:  1)  nValue  Restored integer
    //              2)  zi      X-buffer iterator
    //
    //  Returns:    E_ARGUMENT  If no ptr is supplied
    //              E_OK        Operation successful
    uint64_t    val ;   //  Value
    uint32_t    n ;     //  Counter
    val = nValue = 0 ;
    if (zi.eof())
        return E_OK ;
    if (!(*zi & 0x80))
    {
        nValue = *zi & 0x7f ;
        zi++ ;
        return E_OK ;
    }
    switch  (*zi & 0xF0)
    {
    case 0x80:  //  1000 xxxx: 2 byte value
                val = *zi & 0x0f ;
                val <<= 8 ; zi++ ;  val += *zi ;
                break ;
    case 0x90:  //  1001 xxxx: 3 byte value
                val = *zi & 0x0f ;  for (n = 2 ; n ; n--)   { val <<= 8 ; zi++ ; val += *zi ; }
                break ;
    case 0xA0:  //  1010 xxxx: 4 byte value
                val = *zi & 0x0f ;  for (n = 3 ; n ; n--)   { val <<= 8 ; zi++ ; val += *zi ; }
                break ;
    case 0xB0:  //  1011 xxxx: 5 byte value
                val = *zi & 0x0f ;  for (n = 4 ; n ; n--)   { val <<= 8 ; zi++ ; val += *zi ; }
                break ;
    case 0xC0:  //  1100 xxxx: 6 byte value
                val = *zi & 0x0f ;  for (n = 5 ; n ; n--)   { val <<= 8 ; zi++ ; val += *zi ; }
                break ;
    case 0xD0:  //  1101 xxxx: 7 byte value
                val = *zi & 0x0f ;  for (n = 6 ; n ; n--)   { val <<= 8 ; zi++ ; val += *zi ; }
                break ;
    case 0xE0:  //  1110 xxxx: 8 byte value
                val = *zi & 0x0f ;  for (n = 7 ; n ; n--)   { val <<= 8 ; zi++ ; val += *zi ; }
                break ;
    case 0xF0:  //  1111 xxxx: Value in next 8 bytes
                zi++ ;  val = *zi ; for (n = 7 ; n ; n--)   { val <<= 8 ; zi++ ; val += *zi ; }
                break ;
    }
    nValue = val ;
    return E_OK ;
}
hzEcode ReadSerialUINT32    (uint32_t& nValue, xbufIter& zi)
{
    //  Convert a serialized integer into a 32-bit unsigned integer.
    //
    //  Arguments:  1)  nValue  Restored integer
    //              2)  zi      X-buffer iterator
    //
    //  Returns:    E_ARGUMENT  If no ptr is supplied
    //              E_OK        Operation successful
    uint32_t    val = 0 ;   //  Value
    if (zi.eof())
    {
        nValue = 0 ;
        return E_OK ;
    }
    if (!(*zi & 0x80))
    {
        val = *zi & 0x7f ;
        zi++ ;
        nValue = val ;
        return E_OK ;
    }
    switch  (*zi & 0xE0)
    {
    case 0x80:  //  100x xxxx: 2 byte value
                val = ((*zi & 0x1f) << 8) ;
                zi++ ;  val += *zi ;
                break ;
    case 0xA0:  //  101x xxxx: 3 byte value
                val = ((*zi & 0x1f) << 16) ;
                zi++ ;  val += (*zi << 8) ;
                zi++ ;  val += *zi ;
                break ;
    case 0xC0:  //  110x xxxx: 4 byte value
                val = ((*zi & 0x1f) << 24) ;
                zi++ ;  val += (*zi << 16) ;
                zi++ ;  val += (*zi << 8) ;
                zi++ ;  val += *zi ;
                break ;
    case 0xE0:  //  111x xxxx: Value is in the next 4 bytes
                zi++ ;  val = (*zi << 24) ;
                zi++ ;  val += (*zi << 16) ;
                zi++ ;  val += (*zi << 8) ;
                zi++ ;  val += *zi ;
                break ;
    }
    zi++ ;
    nValue = val ;
    return E_OK ;
}
/*
**  Serialized Integer Write Functions: Write to hzChain, serialized integer of uint64, int64, uint32 and int32
*/
void    WriteSerialUINT64   (hzChain& Z, uint32_t& nLen, uint64_t nValue)
{
    //  Serialize unsigned 64-bit integer to a hzChain.
    //
    //  Arguments:  1)  Z       Chain appended by the length indicator
    //              2)  nLen    The size of the indicator itself (set by this function)
    //              3)  nValue  Indicated length or value
    //
    //  Returns:    None
    if (nValue < 0x80)
    {
        //  Bit 0 is 0, bits 1-7 are the value
        nLen = 1 ;
        Z.AddByte(nValue & 0x7f) ;
    }
    else if (nValue < 0x1000)
    {
        //  2 byte serial integer. Control byte is 1000 xxxx. Total range 12 bits.
        nLen = 2 ;
        Z.AddByte(0x80 + ((nValue & 0x0f00) >> 8)) ;
        Z.AddByte(nValue & 0xff) ;
    }
    else if (nValue < 0x100000)
    {
        //  3 byte serial integer. Control byte is 1001 xxxx. Total range 20 bits.
        nLen = 3 ;
        Z.AddByte(0x90 + ((nValue & 0x0f0000) >> 16)) ;
        Z.AddByte((nValue & 0xff00) >> 8) ;
        Z.AddByte(nValue & 0xff) ;
    }
    else if (nValue < 0x10000000)
    {
        //  4 byte serial integer. Control byte is 1010 xxxx. Total range 28 bits.
        nLen = 4 ;
        Z.AddByte(0xA0 + ((nValue & 0x0f000000) >> 24)) ;
        Z.AddByte((nValue & 0xff0000) >> 16) ;
        Z.AddByte((nValue & 0xff00) >> 8) ;
        Z.AddByte(nValue & 0xff) ;
    }
    else if (nValue < 0x1000000000)
    {
        //  5 byte serial integer. Control byte is 1011 xxxx. Total range 36 bits.
        nLen = 5 ;
        Z.AddByte(0xB0 + ((nValue & 0x0f00000000) >> 32)) ;
        Z.AddByte((nValue & 0xff000000) >> 24) ;
        Z.AddByte((nValue & 0xff0000) >> 16) ;
        Z.AddByte((nValue & 0xff00) >> 8) ;
        Z.AddByte(nValue & 0xff) ;
    }
    else if (nValue < 0x100000000000)
    {
        //  6 byte serial integer. Control byte is 1100 xxxx. Total range 44 bits.
        nLen = 6 ;
        Z.AddByte(0xC0 + ((nValue & 0x0f0000000000) >> 40)) ;
        Z.AddByte((nValue & 0xff00000000) >> 32) ;
        Z.AddByte((nValue & 0xff000000) >> 24) ;
        Z.AddByte((nValue & 0xff0000) >> 16) ;
        Z.AddByte((nValue & 0xff00) >> 8) ;
        Z.AddByte(nValue & 0xff) ;
    }
    else if (nValue < 0x10000000000000)
    {
        //  7 byte serial integer. Control byte is 1101 xxxx. Total range 52 bits.
        nLen = 7 ;
        Z.AddByte(0xD0 + ((nValue & 0x0f000000000000) >> 48)) ;
        Z.AddByte((nValue & 0xff0000000000) >> 40) ;
        Z.AddByte((nValue & 0xff00000000) >> 32) ;
        Z.AddByte((nValue & 0xff000000) >> 24) ;
        Z.AddByte((nValue & 0xff0000) >> 16) ;
        Z.AddByte((nValue & 0xff00) >> 8) ;
        Z.AddByte(nValue & 0xff) ;
    }
    else if (nValue < 0x1000000000000000)
    {
        //  8 byte serial integer. Control byte is 1110 xxxx. Total range 60 bits.
        nLen = 8 ;
        Z.AddByte(0xE0 + ((nValue & 0x0f00000000000000) >> 56)) ;
        Z.AddByte((nValue & 0xff000000000000) >> 48) ;
        Z.AddByte((nValue & 0xff0000000000) >> 40) ;
        Z.AddByte((nValue & 0xff00000000) >> 32) ;
        Z.AddByte((nValue & 0xff000000) >> 24) ;
        Z.AddByte((nValue & 0xff0000) >> 16) ;
        Z.AddByte((nValue & 0xff00) >> 8) ;
        Z.AddByte(nValue & 0xff) ;
    }
    else
    {
        //  9 byte serial integer (8 byte absolute value). Control byte is 1111 0000. Total range 64 bits.
        nLen = 5 ;
        Z.AddByte(0xf0) ;
        Z.AddByte((nValue & 0xff00000000000000) >> 56) ;
        Z.AddByte((nValue & 0xff000000000000) >> 48) ;
        Z.AddByte((nValue & 0xff0000000000) >> 40) ;
        Z.AddByte((nValue & 0xff00000000) >> 32) ;
        Z.AddByte((nValue & 0xff000000) >> 24) ;
        Z.AddByte((nValue & 0xff0000) >> 16) ;
        Z.AddByte((nValue & 0xff00) >> 8) ;
        Z.AddByte(nValue & 0xff) ;
    }
}
void    WriteSerialSINT64   (hzChain& Z, uint32_t& nLen, int64_t nValue)
{
    //  Serialize signed 64-bit integer to a hzChain.
    //
    //  Arguments:  1)  Z       Chain appended by the length indicator
    //              2)  nLen    The size of the indicator itself (set by this function)
    //              3)  nValue  Indicated length or value
    //
    //  Returns:    None
    bool    bNeg ;      //  Set if negative
    char    byte ;      //  Byte to write
    if (nValue < 0)
        { bNeg = true ; nValue *= -1 ; }
    if (nValue < 0x40)
    {
        //  Bit 0 is 0, bit 1 is negator, bits 2 thru 7 are the value
        nLen = 1 ;
        byte = bNeg ? 0x40 : 0 ;
        byte |= nValue ;
        Z.AddByte(byte) ;
    }
    else if (nValue < 0x0800)
    {
        //  Bit 0 is 1, bits 1, 2 and 3 are controls (000), bit 4 is the negator, bits 5-7 are top 3 bits of value, 1 more byte to go
        nLen = 2 ;
        byte = bNeg ? 0x88 : 0x80 ;
        byte |= ((nValue & 0x0700) >> 8) ;
        Z.AddByte(byte) ;
        Z.AddByte(nValue & 0xff) ;
    }
    else if (nValue < 0x080000)
    {
        //  Bit 0 is 1, bits 1, 2 and 3 are controls (001), bit 4 is the negator, bits 5-7 are top 3 bits of value, 2 more bytes to go
        nLen = 3 ;
        byte = bNeg ? 0x98 : 0x90 ;
        byte |= ((nValue & 0x070000) >> 16) ;
        Z.AddByte(byte) ;
        Z.AddByte((nValue & 0xff00) >> 8) ;
        Z.AddByte(nValue & 0xff) ;
    }
    else if (nValue < 0x08000000)
    {
        //  Bit 0 is 1, bits 1 and 2 are controls (010), bit 4 is the negator, bits 5-7 are top 3 bits of value, 3 more bytes to go
        nLen = 4 ;
        byte = bNeg ? 0xA8 : 0xA0 ;
        byte |= ((nValue & 0x07000000) >> 24) ;
        Z.AddByte(byte) ;
        Z.AddByte((nValue & 0xff0000) >> 16) ;
        Z.AddByte((nValue & 0xff00) >> 8) ;
        Z.AddByte(nValue & 0xff) ;
    }
    else if (nValue < 0x0800000000)
    {
        //  Bit 0 is 1, bits 1 and 2 are controls (011), bit 4 is the negator, bits 5-7 are top 3 bits of value, 4 more bytes to go
        nLen = 4 ;
        byte = bNeg ? 0xB8 : 0xB0 ;
        byte |= ((nValue & 0x0700000000) >> 32) ;
        Z.AddByte(byte) ;
        Z.AddByte((nValue & 0xff000000) >> 24) ;
        Z.AddByte((nValue & 0xff0000) >> 16) ;
        Z.AddByte((nValue & 0xff00) >> 8) ;
        Z.AddByte(nValue & 0xff) ;
    }
    else if (nValue < 0x080000000000)
    {
        //  Bit 0 is 1, bits 1 and 2 are controls (100), bit 4 is the negator, bits 5-7 are top 3 bits of value, 5 more bytes to go
        nLen = 4 ;
        byte = bNeg ? 0xC8 : 0xC0 ;
        byte |= ((nValue & 0x0700000000) >> 40) ;
        Z.AddByte(byte) ;
        Z.AddByte((nValue & 0xff00000000) >> 32) ;
        Z.AddByte((nValue & 0xff000000) >> 24) ;
        Z.AddByte((nValue & 0xff0000) >> 16) ;
        Z.AddByte((nValue & 0xff00) >> 8) ;
        Z.AddByte(nValue & 0xff) ;
    }
    else if (nValue < 0x08000000000000)
    {
        //  Bit 0 is 1, bits 1 and 2 are controls (101), bit 4 is the negator, bits 5-7 are top 3 bits of value, 6 more bytes to go
        nLen = 4 ;
        byte = bNeg ? 0xD8 : 0xD0 ;
        byte |= ((nValue & 0x070000000000) >> 48) ;
        Z.AddByte(byte) ;
        Z.AddByte((nValue & 0xff0000000000) >> 40) ;
        Z.AddByte((nValue & 0xff00000000) >> 32) ;
        Z.AddByte((nValue & 0xff000000) >> 24) ;
        Z.AddByte((nValue & 0xff0000) >> 16) ;
        Z.AddByte((nValue & 0xff00) >> 8) ;
        Z.AddByte(nValue & 0xff) ;
    }
    else if (nValue < 0x08000000000000)
    {
        //  Bit 0 is 1, bits 1 and 2 are controls (110), bit 4 is the negator, bits 5-7 are top 3 bits of value, 7 more bytes to go
        nLen = 4 ;
        byte = bNeg ? 0xD8 : 0xD0 ;
        byte |= ((nValue & 0x07000000000000) >> 56) ;
        Z.AddByte(byte) ;
        Z.AddByte((nValue & 0xff000000000000) >> 48) ;
        Z.AddByte((nValue & 0xff0000000000) >> 40) ;
        Z.AddByte((nValue & 0xff00000000) >> 32) ;
        Z.AddByte((nValue & 0xff000000) >> 24) ;
        Z.AddByte((nValue & 0xff0000) >> 16) ;
        Z.AddByte((nValue & 0xff00) >> 8) ;
        Z.AddByte(nValue & 0xff) ;
    }
    else
    {
        //  Bit 0 is 1, bits 1 and 2 are controls (111), bit 4 is the negator, 8 more bytes to go
        nLen = 5 ;
        byte = bNeg ? 0xF8 : 0xF0 ;
        byte |= ((nValue & 0x0f000000) >> 24) ;
        Z.AddByte(0xC0) ;
        Z.AddByte((nValue & 0xff00000000000000) >> 56) ;
        Z.AddByte((nValue & 0xff000000000000) >> 48) ;
        Z.AddByte((nValue & 0xff0000000000) >> 40) ;
        Z.AddByte((nValue & 0xff00000000) >> 32) ;
        Z.AddByte((nValue & 0xff000000) >> 24) ;
        Z.AddByte((nValue & 0xff0000) >> 16) ;
        Z.AddByte((nValue & 0xff00) >> 8) ;
        Z.AddByte(nValue & 0xff) ;
    }
}
void    WriteSerialUINT32   (hzChain& Z, uint32_t& nLen, uint32_t nValue)
{
    //  Serialize unsigned 32-bit integer to a hzChain.
    //
    //  Arguments:  1)  Z       Chain appended by the length indicator
    //              2)  nLen    The size of the indicator itself (set by this function)
    //              3)  nValue  Indicated length or value
    //
    //  Returns:    None
    if (nValue < 0x80)
    {
        //  Bit 0 is 0, bits 1-7 are the value
        nLen = 1 ;
        Z.AddByte(nValue & 0x7f) ;
    }
    else if (nValue < 0x2000)
    {
        //  100xxxxx: 2 byte value
        nLen = 2 ;
        Z.AddByte(0x80 + ((nValue & 0x1f00) >> 8)) ;
        Z.AddByte(nValue & 0xff) ;
    }
    else if (nValue < 0x200000)
    {
        //  101xxxxx: 3 byte value
        nLen = 3 ;
        Z.AddByte(0xA0 + ((nValue & 0x1f0000) >> 16)) ;
        Z.AddByte((nValue & 0xff00) >> 8) ;
        Z.AddByte(nValue & 0xff) ;
    }
    else if (nValue < 0x20000000)
    {
        //  110xxxxx: 4 byte value
        nLen = 4 ;
        Z.AddByte(0xC0 + ((nValue & 0x1f000000) >> 24)) ;
        Z.AddByte((nValue & 0xff0000) >> 16) ;
        Z.AddByte((nValue & 0xff00) >> 8) ;
        Z.AddByte(nValue & 0xff) ;
    }
    else
    {
        //  11100000: Value in the next 4 bytes
        nLen = 5 ;
        Z.AddByte(0xE0) ;
        Z.AddByte((nValue & 0xff000000) >> 24) ;
        Z.AddByte((nValue & 0xff0000) >> 16) ;
        Z.AddByte((nValue & 0xff00) >> 8) ;
        Z.AddByte(nValue & 0xff) ;
    }
}
void    WriteSerialSINT32   (hzChain& Z, uint32_t& nLen, int32_t nValue)
{
    //  Serialize signed 32-bit integer to a hzChain.
    //
    //  Arguments:  1)  Z       Chain appended by the length indicator
    //              2)  nLen    The size of the indicator itself (set by this function)
    //              3)  nValue  Indicated length or value
    //
    //  Returns:    None
    bool    bNeg ;      //  Set if negative
    char    byte ;      //  Byte to write
    if (nValue < 0)
        { bNeg = true ; nValue *= -1 ; }
    if (nValue < 0x40)
    {
        //  Bit 0 is 0, bit 1 is negator, bits 2 thru 7 are the value
        nLen = 1 ;
        byte = bNeg ? 0x40 : 0 ;
        byte |= nValue ;
        Z.AddByte(byte) ;
    }
    else if (nValue < 0x1000)
    {
        //  Bit 0 is 1, bits 1 and 2 are controls (00), bit 3 is the negator, bits 4-7 are top 4 bits of value, 1 more byte to go
        nLen = 2 ;
        byte = bNeg ? 0x90 : 0x80 ;
        byte |= ((nValue & 0x0f00) >> 8) ;
        Z.AddByte(byte) ;
        Z.AddByte(nValue & 0xff) ;
    }
    else if (nValue < 0x400000)
    {
        //  Bit 0 is 1, bits 1 and 2 are controls (01), bit 3 is the negator, bits 4-7 are top 4 bits of value, 2 more bytes to go
        nLen = 3 ;
        byte = bNeg ? 0xB0 : 0xA0 ;
        byte |= ((nValue & 0x0f0000) >> 16) ;
        Z.AddByte(byte) ;
        Z.AddByte((nValue & 0xff00) >> 8) ;
        Z.AddByte(nValue & 0xff) ;
    }
    else if (nValue < 0x40000000)
    {
        //  Bit 0 is 1, bits 1 and 2 are controls (10), bit 3 is the negator, bits 4-7 are top 4 bits of value, 3 more bytes to go
        nLen = 4 ;
        byte = bNeg ? 0xD0 : 0xC0 ;
        byte |= ((nValue & 0x0f000000) >> 24) ;
        Z.AddByte(byte) ;
        Z.AddByte((nValue & 0xff0000) >> 16) ;
        Z.AddByte((nValue & 0xff00) >> 8) ;
        Z.AddByte(nValue & 0xff) ;
    }
    else
    {
        //  Bit 0 is 1, bits 1 and 2 are controls (11), bit 3 is the negator, bits 4-7 are top 4 bits of value, 3 more bytes to go
        nLen = 5 ;
        byte = bNeg ? 0xF0 : 0xE0 ;
        byte |= ((nValue & 0x0f000000) >> 24) ;
        Z.AddByte(0xC0) ;
        Z.AddByte((nValue & 0xff000000) >> 24) ;
        Z.AddByte((nValue & 0xff0000) >> 16) ;
        Z.AddByte((nValue & 0xff00) >> 8) ;
        Z.AddByte(nValue & 0xff) ;
    }
}
/*
**  SECTION 2:  BASE 64 CODEC
*/
//hzString  Base64Encode    (const char* pAscii) ;
//hzString  Base64Decode    (const char* pBase64) ;
/*
**  Support macro definitions for Base-64 CODEC:
*/
#define b64is7bit(c)    ((c) > 0x7f ? 0 : 1)    // Valid 7-Bit ASCII character?
#define b64blocks(l)    (((l) + 2) / 3 * 4 + 1) // Length rounded to 4 byte block.
#define b64octets(l)    ((l) / 4  * 3 + 1)      // Length rounded to 3 byte octet. 
#define _Base64Size(n)  ((((n+2)/3)*4)+4)
#define AsciiSize(n)    (((n/4)*3)+1)
#define Is7Bit(ch)      (ch & 0x80 ? 0:1)
#define IsBase64Ch(ch)  ()
#define _get6bit(z)     (z>='A'&&z<='Z'? z-'A' : z>='a'&&z<='z'? z-'a'+26 : z>='0'&&z<='9'? z-'0'+52 : z=='+'? 62 : z=='/'? 63 : z=='='? 100 : 101)
#define _get1hex(z)     (z>='A' && z<='F' ? z-'A'+10 : z>='a' && z<='f' ? z-'a'+10 : z>='0' && z<='9' ? z-'0' : -1)
// Note:    Tables are in hex to support different collating sequences
static  const char* s_base64_array = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" ;
            //  Array of allowed base-64 characters
static  const uchar  pIndex [] =
{
    //  Collation sequence
    0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
    0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50,
    0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
    0x59, 0x5a, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,
    0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e,
    0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,
    0x77, 0x78, 0x79, 0x7a, 0x30, 0x31, 0x32, 0x33,
    0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2b, 0x2f
} ;
static  const uchar pBase64 [] =
{
    //  Collation sequence
    0x3e, 0x7f, 0x7f, 0x7f, 0x3f, 0x34, 0x35, 0x36,
    0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x7f,
    0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x00, 0x01,
    0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
    0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
    0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
    0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x1a, 0x1b,
    0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
    0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
    0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33
} ;
bool    _b64valid   (uchar* c)
{
    //  Category:   Codec
    //
    // Tests for a valid Base64 char.
    //
    //  Arguments:  1)  c   The test char
    //
    //  Returns:    True    If the char belongs in the Base64 character set
    //              False   Otherwise
    if ((*c < 0x2b) || (*c > 0x7a))
        return false ;
    
    if ((*c = pBase64[*c - 0x2b]) == 0x7f)
        return false ;
    return true ;
}
/*
**  Base-64 functions
*/
void    Base64Encode    (hzChain& Encoded, const char* pRaw)
{
    //  Category:   Codec
    //
    //  Encode 7-Bit ASCII in the input chain to Base64 in the output chain.
    //
    //  Arguments:  1)  Encoded The chain to contain the encoded data
    //              2)  pRaw        The chain containing the 7-Bit ASCII data to encode
    //
    //  Returns:    None
    _hzfunc("Base64Encode(char*)") ;
    const char* r ;             //  Raw input chain iterator
    int32_t     A ;             //  A is top 6 bits of byte 1 of 3 shifted down by 2
    int32_t     B ;             //  B is low 2 bits of byte 2 of 3 shifted up by 4 and top 4 bits of 'b' shifted down by 4
    int32_t     C ;             //  C is low 4 bits of byte 3 of 3 shifted up by 2 and top 2 bits of 'c' shifted down by 6
    int32_t     D ;             //  D is low 6 bits of byte 3
    int32_t     nSeg = 0 ;      //  Newline marker
    uchar       a ;             //  1st byte of 3-byte segment
    uchar       b ;             //  2nd byte of 3-byte segment
    uchar       c ;             //  3rd byte of 3-byte segment
    uchar       bits ;          //  Count of bits in segment (at end can be less than 3)
    char        buf [8] ;       //  Export encoded set of 4 bytes
    Encoded.Clear() ;
    if (!pRaw || !pRaw[0])
        return ;
    for (r = pRaw ; *r ; r++)
    {
        //  Fill the 3 bytes of source
        a = b = c = bits = 0 ;
        a = *r ;
        r++ ;
        bits++ ;
        if (*r)
        {
            b = *r ;
            r++ ;
            bits++ ;
            if (*r)
            {
                c = *r ;
                bits++ ;
            }
        }
        //  Then convert to 4 bytes.
        A  = B = C = D = 0 ;
        A  = ((a & 0xfc) >> 2) ;
        B  = ((a & 0x03) << 4) ;
        B |= ((b & 0xf0) >> 4) ;
        C  = ((b & 0x0f) << 2) ;
        C |= ((c & 0xc0) >> 6) ;
        D  = c & 0x3f ;
        buf[0] = bits ? s_base64_array[A] : '=' ;
        buf[1] = bits ? s_base64_array[B] : '=' ;
        buf[2] = bits > 1 ? s_base64_array[C] : '=' ;
        buf[3] = bits > 2 ? s_base64_array[D] : '=' ;
        buf[4] = 0 ;
        Encoded += buf ;
        nSeg++ ;
        if (nSeg == 19)
        {
            Encoded += "\r\n" ;
            nSeg = 0 ;
        }
    }
}
void    Base64Encode    (hzChain& Encoded, const hzString& Raw)
{
    //  Category:   Codec
    //
    //  Encode 7-Bit ASCII in the input chain to Base64 in the output chain.
    //
    //  Arguments:  1)  Encoded The chain to contain the encoded data
    //              2)  Raw     The chain containing the 7-Bit ASCII data to encode
    //
    //  Returns:    None
    _hzfunc("Base64Encode(hzString)") ;
    const char* r ;             //  Raw input chain iterator
    int32_t     A ;             //  A is top 6 bits of byte 1 of 3 shifted down by 2
    int32_t     B ;             //  B is low 2 bits of byte 2 of 3 shifted up by 4 and top 4 bits of 'b' shifted down by 4
    int32_t     C ;             //  C is low 4 bits of byte 3 of 3 shifted up by 2 and top 2 bits of 'c' shifted down by 6
    int32_t     D ;             //  D is low 6 bits of byte 3
    int32_t     nSeg = 0 ;      //  Newline marker
    uchar       a ;             //  1st byte of 3-byte segment
    uchar       b ;             //  2nd byte of 3-byte segment
    uchar       c ;             //  3rd byte of 3-byte segment
    uchar       bits ;          //  Count of bits in segment (at end can be less than 3)
    char        buf [8] ;       //  Export encoded set of 4 bytes
    Encoded.Clear() ;
    if (!Raw)
        return ;
    for (r = *Raw ; *r ; r++)
    {
        //  Fill the 3 bytes of source
        a = b = c = bits = 0 ;
        a = *r ;
        r++ ;
        bits++ ;
        if (*r)
        {
            b = *r ;
            r++ ;
            bits++ ;
            if (*r)
            {
                c = *r ;
                bits++ ;
            }
        }
        //  Then convert to 4 bytes.
        A  = B = C = D = 0 ;
        A  = ((a & 0xfc) >> 2) ;
        B  = ((a & 0x03) << 4) ;
        B |= ((b & 0xf0) >> 4) ;
        C  = ((b & 0x0f) << 2) ;
        C |= ((c & 0xc0) >> 6) ;
        D  = c & 0x3f ;
        buf[0] = bits ? s_base64_array[A] : '=' ;
        buf[1] = bits ? s_base64_array[B] : '=' ;
        buf[2] = bits > 1 ? s_base64_array[C] : '=' ;
        buf[3] = bits > 2 ? s_base64_array[D] : '=' ;
        buf[4] = 0 ;
        Encoded += buf ;
        nSeg++ ;
        if (nSeg == 19)
        {
            Encoded += "\r\n" ;
            nSeg = 0 ;
        }
    }
}
void    Base64Encode    (hzChain& Encoded, const hzChain& Raw)
{
    //  Category:   Codec
    //
    //  Encode 7-Bit ASCII in the input chain to Base64 in the output chain.
    //
    //  Arguments:  1)  Encoded The chain to contain the encoded data
    //              2)  Raw     The chain containing the 7-Bit ASCII data to encode
    //
    //  Returns:    None
    _hzfunc("Base64Encode(hzChain)") ;
    chIter      r ;             //  Raw input chain iterator
    int32_t     A ;             //  A is top 6 bits of byte 1 of 3 shifted down by 2
    int32_t     B ;             //  B is low 2 bits of byte 2 of 3 shifted up by 4 and top 4 bits of 'b' shifted down by 4
    int32_t     C ;             //  C is low 4 bits of byte 3 of 3 shifted up by 2 and top 2 bits of 'c' shifted down by 6
    int32_t     D ;             //  D is low 6 bits of byte 3
    int32_t     nSeg = 0 ;      //  Newline marker
    uchar       a ;             //  1st byte of 3-byte segment
    uchar       b ;             //  2nd byte of 3-byte segment
    uchar       c ;             //  3rd byte of 3-byte segment
    uchar       bits ;          //  Count of bits in segment (at end can be less than 3)
    char        buf [8] ;       //  Export encoded set of 4 bytes
    for (r = Raw ; !r.eof() ; r++)
    {
        //  Fill the 3 bytes of source
        a = b = c = bits = 0 ;
        a = *r ;
        r++ ;
        bits++ ;
        if (!r.eof())
        {
            b = *r ;
            r++ ;
            bits++ ;
            if (!r.eof())
            {
                c = *r ;
                bits++ ;
            }
        }
        //  Then convert to 4 bytes.
        A  = B = C = D = 0 ;
        A  = ((a & 0xfc) >> 2) ;
        B  = ((a & 0x03) << 4) ;
        B |= ((b & 0xf0) >> 4) ;
        C  = ((b & 0x0f) << 2) ;
        C |= ((c & 0xc0) >> 6) ;
        D  = c & 0x3f ;
        buf[0] = bits ? s_base64_array[A] : '=' ;
        buf[1] = bits ? s_base64_array[B] : '=' ;
        buf[2] = bits > 1 ? s_base64_array[C] : '=' ;
        buf[3] = bits > 2 ? s_base64_array[D] : '=' ;
        buf[4] = 0 ;
        Encoded += buf ;
        nSeg++ ;
        if (nSeg == 19)
        {
            Encoded += "\r\n" ;
            nSeg = 0 ;
        }
    }
}
hzEcode Base64Encode    (hzChain& Encoded, const char* dir, const char* fname)
{
    //  Category:   Codec
    //
    //  Encode the contents of the file with the supplied path, from 7-Bit ASCII to Base64. The encoded data is then in a hzChain instance.
    //
    //  Arguments:  1)  Encoded The chain to contain the encoded data
    //              2)  dir     The directory of the file containing the 7-Bit ASCII data to encode
    //              3)  fname   The file name within the directory
    //
    //  Returns:    E_ARGUMENT  If either the directory or filename are not supplied
    //              E_NOTFOUND  If the filepath specified does not exist as a directory entry of any kind
    //              E_TYPE      If the filepath names a directory entry that is not a file
    //              E_NODATA    If the filepath is a file but empty
    //              E_OPENFAIL  If the file specified could not be opened for reading
    //              E_OK        If the input stream was opened and the content encoded
    _hzfunc("Base64Encode(file)") ;
    std::ifstream   is ;    //  Input file stream
    hzChain     Z ;         //  To store unencoded input file
    hzString    Path ;      //  Full pathname of file
    hzEcode     rc ;        //  Return code
    if (!dir || !dir[0])
        return hzerr(E_ARGUMENT, "No directory supplied") ;
    if (!fname || !fname[0])
        return hzerr(E_ARGUMENT, "No filename supplied") ;
    Path = dir ;
    Path += "/" ;
    Path += fname ;
    rc = OpenInputStrm(is, *Path) ;
    if (rc == E_OK)
    {
        Z << is ;
        is.close() ;
        Base64Encode(Encoded, Z) ;
    }
    return rc ;
}
/*
**  Function Set:   Base64Decode
**
**  Decode a Base64 string to a 7-Bit ASCII string
*/
hzEcode Base64Decode    (hzChain& Decoded, const char* s)
{
    //  Category:   Codec
    //
    //  Decode a Base64 string to a 7-Bit ASCII string
    //
    //  Arguments:  1)  Decoded The decoded string (in hzChain)
    //              2)  s       The Base64 data to decode (as char string)
    //
    //  Returns:    Instance of hzString by value being the decoded data
    _hzfunc("Base64Decode(cstr)") ;
    const char* i ;         //  Cstr iterator
    int32_t     nBytes ;    //  Length of input string
    int32_t     val ;       //  Holds 24 bit value to be written as 3 bytes
    int32_t     a ;         //  1st value
    int32_t     b ;         //  2nd value
    int32_t     c ;         //  3rd value
    int32_t     d ;         //  4th value
    //  Clear the supplied result chain
    Decoded.Clear() ;
    //  Not valid unless the string is an exact multiple of 4 bytes
    nBytes = strlen(s) ;
    if (nBytes % 4)
        return hzerr(E_FORMAT, "Supplied string is not an exact multiple of 4 bytes in length") ;
    //  Loop through collecting sets of 4 chars and writing out sets of 3 chars
    for (i = s ; *i ;)
    {
        a = _get6bit(*i) ; i++ ;
        b = _get6bit(*i) ; i++ ;
        c = _get6bit(*i) ; i++ ;
        d = _get6bit(*i) ; i++ ;
        if (a == 101 || b == 101 || c == 101 || d == 101)
            return E_FORMAT ;
        val = 0 ;
        if (a < 100)    val += (a << 18) ;
        if (b < 100)    val += (b << 12) ;
        if (c < 100)    val += (c << 6) ;
        if (d < 100)    val += d ;
        a = (val & 0xff0000) >> 16 ;
        b = (val & 0xff00) >> 8 ;
        c = val & 0xff ;
        Decoded.AddByte(a) ;
        if (b)
            Decoded.AddByte(b) ;
        if (c)
            Decoded.AddByte(c) ;
        if (*i == CHAR_NL)  i++ ;
        if (*i == CHAR_CR)  i++ ;
    }
    return E_OK ;
}
hzEcode Base64Decode    (hzChain& Decoded, const hzString& str)
{
    return Base64Decode(Decoded, *str) ;
}
hzEcode Base64Decode    (hzChain& Decoded, hzChain::Iter& ci)
{
    //  Category:   Codec
    //
    //  Decode base64 encoded source from the supplied chain iterator to the end of the source.
    //
    //  This function is provided because bese64 encoded matter is often found partway into a chain (see email processing). By use of
    //  this function a separate step of copying the partial chain is avoided
    //
    //  Arguments:  1)  Decoded     Chain to contain result of decode operation
    //              2)  ci          Chain iterator into encoded data
    //
    //  Returns:    E_FORMAT    If the input chain contains unexpected sequences
    //              E_OK        If the input was decoded
    _hzfunc("Base64Decode(chIter)") ;
    chIter      zi ;        //  Iterator
    int32_t     val ;       //  Holds 24 bit value to be written as 3 bytes
    int32_t     a ;         //  Input 1st value
    int32_t     b ;         //  Input 2nd value
    int32_t     c ;         //  Input 3rd value
    int32_t     d ;         //  Input 4th value
    Decoded.Clear() ;
    //  Loop through collecting sets of 4 chars and writing out sets of 3 chars
    zi = ci ;
    for (; !zi.eof() ;)
    {
        if (*zi == CHAR_CR) { zi++ ; continue ; }
        if (*zi == CHAR_NL) { zi++ ; continue ; }
        a = _get6bit(*zi) ; zi++ ;
        b = _get6bit(*zi) ; zi++ ;
        c = _get6bit(*zi) ; zi++ ;
        d = _get6bit(*zi) ; zi++ ;
        if (a == 101 || b == 101 || c == 101 || d == 101)
            return E_FORMAT ;
        val = 0 ;
        if (a < 100)    val += (a << 18) ;
        if (b < 100)    val += (b << 12) ;
        if (c < 100)    val += (c << 6) ;
        if (d < 100)    val += d ;
        a = (val & 0xff0000) >> 16 ;
        b = (val & 0xff00) >> 8 ;
        c = val & 0xff ;
        Decoded.AddByte(a) ;
        if (b)
            Decoded.AddByte(b) ;
        if (c)
            Decoded.AddByte(c) ;
    }
    return E_OK ;
}
hzEcode Base64Decode    (hzChain& Decoded, const hzChain& Raw)
{
    //  Category:   Codec
    //
    //  Decode base64 encoded source
    //
    //  Arguments:  1)  Decoded     The decoded output
    //              2)  Raw         The encoded input
    //
    //  Returns:    E_FORMAT    If the input chain contains unexpected sequences
    //              E_OK        If the input was decoded
    _hzfunc("Base64Decode(hzChain)") ;
    hzChain::Iter   zi ;    //  Chain iterator (needed for call to BaseDecode(hzChain&,hzChain&)
    zi = Raw ;
    return Base64Decode(Decoded, zi) ;
}
hzEcode  Base64Decode   (hzString& Decoded, const char* pRaw)
{
    hzChain     Z ;     //  Temp chain
    hzEcode     rc ;    //  Return code
    Decoded.Clear() ;
    rc = Base64Decode(Z, pRaw) ;
    if (rc == E_OK)
        Decoded = Z ;
    return rc ;
}
hzEcode  Base64Decode   (hzString& Decoded, const hzString& raw)
{
    hzChain     Z ;     //  Temp chain
    hzEcode     rc ;    //  Return code
    Decoded.Clear() ;
    rc = Base64Decode(Z, raw) ;
    if (rc == E_OK)
        Decoded = Z ;
    return rc ;
}
hzEcode  Base64Decode   (hzString& Decoded, const hzChain& raw)
{
    hzChain     Z ;     //  Temp chain
    hzEcode     rc ;    //  Return code
    Decoded.Clear() ;
    rc = Base64Decode(Z, raw) ;
    if (rc == E_OK)
        Decoded = Z ;
    return rc ;
}
hzEcode  Base64Decode   (hzString& Decoded, hzChain::Iter& raw)
{
    hzChain     Z ;     //  Temp chain
    hzEcode     rc ;    //  Return code
    Decoded.Clear() ;
    rc = Base64Decode(Z, raw) ;
    if (rc == E_OK)
        Decoded = Z ;
    return rc ;
}
/*
**  SECTION 3:  Quoted-Printable CODEC
*/
hzEcode QPDecode    (hzString& Decoded, const char* pRaw)
{
    //  Category:   Codec
    //
    //  Convert a QP encoded cstr, back to it's original form.
    //
    //  Arguments:  1)  Decoded The hzString reference that will contain the result. Any pre-existing contents of this are cleared at the outset.
    //              2)  pRaw    The encoded cstr.
    //
    //  Returns:    E_NODATA    If the encoded cstr is empty
    //              E_FORMAT    If there is an error in conversion
    //              E_OK        If the operation is successful
    _hzfunc("QPDecode") ;
    hzChain     R ;         //  Result
    const char* i ;         //  Iterator
    int32_t     val ;       //  Holds 24 bit value to be written as 3 bytes
    if (!pRaw || !pRaw[0])
        return E_NODATA ;
    //  Interate the input. On the occurence of '=', the next two bytes are hex for the actual char.
    i = pRaw ;
    for (; *i ;)
    {
        if (*i == '=')
        {
            i++ ;
            if (*i == CHAR_CR || *i == CHAR_NL)
            {
                //  Just skip to next line
                if (*i == CHAR_CR)  i++ ;
                if (*i == CHAR_NL)  i++ ;
                continue ;
            }
            if (!IsHex(*i))
                return E_FORMAT ;
            val = _get1hex(*i) ;
            i++ ;
            if (!IsHex(*i))
                return E_FORMAT ;
            val *= 16 ;
            val += _get1hex(*i) ;
            R.AddByte(val) ;
            i++ ;
            continue ;
        }
        R.AddByte(*i) ;
        i++ ;
    }
    Decoded = R ;
    //threadLog("decoded=[%s]\n", *Decoded) ;
    return E_OK ;
}
hzEcode QPDecode    (hzChain& Decoded, const hzChain& Raw)
{
    //  Category:   Codec
    //
    //  Convert a hzChain whose content is presumed to encoded using the Quoted-Printable method, back to it's original form.
    //
    //  Arguments:  1)  Decoded The hzChain reference that will contain the result. Any pre-existing contents of this are cleared at the outset.
    //              2)  Raw     The encoded hzChain. This is not altered by this operation.
    //
    //  Returns:    E_NODATA    If the encoded chain is empty
    //              E_FORMAT    If there is an error in conversion
    //              E_OK        If the operation is successful
    _hzfunc("QPDecode") ;
    chIter  zi ;        //  Iterator
    int32_t val ;       //  Holds 24 bit value to be written as 3 bytes
    Decoded.Clear() ;
    if (!Raw.Size())
        return E_NODATA ;
    //  Interate the input. On the occurence of '=', the next two bytes are hex for the actual char.
    zi = Raw ;
    for (; !zi.eof() ;)
    {
        if (*zi == '=')
        {
            zi++ ;
            if (*zi == CHAR_CR || *zi == CHAR_NL)
            {
                //  Just skip to next line
                if (*zi == CHAR_CR) zi++ ;
                if (*zi == CHAR_NL) zi++ ;
                continue ;
            }
            if (zi.eof() || !IsHex(*zi))
                return E_FORMAT ;
            val = _get1hex(*zi) ;
            zi++ ;
            if (zi.eof() || !IsHex(*zi))
                return E_FORMAT ;
            val *= 16 ;
            val += _get1hex(*zi) ;
            Decoded.AddByte(val) ;
            zi++ ;
            continue ;
        }
        Decoded.AddByte(*zi) ;
        zi++ ;
    }
    return E_OK ;
}
hzEcode CharsetStringDecode (hzString& Decoded, const hzString& raw)
{
    //  Perform QP or Base64 decoding on strings in which the form of encoding used, is indicated in the string itself. The indicators are of the general form of =?charset?ecoding?,
    //  e.g. =?utf-8?q? (QP) or =?utf-8?b? (Base64). The decoding is applied to all chars in the string that are enclosed by such an indicator and a terminator (either a 0, or a ?=
    //  sequence).
    //
    //  This function does not actually care about charsets. It applies either QP or Base64 decoding to all chars found between the starting sequence (e.g. =?utf-8?q?), upto the
    //  terminator (either a 0 or a ?= sequence). 
    _hzfunc(__func__) ;
    const char* i ;         //  Iterator
    const char* j ;         //  Aux iterator
    char*       x ;         //  Output iterator
    char*       limit ;     //  Limit of buffer
    char*       outBuf ;    //  Output buffer
    int32_t     val ;       //  Holds 24 bit value to be written as 3 bytes
    int32_t     a ;         //  Input 1st value
    int32_t     b ;         //  Input 2nd value
    int32_t     c ;         //  Input 3rd value
    int32_t     d ;         //  Input 4th value
    Decoded.Clear() ;
    if (!raw)
        return E_OK ;
    x = outBuf = new char[raw.Length() + 4] ;
    limit = outBuf + (raw.Length() + 3) ;
    memset(outBuf, 0, raw.Length() + 4) ;
    for (i = *raw ; *i ; i++)
    {
        if (*i == CHAR_EQUAL && i[1] == CHAR_QUERY)
        {
            for (j = i + 2 ; *j && *j != CHAR_QUERY ; j++) ;
            if (j[0] == CHAR_QUERY && j[2] == CHAR_QUERY)
            {
                if (j[1] == 'b' || j[1] == 'B')
                {
                    //  Use Base64
                    for (i = j + 3 ; *i ; i++)
                    {
                        if (i[0] == CHAR_QUERY && i[1] == CHAR_EQUAL)
                            { i += 2 ; break ; }
                        a = _get6bit(*i) ; i++ ;
                        b = _get6bit(*i) ; i++ ;
                        c = _get6bit(*i) ; i++ ;
                        d = _get6bit(*i) ; i++ ;
                        if (a == 101 || b == 101 || c == 101 || d == 101)
                            return E_FORMAT ;
                        val = 0 ;
                        if (a < 100)    val += (a << 18) ;
                        if (b < 100)    val += (b << 12) ;
                        if (c < 100)    val += (c << 6) ;
                        if (d < 100)    val += d ;
                        a = (val & 0xff0000) >> 16 ;
                        b = (val & 0xff00) >> 8 ;
                        c = val & 0xff ;
                        if (a && x != limit) *x++ = a ;
                        if (b && x != limit) *x++ = b ;
                        if (c && x != limit) *x++ = c ;
                    }
                }
                else if (j[1] == 'q' || j[1] == 'Q')
                {
                    //  Use QP
                    for (i = j + 3 ; *i ; i++)
                    {
                        if (i[0] == CHAR_QUERY && i[1] == CHAR_EQUAL)
                            { i += 2 ; break ; }
                        if (*i == CHAR_EQUAL && IsHex(i[1]) && IsHex(i[2]))
                        {
                            i++ ;
                            val = _get1hex(*i) ;
                            i++ ;
                            val *= 16 ;
                            val += _get1hex(*i) ;
                            if (val && x != limit)
                                *x++ = val ;
                            continue ;
                        }
                        if (*i && x != limit)
                            *x++ = *i ;
                    }
                }
                else
                {
                    //  Don't decode but copy all enclosed chars to the output
                    for (i = j + 3 ; *i ; i++)
                    {
                        if (i[0] == CHAR_QUERY && i[1] == CHAR_EQUAL)
                            { i += 2 ; break ; }
                        if (*i && x != limit)
                            *x++ = *i ;
                    }
                }
                continue ;
            }
            //  Drop through
        }
        if (*i && x != limit)
            *x++ = *i ;
    }
    if (*i && x != limit)
        *x = 0 ;
    Decoded = outBuf ;
    delete outBuf ;
    return E_OK ;
}
#if 0
hzEcode CharsetStringDecode (hzString& Decoded, hzChain::Iter& raw)
{
    //  Perform QP or Base64 decoding on strings in which the form of encoding used, is indicated in the string itself. The indicators are of the general form of =?charset?ecoding?,
    //  e.g. =?utf-8?q? (QP) or =?utf-8?b? (Base64). The decoding is applied to all chars in the string that are enclosed by such an indicator and a terminator (either a 0, or a ?=
    //  sequence).
    //
    //  This function does not actually care about charsets. It applies either QP or Base64 decoding to all chars found between the starting sequence (e.g. =?utf-8?q?), upto the
    //  terminator (either a 0 or a ?= sequence). 
    _hzfunc(__func__) ;
    chIter      zi ;        //  Iterator
    const char* i ;         //  Iterator
    const char* j ;         //  Aux iterator
    char*       x ;         //  Output iterator
    char*       outBuf ;    //  Output buffer
    int32_t     val ;       //  Holds 24 bit value to be written as 3 bytes
    int32_t     a ;         //  Input 1st value
    int32_t     b ;         //  Input 2nd value
    int32_t     c ;         //  Input 3rd value
    int32_t     d ;         //  Input 4th value
    Decoded.Clear() ;
    if (!raw)
        return E_OK ;
    x = outBuf = new char[raw.Length()] ;
    zi = raw ;
    if (zi.eof())
        return E_OK ;
    for (i = *raw ; *i ; i++)
    {
        if (*i == '=')
        {
            if (i[1] == '?')
            {
                for (j = i + 2 ; *j && *j != CHAR_QUERY ; j++) ;
                if (j[0] == CHAR_QUERY && j[2] == CHAR_QUERY)
                {
                    if (j[1] == 'b' || j[1] == 'B')
                    {
                        //  Use Base64
                        for (i = j + 3 ; *i ; i++)
                        {
                            if (i[0] == CHAR_QUERY && i[1] == CHAR_EQUAL)
                                { i += 2 ; break ; }
                            a = _get6bit(*i) ; i++ ;
                            b = _get6bit(*i) ; i++ ;
                            c = _get6bit(*i) ; i++ ;
                            d = _get6bit(*i) ; i++ ;
                            if (a == 101 || b == 101 || c == 101 || d == 101)
                                return E_FORMAT ;
                            val = 0 ;
                            if (a < 100)    val += (a << 18) ;
                            if (b < 100)    val += (b << 12) ;
                            if (c < 100)    val += (c << 6) ;
                            if (d < 100)    val += d ;
                            a = (val & 0xff0000) >> 16 ;
                            b = (val & 0xff00) >> 8 ;
                            c = val & 0xff ;
                            *x++ = a ;
                            if (b)
                                *x++ = b ;
                            if (c)
                                *x++ = c ;
                        }
                    }
                    else if (j[1] == 'q' || j[1] == 'Q')
                    {
                        //  Use QP
                        for (i = j + 3 ; *i ; i++)
                        {
                            if (i[0] == CHAR_QUERY && i[1] == CHAR_EQUAL)
                                { i += 2 ; break ; }
                            if (*i == CHAR_EQUAL && IsHex(i[1]) && IsHex(i[2]))
                            {
                                i++ ;
                                val = _get1hex(*i) ;
                                i++ ;
                                val *= 16 ;
                                val += _get1hex(*i) ;
                                *x++ = val ;
                                continue ;
                            }
                            *x++ = *i ;
                        }
                    }
                    else
                    {
                        //  Don't decode but copy all enclosed chars to the output
                        for (i = j + 3 ; *i ; i++)
                        {
                            if (i[0] == CHAR_QUERY && i[1] == CHAR_EQUAL)
                                { i += 2 ; break ; }
                            *x++ = *i ;
                        }
                    }
                    continue ;
                }
                //  Drop through
            }
            //  Drop through
        }
        *x++ = *i ;
    }
    *x = 0 ;
    Decoded = outBuf ;
    delete outBuf ;
    return E_OK ;
}
#endif
/*
**  SECTION 4:  gzip encoding/decoding
*/
 
hzEcode Gzip    (hzChain& gzipped, const hzChain& orig)
{
    //  Category:   Codec
    //
    //  Perform a gzip compression operation on the supplied chain.
    //
    //  Arguments:  1)  gzipped The hzChain reference that will contain the result. Any pre-existing contents of this are cleared at the outset.
    //              2)  orig    The original hzChain. This is not altered by this operation.
    //
    //  Returns:    E_NODATA    If the original is empty
    //              E_NOINIT    If there is an error in conversion
    //              E_OK        If the operation is successful
    _hzfunc(__func__) ;
    chIter      zi ;        //  Chain iterator for input
    z_stream    defstream;  //  The zlib struct
    char*       tmp ;       //  A string to convert chain to contiguous memory
    char*       buf ;       //  Buffer to construct gzipped content
    int32_t     err ;       //  Return code from init
    gzipped.Clear() ;
    if (!orig.Size())
        return E_NODATA ;
    // Deflate original data
    defstream.zalloc    = Z_NULL;
    defstream.zfree     = Z_NULL;
    defstream.opaque    = Z_NULL;
    // setup tmp as the input and buf as the compressed output
    tmp = new char[orig.Size()+1] ;
    buf = new char[orig.Size()] ;
    //  for (n = 0, zi = orig ; !zi.eof() ; n++, zi++)
    //      tmp[n] = *zi ;
    //  tmp[n] = 0 ;
    zi = orig ;
    zi.Write(tmp, orig.Size()) ;
    tmp[orig.Size()] = 0 ;
    defstream.avail_in  = (uInt) orig.Size() ;
    defstream.next_in   = (Bytef*) tmp;
    defstream.avail_out = (uInt) orig.Size() ;
    defstream.next_out  = (Bytef*) buf;
    //  Initialize the zlib deflation (i.e. compression) internals with deflateInit2(). 
    //
    //  The parameters are as follows: 
    //
    //      z_streamp strm  - Pointer to a zstream struct 
    //
    //      int level       - Compression level. Must be Z_DEFAULT_COMPRESSION, or between 0 and 9: 1 gives best speed, 9 gives best compression, 0 gives no compression. 
    //
    //      int method      - Compression method. Only method supported is "Z_DEFLATED". 
    //
    //      int windowBits  - Base two logarithm of the maximum window size (the size of the history buffer). It should be in the range 8-15. Add 16 to windowBits to write a simple
    //                      gzip header and trailer  around the compressed data instead of a zlib wrapper. The gzip header will have no file name, no extra data, no comment, no mod
    //                      time (set to zero), no header crc, and the operating system will be set to 255 (unknown).  
    //
    //      int memLevel    - Amount of memory allocated for internal compression state. 1 uses minimum memory but is slow and reduces compression ratio; 9 uses maximum memory for
    //                      optimal speed. Default value is 8. 
    //
    //      int strategy    - Used to tune the compression algorithm. Use the value Z_DEFAULT_STRATEGY for normal data, Z_FILTERED for data produced by a filter (or predictor), or
    //                      Z_HUFFMAN_ONLY to force Huffman encoding only (no string match)  
    err = deflateInit2(&defstream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8, Z_DEFAULT_STRATEGY);  
    if (err != Z_OK)  
        return hzerr(E_NOINIT, "Could not run deflate process") ;
    // the actual compression work.
    deflate(&defstream, Z_FINISH);
    deflateEnd(&defstream);
    //  for (n = 0 ; n < defstream.total_out ; n++)
    //      gzipped.AddByte(buf[n]) ;
    gzipped.Append(buf, defstream.total_out) ;
    delete tmp ;
    delete buf ;
    if (!gzipped.Size())
        return hzerr(E_NODATA, "Input %d bytes but no output", orig.Size()) ;
    return E_OK ;
}
hzEcode Gunzip  (hzChain& orig, const hzChain& gzipped)
{
    //  Category:   Codec
    //
    //  On the presumption that the supplied data is zipped by gzip, this functions writes the date to a temporary file, calls 'gunzip' via a system call on this file and reads in
    //  the unziped data. The temporary file and it's unziped associate are then unlinked.
    //
    //  Arguments:  1)  orig    The chain to be populated with the unzipped data
    //              2)  gzipped The chain containing the original (zipped) data
    //
    //  Returns:    E_NODATA    If there is nothing to do
    //              E_WRITEFAIL If the original data could not be written out to the temporary file
    //              E_SYSERROR  If the gunzip command could not be found or failed to process the data
    //              E_READFIAL  If the unzipped data could not be read back from the temporary file
    //              E_OK        If the operation was successful
    //
    //  Note:   Do not unzip files that contain multiple files and/or accross mutiple directories. It won't work and will leave a mess!
    _hzfunc(__func__) ;
    z_stream    infstream ;     //  Input
    chIter      zi ;            //  Chain iterator
    char*       tmp ;           //  Zipped buffer
    char*       buf ;           //  Unzipped buffer
    uint32_t    n ;             //  Buffer iterator
    tmp = new char[gzipped.Size()+1] ;
    buf = new char[20480] ;
    for (n = 0, zi = gzipped ; !zi.eof() ; n++, zi++)
        tmp[n] = *zi ;
    infstream.zalloc = Z_NULL;
    infstream.zfree = Z_NULL;
    infstream.opaque = Z_NULL;
    // setup "b" as the input and "c" as the compressed output
    infstream.avail_in = (uInt) gzipped.Size() ;    //((char*)defstream.next_out - b); // size of input
    infstream.next_in = (Bytef*)tmp ;               // input char array
    infstream.avail_out = 20480 ;                   //  (uInt)sizeof(c); // size of output
    infstream.next_out = (Bytef*)buf ;              // output char array
    // the actual DE-compression work.
    if (inflateInit2(&infstream, 31) != Z_OK)
        return E_NOINIT ;
    inflate(&infstream, Z_NO_FLUSH);
    inflateEnd(&infstream);
    for (n = 0 ; n < infstream.total_out ; n++)
        orig.AddByte(buf[n]) ;
    delete tmp ;
    delete buf ;
    return E_OK ;
}
/*
**  SECTION 5:  SHA512 Digest
*/
#if 0
hzEcode hzSHA512::CalcString    (const hzString S)
{
    _hzfunc("hzSHA512::CalcString") ;
    SHA512_CTX  sha512 ;                        //  The calculation area
    SHA512_Init(&sha512) ;
    SHA512_Update(&sha512, *S, S.Length()) ;
    SHA512_Final(m_Hash, &sha512) ;
    memset(m_Hash, 0, SHA512_DIGEST_LENGTH) ;
    threadLog("Result %s\n", m_Hash) ;
    return E_OK ;
}
hzEcode hzSHA512::CalcChain (const hzChain& Z)
{
    _hzfunc("hzSHA512::CalcString") ;
    chIter      zi ;
    SHA512_CTX  sha512 ;        //  The calculation area
    int32_t     nBytes ;        //  Bytes retrieved from chain
    uchar*      pBuf ;          //  Operating buffer
    memset(m_Hash, 0, SHA512_DIGEST_LENGTH) ;
    SHA512_Init(&sha512) ;
    pBuf = new uchar[2048] ;
    zi = Z ;
    for (;;)
    {
        nBytes = zi.Write(pBuf, 2048) ;
    
        if (!nBytes)
            break ;
        zi += nBytes ;
        SHA512_Update(&sha512, pBuf, nBytes) ;
    }
    SHA512_Final(m_Hash, &sha512) ;
    delete[] pBuf ;
    return E_OK ;
}
const char* hzSHA512::Txt   (void) const
{
    _hzfunc("hzSHA512::Txt") ;
    char*       buf ;       //  Output buffer (allocated from the ...)
    int32_t     i ;         //  Hash iterator
    int32_t     n ;         //  Output iterator
    int32_t     val ;       //  Value
    buf = _thisfn.ScratchPad(132) ;
    for (i = 0 ; i <= SHA512_DIGEST_LENGTH ; i++)
    {
        val = (m_Hash[i] & 0xf0) << 8 ;
        buf[n++] = val > 9 ? 'A' + val - 9 : '0' + val ;
        val = m_Hash[i] & 0x0f ;
        buf[n++] = val > 9 ? 'A' + val - 9 : '0' + val ;
    }
    buf[n] = 0 ;
    return buf ;
}
#endif
/*
**  Rotate amounts used in the algorithm
*/
static  uint32_t    s_sin_vals[] =
{
    //  Please note the following legal notice. This table is derived from the RSA Data Security, Inc. MD5 Message-Digest Algorithm
    //
    //  This array comprises 64 numbers such that the Ith value in the array is equal to the integer part of 4294967296 times abs(sin(I)) where I is in radians.
    //  This is as prescribed in RFC1321.
    0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
    0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
    0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
    0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
    0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
    0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
    0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
    0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391,
} ;
static  uchar   s_sin_oset[] =
{
    //  Please note the following legal notice. This table is derived from the RSA Data Security, Inc. MD5 Message-Digest Algorithm
    0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15,
    1,  6, 11,  0,  5, 10, 15,  4,  9, 14,  3,  8, 13,  2,  7, 12,
    5,  8, 11, 14,  1,  4,  7, 10, 13,  0,  3,  6,  9, 12, 15,  2,
    0,  7, 14,  5, 12,  3, 10,  1,  8, 15,  6, 13,  4, 11,  2,  9,
} ;
static  uchar   s_sin_shft[] =
{
    //  Please note the following legal notice. This table is derived from the RSA Data Security, Inc. MD5 Message-Digest Algorithm
    7, 12, 17, 22,  7, 12, 17, 22,  7, 12, 17, 22,  7, 12, 17, 22,
    5,  9, 14, 20,  5,  9, 14, 20,  5,  9, 14, 20,  5,  9, 14, 20,
    4, 11, 16, 23,  4, 11, 16, 23,  4, 11, 16, 23,  4, 11, 16, 23,  
    6, 10, 15, 21,  6, 10, 15, 21,  6, 10, 15, 21,  6, 10, 15, 21,  
} ;
class   _md5_unit
{
    //  Please note the following legal notice. This class is derived from the RSA Data Security, Inc. MD5 Message-Digest Algorithm
public:
    uint32_t    len ;
    uint32_t    state[4] ;
    uint32_t    resv ;
    _md5_unit   (void)
    {
        state[0] = 0x67452301 ;
        state[1] = 0xefcdab89 ;
        state[2] = 0x98badcfe ;
        state[3] = 0x10325476 ;
    }
} ;
//_md5_unit *nil ;
//void  _md5_encode (uchar* out, _md5_unit& md5Unit)    //uint32_t* in, uint32_t len)
void    _md5_encode (uchar* out, uint32_t* in, uint32_t len)
{
    //  Category:   Codec
    //
    //  Please note the following legal notice. This function is derived from the RSA Data Security, Inc. MD5 Message-Digest Algorithm
    //
    //  Encodes input. Assumes len is xple of 4
    //
    //  Arguments:  1)  out     Pointer into output buffer
    //              2)  in      Pointer into input buffer
    //              3)  len     Buffer length
    //
    //  Returns:    None
    _hzfunc(__func__) ;
    uint32_t    x ;     //  Interim value
    uint32_t    n ;     //  Counter
    for (n = 0 ; n < len ; n++)
    {
        x = *in++ ;
        *out++ = x ;
        *out++ = x >> 8 ;
        *out++ = x >> 16 ;
        *out++ = x >> 24 ;
    }
}
void    _md5_decode (uint32_t* out, uchar* in, uint32_t len)
{
    //  Category:   Codec
    //
    //  Please note the following legal notice. This function is derived from the RSA Data Security, Inc. MD5 Message-Digest Algorithm
    //
    //  Decodes input. Assumes len is xple of 4
    //
    //  Arguments:  1)  out     Pointer into output buffer
    //              2)  in      Pointer into input buffer
    //              3)  len     Buffer length
    //
    //  Returns:    None
    _hzfunc(__func__) ;
    uchar*  e ;     //  Input limiter
    for (e = in + len ; in < e ; in += 4)
        *out++ = in[0] | (in[1] << 8) | (in[2] << 16) | (in[3] << 24) ;
}
void    _xlate_md5  (uchar* workBuf, uint32_t len, uchar* digest, _md5_unit& md5Unit)
{
    //  Category:   Codec
    //
    //  Please note the following legal notice. This function is derived from the RSA Data Security, Inc. MD5 Message-Digest Algorithm
    //
    //  Perform MD5 translation
    //
    //  Arguments:  1)  p       Pointer
    //              2)  len     Length of
    //              3)  digest  Pointer to the
    //              4)  s       Pointer to MD5 unit
    //
    //  Returns:    Pointer to an MD5 unit
    _hzfunc(__func__) ;
    uint32_t    a ;         //  Part a of 32-bit interim value
    uint32_t    b ;         //  Part b of 32-bit interim value
    uint32_t    c ;         //  Part c of 32-bit interim value
    uint32_t    d ;         //  Part d of 32-bit interim value
    uint32_t    tmp ;       //  For variable rotation
    uint32_t    i ;         //  Remainder
    uint32_t    done ;      //  Process complete
    uchar*      pWork ;     //  Pointer into working buffer
    uchar*      end ;       //  Work limit
    uint32_t    x[16] ;
    md5Unit.len += len ;
    pWork = workBuf ;
    i = len & 0x3f ;
    done = 0 ;
    if (i || len == 0)
    {
        done = 1 ;
        //  Pad the input
        if (i < 56)
            i = 56 - i ;
        else
            i = 120 - i ;
        if (i > 0)
        {
            memset(pWork + len, 0, i) ;
            pWork[len] = 0x80 ;
        }
        len += i ;
        //  Append the count
        x[0] = md5Unit.len<<3 ;
        x[1] = md5Unit.len>>29 ;
        _md5_encode(pWork + len, x, 4) ;
    }
    //  Do the process
    for (end = pWork + len ; pWork < end ; pWork += 64)
    {
        a = md5Unit.state[0] ;
        b = md5Unit.state[1] ;
        c = md5Unit.state[2] ;
        d = md5Unit.state[3] ;
        _md5_decode(x, pWork, 64) ;
    
        for(i = 0 ; i < 64 ; i++)
        {
            switch(i>>4)
            {
            case 0: a += (b & c) | (~b & d) ;   break ;
            case 1: a += (b & d) | (c & ~d) ;   break ;
            case 2: a += b ^ c ^ d ;            break ;
            case 3: a += c ^ (b | ~d) ;         break ;
            }
            a += x[s_sin_oset[i]] + s_sin_vals[i] ;
            a = (a << s_sin_shft[i]) | (a >> (32 - s_sin_shft[i])) ;
            a += b ;
    
            //  rotate variables
            tmp = d ;
            d = c ;
            c = b ;
            b = a ;
            a = tmp ;
        }
        md5Unit.state[0] += a ;
        md5Unit.state[1] += b ;
        md5Unit.state[2] += c ;
        md5Unit.state[3] += d ;
    }
    //  return result
    if (done && digest)
    {
        _md5_encode(workBuf, (uint32_t*) &(md5Unit.state), 4) ;
        memcpy(digest, workBuf, 16) ;
    }
    return ;
}
hzMD5::hzMD5    (void)  { Clear() ; }
void    hzMD5::Clear    (void)
{
    m_Parts[0] = m_Parts[1] = m_Parts[2] = m_Parts[3] = 0 ;
}
bool    hzMD5::IsNull   (void) const
{
    return m_Parts[0] == 0 && m_Parts[1] == 0 && m_Parts[2] == 0 && m_Parts[3] == 0 ? true : false ;
}
hzMD5&  hzMD5::operator=    (const char* pMd5)
{
    const char* i ;     //  Input iterator
    uint64_t    val ;   //  Value of part
    uint32_t    x ;     //  Outer loop counter (MD5 parts)
    uint32_t    n ;     //  Inner loop counter (input bytes)
    m_Parts[0] = m_Parts[1] = m_Parts[2] = m_Parts[3] = 0 ;
    if (!pMd5 || pMd5[0] == 0)
        return *this ;
    for (x = 0 ; x < 4 ; x++)
    {
        for (val = 0, i = pMd5, n = 0 ; *i && n < 8 ; i++, n++)
        {
            val *= 16 ;
            if      (*i >= '0' && *i <= '9')    val += (*i - '0') ;
            else if (*i >= 'A' && *i <= 'F')    { val += 10 ; val += (*i - 'A') ; }
            else if (*i >= 'a' && *i <= 'f')    { val += 10 ; val += (*i - 'a') ; }
            else
                break ;
        }
        if (n < 8)
        {
            //  Incorrect length
            break ;
        }
        m_Parts[x] = val ;
    }
    if (x < 4)
        m_Parts[0] = m_Parts[1] = m_Parts[2] = m_Parts[3] = 0 ;
    return *this ;
}
hzMD5&  hzMD5::operator=    (const hzString& S)
{
    if (S.Length() == 32)
        operator=(*S) ;
    else
        m_Parts[0] = m_Parts[1] = m_Parts[2] = m_Parts[3] = 0 ;
    return *this ;
}
bool    hzMD5::operator==   (const hzMD5& op) const
{
    return m_Parts[0] == op.m_Parts[0] && m_Parts[1] == op.m_Parts[1] && m_Parts[2] == op.m_Parts[2] && m_Parts[3] == op.m_Parts[3] ? true : false ;
}
bool    hzMD5::operator!=   (const hzMD5& op) const
{
    return m_Parts[0] == op.m_Parts[0] && m_Parts[1] == op.m_Parts[1] && m_Parts[2] == op.m_Parts[2] && m_Parts[3] == op.m_Parts[3] ? false : true ;
}
const char* hzMD5::Txt  (void) const
{
    //  Category:   Codec
    //
    //  Present MD5 as a string
    //
    //  Arguments:  None
    //  Returns:    Instance of hzString with the MD5 as 32-char hex
    _hzfunc("hzMD5::Txt") ;
    char*   pBuf ;      //  Working buffer
    pBuf = _thisfn.ScratchPad(40) ;
    sprintf(pBuf, "%08x%08x%08x%08x", m_Parts[0], m_Parts[1], m_Parts[2], m_Parts[3]) ;
    return pBuf ;
}
hzEcode hzMD5::CalcMD5File  (const char* filepath)
{
    //  Category:   Codec
    //
    //  Please note the following legal notice. This function is derived from the RSA Data Security, Inc. MD5 Message-Digest Algorithm
    //
    //  Calculate the MD5 digest for the supplied file
    //
    //  Arguments:  1)  filepath    The file to be hashed
    //
    //  Returns:    E_ARGUMENT  If the filepath is not supplied
    //              E_NOTFOUND  If the filepath specified does not exist as a directory entry of any kind
    //              E_TYPE      If the filepath names a directory entry that is not a file
    //              E_NODATA    If the filepath is a file but empty
    //              E_OPENFAIL  If the file specified could not be opened for reading
    //              E_OK        If this MD5 value is calculated
    _hzfunc("hzMD5::CalcMD5(file)") ;
    ifstream    is ;            //  Input stream
    _md5_unit   md5Unit ;       //  MD5 encoding unit
    uchar*      workBuf ;       //  Working data buffer
    int32_t     n ;             //  Buffer limit
    hzEcode     rc ;            //  Return code
    rc = OpenInputStrm(is, filepath) ;
    if (rc != E_OK)
        return rc ;
    n = 0 ;
    workBuf = new uchar[8196] ;
    for (;;)
    {
        is.read((char*) (workBuf + n), 4096) ;
        if (!is.gcount())
            break ;
        n += is.gcount() ;
        if (n & 0x3f)
            continue ;
        //s = _xlate_md5(workBuf, n, 0, md5Unit) ;
        _xlate_md5(workBuf, n, 0, md5Unit) ;
        n = 0 ;
    }
    //_xlate_md5(workBuf, n, m_md5val, md5Unit) ;
    _xlate_md5(workBuf, n, (uchar*) m_Parts, md5Unit) ;
    delete [] workBuf ;
    is.close()  ;
    return E_OK ;
}
hzEcode hzMD5::CalcMD5  (const hzChain& Z)
{
    //  Category:   Codec
    //
    //  Please note the following legal notice. This function is derived from the RSA Data Security, Inc. MD5 Message-Digest Algorithm
    //
    //  Calculate the MD5 digest for the supplied file
    //
    //  Arguments:  1)  filepath    The file to be hashed
    //
    //  Returns:    E_NODATA    If the input chain is empty
    //              E_OK        If this MD5 value is calculated
    _hzfunc("hzMD5::CalcMD5(hzChain)") ;
    _md5_unit   md5Unit ;       //  MD5 encoding unit
    chIter      zi ;            //  Input chain iterator
    uchar*      buf ;           //  Working data buffer
    uint32_t    nBytes ;        //  No of bytes extracted from input chain
    if (!this)
        hzexit(E_CORRUPT, "No instance") ;
    if (!Z.Size())
        return E_NODATA ;
    zi = Z ;
    //s = nil ;
    nBytes = 0 ;
    buf = new uchar[8196] ;
    for (;;)
    {
        nBytes = zi.Write(buf, 4096) ;
        if (!nBytes)
            break ;
        zi += nBytes ;
        _xlate_md5(buf, nBytes, 0, md5Unit) ;
    }
    _xlate_md5(buf, nBytes, (uchar*) m_Parts, md5Unit) ;
    delete [] buf ;
    return E_OK ;
}
hzEcode hzMD5::CalcMD5  (const char* cstr)
{
    //  Category:   Codec
    //
    //  Please note the following legal notice. This function is derived from the RSA Data Security, Inc. MD5 Message-Digest Algorithm.
    //
    //  Calculate the MD5 digest for the supplied null terminated string.
    //
    //  Argument:   cstr    The string to be hashed
    //
    //  Returns:    E_ARGUMENT  If no input cstr is supplied
    //              E_OK        If this MD5 value is calculated
    _hzfunc("hzMD5::CalcMD5(cstr)") ;
    _md5_unit   md5Unit ;   //  MD5 encoding unit
    uchar*      buf ;       //  Working data buffer
    uint32_t    len ;       //  String length
    if (!this)
        hzexit(E_CORRUPT, "No instance") ;
    if (!cstr || !cstr[0])
        return E_NODATA ;
    len = strlen(cstr) ;
    //s = nil ;
    buf = new uchar[(len * 2) + 4] ;
    strcpy((char*) buf, cstr) ;
    _xlate_md5(buf, len, (uchar*) m_Parts, md5Unit) ;
    delete [] buf ;
    return E_OK ;
}