From 0f2f040307d8c1de698497ea2db080eef602c3c6 Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 25 Dec 2018 21:02:49 -0500 Subject: [PATCH] ezpwd --- op25/gr-op25_repeater/lib/ezpwd/asserter | 128 ++ op25/gr-op25_repeater/lib/ezpwd/bch | 485 ++++++ op25/gr-op25_repeater/lib/ezpwd/bch_base | 219 +++ op25/gr-op25_repeater/lib/ezpwd/corrector | 506 +++++++ op25/gr-op25_repeater/lib/ezpwd/definitions | 9 + op25/gr-op25_repeater/lib/ezpwd/ezcod | 725 +++++++++ op25/gr-op25_repeater/lib/ezpwd/output | 344 +++++ op25/gr-op25_repeater/lib/ezpwd/rs | 168 +++ op25/gr-op25_repeater/lib/ezpwd/rs_base | 1344 +++++++++++++++++ op25/gr-op25_repeater/lib/ezpwd/serialize | 1188 +++++++++++++++ .../lib/ezpwd/serialize_definitions | 57 + op25/gr-op25_repeater/lib/ezpwd/timeofday | 73 + 12 files changed, 5246 insertions(+) create mode 100644 op25/gr-op25_repeater/lib/ezpwd/asserter create mode 100644 op25/gr-op25_repeater/lib/ezpwd/bch create mode 100644 op25/gr-op25_repeater/lib/ezpwd/bch_base create mode 100644 op25/gr-op25_repeater/lib/ezpwd/corrector create mode 100644 op25/gr-op25_repeater/lib/ezpwd/definitions create mode 100644 op25/gr-op25_repeater/lib/ezpwd/ezcod create mode 100644 op25/gr-op25_repeater/lib/ezpwd/output create mode 100644 op25/gr-op25_repeater/lib/ezpwd/rs create mode 100644 op25/gr-op25_repeater/lib/ezpwd/rs_base create mode 100644 op25/gr-op25_repeater/lib/ezpwd/serialize create mode 100644 op25/gr-op25_repeater/lib/ezpwd/serialize_definitions create mode 100644 op25/gr-op25_repeater/lib/ezpwd/timeofday diff --git a/op25/gr-op25_repeater/lib/ezpwd/asserter b/op25/gr-op25_repeater/lib/ezpwd/asserter new file mode 100644 index 0000000..5e0a19e --- /dev/null +++ b/op25/gr-op25_repeater/lib/ezpwd/asserter @@ -0,0 +1,128 @@ +#ifndef _EZPWD_ASSERTER +#define _EZPWD_ASSERTER + +#include +#include +#include +#include +#include + +namespace ezpwd { + +#define ISEQUAL( ... ) isequal(__FILE__, __LINE__, __VA_ARGS__ ) +#define ISTRUE( ... ) istrue( __FILE__, __LINE__, __VA_ARGS__ ) +#define ISFALSE( ... ) isfalse(__FILE__, __LINE__, __VA_ARGS__ ) +#define ISNEAR( ... ) isnear( __FILE__, __LINE__, __VA_ARGS__ ) +#define FAILURE( ... ) failure(__FILE__, __LINE__, __VA_ARGS__ ) + + struct asserter { + bool failed; // The last test failed + int failures; // Total number of failures + std::string out; // Last failure + + asserter() + : failed( false ) + , failures( 0 ) + , out() + { + ; + } + + // + // output( ) -- Output description of last failed test (or nothing if successful) + // << + // + std::ostream &output( + std::ostream &lhs ) + const + { + return lhs << out; + } + + // + // (bool) -- Return status of last test + // + operator bool() + { + return failed; + } + + template < typename T > + asserter &istrue( const char *file, int line, const T &a, const std::string &comment = std::string() ) + { + return isequal( file, line, !!a, true, comment ); + } + + template < typename T > + asserter &isfalse( const char *file, int line, const T &a, const std::string &comment = std::string() ) + { + return isequal( file, line, !!a, false, comment ); + } + + template < typename T > + asserter &isequal( const char *file, int line, const T &a, const T &b, const std::string &comment = std::string() ) + { + if ( ! ( a == b )) { + std::ostringstream oss; + oss << a << " != " << b; + return failure( file, line, oss.str(), comment ); + } + return success(); + } + + template < typename T > + asserter &isnear( const char *file, int line, const T &a, const T &b, const T &delta, const std::string &comment = std::string() ) + { + T difference; + difference = ( a < b + ? T( b - a ) + : T( a - b )); + if ( ! ( difference < ( delta < T( 0 ) ? T( -delta ) : T( delta )))) { + std::ostringstream oss; + oss << std::setprecision( 13 ) << a << " != " << b << " +/- " << delta; + return failure( file, line, oss.str(), comment ); + } + return success(); + } + + asserter &failure( const char *file, int line, const std::string &comparison, + const std::string &comment = std::string() ) + { + ++failures; + const char *needle = "/"; + const char *slash = std::find_end( file, file + strlen( file ), + needle, needle + strlen( needle )); + if ( slash == file + strlen( file )) + slash = file; + else + slash += 1; + + std::ostringstream oss; + oss + << std::setw( 24 ) << slash << ", " + << std::setw( -5 ) << line + << "; FAILURE: " << comparison + << ( comment.size() ? ": " : "" ) << comment + << std::endl; + out = oss.str(); + failed = true; + return *this; + } + + asserter &success() + { + out.clear(); + failed = false; + return *this; + } + }; // class asserter +} // namespace ezpwd + +std::ostream &operator<<( + std::ostream &lhs, + ezpwd::asserter &rhs ) +{ + return rhs.output( lhs ); +} + +#endif // _EZPWD_ARRAY diff --git a/op25/gr-op25_repeater/lib/ezpwd/bch b/op25/gr-op25_repeater/lib/ezpwd/bch new file mode 100644 index 0000000..eb45db6 --- /dev/null +++ b/op25/gr-op25_repeater/lib/ezpwd/bch @@ -0,0 +1,485 @@ +/* + * Ezpwd Reed-Solomon -- Reed-Solomon encoder / decoder library + * + * Copyright (c) 2017, Hard Consulting Corporation. + * + * Ezpwd Reed-Solomon is free software: you can redistribute it and/or modify it under the terms of + * the GNU General Public License as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. See the LICENSE file at the top of the + * source tree. Ezpwd Reed-Solomon is also available under Commercial license. The Djelic BCH code + * under djelic/ and the c++/ezpwd/bch_base wrapper is redistributed under the terms of the GPLv2+, + * regardless of the overall licensing terms. + * + * Ezpwd Reed-Solomon 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 General Public License for more details. + */ +#ifndef _EZPWD_BCH +#define _EZPWD_BCH + +#include +#include "rs_base" // Basic DEBUG, EZPWD_... preprocessor stuff, ezpwd::log_, etc. +#include "bch_base" + +namespace ezpwd { + // + // ezpwd::bch_base -- Interface to underlying Djelic Linux Kernel API + // ezpwd::bch -- General BCH codec types; min. CORRECTION capacity, undefined PAYLOAD + // + // These implementations retain the original Djelic Linux Kernel API; specifically, they find + // a BCH codec of the given Galois order M (ie. has codewords of size 2**M-1), and at least the + // target bit-error correction capacity T. They may correct more than T errors, and the number + // of parity ECC bits will be selected by the algorithm. You need to compute the maximum + // non-parity payload by computing _bch->n - _bch->ecc_bits. + // + // The data and parity bits must always be on separate blocks of int8_t/uint8_t-sized data + // in the container. This is required because the underlying API must break the data and parity + // out as separate arrays for processing. So, if the computed ecc_bits is not evenly divisible + // by 8, some care must be taken to ensure that it is packed into exactly ecc_bytes of data at + // the end of the supplied container. Alternatively, it can be kept in a separate container. + // This is probably safest, as the bch_base/bch/BCH classes will never attempt to resize the + // data/parity containers when supplied separately. + // + // Like the Reed-Solomon APIs, the bch_base/bch/BCH APIs will alter the size of a variable + // container in encode(...), to add the BCH ECC "parity" data (eg. std::vector, std::string). + // Fixed containers (eg. std::array) are never resized, and it is assumed that ecc_bytes of + // parity data exist at the end of the container. + // + class bch_base { + public: + ezpwd::bch_control *_bch; + + bch_base( const bch_base & ) = delete; // no copy-constructor + + bch_base( + size_t m, + size_t t, + unsigned int prim_poly = 0 ) + : _bch( ezpwd::init_bch( int( m ), int( t ), prim_poly )) + { + ; + } + + virtual ~bch_base() + { + ezpwd::free_bch( this->_bch ); + } + + size_t ecc_bytes() + const + { + return _bch->ecc_bytes; + } + size_t ecc_bits() + const + { + return _bch->ecc_bits; + } + + size_t t() + const + { + return _bch->t; + } + + // + // << bch_base -- output codec in standard BCH( N, N-ECC, T ) form + // + virtual std::ostream &output( + std::ostream &lhs ) + const + { + return lhs << *this->_bch; + } + + // + // encode -- container interfaces + // + // Returns number of ECC *bits* initialized (to be consistent w/ decode, which returns + // number of bit errors corrected). + // + int encode( + std::string &data ) + const + { + typedef uint8_t uT; + typedef std::pair + uTpair; + data.resize( data.size() + ecc_bytes() ); + return encode( uTpair( (uT *)&data.front(), (uT *)&data.front() + data.size() )); + } + + int encode( + const std::string &data, + std::string &parity ) + const + { + typedef uint8_t uT; + typedef std::pair + cuTpair; + typedef std::pair + uTpair; + parity.resize( ecc_bytes() ); + return encode( cuTpair( (const uT *)&data.front(), (const uT *)&data.front() + data.size() ), + uTpair( (uT *)&parity.front(), (uT *)&parity.front() + parity.size() )); + } + + template < typename T > + int encode( + std::vector &data ) + const + { + typedef typename std::make_unsigned::type + uT; + typedef std::pair + uTpair; + data.resize( data.size() + ecc_bytes() ); + return encode( uTpair( (uT *)&data.front(), (uT *)&data.front() + data.size() )); + } + + template < typename T > + int encode( + const std::vector&data, + std::vector &parity ) + const + { + typedef typename std::make_unsigned::type + uT; + typedef std::pair + cuTpair; + typedef std::pair + uTpair; + parity.resize( ecc_bytes() ); + return encode( cuTpair( (uT *)&data.front(), (uT *)&data.front() + data.size() ), + uTpair( (uT *)&parity.front(), (uT *)&parity.front() + parity.size() )); + } + + template < typename T, size_t N > + int encode( + std::array &data, + int pad = 0 ) // ignore 'pad' symbols at start of array + const + { + typedef typename std::make_unsigned::type + uT; + typedef std::pair + uTpair; + return encode( uTpair( (uT *)&data.front() + pad, (uT *)&data.front() + data.size() )); + } + + + // + // encode -- denote data+parity or data, parity using pairs of uint8_t iterators + // encode -- base implementation, in terms of uint8_t pointers + // + virtual int encode( + const std::pair + &data ) + const + { + return encode( data.first, data.second - data.first - ecc_bytes(), data.second - ecc_bytes() ); + } + + virtual int encode( + const std::pair + &data, + const std::pair + &parity ) + const + { + if ( size_t( parity.second - parity.first ) != ecc_bytes() ) { + EZPWD_RAISE_OR_RETURN( std::runtime_error, "BCH: parity length incompatible with number of ECC bytes", -1 ); + } + return encode( data.first, data.second - data.first, parity.first ); + } + + virtual int encode( + const uint8_t *data, + size_t len, + uint8_t *parity ) + const + { + memset( parity, 0, ecc_bytes() ); // Djelic encode_bch requires ECC to be initialized to 0 + ezpwd::encode_bch( this->_bch, data, len, parity ); + return int( ecc_bits() ); + } + + // + // decode -- container interface, w/ optional corrected bit-error positions reported + // + // Does not correct errors in parity! + // + int decode( + std::string &data, + std::vector *position= 0 ) + const + { + typedef uint8_t uT; + typedef std::pair + uTpair; + return decode( uTpair( (uT *)&data.front(), (uT *)&data.front() + data.size() ), + position ); + } + + int decode( + std::string &data, + std::string &parity, + std::vector *position= 0 ) + const + { + typedef uint8_t uT; + typedef std::pair + uTpair; + return decode( uTpair( (uT *)&data.front(), (uT *)&data.front() + data.size() ), + uTpair( (uT *)&parity.front(), (uT *)&parity.front() + parity.size() ), + position ); + } + + template < typename T > + int decode( + std::vector &data, + std::vector *position= 0 ) + const + { + typedef typename std::make_unsigned::type + uT; + typedef std::pair + uTpair; + return decode( uTpair( (uT *)&data.front(), (uT *)&data.front() + data.size() ), + position ); + } + + template < typename T > + int decode( + std::vector &data, + std::vector &parity, + std::vector *position= 0 ) + const + { + typedef typename std::make_unsigned::type + uT; + typedef std::pair + uTpair; + return decode( uTpair( (uT *)&data.front(), (uT *)&data.front() + data.size() ), + uTpair( (uT *)&parity.front(), (uT *)&parity.front() + parity.size() ), + position ); + } + + template < typename T, size_t N > + int decode( + std::array &data, + int pad = 0, // ignore 'pad' symbols at start of array + std::vector *position= 0 ) + const + { + typedef typename std::make_unsigned::type + uT; + typedef std::pair + uTpair; + return decode( uTpair( (uT *)&data.front() + pad, (uT *)&data.front() + data.size() ), + position ); + } + + // + // decode -- denote data+parity or data, parity using pairs of uint8_t iterators + // decode -- decode and correct BCH codeword, returning number of corrections, or -1 if failed + // + // Corrects data in-place (unlike the basic Djelic Linux Kernel API, which only returns + // error positions. For consistency with ezpwd::rs..., we report all error positions as + // std::vector, even though the underlying Djelic API reports them as arrays of + // unsigned int. + // + virtual int decode( + const std::pair + &data, + std::vector *position= 0 ) + const + { + return decode( data.first, data.second - data.first - ecc_bytes(), data.second - ecc_bytes(), + position ); + } + + virtual int decode( + const std::pair + &data, + const std::pair + &parity, + std::vector *position= 0 ) + const + { + if ( size_t( parity.second - parity.first ) != ecc_bytes() ) { + EZPWD_RAISE_OR_RETURN( std::runtime_error, "BCH: parity length incompatible with number ECC bytes", -1 ); + } + return decode( data.first, data.second - data.first, parity.first, + position ); + } + virtual int decode( + uint8_t *data, + size_t len, + uint8_t *parity, + std::vector *position= 0 ) + const + { + if ( position ) + position->resize( t() * 2 ); // may be able to correct beyond stated capacity! + int corrects = ezpwd::correct_bch( + this->_bch, data, len, parity, 0, 0, + position ? (unsigned int *)&(*position)[0] : 0 ); + if ( position && corrects >= 0 ) + position->resize( corrects ); + return corrects; + } + + // + // {en,de}coded -- returns an encoded/corrected copy of the provided container + // + // NOTE: + // + // Must return exceptions on failure; If exceptions inhibited, returns a + // default-constructed instance of the supplied data container. This may be sufficient to + // reliably deduce failure; if not, this interface should not be used. + // + // Overloads decoded to also allow recovery of corrected error positions and count. + // + template + C encoded( + C data ) + const + { + if ( encode( data ) < 0 ) + EZPWD_RAISE_OR_RETURN( std::runtime_error, "BCH: Could not encode data", C() ); + return data; + } + + template + C decoded( + C data ) + const + { + if ( decode( data ) < 0 ) + EZPWD_RAISE_OR_RETURN( std::runtime_error, "BCH: Could not decode data", C() ); + return data; + } + + template + C decoded( + C data, + std::vector &position ) + const + { + if ( decode( data, &position ) < 0 ) + EZPWD_RAISE_OR_RETURN( std::runtime_error, "BCH: Could not decode data", C() ); + return data; + } + + }; // class bch_base + + template < size_t SYMBOLS, size_t CORRECTION > + class bch + : public bch_base + { + public: + bch() + : bch_base( ezpwd::log_::value, CORRECTION ) + { + ; + } + + virtual ~bch() + { + ; + } + }; // class bch + + // + // std::ostream << ezpwd::bch_base + // + // Output a BCH codec description in standard form eg. BCH( 255, 239, 2 ) + // + inline + std::ostream &operator<<( + std::ostream &lhs, + const ezpwd::bch_base + &rhs ) + { + return rhs.output( lhs ); + } + + // + // ezpwd::BCH -- Standard BCH codec types + // + // Specify and create a standard BCH codec with exactly the specified capacities. We create + // the undering BCH codec using SYMBOLS and CORRECTION capacity; the actual correction capacity + // T, the number of PARITY bits and hence PAYLOAD (CAPACITY - PARITY) is selected automatically + // by the underlying Djelic Linux Kernel BCH codec API. For this interface, we demand that the + // caller *knows* all of these values at compile time, however, mostly for future optimization + // purposes. We validate them, and fail the constructor if they don't match. See bch_test for + // an enumeration of all possible BCH codecs. + // + // In the future, this API may be re-implemented to not use the generic BCH API, but a more + // optimized locally-defined implementation that leverages the fixed SYMBOLS, PAYLOAD and + // CORRECTION capacities to produce more optimal code. + // + template < size_t SYMBOLS, size_t PAYLOAD, size_t CORRECTION > + class BCH + : public bch + { + public: + static const size_t M = ezpwd::log_::value; // Galois field order; eg. 255 --> 8 + static const size_t N = SYMBOLS; + static const size_t T = CORRECTION; + static const size_t LOAD = PAYLOAD; + + BCH() + : bch() + { + if ( this->_bch->t != T || this->_bch->n != N + || this->_bch->n - this->_bch->ecc_bits != LOAD ) { + std::ostringstream err; + this->output( err ) + << " specified doesn't match underlying " << *this->_bch << " produced."; + EZPWD_RAISE_OR_ABORT( std::runtime_error, err.str().c_str() ); + } + } + + virtual ~BCH() + { + ; + } + + // + // << BCH<...> -- output codec in standard BCH( N, N-ECC, T ) form + // + virtual std::ostream &output( + std::ostream &lhs ) + const + { + return lhs + << "BCH( " << std::setw( 3 ) << N + << ", " << std::setw( 3 ) << LOAD + << ", " << std::setw( 3 ) << T + << " )"; + } + }; // class BCH + + + // + // std::ostream << ezpwd::BCH<...> + // + // Output a BCH codec description in standard form eg. BCH( 255, 239, 2 ) + // + // NOTE: clang/gcc disagree on the scoping of operator<< template/non-template functions... + // + template + inline + std::ostream &operator<<( + std::ostream &lhs, + const ezpwd::BCH + &rhs ) + { + return rhs.output( lhs ); + } + +} // namespace ezpwd + +#endif // _EZPWD_BCH diff --git a/op25/gr-op25_repeater/lib/ezpwd/bch_base b/op25/gr-op25_repeater/lib/ezpwd/bch_base new file mode 100644 index 0000000..0aa0592 --- /dev/null +++ b/op25/gr-op25_repeater/lib/ezpwd/bch_base @@ -0,0 +1,219 @@ +/* + * Ezpwd Reed-Solomon -- Reed-Solomon encoder / decoder library + * + * Copyright (c) 2017, Hard Consulting Corporation. + * + * Ezpwd Reed-Solomon is free software: you can redistribute it and/or modify it under the terms of + * the GNU General Public License as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. See the LICENSE file at the top of the + * source tree. Ezpwd Reed-Solomon is also available under Commercial license. The Djelic BCH code + * under djelic/ and the c++/ezpwd/bch_base wrapper is redistributed under the terms of the GPLv2+, + * regardless of the overall licensing terms. + * + * Ezpwd Reed-Solomon 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 General Public License for more details. + */ +#ifndef _EZPWD_BCH_BASE +#define _EZPWD_BCH_BASE + +#include +#include +#include + +// +// Presently, we simply import the Linux Kernel's "C" BCH API directly into the ezpwd:: namespace In +// order to compile, you must (at least) run "make djelic" in the base directory of the +// https://github.com/pjkundert/bch.git repo. +// +namespace ezpwd { + /** + * struct bch_control - BCH control structure + * @m: Galois field order + * @n: maximum codeword size in bits (= 2^m-1) + * @t: error correction capability in bits + * @ecc_bits: ecc exact size in bits, i.e. generator polynomial degree (<=m*t) + * @ecc_bytes: ecc max size (m*t bits) in bytes + * @a_pow_tab: Galois field GF(2^m) exponentiation lookup table + * @a_log_tab: Galois field GF(2^m) log lookup table + * @mod8_tab: remainder generator polynomial lookup tables + * @ecc_buf: ecc parity words buffer + * @ecc_buf2: ecc parity words buffer + * @xi_tab: GF(2^m) base for solving degree 2 polynomial roots + * @syn: syndrome buffer + * @cache: log-based polynomial representation buffer + * @elp: error locator polynomial + * @poly_2t: temporary polynomials of degree 2t + */ + + /** + * init_bch - initialize a BCH encoder/decoder + * @m: Galois field order, should be in the range 5-15 + * @t: maximum error correction capability, in bits + * @prim_poly: user-provided primitive polynomial (or 0 to use default) + * + * Returns: + * a newly allocated BCH control structure if successful, NULL otherwise + * + * This initialization can take some time, as lookup tables are built for fast + * encoding/decoding; make sure not to call this function from a time critical + * path. Usually, init_bch() should be called on module/driver init and + * free_bch() should be called to release memory on exit. + * + * You may provide your own primitive polynomial of degree @m in argument + * @prim_poly, or let init_bch() use its default polynomial. + * + * Once init_bch() has successfully returned a pointer to a newly allocated + * BCH control structure, ecc length in bytes is given by member @ecc_bytes of + * the structure. + */ + + /** + * encode_bch - calculate BCH ecc parity of data + * @bch: BCH control structure + * @data: data to encode + * @len: data length in bytes + * @ecc: ecc parity data, must be initialized by caller + * + * The @ecc parity array is used both as input and output parameter, in order to + * allow incremental computations. It should be of the size indicated by member + * @ecc_bytes of @bch, and should be initialized to 0 before the first call. + * + * The exact number of computed ecc parity bits is given by member @ecc_bits of + * @bch; it may be less than m*t for large values of t. + */ + + /** + * decode_bch - decode received codeword and find bit error locations + * @bch: BCH control structure + * @data: received data, ignored if @calc_ecc is provided + * @len: data length in bytes, must always be provided + * @recv_ecc: received ecc, if NULL then assume it was XORed in @calc_ecc + * @calc_ecc: calculated ecc, if NULL then calc_ecc is computed from @data + * @syn: hw computed syndrome data (if NULL, syndrome is calculated) + * @errloc: output array of error locations + * + * Returns: + * The number of errors found, or -EBADMSG if decoding failed, or -EINVAL if + * invalid parameters were provided + * + * Depending on the available hw BCH support and the need to compute @calc_ecc + * separately (using encode_bch()), this function should be called with one of + * the following parameter configurations - + * + * by providing @data and @recv_ecc only: + * decode_bch(@bch, @data, @len, @recv_ecc, NULL, NULL, @errloc) + * + * by providing @recv_ecc and @calc_ecc: + * decode_bch(@bch, NULL, @len, @recv_ecc, @calc_ecc, NULL, @errloc) + * + * by providing ecc = recv_ecc XOR calc_ecc: + * decode_bch(@bch, NULL, @len, NULL, ecc, NULL, @errloc) + * + * by providing syndrome results @syn: + * decode_bch(@bch, NULL, @len, NULL, NULL, @syn, @errloc) + * + * Once decode_bch() has successfully returned with a positive value, error + * locations returned in array @errloc should be interpreted as follows - + * + * if (errloc[n] >= 8*len), then n-th error is located in ecc (no need for + * data correction) + * + * if (errloc[n] < 8*len), then n-th error is located in data and can be + * corrected with statement data[errloc[n]/8] ^= 1 << (errloc[n] % 8); + * + * Note that this function does not perform any data correction by itself, it + * merely indicates error locations. + */ + + /** + * init_bch - initialize a BCH encoder/decoder + * @m: Galois field order, should be in the range 5-15 + * @t: maximum error correction capability, in bits + * @prim_poly: user-provided primitive polynomial (or 0 to use default) + * + * Returns: + * a newly allocated BCH control structure if successful, NULL otherwise + * + * This initialization can take some time, as lookup tables are built for fast + * encoding/decoding; make sure not to call this function from a time critical + * path. Usually, init_bch() should be called on module/driver init and + * free_bch() should be called to release memory on exit. + * + * You may provide your own primitive polynomial of degree @m in argument + * @prim_poly, or let init_bch() use its default polynomial. + * + * Once init_bch() has successfully returned a pointer to a newly allocated + * BCH control structure, ecc length in bytes is given by member @ecc_bytes of + * the structure. + */ + + extern "C" { + #include "../../djelic_bch.h" + } + + // + // correct_bch -- corrects data (but not parity!), as suggested by decode_bch, above + // + // A convenience interface that defaults all of the not strictly required parameters, and + // automatically corrects bit-errors in data *and* the supplied parity. Does not attempt to + // correct bit errors found in the parity data. If not supplied, 'errloc' is allocated + // internally; otherwise, it is assumed to be of at least size bch->t (the minimum error + // correction capacity of the BCH codec). + // + // However, beware -- at larger values of T, the actual correction capacity of the BCH codec + // could be greater than the requested T. Therefore, it is recommended that you always supply a + // larger than required errloc array; recommend T*2? + // + inline + int correct_bch( + struct bch_control *bch, + uint8_t *data, + unsigned int len, + uint8_t *recv_ecc, + const uint8_t *calc_ecc= 0, + const unsigned int *syn = 0, + unsigned int *errloc = 0 ) // must be sized at least bch->t; often, greater! + { + unsigned int _errloc[511]; // much larger than the correction capacity of largest supported BCH codec + if ( ! errloc ) + errloc = _errloc; + int err = decode_bch( bch, data, len, recv_ecc, calc_ecc, syn, errloc ); + if ( err > 0 ) { + // A +'ve number of bit-error correction locations were found + for ( int n = 0; n < err; ++n ) { + /** + * if (errloc[n] < 8*len), then n-th error is located in data and can be corrected + * with statement data[errloc[n]/8] ^= 1 << (errloc[n] % 8). If in the parity, it + * is assumed to be located at the end of the data, so offset by 'len' bytes. + */ + if ( errloc[n] < 8*len ) { + data[errloc[n] / 8] ^= 1 << ( errloc[n] % 8 ); + } else if ( recv_ecc && errloc[n] < 8 * len + 8 * bch->ecc_bytes ) { + recv_ecc[errloc[n] / 8 - len] + ^= 1 << ( errloc[n] % 8 ); + } + } + } + return err; + } + + // + // << -- output codec in standard BCH( N, N-ECC, T ) form + // + inline + std::ostream &operator<<( + std::ostream &lhs, + const ezpwd::bch_control + &bch ) + { + return lhs + << "BCH( " << std::setw( 3 ) << bch.n + << ", " << std::setw( 3 ) << bch.n - bch.ecc_bits + << ", " << std::setw( 3 ) << bch.t + << " )"; + } + +} // namespace ezpwd + +#endif // _EZPWD_BCH_BASE diff --git a/op25/gr-op25_repeater/lib/ezpwd/corrector b/op25/gr-op25_repeater/lib/ezpwd/corrector new file mode 100644 index 0000000..23a36a0 --- /dev/null +++ b/op25/gr-op25_repeater/lib/ezpwd/corrector @@ -0,0 +1,506 @@ +/* + * Ezpwd Reed-Solomon -- Reed-Solomon encoder / decoder library + * + * Copyright (c) 2014, Hard Consulting Corporation. + * + * Ezpwd Reed-Solomon is free software: you can redistribute it and/or modify it under the terms of + * the GNU General Public License as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. See the LICENSE file at the top of the + * source tree. Ezpwd Reed-Solomon is also available under Commercial license. c++/ezpwd/rs_base + * is redistributed under the terms of the LGPL, regardless of the overall licensing terms. + * + * Ezpwd Reed-Solomon 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 General Public License for more details. + */ +#ifndef _EZPWD_CORRECTOR +#define _EZPWD_CORRECTOR + +#include "rs" +#include "serialize" + +namespace ezpwd { + + // + // best_avg -- collect , guesses, and return the unambiguous best one + // + typedef std::map> // (, (, )) + best_avg_base_t; + class best_avg + : public best_avg_base_t + { + public: + using best_avg_base_t::begin; + using best_avg_base_t::end; + using best_avg_base_t::insert; + using best_avg_base_t::find; + using best_avg_base_t::iterator; + using best_avg_base_t::const_iterator; + using best_avg_base_t::value_type; + using best_avg_base_t::mapped_type; + // + // add -- add the given pct to the current average for str + // + iterator add( + const std::string &str, + int pct ) + { + iterator i = find( str ); + if ( i == end() ) + i = insert( i, value_type( str, mapped_type() )); + i->second.first += 1; + i->second.second += pct; + return i; + } + + // + // best -- return the unambiguously best value (average is >, or == but longer), or end() + // + const_iterator best() + const + { + const_iterator top = end(); + bool uni = false; + for ( const_iterator i = begin(); i != end(); ++i ) { + if ( top == end() + or i->second.second/i->second.first > top->second.second/top->second.first + or ( i->second.second/i->second.first == top->second.second/top->second.first + and i->first.size() > top->first.size())) { + top = i; + uni = true; + } else if ( i->second.second/i->second.first == top->second.second/top->second.first + and i->first.size() == top->first.size()) { + uni = false; + } + } + return uni ? top : end(); + } + + // + // evaluation -- process a (,(,)) into (,) + // sort -- return a multimap indexed by --> + // output -- output the : , sorted by average + // + static std::pair + evaluation( const value_type &val ) + { + return std::pair( val.second.second/val.second.first, val.first ); + } + typedef std::multimap + sorted_t; + sorted_t sort() + const + { + sorted_t dst; + std::transform( begin(), end(), std::inserter( dst, dst.begin() ), evaluation ); + return dst; + } + std::ostream &output( + std::ostream &lhs ) + const + { + for ( auto i : sort() ) + lhs << std::setw( 16 ) << i.second + << ": " << std::setw( 3 ) << i.first + << std::endl; + return lhs; + } + }; +} // namespace ezpwd + +std::ostream &operator<<( + std::ostream &lhs, + const ezpwd::best_avg &rhs ) +{ + return rhs.output( lhs ); +} + +namespace ezpwd { + // + // ezpwd::corrector -- Apply statistical corrections to a string, returning the confidence + // + // All methods are static; no instance is required, as this is primarily used to create + // external language APIs. + // + template < + size_t PARITY, + size_t N = 64, + typename SERIAL = serialize::base< N, serialize::ezpwd< N >>> + class corrector { + public: + static + std::ostream &output( + std::ostream &lhs ) + { + lhs << "corrector"; + return lhs; + } + + // + // parity() -- Returns 'PARITY' base-N symbols of R-S parity to the supplied password + // + static std::string parity( + const std::string &password ) + { + std::string parity; + rscodec.encode( password, parity ); + SERIAL::encode( parity ); + return parity; + } + + // + // encode() -- append PARITY base-N parity symbols to password + // + // The supplied password buffer size must be sufficient to contain PARITY additional + // symbols, plus the terminating NUL. Returns the resultant encoded password size + // (excluding the NUL). + // + static size_t encode( + std::string &password ) + { + password += parity( password ); + return password.size(); + } + + static size_t encode( + char *password, + size_t size ) // maximum available size + { + size_t len = ::strlen( password ); // length w/o terminating NUL + if ( len + PARITY + 1 > size ) + throw std::runtime_error( "ezpwd::rspwd::encode password buffer has insufficient capacity" ); + std::string par = parity( std::string( password, password + len )); + if ( par.size() != PARITY ) + throw std::runtime_error( "ezpwd::rspwd::encode computed parity with incorrect size" ); + std::copy( par.begin(), par.end(), password + len ); + len += PARITY; + password[len] = 0; + return len; + } + + // + // decode([,...]) -- Applies R-S error correction on the encoded string, removing parity + // + // Up to 'PARITY' Reed-Solomon parity symbols are examined, to determine if the supplied + // string is a valid R-S codeword and hence very likely to be correct. Optionally supply a + // vector of erasure positions. + // + // An optional 'minimum' final password length may be provided; no R-S parity is assumed + // to exist in the first 'minimum' password characters (default: PARITY). This prevents + // accidentally finding valid R-S codewords in passwords of known minimum length; validation + // codes, for example. Likewise, the optional 'maximum' allows us to limit the number of + // parity symbols that may be assumed to be missing from the end of the codeword. + // + // Returns a confidence strength rating, which is the ratio: + // + // 100 - ( errors * 2 + erasures ) * 100 / parity + // + // if an R-S codeword was solved, and 0 otherwise. If a codeword is solved, but the number + // of errors and erasures corrected indicates that all parity was consumed, the caller may + // opt to not use the corrected string, because there is a chance that our R-S polynomial + // was overwhelmed with errors and actually returned an incorrect codeword. Therefore, + // solving a codeword using all available parity results in 100 - PARITY * 100 / PARITY == + // 0, which indicates that there is no certainty of correctness; all R-S parity resources + // were used in error/erasure recover, with none left to confirm that the result is actually + // correct. If only zero-strength results are achieved, the longest will be returned (the + // full, original string). + // + // Supports the following forms of error/erasure: + // + // 0) Full parity. All data and parity supplied, and an R-S codeword is solved. + // + // 1) Partial parity. All data and some parity supplied; remainder are deemed erasures. + // + // If PARITY > 2, then up to PARITY/2-1 trailing parity terms are marked as erasures. + // If the R-S codeword is solved and a safe number of errors are found, then we can have + // reasonable confidence that the string is correct. + // + // 1a) Erase errors. Permute the combinations of up to PARITY-1 erasures. + // + // o) Raw password. No parity terms supplied; not an R-S codeword + // + // If none of the error/erasure forms succeed, the password is returned unmodified. + // + // If a non-zero 'minimum' or 'maximum' are provided, they constrain the possible + // resultant password sizes that will be attempted. + // + static + int decode( + std::string &password, + const std::vector + &erasures, + size_t minimum = PARITY,//always deemed at least 1 + size_t maximum = 0 ) // if 0, no limit + { + int confidence; + best_avg best; + + // Full/Partial parity. Apply some parity erasure if we have some erasure/correction + // capability while maintaining at least one excess parity symbol for verification. + // This can potentially result in longer password being returned, if the R-S decoder + // accidentally solves a codeword. + // + // For example, if PARITY=3 (or 4) then (PARITY+1)/2 == 2, and we would only attempt up + // to 1 parity erasure. This would leave 1 parity symbol to replace the 1 erasure, and + // 1 remaining to validate the integrity of the password. + // + // The password must be long enough to contain at least 1 non-parity symbol, and the + // designated number of non-erased parity symbols! However, by convention we'll demand + // that the password contain at least PARITY symbols -- any less, and we can + // accidentally correct the few remaining password symbols. + // + // Also, if any parity symbols won't decode (eg. were entered in error), we must deem + // them to be erasures, too, and if the number of erasures exceeds the capacity of the + // R-S codec, it'll fail (throw an exception, or at best solve with 0 confidence). + for ( size_t era = 0 // how many parity symbols to deem erased + ; era < (PARITY+1)/2 + ; ++era ) { + if ( password.size() < ( minimum ? minimum : 1 ) + PARITY - era ) { +#if defined( DEBUG ) && DEBUG >= 1 + output( std::cout ) + << " Rejected too short password \"" + << password << std::string( era, '_' ) + << "\"" << " (" << era << " parity skipped)" + << std::endl; +#endif + continue; // too few password symbols to start checking parity + } + + if ( maximum and password.size() > maximum + PARITY - era ) { +#if defined( DEBUG ) && DEBUG >= 1 + output( std::cout ) + << " Rejected too long password \"" + << password << std::string( era, '_' ) + << "\"" << " (" << era << " parity skipped)" + << std::endl; +#endif + continue; // too few parity symbols erased to start checking parity + } + + // Copy password, adding 'era' additional NULs + std::string fixed( password.size() + era, 0 ); + std::copy( password.begin(), password.end(), fixed.begin() ); + + // Decode the base-N parity, denoting any invalid (mistyped or trailing NUL) symbols + // as erasures (adjust erasure offsets to be from start of password, not start of + // parity). All newly added 'era' symbols will be NUL, and will be invalid. After + // decoding parity, if we've slipped below our minimum R-S capacity threshold + // (ie. because of mistyped parity symbols), don't attempt. + std::vector all_era; + SERIAL::decode( fixed.begin() + fixed.size() - PARITY, + fixed.begin() + fixed.size(), &all_era, 0, + serialize::ws_invalid, serialize::pd_invalid ); + if ( all_era.size() >= (PARITY+1)/2 ) { +#if defined( DEBUG ) && DEBUG >= 1 + output( std::cout ) + << " Rejected low parity password \"" + << password << std::string( era, '_' ) + << "\"" << " (" << all_era.size() << " parity erasures + " + << era << " skipped)" + << std::endl; +#endif + continue; // Too many missing parity symbols + } + if ( all_era.size() + erasures.size() > PARITY ) { +#if defined( DEBUG ) && DEBUG >= 1 + output( std::cout ) + << " Rejected hi erasure password \"" + << password << std::string( era, '_' ) + << "\"" << " (" << all_era.size() + erasures.size() << " total erasures + " + << era << " skipped)" + << std::endl; +#endif + continue; // Total erasures beyond capacity + } + for ( auto &o : all_era ) + o += fixed.size() - PARITY; + std::copy( erasures.begin(), erasures.end(), std::back_inserter( all_era )); + + // Enough parity to try to decode. A successful R-S decode with 0 (remaining) + // confidence indicates a successfully validated R-S codeword! Use it (ex. parity). + try { + std::vector position; + int corrects= rscodec.decode( fixed, all_era, &position ); + confidence = strength( corrects, all_era, position ); + fixed.resize( fixed.size() - PARITY ); + if ( confidence >= 0 ) + best.add( fixed, confidence ); +#if defined( DEBUG ) && DEBUG >= 1 + output( std::cout ) + << " Reed-Solomon w/ " << era << " of " << PARITY + << " parity erasures " << std::setw( 3 ) << confidence + << "% confidence: \"" << password + << "\" ==> \"" << fixed + << "\" (corrects: " << corrects + << ", erasures at " << all_era + << ", fixed at " << position << "): " + << std::endl + << best; +#endif + } catch ( std::exception &exc ) { +#if defined( DEBUG ) && DEBUG >= 2 // should see only when ezpwd::reed_solomon<...>::decode fails + output( std::cout ) << " invalid part parity password: " << exc.what() << std::endl; +#endif + } + } + + // Partial parity, but below threshold for usable error detection. For the first 1 to + // (PARITY+1)/2 parity symbols (eg. for PARITY == 3, (PARITY+1)/2 == 1 ), we cannot + // perform meaningful error or erasure detection. However, if we see that the terminal + // symbols match the R-S symbols we expect from a correct password, we'll ascribe a + // partial confidence due to the matching parity symbols. + // + // password: sock1t + // w/ 3 parity: sock1tkeB + // password ----^^^^^^ + // ^^^--- parity + // + for ( size_t era = (PARITY+1)/2 // how many parity symbols are not present + ; era < PARITY + ; ++era ) { + if ( password.size() < ( minimum ? minimum : 1 ) + PARITY - era ) { +#if defined( DEBUG ) && DEBUG >= 1 + output( std::cout ) + << " Rejected too short password \"" + << password << std::string( era, '_' ) + << "\"" + << std::endl; +#endif + continue; // too few password symbols to start checking parity + } + if ( maximum and password.size() > maximum + PARITY - era ) { +#if defined( DEBUG ) && DEBUG >= 1 + output( std::cout ) + << " Rejected too long password \"" + << password << std::string( era, '_' ) + << "\"" << " (" << era << " parity skipped)" + << std::endl; +#endif + continue; // too few parity symbols erased to start checking parity + } + std::string fixed = password; + size_t len = password.size() - ( PARITY - era ); + fixed.resize( len ); + encode( fixed ); + auto differs = std::mismatch( fixed.begin(), fixed.end(), password.begin() ); + size_t par_equ = differs.second - password.begin(); + if ( par_equ < len || par_equ > len + PARITY ) + throw std::runtime_error( "miscomputed R-S parity matching length" ); + par_equ -= len; + + // At least one parity symbol is requires to give any confidence + if ( par_equ > 0 ) { + std::string basic( fixed.begin(), fixed.begin() + len ); + confidence = par_equ * 100 / PARITY; // each worth a normal parity symbol + best.add( basic, confidence ); +#if defined( DEBUG ) && DEBUG >= 1 + output( std::cout ) + << " Check Chars. w/ " << era << " of " << PARITY + << " parity missing " << std::setw( 3 ) << confidence + << "% confidence: \"" << password + << "\" ==> \"" << basic + << " (from computed: \"" << fixed << "\")" + << ": " + << std::endl + << best; +#endif + } + } + + // Select the best guess and return its confidence. Otherwise, use raw password? If no + // error/erasure attempts succeeded (if no 'best' w/ confidence >= 0), then we'll use + // the raw password w/ 0 confidence, if it meets the minimum/maximum length + // requirements. + confidence = -1; + if ( password.size() >= ( minimum ? minimum : 1 ) + and ( maximum == 0 or password.size() <= maximum )) + confidence = 0; + + typename best_avg::const_iterator + bi = best.best(); +#if defined( DEBUG ) + output( std::cout ) + << " Selected " << ( bi != best.end() ? "corrected" : "unmodified" ) + << " password \"" << ( bi != best.end() ? bi->first : password ) + << "\" of length " << ( minimum ? minimum : 1) << "-" << maximum + << " (vs. \"" << password + << "\") w/ confidence " << (bi != best.end() ? bi->second.second : confidence ) + << "%, from: " + << std::endl + << best; +#endif + if ( bi != best.end() ) { + auto better = best.evaluation( *bi ); // --> (,) + password = better.second; + confidence = better.first; + } + return confidence; + } + + static + int decode( + std::string &password, + size_t minimum = PARITY, + size_t maximum = 0 ) + { + return decode( password, std::vector(), minimum, maximum ); + } + + // + // decode(,,,) -- C interface to decode() + // + // Traditional C interface. The provided NUL-terminated password+parity is decoded + // (parity removed), and the confidence % is returned. + // + // If any failure occurs, a -'ve value will be returned, and the supplied password + // buffer will be used to contain an error description. + // + static int decode( + char *password, // NUL terminated + size_t siz, // available size + size_t minimum = PARITY,//minimum resultant password length + size_t maximum = 0 ) // maximum '' + { + std::string corrected( password ); + int confidence; + try { + confidence = decode( corrected, minimum, maximum ); + if ( corrected.size() + 1 > siz ) + throw std::runtime_error( "password buffer has insufficient capacity" ); + std::copy( corrected.begin(), corrected.end(), password ); + password[corrected.size()] = 0; + } catch ( std::exception &exc ) { + confidence = -1; + ezpwd::streambuf_to_buffer sbf( password, siz ); + std::ostream( &sbf ) << "corrector<" << PARITY << "> failed: " << exc.what(); + } + return confidence; + } + + // + // rscodec -- A ?-bit RS(N-1,N-1-PARITY) Reed-Solomon codec + // + // Encodes and decodes R-S symbols over the lower 6 bits of the supplied data. Requires + // that the last N (parity) symbols of the data are in the range [0,63]. The excess bits on + // the data symbols are masked and restored during decoding. + // + static const ezpwd::RS + rscodec; + }; + + template < size_t PARITY, size_t N, typename SERIAL > + const ezpwd::RS + corrector::rscodec; + +} // namespace ezpwd + +template < size_t PARITY, size_t N, typename SERIAL > +std::ostream &operator<<( + std::ostream &lhs, + const ezpwd::corrector + &rhs ) +{ + return rhs.output( lhs ); +} + +#endif // _EZPWD_CORRECTOR diff --git a/op25/gr-op25_repeater/lib/ezpwd/definitions b/op25/gr-op25_repeater/lib/ezpwd/definitions new file mode 100644 index 0000000..1ecf9a4 --- /dev/null +++ b/op25/gr-op25_repeater/lib/ezpwd/definitions @@ -0,0 +1,9 @@ +// +// C++ Definitions -- include once in a single C++ compilation unit +// +#ifndef _EZPWD_DEFINITIONS +#define _EZPWD_DEFINITIONS + +#include "serialize_definitions" + +#endif // _EZPWD_DEFINITIONS \ No newline at end of file diff --git a/op25/gr-op25_repeater/lib/ezpwd/ezcod b/op25/gr-op25_repeater/lib/ezpwd/ezcod new file mode 100644 index 0000000..8fe0a90 --- /dev/null +++ b/op25/gr-op25_repeater/lib/ezpwd/ezcod @@ -0,0 +1,725 @@ +/* + * Ezpwd Reed-Solomon -- Reed-Solomon encoder / decoder library + * + * Copyright (c) 2014, Hard Consulting Corporation. + * + * Ezpwd Reed-Solomon is free software: you can redistribute it and/or modify it under the terms of + * the GNU General Public License as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. See the LICENSE file at the top of the + * source tree. Ezpwd Reed-Solomon is also available under Commercial license. c++/ezpwd/rs_base + * is redistributed under the terms of the LGPL, regardless of the overall licensing terms. + * + * Ezpwd Reed-Solomon 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 General Public License for more details. + */ +#ifndef _EZPWD_EZCOD +#define _EZPWD_EZCOD + +#include // M_PI +#include +#include + +#include +#include +#include +#include + +// +// EZCOD 3:10 location code w/ Reed-Solomon Error Correction, and average 3m accuracy +// +// - each successive symbol provides greater precision +// - codes nearby each-other are identical in leading characters +// - average 3m precision achieved in 9 symbols +// - more than 4 base-10 digits of precision in both lat and lon after the decimal +// - from 1 to 3 symbols of Reed-Solomon parity +// - 1 parity symbol supplies validation w/ strength equivalent to a check character +// - 2 parity symbols provides correction of 1 lost symbol (no errors) +// - 3 parity symbols provides correction of any 1 error, with verification, +// or recovery of up to any 3 lost symbols (with no other errors) +// + +// +// To achieve at least 4 decimal digits of precision after the decimal point, we must have +// defined lat to within 1 part in 1,800,000, and lon to within 1 part in 3,600,000. As each symbol +// supplies bits, we'll refine the computed lat/lon further, reducing the outstanding fraction of +// "parts" yet to be defined. +// +// bits +// symbols latitude longitude +// bits mul parts bits mul parts +// 1 2 4 4 3 8 8 +// 2 2 4 16 3 8 64 +// 3 3 8 128 2 4 256 // not quite integer lat/lon accuracy +// +// 4 2 4 512 3 8 2,048 +// 5 3 8 4,096 2 4 8,192 +// 6 2 4 16,384 3 8 65,536 +// +// 7 3 8 131,072 2 4 262,144 +// 8 2 4 524,288 3 8 2,097,152 +// 9 3 8 4,194,304 2 4 8,388,608 parts resolution in 3:10 code +// over [-90, 90] over [-180,180] yields ~3m resolution +// +// vs. 1,800,000 3,600,000 parts resolution in 10:10 code +// over [-90, 90] over [-180,180] yields ~10m resolution +// +// Therefore, within 9 symbols we define lat and lon with better than double the precision of +// 10:10 code's 4 decimal digits after the decimal point. This yields an approximate lineal +// precision of 40,075,000m / 8,388,608 == ~5m in both dimensions at the equator, vs. 40,075,000m / +// 3,600,000 == ~11m for 10:10 codes. +// +// The 10:10 code provides a single check character, which provides about P(1-1/32) certainty +// that the provided code is correct. With EZCOD 3:10/11/12 codes, we provide varying levels of +// detection/correction strength. +// +// - 1 parity symbol: act as a check character (like 10:10 codes), or provide 1 symbol of erasure +// (lost symbol) recovery with no excess parity for validation. +// +// - 2 parity symbols: provide 1 symbol of erasure correction (w/ no other errors) with 1 excess parity +// symbol for validation, or 1 symbol of error detection with no excess parity for validation. +// +// - 3 parity symbols: correct 1 error anywhere w/ 1 excess parity symbol for validation, or up +// to 3 erasures with no excess parity for validation. +// +// Therefore, we'll provide Reed-Solomon RS(31,28-30) error correction (5 bit symbols, +// indicating 31 symbols in the field, and from 1 to 3 roots, therefore up to 28 data symbols in the +// field) over the 9 lat/lon data symbols. +// +// +// MINIMIZING ERROR +// +// Each input lat/lon coordinate will be effectively truncated by the encoding procedure to the +// level of precision (parts) encoded by each symbol. Subsequent symbols then add their (smaller) +// parts to increase precision. +// +// After the last symbol, we know that the actual input coordinates where somewhere +// within the rectangle: +// +// [0,0] -> [0,lon_precision] -> [lat_precision,lon_precision] -> [lat_precision,0] +// +// At first glance, the best way is to perform rounding instead of truncation on ecoding, by +// simply adding 1/2 of the precision. Then, the unmodified output lat/lon decoded represents the +// point nearest actual input coordinate. However, this is NOT ideal. Remember -- the decoding may +// not have access to all the symbols! We want to minimize the error even if only some of the +// symbols are available. Thus, we must apply a correction on decoding. +// +// One way gain rounding instead of truncation on decoding is, after adding the last symbol's +// precision, to add 50% of the value represented by the first bit of the next (missing) symbol's +// precision parts. This would be analogous to receiving the first 2 digits of a 3 digit number: +// +// original: 123 +// received: 12_ +// range: [120,130) +// guessed: 125 (add 1/2 of the parts represented by the missing digit) +// +// If this is done, then the resulting coordinate would be in the middle of the rectangle of +// possible input lat/lon values that could have resulted in the encoded value. This also works if +// we don't receive and decode all of the symbols; We'll end up with a lat/lon in the middle of the +// (large) rectangle of possible input coordinates. +// + + +namespace ezpwd { + + class ezcod_base { + public: + double latitude; // [-90,+90] angle, degrees + double latitude_error; // total error bar, in meters + double longitude; // [-180,180] + double longitude_error; // total error bar, in meters + double accuracy; // linear accuracy radius, in meters + int confidence; // % parity in excess of last decode + double certainty; // and the associated probability + + explicit ezcod_base( + double _lat = 0, + double _lon = 0 ) + : latitude( _lat ) + , latitude_error( 0 ) + , longitude( _lon ) + , longitude_error( 0 ) + , accuracy( 0 ) + , confidence( 100 ) + , certainty( 1 ) + { + ; + } + virtual ~ezcod_base() + { + ; + } + + typedef std::pair + symbols_t; + virtual symbols_t symbols() + const + = 0; + virtual std::ostream &output( + std::ostream &lhs ) + const + = 0; + virtual std::string encode( + unsigned _preci = 0 ) // override precision + const + = 0; + virtual int decode( + const std::string &_ezcod ) + = 0; + }; +} // namespace ezpwd + +inline std::ostream &operator<<( + std::ostream &lhs, + const ezpwd::ezcod_base + &rhs ) +{ + return rhs.output( lhs ); +} + +namespace ezpwd { + + // + // ezcod -- defaults to 1 PARITY and 9 location symbols (3m) of PRECISION + // + template < unsigned P=1, unsigned L=9 > + class ezcod + : public ezcod_base { + + private: + typedef std::array + bits_t; + static const bits_t bits; + typedef std::array, 12> + parts_t; + static const parts_t parts; + +#if defined( DEBUG ) + public: +#endif + static const ezpwd::RS<31,31-P> + rscodec; + + public: + static constexpr const unsigned PARITY = P; // specified symbols of R-S parity + static constexpr const unsigned PRECISION = L; // default symbols of location precision + static constexpr const unsigned CHUNK = 3; // default chunk size + + static constexpr const char SEP_NONE = -1; + static constexpr const char SEP_DEFAULT = 0; + static constexpr const char SEP_DOT = '.'; + static constexpr const char SEP_BANG = '!'; + static constexpr const char SEP_SPACE = ' '; + + static constexpr const char CHK_NONE = -1; + static constexpr const char CHK_DEFAULT = 0; + static constexpr const char CHK_DASH = '-'; + static constexpr const char CHK_SPACE = ' '; + + unsigned precision; + unsigned chunk; // Location symbol chunk sizes + char separator; // Separator between location and parity symbols + char space; // Fill space between location symbol chunks + + // + // ezcod() -- supply non-defaults for location precision, chunk size, etc. + // + explicit ezcod( + double _lat = 0, + double _lon = 0, + unsigned _preci = 0, + unsigned _chunk = 0, + char _seper = 0, + char _space = 0 ) + : ezcod_base( _lat, _lon ) + , precision( _preci ? _preci : PRECISION ) + , chunk( _chunk ? _chunk : CHUNK ) + , separator( _seper ) + , space( _space ) + { + if ( P < 1 ) + throw std::runtime_error( "ezpwd::ezcod:: At least one parity symbol must be specified" ); + if ( precision < 1 || precision > bits.max_size() ) + throw std::runtime_error( std::string( "ezpwd::ezcod:: Only 1-" ) + + std::to_string( bits.max_size() ) + + " location symbol may be specified" ); + } + explicit ezcod( + const std::string &_ezcod, + unsigned _preci = 0, + unsigned _chunk = 0, + char _seper = 0, + char _space = 0 ) + : ezcod( 0, 0, _preci, _chunk, _seper, _space ) + { + decode( _ezcod ); + } + virtual ~ezcod() + { + ; + } + + // + // symbols -- return working parity and location precision + // + virtual ezcod_base::symbols_t + symbols() + const + { + return ezcod_base::symbols_t( P, precision ); + } + + virtual std::ostream &output( + std::ostream &lhs ) + const + { + std::streamsize prec = lhs.precision(); + std::ios_base::fmtflags + flg = lhs.flags(); + lhs.precision( 10 ); + std::string uni = "m "; + double acc = accuracy; + double dec = 2; + if ( acc > 1000 ) { + uni = "km"; + acc /= 1000; + } else if ( acc < 1 ) { + uni = "mm"; + acc *= 1000; + } + if ( acc >= 100 ) + dec = 0; + else if ( acc >= 10 ) + dec = 1; + + lhs << encode( precision ) + << " (" << std::setw( 3 ) << confidence + << "%) == " << std::showpos << std::fixed << std::setprecision( 10 ) << std::setw( 15 ) << latitude + << ", " << std::showpos << std::fixed << std::setprecision( 10 ) << std::setw( 15 ) << longitude + << " +/- " << std::noshowpos << std::fixed << std::setprecision( dec ) << std::setw( 6 ) << acc << uni; + lhs.precision( prec ); + lhs.flags( flg ); + return lhs; + } + + // + // encode() -- encode the lat/lon to 'precision' symbols EZCOD representation + // + virtual std::string encode( + unsigned _preci = 0 ) // override precision + const + { + // Convert lat/lon into a fraction of number of parts assigned to each + double lat_frac= ( latitude + 90 ) / 180; + if ( lat_frac < 0 || lat_frac > 1 ) + throw std::runtime_error( "ezpwd::ezcod::encode: Latitude not in range [-90,90]" ); + double lon_frac= ( longitude + 180 ) / 360; + if ( lon_frac < 0 || lon_frac > 1 ) + throw std::runtime_error( "ezpwd::ezcod::encode: Longitude not in range [-180,180]" ); + if ( _preci == 0 ) + _preci = precision; + if ( _preci < 1 || _preci > bits.max_size() ) + throw std::runtime_error( std::string( "ezpwd::ezcod:: Only 1-" ) + + std::to_string( bits.max_size() ) + + " location symbol may be specified" ); + + // Compute the integer number of lat/lon parts represented by each coordinate, for the + // specified level of precision, and then truncate to the range [0,..._parts), + // eg. Latitude 90 --> 89.999... + uint32_t lat_parts = parts[_preci-1].first; // [ -90,90 ] / 4,194,304 parts in 9 symbols + uint32_t lon_parts = parts[_preci-1].second; // [-180,180] / 8,388,608 parts '' + + uint32_t lat_rem = std::min( lat_parts-1, uint32_t( lat_parts * lat_frac )); + uint32_t lon_rem = std::min( lon_parts-1, uint32_t( lon_parts * lon_frac )); + + // Initial loop condition; lat/lon multiplier is left at the base multiplier of the + // previous loop. Then, loop computing the units multiplier, and hten removing the most + // significant bits (multiples of the units multiplier). They will both reach 1 + unsigned int lat_mult= lat_parts; + unsigned int lon_mult= lon_parts; + + std::string res; + res.reserve( _preci // approximate result length + + ( chunk && chunk < _preci + ? _preci / chunk - 1 + : 0 ) + + 1 + P ); + for ( auto &b : bits ) { + unsigned char lat_bits= b.first; + unsigned char lon_bits= b.second; + lat_mult >>= lat_bits; + lon_mult >>= lon_bits; + if ( ! lat_mult || ! lon_mult ) + break; + + // Each set of bits represents the number of times the current multiplier (after + // division by the number of bits we're outputting) would go into the remainder. + // Eg. If _mult was 1024, and _rem is 123 and _bits is 3, we're going to put out + // the next 3 bits of the value 199. The last value ended removing all multiples of + // 1024. So, we first get the new multiplier: 1024 >> 3 == 128. So, we're + // indicating, as a 3-bit value, how many multiples of 128 there are in the value + // 199: 199 / 128 == 1, so the 3-bit value we output is 001 + uint32_t lat_val = lat_rem / lat_mult; + lat_rem -= lat_val * lat_mult; + + uint32_t lon_val = lon_rem / lon_mult; + lon_rem -= lon_val * lon_mult; + + res += char( ( lat_val << lon_bits ) | lon_val ); + } + + // Add the R-S parity symbols and base-32 encode, add parity separator and chunk + rscodec.encode( res ); + serialize::base32::encode( res ); + switch( separator ) { + case SEP_NONE: + break; + case SEP_DOT: default: + res.insert( _preci, 1, SEP_DOT ); + break; + case SEP_BANG: + case SEP_SPACE: + res.insert( _preci, 1, separator ); + break; + } + if ( space != CHK_NONE && chunk && chunk < _preci ) { + for ( unsigned c = _preci / chunk - 1; c > 0; --c ) { + switch ( space ) { + case CHK_NONE: + break; + case CHK_SPACE: default: + res.insert( c * chunk, 1, CHK_SPACE ); + break; + case CHK_DASH: + res.insert( c * chunk, 1, space ); + break; + } + } + } + + return res; + } + + // + // deserialize -- Extract base-32, skip whitespace, mark invalid symbols as erasures + // validate -- Remove base-32 encoding, validate and remove parity, returning confidence + // decode -- Attempt to decode a lat/lon, returning the confidence percentage + // + // If data but no parity symbols are supplied, no error checking is performed, and the + // confidence returned will be 0%. No erasures within the supplied data are allowed (as + // there is no capacity to correct them), and an exception will be thrown. + // + // If parity is supplied, then erasures are allowed. So long as the total number of + // erasures is <= the supplied parity symbols, then the decode will proceed (using the + // parity symbols to fill in the erasures), and the returned confidence will reflect the + // amount of unused parity capacity. Each erasure consumes one parity symbol to repair. + // + // We'll allow question-mark or any of the slash characters: "_/\?" to indicate an + // erasure. Either of the "!." symbol may be used to indicates the split between location + // symbols and parity symbols, and must be in a position that corresponds to the indicated + // number of location (this->precision) and parity 'P' symbols. Whitespace symbols and dash + // are ignored: " -". + // + // Thus, an EZCOD like "R3U 1JU QUY!0" may only be decoded by an ezcod. Without + // the "!" or ".", it could be an ezcod w/precision == 8 -- there's no way to know for + // sure. If no explicit position-parity separator is given, then we assume the default: + // this->precision location symbols, then up to P parity symbols. If additional parity + // symbols are supplied after the separator, then However, an ezcod... + // + // If an explicit "!" or "." separator IS provided, then we will attempt to decode the + // position with the given number of position symbols, and up to P parity symbols. + // + // NOTE + // + // Due to a perhaps unexpected feature of R-S codewords, a codeword with MORE parity + // can be successfully decoded by an R-S codec specifying LESS parity symbols. It so + // happens that the data plus (excess) parity + (remaining) parity is STILL a valid codeword + // (so long as the R-S Galois parameters are identical). + // + // Therefore, EZCODs with more parity are accepted by EZCOD parsers configured for less + // parity. Of course, they will have less error/erasure correction strength -- using the + // correctly configured EZCOD codec expecting more R-S parity will maximize the value of all + // the supplied parity. + // + // The full amount of parity (ie. everything after the location/parity separator) is + // discarded in all cases, before the EZCOD location is decoded. + // + private: + + unsigned deserialize( + std::string &dec, + std::vector &erasure, + std::vector &invalid ) + const + { + serialize::base32::decode( dec, &erasure, &invalid ); + + // The special symbol '!' or '.' indicates the end of the EZCOD location symbols and the + // beginning of parity: ensure the symbol counts are consistent with the encoding. By + // default the parity symbols begin at offset precision. If we see more than precision + // symbols, we assume that the Lth and subsequent symbols are parity. If a + // location/parity separator is provided, it must be at position this->precision! + // Return offset of start of parity in codeword. + unsigned parbeg = this->PRECISION; // Parity begins after Location, by default + for ( unsigned i = 0; i < invalid.size(); ++i ) { + switch ( invalid[i] ) { + case '!': case '.': + // Remember the offset of the first parity symbol (it'll be in the position of + // the last '!' or '.' symbol we're about to erase), and adjust the indices of + // any erasures following. + parbeg = erasure[i]; + dec.erase( parbeg, 1 ); + invalid.erase( invalid.begin() + i ); + erasure.erase( erasure.begin() + i ); + for ( unsigned j = i; j < erasure.size(); ++j ) + erasure[j] -= 1; + break; + case '_': case '/': case '\\': case '?': + break; + default: + throw std::runtime_error( std::string( "ezpwd::ezcod::decode: invalid symbol presented: '" ) + + invalid[i] + "'" ); + } + } +#if defined( DEBUG ) && DEBUG >= 1 + std::cout << " --> 0x" << std::vector( dec.begin(), dec.begin() + std::min( size_t( parbeg ), dec.length()) ) + << " + 0x" << std::vector( dec.begin() + std::min( size_t( parbeg ), dec.length()), + dec.begin() + dec.length() ) + << " parity" << std::endl; +#endif + return parbeg; + } + + int validate( + std::string &dec ) + const + { + // Compute and return validity (which may later be assigned to this->confidence) + int validity = 0; // if no R-S parity provided + +#if defined( DEBUG ) && DEBUG >= 1 + std::cout << *this << " validate( " << dec << " ) "; +#endif + std::vector erasure; + std::vector invalid; + unsigned parbeg = deserialize( dec, erasure, invalid ); + + if ( dec.size() > parbeg || erasure.size() > 0 ) { + // Some R-S parity symbol(s) were provided (or erasures were marked). See if we can + // successfully decode/correct, or (at least) use one parity symbol as a check + // character. If we identify more erasures than R-S parity, we must fail; we can't + // recover the data. This will of course be the case if we have *any* erasures in + // the data, and no parity. + unsigned parity = 0; + if ( dec.size() > parbeg ) + parity = dec.size() - parbeg; + while ( dec.size() < parbeg + P ) { + erasure.push_back( dec.size() ); + dec.resize( dec.size() + 1 ); + } +#if defined( DEBUG ) && DEBUG >= 2 + std::cout << " --> erasures: " << erasure.size() << " vs. parity: " << parity + << ": " << std::vector( dec.begin(), dec.end() ) << std::endl; +#endif + if ( erasure.size() > parity ) { + // We cannot do R-S decoding; not enough parity symbols to even cover erasures. + // If parity symbol(s) were provided ('parity' > 0), and all erasures were due the + // missing remaining parity symbols, we can use the existing parity symbol(s) as + // "check character(s)", by simply re-encoding the supplied non-parity data, and + // see if the generated parity symbol(s) match the supplied parity. This has + // basically the same strength as the 10:10 code's check character. + if ( parity + erasure.size() == P ) { + // All erasures must be at end, in remaining parity symbols! + std::string chk( dec.begin(), dec.begin() + parbeg ); + rscodec.encode( chk ); + // each parity symbol provided must match the corresponding encoded chk symbol + for ( unsigned i = 0; i < parity; ++i ) + if ( dec[parbeg+i] != chk[parbeg+i] ) + throw std::runtime_error( "ezpwd::ezcod::decode: Error correction failed; check character mismatch" ); + // Check character(s) matched; erasure.size()/P of confidence gone + validity = ezpwd::strength

( erasure.size(), erasure, erasure ); + } else + throw std::runtime_error( "ezpwd::ezcod::decode: Error correction failed; too many erasures" ); + } else { + // We can try R-S decoding; we have (at least) enough parity to try to recover + // any missing symbol(s). + std::vectorposition; + int corrects= rscodec.decode( dec, erasure, &position ); + if ( corrects < 0 ) + throw std::runtime_error( "ezpwd::ezcod::decode: Error correction failed; R-S decode failed" ); + // Compute confidence, from spare parity capacity. Since R-S decode will not + // return the position of erasures that turn out (by accident) to be correct, + // but they have consumed parity capacity, we re-add them into the correction + // position vector. If the R-S correction reports more corrections than the + // parity can possibly have handled correctly, (eg. 2 reported erasures and an + // unexpected error), then the decode is almost certainly incorrect; fail. + validity = ezpwd::strength

( corrects, erasure, position ); + if ( validity < 0 ) + throw std::runtime_error( "ezpwd::ezcod::decode: Error correction failed; R-S decode overwhelmed" ); + } + if ( dec.size() > parbeg ) + dec.resize( parbeg ); // Discard any parity symbols + } + return validity; + } + + public: + virtual int decode( const std::string &str ) + { + // Decode the R-S encoding, computing the confidence. Will raise an exception on any + // error. Don't change this->confidence, this->latitude, ... until there is no longer a + // chance of exception. + std::string decoded( str ); + int validity= validate( decoded ); + + // Unpack the supplied location data; we'll take as much as we are given, up to the + // maximum possible 12 symbols supported (9 symbols yielding ~3m resolution). + uint32_t lat_tot = 0; + uint32_t lon_tot = 0; + + uint32_t lat_mult= 1; + uint32_t lon_mult= 1; + + auto di = decoded.begin(); + for ( auto &b : bits ) { + if ( di == decoded.end() ) + break; + unsigned char c = *di++; + + unsigned char lat_bits= b.first; + unsigned char lon_bits= b.second; + + uint32_t lat_val = c >> lon_bits; + uint32_t lon_val = c & (( 1 << lon_bits ) - 1 ); + + lat_mult <<= lat_bits; + lat_tot <<= lat_bits; + lat_tot += lat_val; + + lon_mult <<= lon_bits; + lon_tot <<= lon_bits; + lon_tot += lon_val; + } + + // Convert the sum of lat/lon parts back into degrees, and round the (truncated) value + // to the middle of the error rectangle. This allows us to minimize error even if we + // didn't have access to all of the origin symbols to decode. The absolute error bar as + // a proportional factor [0,1) for lat/lon is at most the scale of the last parts + // multiplier used. We'll use this later to compute the error in meters; for example, + // if the last value we added worked out to be worth units of 25m of the circumference, + // then we must now be within [0,25m) of the original point. + double lat_err = 1.0 / lat_mult; + double lon_err = 1.0 / lon_mult; + latitude = 180 * ( double( lat_tot ) / lat_mult + lat_err / 2 ) - 90; + longitude = 360 * ( double( lon_tot ) / lon_mult + lon_err / 2 ) - 180; + + // Remember the decoded location precision for future encoding (overrides the default). + // Compute the certainty probability (0.0,1.0] given the number of parity symbols in + // excess: Given a base-32 symbol: 1 - 1 / ( 32 ^ P ) where P is the number of + // unconsumed parity. + precision = decoded.size(); + confidence = validity; + certainty = 0.0; + if ( PARITY * confidence / 100 ) + certainty = 1.0 - 1.0 / std::pow( double( 32.0 ), + double( PARITY * confidence / 100 )); + + // Compute the resolution error (in m.) of the decoded lat/lon and compute the minimum + // accuracy -- the radius of the circle around the computed latitude/longitude, inside + // which the original latitude/longitude must have been. + // + // original latitude error bar + // \ / + // o - + // | longitude error bar + // | / + // |--x--| + // /| + // / | + // computed - + // + // The maximum distance is the length of the diagonal of the error rectangle defined by + // 1/2 the latitude/longitude error bars. + // + double lon_circ= 1 * M_PI * 6371000; + double lat_circ= 2 * M_PI * 6371000 * std::cos( latitude * M_PI / 180 ); + latitude_error = lat_err * lon_circ; + longitude_error = lon_err * lat_circ; + + accuracy = sqrt( latitude_error / 2 * latitude_error / 2 + + longitude_error / 2 * longitude_error / 2 ); + return confidence; + } + }; // class ezcod + + + // + // ezcod::rscodec -- Reed-Solomon parity codec + // ezcod::bits -- distribution of lat/lon precision in each code symbol + // + // Quickly establishes an extra bit of precision for Longitude, and then evenly distributes + // future precision between lat/lon, always maintaining extra precision for Longitude. + // + template < unsigned P, unsigned L > + const ezpwd::RS<31,31-P> ezcod::rscodec; + + // Number of lat/lon bits represented for each location symbol + template < unsigned P, unsigned L > + const typename ezcod::bits_t + ezcod::bits = { + { + // bits per symbol lat lon + ezcod::bits_t::value_type( 2, 3 ), + ezcod::bits_t::value_type( 2, 3 ), + ezcod::bits_t::value_type( 3, 2 ), + // -- -- + // 7 8 + ezcod::bits_t::value_type( 2, 3 ), + ezcod::bits_t::value_type( 3, 2 ), + ezcod::bits_t::value_type( 2, 3 ), + // -- -- + // 14 16 + ezcod::bits_t::value_type( 3, 2 ), + ezcod::bits_t::value_type( 2, 3 ), + ezcod::bits_t::value_type( 3, 2 ), + // -- -- + // 22 23 + ezcod::bits_t::value_type( 2, 3 ), + ezcod::bits_t::value_type( 3, 2 ), + ezcod::bits_t::value_type( 2, 3 ), + // -- -- + // 29 31 + } + }; + + // Total number of parts that lat/lon is subdivided into, for that number of location symbols. + template < unsigned P, unsigned L > + const typename ezcod::parts_t + ezcod::parts = { + { + // parts per symbol lat parts lon parts lat lon bits + ezcod::parts_t::value_type( 1UL << 2, 1UL << 3 ), // 2, 3 + ezcod::parts_t::value_type( 1UL << 4, 1UL << 6 ), // 2, 3 + ezcod::parts_t::value_type( 1UL << 7, 1UL << 8 ), // 3, 2 + // -- -- + // 7 8 + ezcod::parts_t::value_type( 1UL << 9, 1UL << 11 ), // 2, 3 + ezcod::parts_t::value_type( 1UL << 12, 1UL << 13 ), // 3, 2 + ezcod::parts_t::value_type( 1UL << 14, 1UL << 16 ), // 2, 3 + // -- -- + // 14 16 + ezcod::parts_t::value_type( 1UL << 17, 1UL << 18 ), // 3, 2 + ezcod::parts_t::value_type( 1UL << 19, 1UL << 21 ), // 2, 3 + ezcod::parts_t::value_type( 1UL << 22, 1UL << 23 ), // 3, 2 + // -- -- + // 22 23 + ezcod::parts_t::value_type( 1UL << 24, 1UL << 26 ), // 2, 3 + ezcod::parts_t::value_type( 1UL << 27, 1UL << 28 ), // 3, 2 + ezcod::parts_t::value_type( 1UL << 29, 1UL << 31 ), // 2, 3 + // -- -- + // 29 31 + } + }; +} // namespace ezpwd + +#endif // _EZPWD_EZCOD diff --git a/op25/gr-op25_repeater/lib/ezpwd/output b/op25/gr-op25_repeater/lib/ezpwd/output new file mode 100644 index 0000000..230be80 --- /dev/null +++ b/op25/gr-op25_repeater/lib/ezpwd/output @@ -0,0 +1,344 @@ +#ifndef _EZPWD_OUTPUT +#define _EZPWD_OUTPUT + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// +// ezpwd::hexchr -- escape/hexify char c, output using func/meth f, in width w >= 2 +// ezpwd::hexify -- hexify something that can be converted to an unsigned char +// ezpwd::hexout -- hexify each element in the range (beg,end], limited by stream's width +// +// std::ostream << ezpwd::hexify( c ) // output any char escaped/hex +// std::ostream << ezpwd::hexout( beg, end ) // output any char iterator to ostream +// std::ostream << std::vector +// std::ostream << std::array +// ezpwd::hexchr( c, [](unsigned char c){...;} )// output escaped/hex char via functor +// ezpwd::hexout( beg, end, FILE* ) // output any char iterator to FILE* +// +// Output unprintable unsigned char data in hex, escape printable/whitespace data. +// +namespace ezpwd { + + struct hexify { + unsigned char c; + std::streamsize w; + explicit hexify( + unsigned char _c, + std::streamsize _w = 2 ) + : c( _c ) + , w( _w ) + { ; } + explicit hexify( + char _c, + std::streamsize _w = 2 ) + : c( (unsigned char)_c ) + , w( _w ) + { ; } + }; + struct hexstr { + const std::string &s; + explicit hexstr( + const std::string &_s ) + : s( _s ) + { ; } + }; + + template // a functor taking a char + void + hexchr( unsigned char c, F f = []( unsigned char c ) { std::cout.put( c );}, size_t w = 2 ) + { + for ( ; w > 2; --w ) + f( ' ' ); + if ( std::isprint( c ) || std::isspace( c ) + || c == '\0' || c == '\a' || c == '\b' || c == 0x1B ) { // '\e' is not standard + switch ( c ) { + case 0x00: f( '\\' ); f( '0' ); break; // NUL + case 0x07: f( '\\' ); f( 'a' ); break; // BEL + case 0x08: f( '\\' ); f( 'b' ); break; // BS + case 0x09: f( '\\' ); f( 't' ); break; // HT + case 0x0A: f( '\\' ); f( 'n' ); break; // LF + case 0x0B: f( '\\' ); f( 'v' ); break; // VT + case 0x0C: f( '\\' ); f( 'f' ); break; // FF + case 0x0D: f( '\\' ); f( 'r' ); break; // CR + case 0x1B: f( '\\' ); f( 'e' ); break; // ESC + case '\"': f( '\\' ); f( '"' ); break; // " + case '\'': f( '\\' ); f( '\''); break; // ' + case '\\': f( '\\' ); f( '\\'); break; // '\' + default: f( ' ' ); f( c ); // space, any other printable character + } + } else { + f( "0123456789ABCDEF"[( c >> 4 ) & 0x0f ] ); + f( "0123456789ABCDEF"[( c >> 0 ) & 0x0f ] ); + } + } + + + inline + std::ostream &operator<<( + std::ostream &lhs, + const ezpwd::hexify&rhs ) + { + ezpwd::hexchr( rhs.c, [ &lhs ]( unsigned char c ) { lhs.put( c ); }, rhs.w ); + return lhs; + } + + template < typename iter_t > + inline + std::ostream &hexout( + std::ostream &lhs, + const iter_t &beg, + const iter_t &end ) + { + std::streamsize wid = lhs.width( 0 ); + int col = 0; + for ( auto i = beg; i != end; ++i ) { + if ( wid && col == wid ) { + lhs << std::endl; + col = 0; + } + lhs << hexify( *i ); + ++col; + } + return lhs; + } + + template < typename iter_t > + inline + std::FILE *hexout( + const iter_t &beg, + const iter_t &end, + std::FILE *lhs ) + { + for ( auto i = beg; i != end; ++i ) { + ezpwd::hexchr( *i, [ lhs ]( unsigned char c ) { std::fputc( c, lhs ); } ); + } + return lhs; + } + + inline + std::ostream &operator<<( + std::ostream &lhs, + const ezpwd::hexstr&rhs ) + { + return ezpwd::hexout( lhs, rhs.s.begin(), rhs.s.end() ); + } +} // namespace ezpwd + +namespace std { + template < size_t S > + inline + std::ostream &operator<<( + std::ostream &lhs, + const std::array + &rhs ) + { + return ezpwd::hexout( lhs, rhs.begin(), rhs.end() ); + } + + inline + std::ostream &operator<<( + std::ostream &lhs, + const std::vector + &rhs ) + { + return ezpwd::hexout( lhs, rhs.begin(), rhs.end() ); + } + + // + // << pair + // << set -- sorted by T + // << map -- sorted by T (key) + // << vector + // + // Handle output of various container types. + // + // Output pairs and sets of pairs, respecting specified widths (as appropriate). For example + // a set of pairs of integeters 's', if output as "... << std::setw( 13 ) << s;", would yield: + // + // ( 1, 2) ( 3, 4) ... + // + + template + std::ostream &operator<<( + std::ostream &lhs, + const std::pair &rhs ) + { + std::streamsize w = std::max( std::streamsize( 0 ), + std::streamsize( lhs.width() - 3 )); + lhs << std::setw( 0 ) + << '(' << std::setw( w / 2 ) << rhs.first + << ',' << std::setw( w - w / 2 ) << rhs.second + << ')'; + return lhs; + } + + template + std::ostream &operator<<( + std::ostream &lhs, + const std::set &rhs ) + { + std::streamsize w = lhs.width(); // If width is set, use if for each item + for ( typename std::set::const_iterator + si = rhs.begin() + ; si != rhs.end() + ; ++si ) { + if ( si != rhs.begin()) + lhs << ' '; + lhs << std::setw( w ) << *si; + } + lhs << std::setw( 0 ); // If container empty, must clear + return lhs; + } + +template +std::ostream &operator<<( + std::ostream &lhs, + const std::map&rhs ) +{ + std::streamsize w = lhs.width(); // If width is set, use if for each item + std::vector key; + for ( typename std::map::const_iterator + mi = rhs.begin() + ; mi != rhs.end() + ; ++mi ) + key.push_back( mi->first ); + std::sort( key.begin(), key.end() ); + for ( typename std::vector::const_iterator + ki = key.begin() + ; ki != key.end() + ; ++ki ) { + if ( ki != key.begin()) + lhs << ' '; + lhs << std::setw( w ) << *rhs.find( *ki ); + } + lhs << std::setw( 0 ); // If container empty, must clear + return lhs; +} + +template +std::ostream &operator<<( + std::ostream &lhs, + const std::vector &rhs ) +{ + for( size_t i = 0; i < rhs.size(); ++i ) { + if ( i ) + lhs << ", "; + lhs << rhs[i]; + } + + return lhs; +} +} // namespace std + +// +// ezpwd::buf_t -- describe a C string buffer, to allow C++ output operations +// ezpwd::streambuf_to_buf_t -- output charcters, always NUL terminated +// +// << ... -- Copy the into the C buffer, always NUL terminating +// +// Copies contents into buffer, and always NUL-terminates. Returns advanced buf_t (NOT +// including the terminating NUL, suitable for repeating ... << operations. +// +// std::ostream( & ) << ... +// +// Use standard ostream operations to send output to a C buffer, always NUL +// terminating, and never exceeding capacity. +// +namespace ezpwd { + + typedef std::pair + buf_t; + + class streambuf_to_buffer + : public std::streambuf { + private: + char *_buf; + size_t _siz; + public: + // + // streambuf_to_buf_t -- remember buf_t details + // ~streambuf_to_buf_t -- no virtual destructor required; nothing to clean up + // + streambuf_to_buffer( + char *buf, + size_t siz ) + : _buf( buf ) + , _siz( siz ) + { + if ( _siz > 0 ) + *_buf = 0; + } + explicit streambuf_to_buffer( + const buf_t &buf ) + : streambuf_to_buffer( buf.first, buf.second ) + { + ; + } + + // + // overflow -- Append c, always NUL terminating + // + virtual int overflow( + int c ) + { + if ( _siz <= 1 ) + return EOF; // No room for c and NUL; EOF + if ( EOF == c ) + return 0; // EOF provided; do nothing + --_siz; + *_buf++ = char( c ); + *_buf = 0; + return c; + } + }; // class streambuf_to_buffer + +} // namespace ezpwd + +namespace std { + + inline + ezpwd::buf_t operator<<( + const ezpwd::buf_t &buf, + const std::string &str ) + { + if ( buf.first && str.size() + 1 <= buf.second ) { + std::copy( str.begin(), str.end(), buf.first ); + buf.first[str.size()] = 0; + return ezpwd::buf_t( buf.first + str.size(), buf.second - str.size() ); + } else if ( buf.first && buf.second ) { + std::copy( str.begin(), str.begin() + buf.second - 1, buf.first ); + buf.first[buf.second-1] = 0; + return ezpwd::buf_t( buf.first + buf.second - 1, 1 ); + } + return buf; // NULL pointer or 0 size. + } + + + // + // << ... + // + // Useful (but inefficient) standard output formatting directly to a std::string. Use only for + // testing code, for efficiency reasons... + // + template < typename T > + inline + std::string operator<<( + const std::string &lhs, + const T &rhs ) + { + std::ostringstream oss; + oss << rhs; + return std::string( lhs ).append( oss.str() ); + } + +} // namespace std + +#endif // _EZPWD_OUTPUT diff --git a/op25/gr-op25_repeater/lib/ezpwd/rs b/op25/gr-op25_repeater/lib/ezpwd/rs new file mode 100644 index 0000000..3479a49 --- /dev/null +++ b/op25/gr-op25_repeater/lib/ezpwd/rs @@ -0,0 +1,168 @@ +/* + * Ezpwd Reed-Solomon -- Reed-Solomon encoder / decoder library + * + * Copyright (c) 2014, Hard Consulting Corporation. + * + * Ezpwd Reed-Solomon is free software: you can redistribute it and/or modify it under the terms of + * the GNU General Public License as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. See the LICENSE file at the top of the + * source tree. Ezpwd Reed-Solomon is also available under Commercial license. c++/ezpwd/rs_base + * is redistributed under the terms of the LGPL, regardless of the overall licensing terms. + * + * Ezpwd Reed-Solomon 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 General Public License for more details. + */ +#ifndef _EZPWD_RS +#define _EZPWD_RS + +#include "rs_base" + +// +// ezpwd::RS -- Implements an RS(SYMBOLS,PAYLOAD) codec +// ezpwd::RS_CCSDS<...> -- CCSDS standard 8-bit R-S codec +// +// Support for Reed-Solomon codecs for symbols of 2 to 16 bits is supported. The R-S "codeword" +// for an N-bit symbol is defined to be 2^N-1 symbols in size. For example, for 5-bit symbols, +// 2^5-1 == 31, so the notation for defining an Reed-Solomon codec for 5-bit symbols is always: +// RS(31,PAYLOAD), where PAYLOAD is always some value less than 31. The difference is the number of +// "parity" symbols. +// +// For example, to define an RS codeword of 31 symbols w/ 4 symbols of parity and up to 27 +// symbols of data, you would say: RS(31,27). Of course, you can supply smaller amounts of data; +// the balance is assumed to be NUL (zero) symbols. +// +namespace ezpwd { + + // + // __RS( ... ) -- Define a reed-solomon codec + // + // @SYMBOLS: Total number of symbols; must be a power of 2 minus 1, eg 2^8-1 == 255 + // @PAYLOAD: The maximum number of non-parity symbols, eg 253 ==> 2 parity symbols + // @POLY: A primitive polynomial appropriate to the SYMBOLS size + // @FCR: The first consecutive root of the Reed-Solomon generator polynomial + // @PRIM: The primitive root of the generator polynomial + // +# define __RS_TYP( TYPE, SYMBOLS, PAYLOAD, POLY, FCR, PRIM ) \ + ezpwd::reed_solomon< \ + TYPE, \ + ezpwd::log_< (SYMBOLS) + 1 >::value, \ + (SYMBOLS) - (PAYLOAD), FCR, PRIM, \ + ezpwd::gfpoly< \ + ezpwd::log_< (SYMBOLS) + 1 >::value, \ + POLY >> + +# define __RS( NAME, TYPE, SYMBOLS, PAYLOAD, POLY, FCR, PRIM ) \ + __RS_TYP( TYPE, SYMBOLS, PAYLOAD, POLY, FCR, PRIM ) { \ + NAME() \ + : __RS_TYP( TYPE, SYMBOLS, PAYLOAD, POLY, FCR, PRIM )() \ + {;} \ + } + + // + // RS -- Standard partial specializations for Reed-Solomon codec type access + // + // Normally, Reed-Solomon codecs are described with terms like RS(255,252). Obtain various + // standard Reed-Solomon codecs using macros of a similar form, eg. RS<255, 252>. Standard PLY, + // FCR and PRM values are provided for various SYMBOL sizes, along with appropriate basic types + // capable of holding all internal Reed-Solomon tabular data. + // + // In order to provide "default initialization" of const RS<...> types, a user-provided + // default constructor must be provided. + // + template < size_t SYMBOLS, size_t PAYLOAD > struct RS; + template < size_t PAYLOAD > struct RS< 3, PAYLOAD> : public __RS( RS, uint8_t, 3, PAYLOAD, 0x7, 1, 1 ); + template < size_t PAYLOAD > struct RS< 7, PAYLOAD> : public __RS( RS, uint8_t, 7, PAYLOAD, 0xb, 1, 1 ); + template < size_t PAYLOAD > struct RS< 15, PAYLOAD> : public __RS( RS, uint8_t, 15, PAYLOAD, 0x13, 1, 1 ); + template < size_t PAYLOAD > struct RS< 31, PAYLOAD> : public __RS( RS, uint8_t, 31, PAYLOAD, 0x25, 1, 1 ); + template < size_t PAYLOAD > struct RS< 63, PAYLOAD> : public __RS( RS, uint8_t, 63, PAYLOAD, 0x43, 1, 1 ); + template < size_t PAYLOAD > struct RS< 127, PAYLOAD> : public __RS( RS, uint8_t, 127, PAYLOAD, 0x89, 1, 1 ); + template < size_t PAYLOAD > struct RS< 255, PAYLOAD> : public __RS( RS, uint8_t, 255, PAYLOAD, 0x11d, 1, 1 ); + template < size_t PAYLOAD > struct RS< 511, PAYLOAD> : public __RS( RS, uint16_t, 511, PAYLOAD, 0x211, 1, 1 ); + template < size_t PAYLOAD > struct RS< 1023, PAYLOAD> : public __RS( RS, uint16_t, 1023, PAYLOAD, 0x409, 1, 1 ); + template < size_t PAYLOAD > struct RS< 2047, PAYLOAD> : public __RS( RS, uint16_t, 2047, PAYLOAD, 0x805, 1, 1 ); + template < size_t PAYLOAD > struct RS< 4095, PAYLOAD> : public __RS( RS, uint16_t, 4095, PAYLOAD, 0x1053, 1, 1 ); + template < size_t PAYLOAD > struct RS< 8191, PAYLOAD> : public __RS( RS, uint16_t, 8191, PAYLOAD, 0x201b, 1, 1 ); + template < size_t PAYLOAD > struct RS<16383, PAYLOAD> : public __RS( RS, uint16_t, 16383, PAYLOAD, 0x4443, 1, 1 ); + template < size_t PAYLOAD > struct RS<32767, PAYLOAD> : public __RS( RS, uint16_t, 32767, PAYLOAD, 0x8003, 1, 1 ); + template < size_t PAYLOAD > struct RS<65535, PAYLOAD> : public __RS( RS, uint16_t, 65535, PAYLOAD, 0x1100b, 1, 1 ); + + template < size_t SYMBOLS, size_t PAYLOAD > struct RS_CCSDS; + template < size_t PAYLOAD > struct RS_CCSDS<255, PAYLOAD> : public __RS( RS_CCSDS, uint8_t, 255, PAYLOAD, 0x187, 112, 11 ); + + + // + // strength -- compute strength (given N parity symbols) of R-S correction + // + // Returns a confidence strength rating, which is the ratio: + // + // 100 - ( errors * 2 + erasures ) * 100 / parity + // + // which is proportional to the number of parity symbols unused by the reported number of + // corrected symbols. If 0, then all parity resources were consumed to recover the R-S + // codeword, and we can have no confidence in the result. If -'ve, indicates more parity + // resources were consumed than available, indicating that the result is likely incorrect. + // + // Accounts for the fact that a signalled erasure may not be reported in the corrected + // position vector, if the symbol happens to match the computed value. Note that even if the + // error or erasure occurs within the "parity" portion of the codeword, this doesn't reduce the + // effective strength -- all symbols in the R-S complete codeword are equally effective in + // recovering any other symbol in error/erasure. + // + template < size_t PARITY > + int strength( + int corrected, + const std::vector&erasures, // original erasures positions + const std::vector&positions ) // all reported correction positions + { + // -'ve indicates R-S failure; all parity consumed, but insufficient to correct the R-S + // codeword. Missing an unknown number of additional required parity symbols, so just + // return -1 as the strength. + if ( corrected < 0 ) { +#if defined( DEBUG ) && DEBUG >= 2 + std::cout + << corrected << " corrections (R-S decode failure) == -1 confidence" + << std::endl; +#endif + return -1; + } + if ( corrected != int( positions.size() )) + EZPWD_RAISE_OR_RETURN( std::runtime_error, "inconsistent R-S decode results", -1 ); + + // Any erasures that don't turn out to contain errors are not returned as fixed positions. + // However, they have consumed parity resources. Search for each erasure location in + // positions, and if not reflected, add to the corrected/missed counters. + int missed = 0; + for ( auto e : erasures ) { + if ( std::find( positions.begin(), positions.end(), e ) == positions.end() ) { + ++corrected; + ++missed; +#if defined( DEBUG ) && DEBUG >= 2 + std::cout + << corrected << " corrections (R-S erasure missed): " << e + << std::endl; +#endif + } + } + int errors = corrected - erasures.size(); + int consumed= errors * 2 + erasures.size(); + int confidence= 100 - consumed * 100 / PARITY; +#if defined( DEBUG ) && DEBUG >= 2 + std::cout + << corrected << " corrections (R-S decode success)" + << " at: " << positions + << ", " << erasures.size() + missed + << " erasures (" << missed + << " unreported) at: " << erasures + << ") ==> " << errors + << " errors, and " << consumed << " / " << PARITY + << " parity used == " << confidence + << "% confidence" + << std::endl; +#endif + return confidence; + } + +} // namespace ezpwd + +#endif // _EZPWD_RS diff --git a/op25/gr-op25_repeater/lib/ezpwd/rs_base b/op25/gr-op25_repeater/lib/ezpwd/rs_base new file mode 100644 index 0000000..5dad4e3 --- /dev/null +++ b/op25/gr-op25_repeater/lib/ezpwd/rs_base @@ -0,0 +1,1344 @@ +/* + * Ezpwd Reed-Solomon -- Reed-Solomon encoder / decoder library + * + * Copyright (c) 2014, Hard Consulting Corporation. + * + * Ezpwd Reed-Solomon is free software: you can redistribute it and/or modify it under the terms of + * the GNU General Public License as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. See the LICENSE file at the top of the + * source tree. Ezpwd Reed-Solomon is also available under Commercial license. The + * c++/ezpwd/rs_base file is redistributed under the terms of the LGPL, regardless of the overall + * licensing terms. + * + * Ezpwd Reed-Solomon 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 General Public License for more details. + * + * The core Reed-Solomon codec implementation in c++/ezpwd/rs_base is by Phil Karn, converted to C++ + * by Perry Kundert (perry@hardconsulting.com), and may be used under the terms of the LGPL. Here + * is the terms from Phil's README file (see phil-karn/fec-3.0.1/README): + * + * COPYRIGHT + * + * This package is copyright 2006 by Phil Karn, KA9Q. It may be used + * under the terms of the GNU Lesser General Public License (LGPL). See + * the file "lesser.txt" in this package for license details. + * + * The c++/ezpwd/rs_base file is, therefore, redistributed under the terms of the LGPL, while the + * rest of Ezpwd Reed-Solomon is distributed under either the GPL or Commercial licenses. + * Therefore, even if you have obtained Ezpwd Reed-Solomon under a Commercial license, you must make + * available the source code of the c++/ezpwd/rs_base file with your product. One simple way to + * accomplish this is to include the following URL in your code or documentation: + * + * https://github.com/pjkundert/ezpwd-reed-solomon/blob/master/c++/ezpwd/rs_base + * + * + * The Linux 3.15.1 version of lib/reed_solomon was also consulted as a cross-reference, which (in + * turn) is basically verbatim copied from Phil Karn's LGPL implementation, to ensure that no new + * defects had been found and fixed; there were no meaningful changes made to Phil's implementation. + * I've personally been using Phil's implementation for years in a heavy industrial use, and it is + * rock-solid. + * + * However, both Phil's and the Linux kernel's (copy of Phil's) implementation will return a + * "corrected" decoding with impossible error positions, in some cases where the error load + * completely overwhelms the R-S encoding. These cases, when detected, are rejected in this + * implementation. This could be considered a defect in Phil's (and hence the Linux kernel's) + * implementations, which results in them accepting clearly incorrect R-S decoded values as valid + * (corrected) R-S codewords. We chose the report failure on these attempts. + * + */ +#ifndef _EZPWD_RS_BASE +#define _EZPWD_RS_BASE + +#include +#include +#include +#include +#include +#include +#include + +// +// Preprocessor defines available: +// +// EZPWD_NO_EXCEPTS -- define to use no exceptions; return -1, or abort on catastrophic failures +// EZPWD_NO_MOD_TAB -- define to force no "modnn" Galois modulo table acceleration +// EZPWD_ARRAY_SAFE -- define to force usage of bounds-checked arrays for most tabular data +// EZPWD_ARRAY_TEST -- define to force erroneous sizing of some arrays for non-production testing +// + +#if defined( DEBUG ) && DEBUG >= 2 +# include "output" // ezpwd::hex... std::ostream shims for outputting containers of uint8_t data +#endif + +#if defined( EZPWD_NO_EXCEPTS ) +# include // No exceptions; don't use C++ ostream +# define EZPWD_RAISE_OR_ABORT( typ, str ) do { \ + std::fputs(( str ), stderr ); std::fputc( '\n', stderr ); \ + abort(); \ + } while ( false ) +# define EZPWD_RAISE_OR_RETURN( typ, str, ret ) return ( ret ) +#else +# define EZPWD_RAISE_OR_ABORT( typ, str ) throw ( typ )( str ) +# define EZPWD_RAISE_OR_RETURN( typ, str, ret ) throw ( typ )( str ) +#endif + +namespace ezpwd { + + // ezpwd::log_ -- compute the log base B of N at compile-time + template struct log_{ enum { value = 1 + log_::value }; }; + template struct log_<1, B>{ enum { value = 0 }; }; + template struct log_<0, B>{ enum { value = 0 }; }; + + // + // reed_solomon_base - Reed-Solomon codec generic base class + // + class reed_solomon_base { + public: + virtual size_t datum() const = 0; // a data element's bits + virtual size_t symbol() const = 0; // a symbol's bits + virtual int size() const = 0; // R-S block size (maximum total symbols) + virtual int nroots() const = 0; // R-S roots (parity symbols) + virtual int load() const = 0; // R-S net payload (data symbols) + + virtual ~reed_solomon_base() + { + ; + } + reed_solomon_base() + { + ; + } + + virtual std::ostream &output( + std::ostream &lhs ) + const + { + return lhs << "RS(" << this->size() << "," << this->load() << ")"; + } + + // + // {en,de}code -- Compute/Correct errors/erasures in a Reed-Solomon encoded container + // + /// The encoded parity symbols may be included in 'data' (len includes nroots() parity + /// symbols), or may (optionally) supplied separately in (at least nroots()-sized) + /// 'parity'. + /// + /// For decode, optionally specify some known erasure positions (up to nroots()). If + /// non-empty 'erasures' is provided, it contains the positions of each erasure. If a + /// non-zero pointer to a 'position' vector is provided, its capacity will be increased to + /// be capable of storing up to 'nroots()' ints; the actual deduced error locations will be + /// returned. + /// + /// RETURN VALUE + /// + /// Return -1 on error. The encode returns the number of parity symbols produced; + /// decode returns the number of symbols corrected. Both errors and erasures are included, + /// so long as they are actually different than the deduced value. In other words, if a + /// symbol is marked as an erasure but it actually turns out to be correct, it's index will + /// NOT be included in the returned count, nor the modified erasure vector! + /// + + // + // encode() -- extend string to contain parity, or place in supplied parity string + // encode() -- extend vector to contain parity, or place in supplied parity vector + // encode() -- ignore 'pad' elements of array, puts nroots() parity symbols at end + // encode(pair) -- encode all except the last nroots() of the data, put parity at end + // encode(pair, pair) -- encode data between first pair, put parity in second pair + // + int encode( + std::string &data ) + const + { + typedef uint8_t uT; + typedef std::pair + uTpair; + data.resize( data.size() + nroots() ); + return encode( uTpair( (uT *)&data.front(), (uT *)&data.front() + data.size() )); + } + + int encode( + const std::string &data, + std::string &parity ) + const + { + typedef uint8_t uT; + typedef std::pair + cuTpair; + typedef std::pair + uTpair; + parity.resize( nroots() ); + return encode( cuTpair( (const uT *)&data.front(), (const uT *)&data.front() + data.size() ), + uTpair( (uT *)&parity.front(), (uT *)&parity.front() + parity.size() )); + } + + template < typename T > + int encode( + std::vector &data ) + const + { + typedef typename std::make_unsigned::type + uT; + typedef std::pair + uTpair; + data.resize( data.size() + nroots() ); + return encode( uTpair( (uT *)&data.front(), (uT *)&data.front() + data.size() )); + } + template < typename T > + int encode( + const std::vector&data, + std::vector &parity ) + const + { + typedef typename std::make_unsigned::type + uT; + typedef std::pair + cuTpair; + typedef std::pair + uTpair; + parity.resize( nroots() ); + return encode( cuTpair( (uT *)&data.front(), (uT *)&data.front() + data.size() ), + uTpair( (uT *)&parity.front(), (uT *)&parity.front() + parity.size() )); + } + + template < typename T, size_t N > + int encode( + std::array &data, + int pad = 0 ) // ignore 'pad' symbols at start of array + const + { + typedef typename std::make_unsigned::type + uT; + typedef std::pair + uTpair; + return encode( uTpair( (uT *)&data.front() + pad, (uT *)&data.front() + data.size() )); + } + + virtual int encode( + const std::pair + &data ) + const + = 0; + virtual int encode( + const std::pair + &data, + const std::pair + &parity ) + const + = 0; + virtual int encode( + const std::pair + &data ) + const + = 0; + virtual int encode( + const std::pair + &data, + const std::pair + &parity ) + const + = 0; + virtual int encode( + const std::pair + &data ) + const + = 0; + virtual int encode( + const std::pair + &data, + const std::pair + &parity ) + const + = 0; + + int decode( + std::string &data, + const std::vector + &erasure = std::vector(), + std::vector *position= 0 ) + const + { + typedef uint8_t uT; + typedef std::pair + uTpair; + return decode( uTpair( (uT *)&data.front(), (uT *)&data.front() + data.size() ), + erasure, position ); + } + + int decode( + std::string &data, + std::string &parity, + const std::vector + &erasure = std::vector(), + std::vector *position= 0 ) + const + { + typedef uint8_t uT; + typedef std::pair + uTpair; + return decode( uTpair( (uT *)&data.front(), (uT *)&data.front() + data.size() ), + uTpair( (uT *)&parity.front(), (uT *)&parity.front() + parity.size() ), + erasure, position ); + } + + template < typename T > + int decode( + std::vector &data, + const std::vector + &erasure = std::vector(), + std::vector *position= 0 ) + const + { + typedef typename std::make_unsigned::type + uT; + typedef std::pair + uTpair; + return decode( uTpair( (uT *)&data.front(), (uT *)&data.front() + data.size() ), + erasure, position ); + } + + template < typename T > + int decode( + std::vector &data, + std::vector &parity, + const std::vector + &erasure = std::vector(), + std::vector *position= 0 ) + const + { + typedef typename std::make_unsigned::type + uT; + typedef std::pair + uTpair; + return decode( uTpair( (uT *)&data.front(), (uT *)&data.front() + data.size() ), + uTpair( (uT *)&parity.front(), (uT *)&parity.front() + parity.size() ), + erasure, position ); + } + + template < typename T, size_t N > + int decode( + std::array &data, + int pad = 0, // ignore 'pad' symbols at start of array + const std::vector + &erasure = std::vector(), + std::vector *position= 0 ) + const + { + typedef typename std::make_unsigned::type + uT; + typedef std::pair + uTpair; + return decode( uTpair( (uT *)&data.front() + pad, (uT *)&data.front() + data.size() ), + erasure, position ); + } + + virtual int decode( + const std::pair + &data, + const std::vector + &erasure = std::vector(), + std::vector *position= 0 ) + const + = 0; + virtual int decode( + const std::pair + &data, + const std::pair + &parity, + const std::vector + &erasure = std::vector(), + std::vector *position= 0 ) + const + = 0; + virtual int decode( + const std::pair + &data, + const std::vector + &erasure = std::vector(), + std::vector *position= 0 ) + const + = 0; + virtual int decode( + const std::pair + &data, + const std::pair + &parity, + const std::vector + &erasure = std::vector(), + std::vector *position= 0 ) + const + = 0; + virtual int decode( + const std::pair + &data, + const std::vector + &erasure = std::vector(), + std::vector *position= 0 ) + const + = 0; + virtual int decode( + const std::pair + &data, + const std::pair + &parity, + const std::vector + &erasure = std::vector(), + std::vector *position= 0 ) + const + = 0; + }; // class reed_solomon_base + + // + // std::ostream << ezpwd::reed_solomon<...> + // + // Output a R-S codec description in standard form eg. RS(255,253) + // + inline + std::ostream &operator<<( + std::ostream &lhs, + const ezpwd::reed_solomon_base + &rhs ) + { + return rhs.output( lhs ); + } + + // + // gfpoly - default field polynomial generator functor. + // + template < int SYM, int PLY > + struct gfpoly { + int operator() ( int sr ) + const + { + if ( sr == 0 ) + sr = 1; + else { + sr <<= 1; + if ( sr & ( 1 << SYM )) + sr ^= PLY; + sr &= (( 1 << SYM ) - 1); + } + return sr; + } + }; + + // + // class reed_solomon_tabs -- R-S tables common to all RS(NN,*) with same SYM, PRM and PLY + // + template < typename TYP, int SYM, int PRM, class PLY > + class reed_solomon_tabs + : public reed_solomon_base { + + public: + typedef TYP symbol_t; + static const size_t DATUM = 8 * sizeof TYP(); // bits / TYP + static const size_t SYMBOL = SYM; // bits / symbol + static const int MM = SYM; + static const int SIZE = ( 1 << SYM ) - 1; // maximum symbols in field + static const int NN = SIZE; + static const int A0 = SIZE; + static const int MODS // modulo table: 1/2 the symbol size squared, up to 4k +#if defined( EZPWD_NO_MOD_TAB ) + = 0; +#else + = SYM > 8 ? ( 1 << 12 ) : ( 1 << SYM << SYM/2 ); +#endif + + static int iprim; // initialized to -1, below + + protected: + static std::array +#else +# warning "EZPWD_ARRAY_TEST: Erroneously declaring alpha_to size!" + NN > +#endif + alpha_to; + static std::array + index_of; + static std::array + mod_of; + virtual ~reed_solomon_tabs() + { + ; + } + reed_solomon_tabs() + : reed_solomon_base() + { + // Do init if not already done. We check one value which is initialized to -1; this is + // safe, 'cause the value will not be set 'til the initializing thread has completely + // initialized the structure. Worst case scenario: multiple threads will initialize + // identically. No mutex necessary. + if ( iprim >= 0 ) + return; + +#if defined( DEBUG ) && DEBUG >= 1 + std::cout << "RS(" << SIZE << ",*): Initialize for " << NN << " symbols size, " << MODS << " modulo table." << std::endl; +#endif + // Generate Galois field lookup tables + index_of[0] = A0; // log(zero) = -inf + alpha_to[A0] = 0; // alpha**-inf = 0 + PLY poly; + int sr = poly( 0 ); + for ( int i = 0; i < NN; i++ ) { + index_of[sr] = i; + alpha_to[i] = sr; + sr = poly( sr ); + } + // If it's not primitive, raise exception or abort + if ( sr != alpha_to[0] ) { + EZPWD_RAISE_OR_ABORT( std::runtime_error, "reed-solomon: Galois field polynomial not primitive" ); + } + + // Generate modulo table for some commonly used (non-trivial) values + for ( int x = NN; x < NN + MODS; ++x ) + mod_of[x-NN] = _modnn( x ); + // Find prim-th root of 1, index form, used in decoding. + int iptmp = 1; + while ( iptmp % PRM != 0 ) + iptmp += NN; + iprim = iptmp / PRM; + } + + // + // modnn -- modulo replacement for galois field arithmetics, optionally w/ table acceleration + // + // @x: the value to reduce (will never be -'ve) + // + // where + // MM = number of bits per symbol + // NN = (2^MM) - 1 + // + // Simple arithmetic modulo would return a wrong result for values >= 3 * NN + // + TYP _modnn( + int x ) + const + { + while ( x >= NN ) { + x -= NN; + x = ( x >> MM ) + ( x & NN ); + } + return x; + } + TYP modnn( + int x ) + const + { + while ( x >= NN + MODS ) { + x -= NN; + x = ( x >> MM ) + ( x & NN ); + } + if ( MODS && x >= NN ) + x = mod_of[x-NN]; + return x; + } + }; + + // + // class reed_solomon - Reed-Solomon codec + // + // @TYP: A symbol datum; {en,de}code operates on arrays of these + // @DATUM: Bits per datum (a TYP()) + // @SYM{BOL}, MM: Bits per symbol + // @NN: Symbols per block (== (1< instances with the same template type parameters share a common + // (static) set of alpha_to, index_of and genpoly tables. The first instance to be constructed + // initializes the tables. + // + // Each specialized type of reed_solomon implements a specific encode/decode method + // appropriate to its datum 'TYP'. When accessed via a generic reed_solomon_base pointer, only + // access via "safe" (size specifying) containers or iterators is available. + // + template < typename TYP, int SYM, int RTS, int FCR, int PRM, class PLY > + class reed_solomon + : public reed_solomon_tabs { + + public: + typedef reed_solomon_tabs + tabs_t; + using tabs_t::DATUM; + using tabs_t::SYMBOL; + using tabs_t::MM; + using tabs_t::SIZE; + using tabs_t::NN; + using tabs_t::A0; + + using tabs_t::iprim; + + using tabs_t::alpha_to; + using tabs_t::index_of; + + using tabs_t::modnn; + + static const int NROOTS = RTS; + static const int LOAD = SIZE - NROOTS; // maximum non-parity symbol payload + + protected: + static std::array + genpoly; + + public: + virtual size_t datum() const + { + return DATUM; + } + + virtual size_t symbol() const + { + return SYMBOL; + } + + virtual int size() const + { + return SIZE; + } + + virtual int nroots() const + { + return NROOTS; + } + + virtual int load() const + { + return LOAD; + } + + using reed_solomon_base::encode; + virtual int encode( + const std::pair + &data ) + const + { + return encode_mask( data.first, data.second - data.first - NROOTS, data.second - NROOTS ); + } + + virtual int encode( + const std::pair + &data, + const std::pair + &parity ) + const + { + if ( parity.second - parity.first != NROOTS ) { + EZPWD_RAISE_OR_RETURN( std::runtime_error, "reed-solomon: parity length incompatible with number of roots", -1 ); + } + return encode_mask( data.first, data.second - data.first, parity.first ); + } + + virtual int encode( + const std::pair + &data ) + const + { + return encode_mask( data.first, data.second - data.first - NROOTS, data.second - NROOTS ); + } + + virtual int encode( + const std::pair + &data, + const std::pair + &parity ) + const + { + if ( parity.second - parity.first != NROOTS ) { + EZPWD_RAISE_OR_RETURN( std::runtime_error, "reed-solomon: parity length incompatible with number of roots", -1 ); + } + return encode_mask( data.first, data.second - data.first, parity.first ); + } + + virtual int encode( + const std::pair + &data ) + const + { + return encode_mask( data.first, data.second - data.first - NROOTS, data.second - NROOTS ); + } + + virtual int encode( + const std::pair + &data, + const std::pair + &parity ) + const + { + if ( parity.second - parity.first != NROOTS ) { + EZPWD_RAISE_OR_RETURN( std::runtime_error, "reed-solomon: parity length incompatible with number of roots", -1 ); + } + return encode_mask( data.first, data.second - data.first, parity.first ); + } + + template < typename INP > + int encode_mask( + const INP *data, + int len, + INP *parity ) // pointer to all NROOTS parity symbols + + const + { + if ( len < 1 ) { + EZPWD_RAISE_OR_RETURN( std::runtime_error, "reed-solomon: must provide space for all parity and at least one non-parity symbol", -1 ); + } + + const TYP *dataptr; + TYP *pariptr; + const size_t INPUT = 8 * sizeof ( INP ); + + if ( DATUM != SYMBOL || DATUM != INPUT ) { + // Our DATUM (TYP) size (eg. uint8_t ==> 8, uint16_t ==> 16, uint32_t ==> 32) + // doesn't exactly match our R-S SYMBOL size (eg. 6), or our INP size; Must mask and + // copy. The INP data must fit at least the SYMBOL size! + if ( SYMBOL > INPUT ) { + EZPWD_RAISE_OR_RETURN( std::runtime_error, "reed-solomon: output data type too small to contain symbols", -1 ); + } + std::array tmp; + TYP msk = static_cast( ~0UL << SYMBOL ); + for ( int i = 0; i < len; ++i ) + tmp[LOAD - len + i] = data[i] & ~msk; + dataptr = &tmp[LOAD - len]; + pariptr = &tmp[LOAD]; + + encode( dataptr, len, pariptr ); + + // we copied/masked data; copy the parity symbols back (may be different sizes) + for ( int i = 0; i < NROOTS; ++i ) + parity[i] = pariptr[i]; + } else { + // Our R-S SYMBOL size, DATUM size and INP type size exactly matches; use in-place. + dataptr = reinterpret_cast( data ); + pariptr = reinterpret_cast( parity ); + + encode( dataptr, len, pariptr ); + } + return NROOTS; + } + + using reed_solomon_base::decode; + virtual int decode( + const std::pair + &data, + const std::vector + &erasure = std::vector(), + std::vector *position= 0 ) + const + { + return decode_mask( data.first, data.second - data.first, (uint8_t *)0, + erasure, position ); + } + + virtual int decode( + const std::pair + &data, + const std::pair + &parity, + const std::vector + &erasure = std::vector(), + std::vector *position= 0 ) + const + { + if ( parity.second - parity.first != NROOTS ) { + EZPWD_RAISE_OR_RETURN( std::runtime_error, "reed-solomon: parity length incompatible with number of roots", -1 ); + } + return decode_mask( data.first, data.second - data.first, parity.first, + erasure, position ); + } + + virtual int decode( + const std::pair + &data, + const std::vector + &erasure = std::vector(), + std::vector *position= 0 ) + const + { + return decode_mask( data.first, data.second - data.first, (uint16_t *)0, + erasure, position ); + } + + virtual int decode( + const std::pair + &data, + const std::pair + &parity, + const std::vector + &erasure = std::vector(), + std::vector *position= 0 ) + const + { + if ( parity.second - parity.first != NROOTS ) { + EZPWD_RAISE_OR_RETURN( std::runtime_error, "reed-solomon: parity length incompatible with number of roots", -1 ); + } + return decode_mask( data.first, data.second - data.first, parity.first, + erasure, position ); + } + + virtual int decode( + const std::pair + &data, + const std::vector + &erasure = std::vector(), + std::vector *position= 0 ) + const + { + return decode_mask( data.first, data.second - data.first, (uint32_t *)0, + erasure, position ); + } + + virtual int decode( + const std::pair + &data, + const std::pair + &parity, + const std::vector + &erasure = std::vector(), + std::vector *position= 0 ) + const + { + if ( parity.second - parity.first != NROOTS ) { + EZPWD_RAISE_OR_RETURN( std::runtime_error, "reed-solomon: parity length incompatible with number of roots", -1 ); + } + return decode_mask( data.first, data.second - data.first, parity.first, + erasure, position ); + } + + // + // decode_mask -- mask INP data into valid SYMBOL data + // + /// Incoming data may be in a variety of sizes, and may contain information beyond the + /// R-S symbol capacity. For example, we might use a 6-bit R-S symbol to correct the lower + /// 6 bits of an 8-bit data character. This would allow us to correct common substitution + /// errors (such as '2' for '3', 'R' for 'T', 'n' for 'm'). + /// + template < typename INP > + int decode_mask( + INP *data, + int len, + INP *parity = 0, // either 0, or pointer to all parity symbols + const std::vector + &erasure = std::vector(), + std::vector *position= 0 ) + const + { + if ( len < ( parity ? 0 : NROOTS ) + 1 ) { + EZPWD_RAISE_OR_RETURN( std::runtime_error, "reed-solomon: must provide all parity and at least one non-parity symbol", -1 ); + } + if ( ! parity ) { + len -= NROOTS; + parity = data + len; + } + + TYP *dataptr; + TYP *pariptr; + const size_t INPUT = 8 * sizeof ( INP ); + + std::array tmp; + TYP msk = static_cast( ~0UL << SYMBOL ); + const bool cpy = DATUM != SYMBOL || DATUM != INPUT; + if ( cpy ) { + // Our DATUM (TYP) size (eg. uint8_t ==> 8, uint16_t ==> 16, uint32_t ==> 32) + // doesn't exactly match our R-S SYMBOL size (eg. 6), or our INP size; Must copy. + // The INP data must fit at least the SYMBOL size! + if ( SYMBOL > INPUT ) { + EZPWD_RAISE_OR_RETURN( std::runtime_error, "reed-solomon: input data type too small to contain symbols", -1 ); + } + for ( int i = 0; i < len; ++i ) { + tmp[LOAD - len + i] = data[i] & ~msk; + } + dataptr = &tmp[LOAD - len]; + for ( int i = 0; i < NROOTS; ++i ) { + if ( TYP( parity[i] ) & msk ) { + EZPWD_RAISE_OR_RETURN( std::runtime_error, "reed-solomon: parity data contains information beyond R-S symbol size", -1 ); + } + tmp[LOAD + i] = parity[i]; + } + pariptr = &tmp[LOAD]; + } else { + // Our R-S SYMBOL size, DATUM size and INPUT type sizes exactly matches + dataptr = reinterpret_cast( data ); + pariptr = reinterpret_cast( parity ); + } + + int corrects; + if ( ! erasure.size() && ! position ) { + // No erasures, and error position info not wanted. + corrects = decode( dataptr, len, pariptr ); + } else { + // Either erasure location info specified, or resultant error position info wanted; + // Prepare pos (a temporary, if no position vector provided), and copy any provided + // erasure positions. After number of corrections is known, resize the position + // vector. Thus, we use any supplied erasure info, and optionally return any + // correction position info separately. + std::vector _pos; + std::vector &pos = position ? *position : _pos; + pos.resize( std::max( size_t( NROOTS ), erasure.size() )); + std::copy( erasure.begin(), erasure.end(), pos.begin() ); + corrects = decode( dataptr, len, pariptr, + &pos.front(), erasure.size() ); + if ( corrects > int( pos.size() )) { + EZPWD_RAISE_OR_RETURN( std::runtime_error, "reed-solomon: FATAL: produced too many corrections; possible corruption!", -1 ); + } + pos.resize( std::max( 0, corrects )); + } + + if ( cpy && corrects > 0 ) { + for ( int i = 0; i < len; ++i ) { + data[i] &= msk; + data[i] |= tmp[LOAD - len + i]; + } + for ( int i = 0; i < NROOTS; ++i ) { + parity[i] = tmp[LOAD + i]; + } + } + return corrects; + } + + virtual ~reed_solomon() + { + ; + } + reed_solomon() + : reed_solomon_tabs() + { + // We check one element of the array; this is safe, 'cause the value will not be + // initialized 'til the initializing thread has completely initialized the array. Worst + // case scenario: multiple threads will initialize identically. No mutex necessary. + if ( genpoly[0] ) + return; + +#if defined( DEBUG ) && DEBUG >= 2 + std::cout << "RS(" << SIZE << "," << LOAD << "): Initialize for " << NROOTS << " roots." << std::endl; +#endif + std::array + tmppoly; // uninitialized + // Form RS code generator polynomial from its roots. Only lower-index entries are + // consulted, when computing subsequent entries; only index 0 needs initialization. + tmppoly[0] = 1; + for ( int i = 0, root = FCR * PRM; i < NROOTS; i++, root += PRM ) { + tmppoly[i + 1] = 1; + // Multiply tmppoly[] by @**(root + x) + for ( int j = i; j > 0; j-- ) { + if ( tmppoly[j] != 0 ) + tmppoly[j] = tmppoly[j - 1] + ^ alpha_to[modnn(index_of[tmppoly[j]] + root)]; + else + tmppoly[j] = tmppoly[j - 1]; + } + // tmppoly[0] can never be zero + tmppoly[0] = alpha_to[modnn(index_of[tmppoly[0]] + root)]; + } + // convert NROOTS entries of tmppoly[] to genpoly[] in index form for quicker encoding, + // in reverse order so genpoly[0] is last element initialized. + for ( int i = NROOTS; i >= 0; --i ) + genpoly[i] = index_of[tmppoly[i]]; + } + + int encode( + const TYP *data, + int len, + TYP *parity ) // at least nroots + const + { + // Check length parameter for validity + int pad = NN - NROOTS - len; + if ( pad < 0 || pad >= NN ) { + EZPWD_RAISE_OR_RETURN( std::runtime_error, "reed-solomon: data length incompatible with block size and error correction symbols", -1 ); + } + for ( int i = 0; i < NROOTS; i++ ) + parity[i] = 0; + for ( int i = 0; i < len; i++ ) { + TYP feedback= index_of[data[i] ^ parity[0]]; + if ( feedback != A0 ) + for ( int j = 1; j < NROOTS; j++ ) + parity[j] ^= alpha_to[modnn(feedback + genpoly[NROOTS - j])]; + + std::rotate( parity, parity + 1, parity + NROOTS ); + if ( feedback != A0 ) + parity[NROOTS - 1] = alpha_to[modnn(feedback + genpoly[0])]; + else + parity[NROOTS - 1] = 0; + } +#if defined( DEBUG ) && DEBUG >= 2 + std::cout << *this << " encode " << std::vector( data, data + len ) + << " --> " << std::vector( parity, parity + NROOTS ) << std::endl; +#endif + return NROOTS; + } + + int decode( + TYP *data, + int len, + TYP *parity, // Requires: at least NROOTS + int *eras_pos= 0, // Capacity: at least NROOTS + int no_eras = 0, // Maximum: at most NROOTS + TYP *corr = 0 ) // Capacity: at least NROOTS + const + { + typedef std::array< TYP, NROOTS > + typ_nroots; + typedef std::array< TYP, NROOTS+1 > + typ_nroots_1; + typedef std::array< int, NROOTS > + int_nroots; + + typ_nroots_1 lambda { { 0 } }; + typ_nroots syn; + typ_nroots_1 b; + typ_nroots_1 t; + typ_nroots_1 omega; + int_nroots root; + typ_nroots_1 reg; + int_nroots loc; + int count = 0; + + // Check length parameter and erasures for validity + int pad = NN - NROOTS - len; + if ( pad < 0 || pad >= NN ) { + EZPWD_RAISE_OR_RETURN( std::runtime_error, "reed-solomon: data length incompatible with block size and error correction symbols", -1 ); + } + if ( no_eras ) { + if ( no_eras > NROOTS ) { + EZPWD_RAISE_OR_RETURN( std::runtime_error, "reed-solomon: number of erasures exceeds capacity (number of roots)", -1 ); + } + for ( int i = 0; i < no_eras; ++i ) { + if ( eras_pos[i] < 0 || eras_pos[i] >= len + NROOTS ) { + EZPWD_RAISE_OR_RETURN( std::runtime_error, "reed-solomon: erasure positions outside data+parity", -1 ); + } + } + } + + // form the syndromes; i.e., evaluate data(x) at roots of g(x) + for ( int i = 0; i < NROOTS; i++ ) + syn[i] = data[0]; + + for ( int j = 1; j < len; j++ ) { + for ( int i = 0; i < NROOTS; i++ ) { + if ( syn[i] == 0 ) { + syn[i] = data[j]; + } else { + syn[i] = data[j] + ^ alpha_to[modnn(index_of[syn[i]] + ( FCR + i ) * PRM)]; + } + } + } + + for ( int j = 0; j < NROOTS; j++ ) { + for ( int i = 0; i < NROOTS; i++ ) { + if ( syn[i] == 0 ) { + syn[i] = parity[j]; + } else { + syn[i] = parity[j] + ^ alpha_to[modnn(index_of[syn[i]] + ( FCR + i ) * PRM)]; + } + } + } + + // Convert syndromes to index form, checking for nonzero condition + TYP syn_error = 0; + for ( int i = 0; i < NROOTS; i++ ) { + syn_error |= syn[i]; + syn[i] = index_of[syn[i]]; + } + + int deg_lambda = 0; + int deg_omega = 0; + int r = no_eras; + int el = no_eras; + if ( ! syn_error ) { + // if syndrome is zero, data[] is a codeword and there are no errors to correct. + count = 0; + goto finish; + } + + lambda[0] = 1; + if ( no_eras > 0 ) { + // Init lambda to be the erasure locator polynomial. Convert erasure positions + // from index into data, to index into Reed-Solomon block. + lambda[1] = alpha_to[modnn(PRM * (NN - 1 - ( eras_pos[0] + pad )))]; + for ( int i = 1; i < no_eras; i++ ) { + TYP u = modnn(PRM * (NN - 1 - ( eras_pos[i] + pad ))); + for ( int j = i + 1; j > 0; j-- ) { + TYP tmp = index_of[lambda[j - 1]]; + if ( tmp != A0 ) { + lambda[j] ^= alpha_to[modnn(u + tmp)]; + } + } + } + } + +#if DEBUG >= 1 + // Test code that verifies the erasure locator polynomial just constructed + // Needed only for decoder debugging. + + // find roots of the erasure location polynomial + for( int i = 1; i<= no_eras; i++ ) + reg[i] = index_of[lambda[i]]; + + count = 0; + for ( int i = 1, k = iprim - 1; i <= NN; i++, k = modnn( k + iprim )) { + TYP q = 1; + for ( int j = 1; j <= no_eras; j++ ) { + if ( reg[j] != A0 ) { + reg[j] = modnn( reg[j] + j ); + q ^= alpha_to[reg[j]]; + } + } + if ( q != 0 ) + continue; + // store root and error location number indices + root[count] = i; + loc[count] = k; + count++; + } + if ( count != no_eras ) { + std::cout << "ERROR: count = " << count << ", no_eras = " << no_eras + << "lambda(x) is WRONG" + << std::endl; + count = -1; + goto finish; + } +#if DEBUG >= 2 + if ( count ) { + std::cout + << "Erasure positions as determined by roots of Eras Loc Poly: "; + for ( int i = 0; i < count; i++ ) + std::cout << loc[i] << ' '; + std::cout << std::endl; + std::cout + << "Erasure positions as determined by roots of eras_pos array: "; + for ( int i = 0; i < no_eras; i++ ) + std::cout << eras_pos[i] << ' '; + std::cout << std::endl; + } +#endif +#endif + + for ( int i = 0; i < NROOTS + 1; i++ ) + b[i] = index_of[lambda[i]]; + + // + // Begin Berlekamp-Massey algorithm to determine error+erasure locator polynomial + // + while ( ++r <= NROOTS ) { // r is the step number + // Compute discrepancy at the r-th step in poly-form + TYP discr_r = 0; + for ( int i = 0; i < r; i++ ) { + if (( lambda[i] != 0 ) && ( syn[r - i - 1] != A0 )) { + discr_r ^= alpha_to[modnn(index_of[lambda[i]] + syn[r - i - 1])]; + } + } + discr_r = index_of[discr_r]; // Index form + if ( discr_r == A0 ) { + // 2 lines below: B(x) <-- x*B(x) + // Rotate the last element of b[NROOTS+1] to b[0] + std::rotate( b.begin(), b.begin()+NROOTS, b.end() ); + b[0] = A0; + } else { + // 7 lines below: T(x) <-- lambda(x)-discr_r*x*b(x) + t[0] = lambda[0]; + for ( int i = 0; i < NROOTS; i++ ) { + if ( b[i] != A0 ) { + t[i + 1] = lambda[i + 1] + ^ alpha_to[modnn(discr_r + b[i])]; + } else + t[i + 1] = lambda[i + 1]; + } + if ( 2 * el <= r + no_eras - 1 ) { + el = r + no_eras - el; + // 2 lines below: B(x) <-- inv(discr_r) * lambda(x) + for ( int i = 0; i <= NROOTS; i++ ) { + b[i] = ((lambda[i] == 0) + ? A0 + : modnn(index_of[lambda[i]] - discr_r + NN)); + } + } else { + // 2 lines below: B(x) <-- x*B(x) + std::rotate( b.begin(), b.begin()+NROOTS, b.end() ); + b[0] = A0; + } + lambda = t; + } + } + + // Convert lambda to index form and compute deg(lambda(x)) + for ( int i = 0; i < NROOTS + 1; i++ ) { + lambda[i] = index_of[lambda[i]]; + if ( lambda[i] != NN ) + deg_lambda = i; + } + // Find roots of error+erasure locator polynomial by Chien search + reg = lambda; + count = 0; // Number of roots of lambda(x) + for ( int i = 1, k = iprim - 1; i <= NN; i++, k = modnn( k + iprim )) { + TYP q = 1; // lambda[0] is always 0 + for ( int j = deg_lambda; j > 0; j-- ) { + if ( reg[j] != A0 ) { + reg[j] = modnn( reg[j] + j ); + q ^= alpha_to[reg[j]]; + } + } + if ( q != 0 ) + continue; // Not a root + // store root (index-form) and error location number +#if DEBUG >= 2 + std::cout << "count " << count << " root " << i << " loc " << k << std::endl; +#endif + root[count] = i; + loc[count] = k; + // If we've already found max possible roots, abort the search to save time + if ( ++count == deg_lambda ) + break; + } + if ( deg_lambda != count ) { + // deg(lambda) unequal to number of roots => uncorrectable error detected + count = -1; + goto finish; + } + // + // Compute err+eras evaluator poly omega(x) = s(x)*lambda(x) (modulo x**NROOTS). in + // index form. Also find deg(omega). + // + deg_omega = deg_lambda - 1; + for ( int i = 0; i <= deg_omega; i++ ) { + TYP tmp = 0; + for ( int j = i; j >= 0; j-- ) { + if (( syn[i - j] != A0 ) && ( lambda[j] != A0 )) + tmp ^= alpha_to[modnn(syn[i - j] + lambda[j])]; + } + omega[i] = index_of[tmp]; + } + + // + // Compute error values in poly-form. num1 = omega(inv(X(l))), num2 = inv(X(l))**(fcr-1) + // and den = lambda_pr(inv(X(l))) all in poly-form + // + for ( int j = count - 1; j >= 0; j-- ) { + TYP num1 = 0; + for ( int i = deg_omega; i >= 0; i-- ) { + if ( omega[i] != A0 ) + num1 ^= alpha_to[modnn(omega[i] + i * root[j])]; + } + TYP num2 = alpha_to[modnn(root[j] * ( FCR - 1 ) + NN)]; + TYP den = 0; + + // lambda[i+1] for i even is the formal derivative lambda_pr of lambda[i] + for ( int i = std::min(deg_lambda, NROOTS - 1) & ~1; i >= 0; i -= 2 ) { + if ( lambda[i + 1] != A0 ) { + den ^= alpha_to[modnn(lambda[i + 1] + i * root[j])]; + } + } +#if defined( DEBUG ) && DEBUG >= 1 + if ( den == 0 ) { + std::cout << "ERROR: denominator = 0" << std::endl; + count = -1; + goto finish; + } +#endif + // Apply error to data. Padding ('pad' unused symbols) begin at index 0. + if ( num1 != 0 ) { + if ( loc[j] < pad ) { + // If the computed error position is in the 'pad' (the unused portion of the + // R-S data capacity), then our solution has failed -- we've computed a + // correction location outside of the data and parity we've been provided! +#if DEBUG >= 2 + std::cout + << "ERROR: RS(" << SIZE <<"," << LOAD + << ") computed error location: " << loc[j] + << " within " << pad << " pad symbols, not within " + << LOAD - pad << " data or " << NROOTS << " parity" + << std::endl; +#endif + count = -1; + goto finish; + } + + TYP cor = alpha_to[modnn(index_of[num1] + + index_of[num2] + + NN - index_of[den])]; + // Store the error correction pattern, if a correction buffer is available + if ( corr ) + corr[j] = cor; + // If a data/parity buffer is given and the error is inside the message or + // parity data, correct it + if ( loc[j] < ( NN - NROOTS )) { + if ( data ) { + data[loc[j] - pad] ^= cor; + } + } else if ( loc[j] < NN ) { + if ( parity ) + parity[loc[j] - ( NN - NROOTS )] ^= cor; + } + } + } + + finish: +#if defined( DEBUG ) && DEBUG > 0 + if ( count > NROOTS ) + std::cout << "ERROR: Number of corrections: " << count << " exceeds NROOTS: " << NROOTS << std::endl; +#endif +#if defined( DEBUG ) && DEBUG > 1 + std::cout << "data x" << std::setw( 3 ) << len << ": " << std::vector( data, data + len ) << std::endl; + std::cout << "parity x" << std::setw( 3 ) << NROOTS << ": " << std::string( len * 2, ' ' ) << std::vector( parity, parity + NROOTS ) << std::endl; + if ( count > 0 ) { + std::string errors( 2 * ( len + NROOTS ), ' ' ); + for ( int i = 0; i < count; ++i ) { + errors[2*(loc[i]-pad)+0] = 'E'; + errors[2*(loc[i]-pad)+1] = 'E'; + } + for ( int i = 0; i < no_eras; ++i ) { + errors[2*(eras_pos[i])+0] = 'e'; + errors[2*(eras_pos[i])+1] = 'e'; + } + std::cout << "e)ra,E)rr x" << std::setw( 3 ) << count << ": " << errors << std::endl; + } +#endif + if ( eras_pos != NULL ) { + for ( int i = 0; i < count; i++) + eras_pos[i] = loc[i] - pad; + } + return count; + } + }; // class reed_solomon + + // + // Define the static reed_solomon...<...> members; allowed in header for template types. + // + // The reed_solomon_tags<...>::iprim < 0 is used to indicate to the first instance that the + // static tables require initialization. + // + template < typename TYP, int SYM, int PRM, class PLY > + int reed_solomon_tabs< TYP, SYM, PRM, PLY >::iprim = -1; + + template < typename TYP, int SYM, int PRM, class PLY > + std::array< TYP, reed_solomon_tabs< TYP, SYM, PRM, PLY > +#if not defined( EZPWD_ARRAY_TEST ) + ::NN + 1 > +#else +# warning "EZPWD_ARRAY_TEST: Erroneously defining alpha_to size!" + ::NN > +#endif + reed_solomon_tabs< TYP, SYM, PRM, PLY >::alpha_to; + + template < typename TYP, int SYM, int PRM, class PLY > + std::array< TYP, reed_solomon_tabs< TYP, SYM, PRM, PLY >::NN + 1 > + reed_solomon_tabs< TYP, SYM, PRM, PLY >::index_of; + template < typename TYP, int SYM, int PRM, class PLY > + std::array< TYP, reed_solomon_tabs< TYP, SYM, PRM, PLY >::MODS > + reed_solomon_tabs< TYP, SYM, PRM, PLY >::mod_of; + + template < typename TYP, int SYM, int RTS, int FCR, int PRM, class PLY > + std::array< TYP, reed_solomon< TYP, SYM, RTS, FCR, PRM, PLY >::NROOTS + 1 > + reed_solomon< TYP, SYM, RTS, FCR, PRM, PLY >::genpoly; + +} // namespace ezpwd + +#endif // _EZPWD_RS_BASE diff --git a/op25/gr-op25_repeater/lib/ezpwd/serialize b/op25/gr-op25_repeater/lib/ezpwd/serialize new file mode 100644 index 0000000..ae4a62c --- /dev/null +++ b/op25/gr-op25_repeater/lib/ezpwd/serialize @@ -0,0 +1,1188 @@ +/* + * Ezpwd Reed-Solomon -- Reed-Solomon encoder / decoder library + * + * Copyright (c) 2014, Hard Consulting Corporation. + * + * Ezpwd Reed-Solomon is free software: you can redistribute it and/or modify it under the terms of + * the GNU General Public License as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. See the LICENSE file at the top of the + * source tree. Ezpwd Reed-Solomon is also available under Commercial license. c++/ezpwd/rs_base + * is redistributed under the terms of the LGPL, regardless of the overall licensing terms. + * + * Ezpwd Reed-Solomon 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 General Public License for more details. + */ +#ifndef _EZPWD_SERIALIZE +#define _EZPWD_SERIALIZE + +#include +#include + +#include "output" + +// +// EZPWD (no padding) and RFC4648 (enforce padding) base-N codecs +// +// scatter -- 8-bit binary data into 5-bit (base32) and 6-bit (base64) chunks (optionally w/pad) +// gather -- collect up 5/6-bit binary data back into 8-bit characters (optionally w/pad) +// ..._standard -- enforce RFC4648-standard padding +// +// Scatters or gathers 8-bit binary character data to 5/6-bit symbols, suitable for base32/64 +// encoding. +// +// encode -- convert binary data to ASCII base32/64, in-place +// decode -- convert ASCII base32/64 to binary data, in-place +// ..._standard -- enforce RFC4648-standard padding +// +// Transforms iterable containers of char between ASCII symbols and binary data, always in-place. +// The decode may alter the size of the result (by ignoring whitespace). +// +// In general the ezpwd::base32/64 en/decoders are designed to produce easily human-usable +// encodings, and can ignore common whitespace characters and '-' to allow human-readable +// formatting. The RFC4648 Standard base 32/64 and Crockford base32 encodings are also supported. +// +// Adding new symbol encodings (and even new bases, up to base-128) is trivial. +// +namespace ezpwd { + + namespace serialize { + + enum chr_t { + pd = -1, // padding + nv = -2, // invalid + ws = -3, // whitespace + }; + + enum ws_use_t { + ws_invalid = 0, // Whitespace is invalid + ws_ignored = 1, // Whitespace ignored (the default) + }; + + enum pd_use_t { + pd_invalid = 0, // Padding is not expected (invalid) + pd_ignored = 1, // Padding is ignored, and automatically supplied if required (the default) + pd_enforce = 2, // Padding is expected and enforced + }; + + // + // serialize::tables -- base class for TABLES in all base + // + // Every serialize::table specialization must have an encoder table of size BASE, + // and a decoder table of size 127. + // + + // + // hex<16/32> -- RFC4648 4-bit (standard 16 symbol) and 5-bit (32 symbol) + // standard<16/32/64> -- RFC4648 standard tables + // standard_url<64> -- RFC4648 standard tables (base-64 URL-safe) + // ezpwd<16/32/64> -- EZPWD tables + // crockford<32> -- Crockford (base32 only) + // + // These types are passed as the TABLE template parameter to base<32/64,TABLE> class + // template instantiations. They must specify an encoder table of size BASE, and a decoder + // table of size 127. + // + template < size_t N > struct hex { }; + template < size_t N > struct uuencode { }; + template < size_t N > struct standard { }; + template < size_t N > struct standard_url { }; + template < size_t N > struct ezpwd { }; + template < size_t N > struct crockford { }; + + // + // base<16> tables -- basic hexadecimal supported (hex, standard, ezpwd identical) + // + template <> struct hex<16> { + static const constexpr std::array + encoder { { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', + } }; + static const constexpr std::array + decoder { { + nv, nv, nv, nv, nv, nv, nv, nv, nv, ws, ws, ws, ws, ws, nv, nv, // 9-13: ,,,, + nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, // + ws, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, // !"#$%&`()*+,-./ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, nv, nv, nv, nv, nv, nv, // 0123456789:;<=>? '=' is pad + nv, 10, 11, 12, 13, 14, 15, nv, nv, nv, nv, nv, nv, nv, nv, nv, // @ABCDEFGHIJKLMNO + nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, // PQRSTUVWXYZ[\]^_ + nv, 10, 11, 12, 13, 14, 15, nv, nv, nv, nv, nv, nv, nv, nv, nv, // `abcdefghijklmno + nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, // pqrstuvwxyz{|}~ + } }; + }; // struct serialize::hex<16> + + template <> struct standard<16> { + static const constexpr std::array + encoder = hex<16>::encoder; + static const constexpr std::array + decoder = hex<16>::decoder; + }; + + template <> struct ezpwd<16> { + static const constexpr std::array + encoder = hex<16>::encoder; + static const constexpr std::array + decoder = hex<16>::decoder; + }; + + // + // base<32> tables -- ezpwd, and RFC4648 hex32, crockford and standard + // + template <> struct hex<32> { + static const constexpr std::array + encoder { { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', + 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', + 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', + } }; + static const constexpr std::array + decoder { { + nv, nv, nv, nv, nv, nv, nv, nv, nv, ws, ws, ws, ws, ws, nv, nv, // 9-13: ,,,, + nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, // + ws, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, // !"#$%&`()*+,-./ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, nv, nv, nv, nv, nv, nv, // 0123456789:;<=>? '=' is pad + nv, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // @ABCDEFGHIJKLMNO + 25, 26, 27, 28, 29, 30, 31, nv, nv, nv, nv, nv, nv, nv, nv, nv, // PQRSTUVWXYZ[\]^_ + nv, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // `abcdefghijklmno + 25, 26, 27, 28, 29, 30, 31, nv, nv, nv, nv, nv, nv, nv, nv, // pqrstuvwxyz{|}~ + } }; + }; // struct serialize::hex<32> + + template <> struct standard<32> { // RFC4648 Standard + static const constexpr std::array + encoder { { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', '2', '3', '4', '5', '6', '7', + } }; + static const constexpr std::array + decoder { { + nv, nv, nv, nv, nv, nv, nv, nv, nv, ws, ws, ws, ws, ws, nv, nv, // 9-13: ,,,, + nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, // + ws, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, // !"#$%&`()*+,-./ + nv, nv, 26, 27, 28, 29, 30, 31, nv, nv, nv, nv, nv, pd, nv, nv, // 0123456789:;<=>? + nv, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // @ABCDEFGHIJKLMNO '=' is pad + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, nv, nv, nv, nv, nv, // PQRSTUVWXYZ[\]^_ + nv, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // `abcdefghijklmno + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, nv, nv, nv, nv, // pqrstuvwxyz{|}~ + } }; + }; // struct serialize::standard<32> + + template <> struct ezpwd<32> { + static const constexpr std::array + encoder { { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', + 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', + 'Q', 'R', 'T', 'U', 'V', 'W', 'X', 'Y', + } }; + + static const constexpr std::array + decoder { { + nv, nv, nv, nv, nv, nv, nv, nv, nv, ws, ws, ws, ws, ws, nv, nv, // 9-13: ,,,, + nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, // + ws, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, ws, nv, nv, // !"#$%&`()*+,-./ '-' is whitespace + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, nv, nv, nv, pd, nv, nv, // 0123456789:;<=>? '=' is pad + nv, 10, 11, 12, 13, 14, 15, 16, 17, 1, 18, 19, 20, 21, 22, 0, // @ABCDEFGHIJKLMNO + 23, 24, 25, 5, 26, 27, 28, 29, 30, 31, 2, nv, nv, nv, nv, nv, // PQRSTUVWXYZ[\]^_ + nv, 10, 11, 12, 13, 14, 15, 16, 17, 1, 18, 19, 20, 21, 22, 0, // `abcdefghijklmno + 23, 24, 25, 5, 26, 27, 28, 29, 30, 31, 2, nv, nv, nv, nv, // pqrstuvwxyz{|}~ + } }; + }; // struct serialize::ezpwd<32> + + template <> struct crockford<32> { + static const constexpr std::array + encoder { { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', + 'G', 'H', 'J', 'K', 'M', 'N', 'P', 'Q', + 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z', + } }; + + static const constexpr std::array + decoder { { + nv, nv, nv, nv, nv, nv, nv, nv, nv, ws, ws, ws, ws, ws, nv, nv, // 9-13: ,,,, + nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, // + ws, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, ws, nv, nv, // !"#$%&`()*+,-./ '-' is whitespace + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, nv, nv, nv, pd, nv, nv, // 0123456789:;<=>? '=' is pad + nv, 10, 11, 12, 13, 14, 15, 16, 17, 1, 18, 19, 1, 20, 21, 0, // @ABCDEFGHIJKLMNO + 22, 23, 24, 25, 26, nv, 27, 28, 29, 30, 31, nv, nv, nv, nv, nv, // PQRSTUVWXYZ[\]^_ + nv, 10, 11, 12, 13, 14, 15, 16, 17, 1, 18, 19, 1, 20, 21, 0, // `abcdefghijklmno + 22, 23, 24, 25, 26, nv, 27, 28, 29, 30, 31, nv, nv, nv, nv, // pqrstuvwxyz{|}~ + } }; + }; // struct serialize::crockford<32> + + // + // base<64> tables + // + template <> struct uuencode<64> { + static const constexpr std::array + encoder { { + ' ', '!', '"', '#', '$', '%', '&','\'', + '(', ')', '*', '+', ',', '-', '.', '/', + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', ':', ';', '<', '=', '>', '?', + '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', + 'X', 'Y', 'Z', '[','\\', ']', '^', '_', + } }; + static const constexpr std::array + decoder { { + nv, nv, nv, nv, nv, nv, nv, nv, nv, ws, ws, ws, ws, ws, nv, nv, // 9-13: ,,,, + nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, // + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, // !"#$%&`()*+,-./ + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, // 0123456789:;<=>? '=' is pad + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, // @ABCDEFGHIJKLMNO + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, // PQRSTUVWXYZ[\]^_ + nv, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, // `abcdefghijklmno + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, nv, nv, nv, nv, nv, // pqrstuvwxyz{|}~ + } }; + }; // struct serialize::standard<64> + + template <> struct standard<64> { + static const constexpr std::array // RFC4648 Standard + encoder { { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/', + } }; + static const constexpr std::array + decoder { { + nv, nv, nv, nv, nv, nv, nv, nv, nv, ws, ws, ws, ws, ws, nv, nv, // 9-13: ,,,, + nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, // + ws, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, 62, nv, nv, nv, 63, // !"#$%&`()*+,-./ + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, nv, nv, nv, pd, nv, nv, // 0123456789:;<=>? '=' is pad + nv, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // @ABCDEFGHIJKLMNO + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, nv, nv, nv, nv, nv, // PQRSTUVWXYZ[\]^_ + nv, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // `abcdefghijklmno + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, nv, nv, nv, nv, // pqrstuvwxyz{|}~ + } }; + }; // struct serialize::standard<64> + + template <> struct standard_url<64> { + static const constexpr std::array + encoder { { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '-', '_', + } }; + static const constexpr std::array + decoder { { + nv, nv, nv, nv, nv, nv, nv, nv, nv, ws, ws, ws, ws, ws, nv, nv, // 9-13: ,,,, + nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, // + ws, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, 62, nv, nv, // !"#$%&`()*+,-./ + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, nv, nv, nv, pd, nv, nv, // 0123456789:;<=>? '=' is pad + nv, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // @ABCDEFGHIJKLMNO + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, nv, nv, nv, nv, 63, // PQRSTUVWXYZ[\]^_ + nv, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // `abcdefghijklmno + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, nv, nv, nv, nv, // pqrstuvwxyz{|}~ + } }; + }; // struct serialize::standar_url<64> + + template <> struct ezpwd<64> { + static const constexpr std::array // '+' and '.' are URL safe, and we treat '-' as whitespace + encoder { { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '.', + } }; + static const constexpr std::array + decoder { { + nv, nv, nv, nv, nv, nv, nv, nv, nv, ws, ws, ws, ws, ws, nv, nv, // 9-13: ,,,, + nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, // + ws, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, 62, nv, ws, 63, nv, // !"#$%&`()*+,-./ '-' is whitespace + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, nv, nv, nv, pd, nv, nv, // 0123456789:;<=>? '=' is pad + nv, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // @ABCDEFGHIJKLMNO + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, nv, nv, nv, nv, 63, // PQRSTUVWXYZ[\]^_ + nv, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // `abcdefghijklmno + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, nv, nv, nv, nv, // pqrstuvwxyz{|}~ + } }; + }; // struct serialize::ezpwd<64> + + // + // base_generic -- generic base-N conversion using TABLES. Doesn't have scatter/gather. + // + // Every base-N converter requires a TABLE class of matching N, and can encode/decode. + // in-place using the table::encoder/decoder arrays. Only specific values of N have class + // template specialization which includes scatter/gather methods -- the generic base-N + // template does not.. + // + template < size_t N, typename TABLES = ezpwd< N >> + class base_generic + : public TABLES { + public: + static + std::ostream &output( + std::ostream &lhs ) + { + lhs << "base<" << N; +#if defined( DEBUG ) && DEBUG >= 3 + lhs << "," << TABLES::encoder; +#endif + lhs << ">"; + return lhs; + } + + // + // base::encode() -- encode the supplied sequence of data in the domain (0,N] to base-N + // base::encode(,) -- encode the supplied std::string of (0,N] symbols in-place to base-N + // + // Items from iterator must be convertible to a table index (ie. not -'ve, and within + // the table size N). If the table contains 'ws' entries, they may optionally be ignored. + // If the input symbol indexes outside the provided encoder table (or an 'nv' entry), then an + // exception will be thrown. + // + // If a non-zero 'pad' character is supplied, then the -1 (pd) character value will be + // allowed (normally occurring only at the end of the input range), and a 'pad' will be + // emitted for each one. + // + template < typename I > + static + I encode( + I begin, + I end, + char pad = '=' ) // '=' for standards-compliance + { + for ( I i = begin; i != end; ++i ) { + if ( *i >= 0 and size_t( *i ) < TABLES::encoder.size() ) + *i = TABLES::encoder[*i]; + else if ( pd == *i and pad ) + *i = pad; + else + throw std::runtime_error( + std::string( "ezpwd::serialize::encode: invalid base-" ) << N + << " binary symbol presented: " << *i ); + } + return end; + } + + static + std::string &encode( + std::string &symbols, + char pad = '=' ) // '=' for standards-compliance + { + encode( symbols.begin(), symbols.end(), pad ); + return symbols; + } + + // + // base::decode(,) -- decode base-N symbols in-place, collapsing spaces. + // base::decode() -- decode base-N symbols in supplied std::string, collapsing spaces, in-place. + // + // Items from iterator must be convertible to a table index (ie. not -'ve, and within + // the table size, which is always 127). + // + // ws_ignored -- skip whitepace/- symbols (the default) + // ws_invalid -- consider whitespace/- symbols as invalid symbols + // + // pd_invalid -- consider padding symbols as invalid + // pd_ignored -- skip padding symbols + // pd_enforce -- leave any padding symbols in place (the default) + // + // If erasure vector supplied, marks invalid symbols as erasures; otherwise, throws + // exception. Ignores whitespace. Will return an iterator to just after the last output + // symbol used in the provided range (eg. to shorten the container, if desired), leaving any + // remaining symbols unchanged. The version returns the same string reference + // passed in (shortened, if spaces/padding ignored). + // + // If an invalid vector is supplied, we'll also return the offending input symbol(s); if + // an exception is raised (no erasure vector supplied), only one symbol will be in invalid. + // + // NOTE: will quite likely return an iterator before the supplied 'end', indicating that the + // output has been truncated (shortened), due to collapsing spaces! + // + template < typename I > + static + I decode( + I begin, + I end, + std::vector *erasure, // Deem invalid symbols as erasures + std::vector *invalid = 0, // and return the symbols + ws_use_t ws_use = ws_ignored, // skip any whitespace + pd_use_t pd_use = pd_invalid ) // no padding expected; invalid + { + if ( erasure ) + erasure->clear(); + if ( invalid ) + invalid->clear(); + I i, o; + for ( i = o = begin; i != end; ++i ) { + size_t ti( *i ); + char c = ti < TABLES::decoder.size() ? TABLES::decoder[ti] : char( nv ); + if ( ws == c ) + switch ( ws_use ) { + case ws_invalid: + c = nv; + break; + case ws_ignored: default: + continue; + } + if ( pd == c ) + switch ( pd_use ) { + case pd_invalid: + c = nv; + break; + case pd_enforce: + break; + case pd_ignored: default: + continue; + } + if ( nv == c ) { + // Invalid symbol; optionally remember them. Mark as erasure? Or throw. + if ( invalid ) + invalid->push_back( *i ); + if ( erasure ) { + erasure->push_back( o - begin ); // index of offending symbol in output + c = 0; // will be output w/ 0 value + } else { + throw std::runtime_error( + std::string( "ezpwd::serialize::decode: invalid base-" ) << N + << " ASCII symbol presented: " << int( *i ) << " '" << *i << "'" ); + } + } + *o++ = c; + } + return o; + } + + template < typename I > + static + I decode( + I begin, + I end, + ws_use_t ws_use = ws_ignored, + pd_use_t pd_use = pd_invalid ) + { + return decode( begin, end, 0, 0, ws_use, pd_use ); + } + + template < typename I > + static + I decode_standard( + I begin, + I end ) + { + return decode( begin, end, 0, 0, ws_ignored, pd_enforce ); // RFC4648 padding + } + + static + std::string &decode( + std::string &symbols, + std::vector *erasure = 0, + std::vector *invalid = 0, + ws_use_t ws_use = ws_ignored, + pd_use_t pd_use = pd_invalid ) + { + auto last = decode( symbols.begin(), symbols.end(), erasure, invalid, + ws_use, pd_use ); + if ( last != symbols.end() ) + symbols.resize( last - symbols.begin() ); // eliminated some whitespace + return symbols; + } + static + std::string &decode_standard( + std::string &symbols, + std::vector *erasure = 0, + std::vector *invalid = 0 ) + { + return decode( symbols, erasure, invalid, ws_ignored, pd_enforce ); // RFC4648 padding + } + + // + // gather_next -- return next symbol to gather into 8-bit output + // + // Fails if padding mixed in with data, or a data symbol exceeds encoding bit depth + // (eg. 5 bits for base-32). + // + // If auto-padding, allow the caller to differentiate + // + template < typename I > + static + int gather_next( I &beg, I end, pd_use_t pd_use, int previous ) + { + if ( beg == end ) { + if ( pd_enforce == pd_use ) + throw std::logic_error( std::string( "base-" ) << N << " gather error; insufficient data"); + // automatically pad; return nv on underflow while un-emitted data remains, finally pd + return previous >= 0 ? nv : pd; + } + int c = *beg++; + if ( previous < 0 and c >= 0 ) + throw std::logic_error( + std::string( "base-" ) << N << " gather error; data following padding" ); + if ( c >= char( N ) or c < pd ) + throw std::logic_error( + std::string( "base-" ) << N << " gather error; symbol value " << int( c ) << " beyond capacity" ); + return c; + } + }; // class serialize::base_generic + } // namespace serialize + + template < size_t N, typename TABLES > + std::ostream &operator<<( + std::ostream &lhs, + const ezpwd::serialize::base_generic + &rhs ) + { + return rhs.output( lhs ); + } + + namespace serialize { + // + // ezpwd::serialize::base -- Arbitrary bases (other than those with specializations below) + // + // Can en/decode, but no scatter/gather implementation, nor encode_size. + // + template < size_t N, typename TABLES > + class base + : public base_generic< N, TABLES > { + public: + using base_generic< N, TABLES >::encode; + using base_generic< N, TABLES >::decode; + }; + + + // + // ezpwd::serialize::base<16> -- transform individual characters between 4-bit binary and a base-16 + // + template < typename TABLES > + class base< 16, TABLES > + : public base_generic< 16, TABLES > { + public: + using base_generic< 16, TABLES >::encode; + using base_generic< 16, TABLES >::decode; + using base_generic< 16, TABLES >::gather_next; + + static constexpr size_t encode_size( + size_t len, + pd_use_t /* pd_use */ = pd_invalid ) + { + return len * 2; // encoding base-16 always exactly doubles size + } + + // + // base<16>::scatter -- distribute a range of input bytes to an output iterator in 4-bit chunks + // + // The same implementation is used for both random-access iterators, and for all + // other forward iterators. + // + template < typename I, typename O, typename iterator_tag > + static + O scatter( + I beg, + I end, + O out, + pd_use_t, // ignored; never needs to pad + iterator_tag ) // ignored + { + while ( beg != end ) { + int c0 = *beg++; + *out++ = char((c0 & 0xff) >> 4); + *out++ = char((c0 & 0x0f)); + } + return out; + } + + template < typename I, typename O > + static + O scatter( + I beg, + I end, + O out, + pd_use_t pd_use = pd_invalid ) + { + return scatter( beg, end, out, pd_use, typename std::iterator_traits::iterator_category() ); + } + + template < typename I, typename O > + static + O scatter_standard( + I beg, + I end, + O out ) + { + return scatter( beg, end, out, pd_enforce, typename std::iterator_traits::iterator_category() ); + } + + // + // base<16>::gather -- collect 4-bit chunks into 8-bit characters + // + // If underflow occurs (not enough data collected to output complete last char), then an + // exception will be raised. However, if 'pad' is set, then output will automatically be + // padded, flushing out any un-emitted data remaining in previous 4-bit base-16 symbol. + // + // For correct base-16 data produced by 'scatter', this will allow 'gather' to always + // produce the identical output as was originally provided to 'scatter'. However, if simply + // truncated base-16 input is provided (eg. only 1 symbols of an 2-symbol 8-bit + // base-16 group), then the final 8-bit symbol from the original data will be missing. + // + template < typename I, typename O > + static + O gather( + I beg, + I end, + O out, + pd_use_t pd_use = pd_invalid ) + { + while ( beg != end ) { + int c0 = gather_next( beg, end, pd_use, 0 ); + int c1 = gather_next( beg, end, pd_use, c0 ); + if ( c0 >= 0 and c1 >= 0 ) + *out++ = ( ((c0 < 0 ? 0 : c0 ) << 4) + | (c1 < 0 ? 0 : c1 )); + } + return out; + } + template < typename I, typename O > + static + O gather_standard( + I beg, + I end, + O out ) + { + return gather( beg, end, out, pd_enforce ); + } + }; // ezpwd::base<16> + + // + // ezpwd::serialize::base16... + // + // Shortcut typedefs for the available base16 codec. + // + typedef base< 16, hex< 16 >> + base16; + typedef base< 16, hex< 16 >> + base16_standard; + + + // + // ezpwd::serialize::base<32> -- transform individual characters between 5-bit binary and a base-32 + // + // The char values [0,32) are mapped by base<32>::encode onto something like + // + // 0123456789ABCDEFGHJKLMNPQRTUVWXY TABLES=ezpwd<32> (default) + // 0123456789ABCDEFGHJKMNPQRSTVWXYZ TABLES=crockford<32> + // ABCDEFGHIJKLMNOPQRSTUVWXYZ234567 TABLES=standard<32> + // + // and base<32>::decode performs the inverse. In addition to folding lower-case to + // upper-case, the following mappings occur in the ezpwd<32>::decoder table: + // + // O -> 0 + // Z -> 2 + // S -> 5 + // I -> 1 + // + // B -> 8 could not be included due to the limited size of the capitalized alpha-numeric + // symbol pool, unfortunately, but the other substitutions were prioritized as they are + // more likely to occur in standard printed text. + // + // Any characters encountered outside [0,32) by encode and outside the above set by + // decode raise an exception, unless an erasure vector is provided, in which case we supply + // a 0 value and store the index of the invalid symbol in the vector. + // + template < typename TABLES > + class base< 32, TABLES > + : public base_generic< 32, TABLES > { + public: + using base_generic< 32, TABLES >::encode; + using base_generic< 32, TABLES >::decode; + using base_generic< 32, TABLES >::gather_next; + + static constexpr size_t encode_size( + size_t len, + pd_use_t pd_use = pd_invalid ) + { + return ( pd_enforce == pd_use + ? ( len + 4 ) / 5 * 8 + : ( len * 8 + 4 ) / 5 ); + } + + // + // base<32>::scatter -- distribute a range of input bytes to an output iterator in 5-bit chunks + // + // Separate implementation are provided for random-access iterators (with fewer + // comparisons necessary) and for all other forward iterators. + // + template < typename I, typename O, typename iterator_tag > + static + O scatter( + I beg, + I end, + O out, + pd_use_t pd_use, + iterator_tag ) + { + while ( beg != end ) { + int c0 = *beg++; + *out++ = char((c0 & 0xff) >> 3); + + if ( beg == end ) { + *out++ = char((c0 & 0x07) << 2); + if ( pd_enforce == pd_use ) { + *out++ = EOF; + *out++ = EOF; + *out++ = EOF; + *out++ = EOF; + *out++ = EOF; + *out++ = EOF; + } + return out; + } + + int c1 = *beg++; + *out++ = char((c0 & 0x07) << 2 | (c1 & 0xff) >> 6); + *out++ = char((c1 & 0x3f) >> 1); + + if ( beg == end ) { + *out++ = char((c1 & 0x01) << 4); + if ( pd_enforce == pd_use ) { + *out++ = EOF; + *out++ = EOF; + *out++ = EOF; + *out++ = EOF; + } + return out; + } + + int c2 = *beg++; + *out++ = char((c1 & 0x01) << 4 | (c2 & 0xff) >> 4); + + if ( beg == end ) { + *out++ = char((c2 & 0x0f) << 1); + if ( pd_enforce == pd_use ) { + *out++ = EOF; + *out++ = EOF; + *out++ = EOF; + } + return out; + } + + int c3 = *beg++; + *out++ = char((c2 & 0x0f) << 1 | (c3 & 0xff) >> 7); + *out++ = char((c3 & 0x7f) >> 2); + + if ( beg == end ) { + *out++ = char((c3 & 0x03) << 3); + if ( pd_enforce == pd_use ) { + *out++ = EOF; + } + return out; + } + + int c4 = *beg++; + *out++ = char((c3 & 0x03) << 3 | (c4 & 0xff) >> 5); + *out++ = char((c4 & 0x1f)); + } + + return out; + } + + template < typename I, typename O > + static + O scatter( + I beg, + I end, + O out, + pd_use_t pd_use, + std::random_access_iterator_tag ) + { + while ( end - beg >= 5 ) { + int c0 = *beg++; + *out++ = char((c0 & 0xff) >> 3); + int c1 = *beg++; + *out++ = char((c0 & 0x07) << 2 | (c1 & 0xff) >> 6); + *out++ = char((c1 & 0x3f) >> 1); + int c2 = *beg++; + *out++ = char((c1 & 0x01) << 4 | (c2 & 0xff) >> 4); + int c3 = *beg++; + *out++ = char((c2 & 0x0f) << 1 | (c3 & 0xff) >> 7); + *out++ = char((c3 & 0x7f) >> 2); + int c4 = *beg++; + *out++ = char((c3 & 0x03) << 3 | (c4 & 0xff) >> 5); + *out++ = char((c4 & 0x1f)); + } + + switch ( end - beg ) { + case 4: { + int c0 = *beg++; + *out++ = char((c0 & 0xff) >> 3); + int c1 = *beg++; + *out++ = char((c0 & 0x07) << 2 | (c1 & 0xff) >> 6); + *out++ = char((c1 & 0x3f) >> 1); + int c2 = *beg++; + *out++ = char((c1 & 0x01) << 4 | (c2 & 0xff) >> 4); + int c3 = *beg++; + *out++ = char((c2 & 0x0f) << 1 | (c3 & 0xff) >> 7); + *out++ = char((c3 & 0x7f) >> 2); + *out++ = char((c3 & 0x03) << 3); + if ( pd_enforce == pd_use ) { + *out++ = EOF; + } + return out; + } + case 3: { + int c0 = *beg++; + *out++ = char((c0 & 0xff) >> 3); + int c1 = *beg++; + *out++ = char((c0 & 0x07) << 2 | (c1 & 0xff) >> 6); + *out++ = char((c1 & 0x3f) >> 1); + int c2 = *beg++; + *out++ = char((c1 & 0x01) << 4 | (c2 & 0xff) >> 4); + *out++ = char((c2 & 0x0f) << 1); + if ( pd_enforce == pd_use ) { + *out++ = EOF; + *out++ = EOF; + *out++ = EOF; + } + return out; + } + case 2: { + int c0 = *beg++; + *out++ = char((c0 & 0xff) >> 3); + int c1 = *beg++; + *out++ = char((c0 & 0x07) << 2 | (c1 & 0xff) >> 6); + *out++ = char((c1 & 0x3f) >> 1); + *out++ = char((c1 & 0x01) << 4); + if ( pd_enforce == pd_use ) { + *out++ = EOF; + *out++ = EOF; + *out++ = EOF; + *out++ = EOF; + } + return out; + } + case 1: { + int c0 = *beg++; + *out++ = char((c0 & 0xff) >> 3); + *out++ = char((c0 & 0x07) << 2); + if ( pd_enforce == pd_use ) { + *out++ = EOF; + *out++ = EOF; + *out++ = EOF; + *out++ = EOF; + *out++ = EOF; + *out++ = EOF; + } + return out; + } + default: + return out; + } + } + + template < typename I, typename O > + static + O scatter( + I beg, + I end, + O out, + pd_use_t pd_use = pd_invalid ) + { + return scatter( beg, end, out, pd_use, typename std::iterator_traits::iterator_category() ); + } + + template < typename I, typename O > + static + O scatter_standard( + I beg, + I end, + O out ) + { + return scatter( beg, end, out, pd_enforce, typename std::iterator_traits::iterator_category() ); + } + + // + // base<32>::gather -- collect 5-bit chunks into 8-bit characters + // + // If underflow occurs (not enough data collected to output complete last char), then an + // exception will be raised. However, if 'pad' is set, then output will automatically be + // padded, discarding any un-emitted data remaining in previous 5-bit base-32 symbols. + // + // For correct base-32 data produced by 'scatter', this will allow 'gather' to always + // produce the identical output as was originally provided to 'scatter'. However, if simply + // truncated base-32 input is provided (eg. only 1, 3 or 5 symbols of an 8-symbol 40-bit + // base-32 group), then the final 8-bit symbol from the original data will be missing. + // + template < typename I, typename O > + static + O gather( + I beg, + I end, + O out, + pd_use_t pd_use = pd_invalid ) + { + while ( beg != end ) { + int c0 = gather_next( beg, end, pd_use, 0 ); + int c1 = gather_next( beg, end, pd_use, c0 ); + if ( c0 >= 0 and c1 >= 0 ) + *out++ = ( ((c0 < 0 ? 0 : c0 ) << 3) + | (c1 < 0 ? 0 : c1 ) >> 2 ); + int c2 = gather_next( beg, end, pd_use, c1 ); + int c3 = gather_next( beg, end, pd_use, c2 ); + if ( c1 >= 0 and c2 >= 0 and c3 >= 0 ) + *out++ = ( ((c1 < 0 ? 0 : c1) & 0x03) << 6 + | (c2 < 0 ? 0 : c2) << 1 + | (c3 < 0 ? 0 : c3) >> 4 ); + int c4 = gather_next( beg, end, pd_use, c3 ); + if ( c3 >= 0 and c4 >= 0 ) + *out++ = ( ((c3 < 0 ? 0 : c3) & 0x0f) << 4 + | (c4 < 0 ? 0 : c4) >> 1 ); + int c5 = gather_next( beg, end, pd_use, c4 ); + int c6 = gather_next( beg, end, pd_use, c5 ); + if ( c4 >=0 and c5 >= 0 and c6 >= 0 ) + *out++ = ( ((c4 < 0 ? 0 : c4) & 0x01) << 7 + | (c5 < 0 ? 0 : c5) << 2 + | (c6 < 0 ? 0 : c6) >> 3 ); + int c7 = gather_next( beg, end, pd_use, c6 ); + if ( c6 >= 0 and c7 >= 0 ) + *out++ = ( ((c6 < 0 ? 0 : c6 ) & 0x07) << 5 + | (c7 < 0 ? 0 : c7 )); + } + return out; + } + template < typename I, typename O > + static + O gather_standard( + I beg, + I end, + O out ) + { + return gather( beg, end, out, pd_enforce ); + } + }; // ezpwd::base<32> + + // + // ezpwd::serialize::base32... + // + // Shortcut typedefs for the available base32 codecs. + // + typedef base< 32, ezpwd< 32 >> + base32; + typedef base< 32, hex< 32 >> + base32_hex; + typedef base< 32, serialize::standard< 32 >> + base32_standard; + typedef base< 32, crockford< 32 >> + base32_crockford; + + + // + // ezpwd::serialize::base<64> -- transform individual characters between 6-bit binary and base-64 + // + // The char values [0,64) are mapped by base64::encode onto either the + // standard or ezpwd (url-safe) tables: + // + // ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ -- RFC4648 standard + // ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_ -- RFC4648 standard_url + // ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+. -- ezpwd + // + // and base<64>::decode performs the inverse (handles both standard and url-safe encodings). + // + // Any characters encountered outside [0,64) by encode and outside the above set + // by decode raise an exception. + // + template < typename TABLES > + class base< 64, TABLES > + : public base_generic< 64, TABLES > { + public: + using base_generic< 64, TABLES >::encode; + using base_generic< 64, TABLES >::decode; + using base_generic< 64, TABLES >::gather_next; + + static constexpr size_t encode_size( + size_t len, + pd_use_t pd_use = pd_invalid ) + { + return ( pd_enforce == pd_use + ? ( len + 2 ) / 3 * 4 + : ( len * 4 + 2 ) / 3 ); + } + + // + // base<64>::scatter -- distribute a range of input bytes to an output iterator in 6-bit chunks + // + template < typename I, typename O, typename iterator_tag > + static + O scatter( + I beg, + I end, + O out, + pd_use_t pd_use, + iterator_tag ) + { + while ( beg != end ) { + int c0 = *beg++; + *out++ = char((c0 & 0xff) >> 2); + + if ( beg == end ) { + *out++ = char((c0 & 0x03) << 4); + if ( pd_enforce == pd_use ) { + *out++ = EOF; + *out++ = EOF; + } + return out; + } + + int c1 = *beg++; + *out++ = char((c0 & 0x03) << 4 | (c1 & 0xff) >> 4); + + if ( beg == end ) { + *out++ = char((c1 & 0x0f) << 2); + if ( pd_enforce == pd_use ) { + *out++ = EOF; + } + return out; + } + + int c2 = *beg++; + *out++ = char((c1 & 0x0f) << 2 | (c2 & 0xff) >> 6); + *out++ = char((c2 & 0x3f)); + } + + return out; + } + + template < typename I, typename O > + static + O scatter( + I beg, + I end, + O out, + pd_use_t pd_use, + std::random_access_iterator_tag ) + { + while ( end - beg >= 3 ) { + int c0 = *beg++; + *out++ = char((c0 & 0xff) >> 2); + int c1 = *beg++; + *out++ = char((c0 & 0x03) << 4 | (c1 & 0xff) >> 4); + int c2 = *beg++; + *out++ = char((c1 & 0x0f) << 2 | (c2 & 0xff) >> 6); + *out++ = char((c2 & 0x3f)); + } + + switch ( end - beg ) { + case 2: { + int c0 = *beg++; + *out++ = char((c0 & 0xff) >> 2); + int c1 = *beg++; + *out++ = char((c0 & 0x03) << 4 | (c1 & 0xff) >> 4); + *out++ = char((c1 & 0x0f) << 2); + if ( pd_enforce == pd_use ) { + *out++ = EOF; + } + return out; + } + case 1: { + int c0 = *beg++; + *out++ = char((c0 & 0xff) >> 2); + *out++ = char((c0 & 0x03) << 4); + if ( pd_enforce == pd_use ) { + *out++ = EOF; + *out++ = EOF; + } + return out; + } + default: + return out; + } + } + + template < typename I, typename O > + static + O scatter( + I beg, + I end, + O out, + pd_use_t pd_use = pd_invalid ) + { + return scatter( beg, end, out, pd_use, typename std::iterator_traits::iterator_category() ); + } + + + template < typename I, typename O > + static + O scatter_standard( + I beg, + I end, + O out ) + { + return scatter( beg, end, out, pd_enforce, typename std::iterator_traits::iterator_category() ); + } + + // + // base<64>::gather -- collect 6-bit chunks into 8-bit characters + // + template < typename I, typename O > + static + O gather( + I beg, + I end, + O out, + pd_use_t pd_use = pd_invalid ) + { + while ( beg != end ) { + int c0 = gather_next( beg, end, pd_use, 0 ); + int c1 = gather_next( beg, end, pd_use, c0 ); + if ( c0 >= 0 and c1 >= 0 ) + *out++ = ( ((c0 < 0 ? 0 : c0 ) << 2 ) + | (c1 < 0 ? 0 : c1 ) >> 4 ); + int c2 = gather_next( beg, end, pd_use, c1 ); + if ( c1 >= 0 and c2 >= 0 ) + *out++ = ( ((c1 < 0 ? 0 : c1) & 0x0f) << 4 + | (c2 < 0 ? 0 : c2) >> 2 ); + int c3 = gather_next( beg, end, pd_use, c2 ); + if ( c2 >= 0 and c3 >= 0 ) + *out++ = ( ((c2 < 0 ? 0 : c2) & 0x03) << 6 + | (c3 < 0 ? 0 : c3)); + } + return out; + } + template < typename I, typename O > + static + O gather_standard( + I beg, + I end, + O out ) + { + return gather( beg, end, out, pd_enforce ); + } + }; // ezpwd::serialize::base<64> + + // + // ezpwd::serialize::base64... + // + // Shortcut typedefs for the standard base-64 codecs. + // + typedef base< 64, ezpwd< 64 >> + base64; + typedef base< 64, standard< 64 >> + base64_standard; + typedef base< 64, standard_url< 64 >> + base64_standard_url; + typedef base< 64, uuencode< 64 >> + base64_uuencode; + } // namespace ezpwd::serialize + +} // namespace ezpwd + +#endif // _EZPWD_SERIALIZE diff --git a/op25/gr-op25_repeater/lib/ezpwd/serialize_definitions b/op25/gr-op25_repeater/lib/ezpwd/serialize_definitions new file mode 100644 index 0000000..00b3fb7 --- /dev/null +++ b/op25/gr-op25_repeater/lib/ezpwd/serialize_definitions @@ -0,0 +1,57 @@ + +// +// The encoder/decoder tables for all ezpwd::serialize::... base codecs +// +// Must be included in exactly one C++ compilation unit. +// + +#ifndef _EZPWD_SERIALIZE_DEFINITIONS +#define _EZPWD_SERIALIZE_DEFINITIONS + +#include "serialize" + +// +// base<16> tables for RFC4864 standard +// +const constexpr std::array + ezpwd::serialize::hex<16>::encoder; +const constexpr std::array + ezpwd::serialize::hex<16>::decoder; + +// +// base<32> tables for RFC4864 standard, and the Hex32, EZPWD and Crockford codecs +// +const constexpr std::array + ezpwd::serialize::hex<32>::encoder; +const constexpr std::array + ezpwd::serialize::hex<32>::decoder; +const constexpr std::array + ezpwd::serialize::standard<32>::encoder; +const constexpr std::array + ezpwd::serialize::standard<32>::decoder; +const constexpr std::array + ezpwd::serialize::ezpwd<32>::encoder; +const constexpr std::array + ezpwd::serialize::ezpwd<32>::decoder; +const constexpr std::array + ezpwd::serialize::crockford<32>::encoder; +const constexpr std::array + ezpwd::serialize::crockford<32>::decoder; + +// +// base<64> tables for RFC4864 standard (regular and url), and the EZPWD codecs +// +const constexpr std::array + ezpwd::serialize::standard<64>::encoder; +const constexpr std::array + ezpwd::serialize::standard<64>::decoder; +const constexpr std::array + ezpwd::serialize::standard_url<64>::encoder; +const constexpr std::array + ezpwd::serialize::standard_url<64>::decoder; +const constexpr std::array + ezpwd::serialize::ezpwd<64>::encoder; +const constexpr std::array + ezpwd::serialize::ezpwd<64>::decoder; + +#endif // _EZPWD_SERIALIZE_DEFINITIONS diff --git a/op25/gr-op25_repeater/lib/ezpwd/timeofday b/op25/gr-op25_repeater/lib/ezpwd/timeofday new file mode 100644 index 0000000..3016138 --- /dev/null +++ b/op25/gr-op25_repeater/lib/ezpwd/timeofday @@ -0,0 +1,73 @@ +#ifndef _EZPWD_TIMEOFDAY +#define _EZPWD_TIMEOFDAY + +#include + +// +// ezpwd::timeofday -- Return current time. +// ezpwd::epoch -- The UNIX epoch. +// ezpwd::seconds -- convert timeval to a real-valued seconds +// < -- less-than comparison on timevals +// - -- difference on timevals +// +namespace ezpwd { + inline + timeval timeofday() + { + timeval tv; + ::gettimeofday( &tv, NULL ); + return tv; + } + + timeval epoch() + { + timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 0; + return tv; + } + + inline + double seconds( const timeval &rhs ) + { + return rhs.tv_usec / 1000000.0 + rhs.tv_sec; + } +} // namespace ezpwd + +inline +bool operator<( + const timeval &lhs, + const timeval &rhs ) +{ + return ( lhs.tv_sec < rhs.tv_sec + || (( lhs.tv_sec == rhs.tv_sec ) + && ( lhs.tv_usec < rhs.tv_usec ))); +} + +inline +timeval operator-( + const timeval &lhs, + timeval rhs ) // copy; adjusted... +{ + timeval result; + if ( lhs < rhs ) { + result = ezpwd::epoch(); + } else { + // See http://www.gnu.org/software/libc/manual/html_node/Elapsed-Time.html + if ( lhs.tv_usec < rhs.tv_usec ) { + int sec = ( rhs.tv_usec - lhs.tv_usec ) / 1000000 + 1; + rhs.tv_usec -= sec * 1000000; + rhs.tv_sec += sec; + } + if ( lhs.tv_usec - rhs.tv_usec > 1000000 ) { + int sec = ( lhs.tv_usec - rhs.tv_usec ) / 1000000; + rhs.tv_usec += sec * 1000000; + rhs.tv_sec -= sec; + } + result.tv_sec = lhs.tv_sec - rhs.tv_sec; + result.tv_usec = lhs.tv_usec - rhs.tv_usec; + } + return result; +} + +#endif // _EZPWD_TIMEOFDAY