This commit is contained in:
Max 2018-12-25 21:02:49 -05:00
parent 74db47bd06
commit 0f2f040307
12 changed files with 5246 additions and 0 deletions

View File

@ -0,0 +1,128 @@
#ifndef _EZPWD_ASSERTER
#define _EZPWD_ASSERTER
#include <algorithm>
#include <cstring>
#include <iostream>
#include <iomanip>
#include <sstream>
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( <std::ostream> ) -- Output description of last failed test (or nothing if successful)
// <std::ostream> << <asserter>
//
std::ostream &output(
std::ostream &lhs )
const
{
return lhs << out;
}
//
// (bool) <asserter> -- 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

View File

@ -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 <sstream>
#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<SYMBOLS, CORRECTION> -- 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;
}
//
// <ostream> << 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<uT *, uT *>
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<const uT *, const uT *>
cuTpair;
typedef std::pair<uT *, uT *>
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<T> &data )
const
{
typedef typename std::make_unsigned<T>::type
uT;
typedef std::pair<uT *, uT *>
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<T>&data,
std::vector<T> &parity )
const
{
typedef typename std::make_unsigned<T>::type
uT;
typedef std::pair<const uT *, const uT *>
cuTpair;
typedef std::pair<uT *, uT *>
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<T,N> &data,
int pad = 0 ) // ignore 'pad' symbols at start of array
const
{
typedef typename std::make_unsigned<T>::type
uT;
typedef std::pair<uT *, uT *>
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<uint8_t *, uint8_t *>
&data )
const
{
return encode( data.first, data.second - data.first - ecc_bytes(), data.second - ecc_bytes() );
}
virtual int encode(
const std::pair<const uint8_t *, const uint8_t *>
&data,
const std::pair<uint8_t *, uint8_t *>
&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<int> *position= 0 )
const
{
typedef uint8_t uT;
typedef std::pair<uT *, uT *>
uTpair;
return decode( uTpair( (uT *)&data.front(), (uT *)&data.front() + data.size() ),
position );
}
int decode(
std::string &data,
std::string &parity,
std::vector<int> *position= 0 )
const
{
typedef uint8_t uT;
typedef std::pair<uT *, uT *>
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<T> &data,
std::vector<int> *position= 0 )
const
{
typedef typename std::make_unsigned<T>::type
uT;
typedef std::pair<uT *, uT *>
uTpair;
return decode( uTpair( (uT *)&data.front(), (uT *)&data.front() + data.size() ),
position );
}
template < typename T >
int decode(
std::vector<T> &data,
std::vector<T> &parity,
std::vector<int> *position= 0 )
const
{
typedef typename std::make_unsigned<T>::type
uT;
typedef std::pair<uT *, uT *>
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<T,N> &data,
int pad = 0, // ignore 'pad' symbols at start of array
std::vector<int> *position= 0 )
const
{
typedef typename std::make_unsigned<T>::type
uT;
typedef std::pair<uT *, uT *>
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<int>, even though the underlying Djelic API reports them as arrays of
// unsigned int.
//
virtual int decode(
const std::pair<uint8_t *, uint8_t *>
&data,
std::vector<int> *position= 0 )
const
{
return decode( data.first, data.second - data.first - ecc_bytes(), data.second - ecc_bytes(),
position );
}
virtual int decode(
const std::pair<uint8_t *, uint8_t *>
&data,
const std::pair<uint8_t *, uint8_t *>
&parity,
std::vector<int> *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<int> *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 <typename C>
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 <typename C>
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 <typename C>
C decoded(
C data,
std::vector<int> &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_<SYMBOLS + 1>::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<SYMBOLS, PAYLOAD, CAPACITY> -- 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<SYMBOLS, CORRECTION>
{
public:
static const size_t M = ezpwd::log_<SYMBOLS + 1>::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<SYMBOLS, CORRECTION>()
{
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()
{
;
}
//
// <ostream> << 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 <size_t SYMBOLS, size_t PAYLOAD, size_t CORRECTION>
inline
std::ostream &operator<<(
std::ostream &lhs,
const ezpwd::BCH<SYMBOLS, PAYLOAD, CORRECTION>
&rhs )
{
return rhs.output( lhs );
}
} // namespace ezpwd
#endif // _EZPWD_BCH

View File

@ -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 <iostream>
#include <iomanip>
#include <vector>
//
// 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;
}
//
// <ostream> << <ezpwd::bch_control> -- 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

View File

@ -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 <password>,<confidence> guesses, and return the unambiguous best one
//
typedef std::map<std::string, std::pair<int, int>> // (<password>, (<count>, <avgsum>))
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 <string> 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 (<password>,(<count>,<avgsum>)) into (<average>,<password>)
// sort -- return a multimap indexed by <average> --> <string>
// output -- output the <string>: <average>, sorted by average
//
static std::pair<const int,const std::string &>
evaluation( const value_type &val )
{
return std::pair<const int,const std::string &>( val.second.second/val.second.first, val.first );
}
typedef std::multimap<const int,const std::string &>
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<PARITY=" << PARITY << ",N=" << N << ",SERIAL=" << SERIAL() << ">";
return lhs;
}
//
// parity(<string>) -- 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(<string>) -- 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(<string>[,...]) -- 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<int>
&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<int> 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<int> position;
int corrects= rscodec.decode( fixed, all_era, &position );
confidence = strength<PARITY>( 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 ); // --> (<average>,<password>)
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<int>(), minimum, maximum );
}
//
// decode(<char*>,<size_t>,<size_t>,<size_t>) -- C interface to decode(<string>)
//
// 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<N-1,N-1-PARITY>
rscodec;
};
template < size_t PARITY, size_t N, typename SERIAL >
const ezpwd::RS<N-1,N-1-PARITY>
corrector<PARITY,N,SERIAL>::rscodec;
} // namespace ezpwd
template < size_t PARITY, size_t N, typename SERIAL >
std::ostream &operator<<(
std::ostream &lhs,
const ezpwd::corrector<PARITY,N,SERIAL>
&rhs )
{
return rhs.output( lhs );
}
#endif // _EZPWD_CORRECTOR

View File

@ -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

View File

@ -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 <math.h> // M_PI
#include <cmath>
#include <cctype>
#include <cstdint>
#include <ezpwd/rs>
#include <ezpwd/output>
#include <ezpwd/serialize>
//
// 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<unsigned char, unsigned char>
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<P,L> -- 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<symbols_t, 12>
bits_t;
static const bits_t bits;
typedef std::array<std::pair<uint32_t, uint32_t>, 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<P,L>() -- 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<P=1>. Without
// the "!" or ".", it could be an ezcod<P=2> 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...<P=3>
//
// 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<int> &erasure,
std::vector<char> &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<uint8_t>( dec.begin(), dec.begin() + std::min( size_t( parbeg ), dec.length()) )
<< " + 0x" << std::vector<uint8_t>( 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<int> erasure;
std::vector<char> 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<uint8_t>( 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<P>( 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::vector<int>position;
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<P>( 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<P,L>::rscodec;
// Number of lat/lon bits represented for each location symbol
template < unsigned P, unsigned L >
const typename ezcod<P,L>::bits_t
ezcod<P,L>::bits = {
{
// bits per symbol lat lon
ezcod<P,L>::bits_t::value_type( 2, 3 ),
ezcod<P,L>::bits_t::value_type( 2, 3 ),
ezcod<P,L>::bits_t::value_type( 3, 2 ),
// -- --
// 7 8
ezcod<P,L>::bits_t::value_type( 2, 3 ),
ezcod<P,L>::bits_t::value_type( 3, 2 ),
ezcod<P,L>::bits_t::value_type( 2, 3 ),
// -- --
// 14 16
ezcod<P,L>::bits_t::value_type( 3, 2 ),
ezcod<P,L>::bits_t::value_type( 2, 3 ),
ezcod<P,L>::bits_t::value_type( 3, 2 ),
// -- --
// 22 23
ezcod<P,L>::bits_t::value_type( 2, 3 ),
ezcod<P,L>::bits_t::value_type( 3, 2 ),
ezcod<P,L>::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<P,L>::parts_t
ezcod<P,L>::parts = {
{
// parts per symbol lat parts lon parts lat lon bits
ezcod<P,L>::parts_t::value_type( 1UL << 2, 1UL << 3 ), // 2, 3
ezcod<P,L>::parts_t::value_type( 1UL << 4, 1UL << 6 ), // 2, 3
ezcod<P,L>::parts_t::value_type( 1UL << 7, 1UL << 8 ), // 3, 2
// -- --
// 7 8
ezcod<P,L>::parts_t::value_type( 1UL << 9, 1UL << 11 ), // 2, 3
ezcod<P,L>::parts_t::value_type( 1UL << 12, 1UL << 13 ), // 3, 2
ezcod<P,L>::parts_t::value_type( 1UL << 14, 1UL << 16 ), // 2, 3
// -- --
// 14 16
ezcod<P,L>::parts_t::value_type( 1UL << 17, 1UL << 18 ), // 3, 2
ezcod<P,L>::parts_t::value_type( 1UL << 19, 1UL << 21 ), // 2, 3
ezcod<P,L>::parts_t::value_type( 1UL << 22, 1UL << 23 ), // 3, 2
// -- --
// 22 23
ezcod<P,L>::parts_t::value_type( 1UL << 24, 1UL << 26 ), // 2, 3
ezcod<P,L>::parts_t::value_type( 1UL << 27, 1UL << 28 ), // 3, 2
ezcod<P,L>::parts_t::value_type( 1UL << 29, 1UL << 31 ), // 2, 3
// -- --
// 29 31
}
};
} // namespace ezpwd
#endif // _EZPWD_EZCOD

View File

@ -0,0 +1,344 @@
#ifndef _EZPWD_OUTPUT
#define _EZPWD_OUTPUT
#include <cstdio>
#include <streambuf>
#include <iostream>
#include <cctype>
#include <iomanip>
#include <sstream>
#include <set>
#include <map>
#include <vector>
//
// 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<unsigend char>
// std::ostream << std::array<unsigend char, N>
// 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 <typename F> // 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<unsigned char,S>
&rhs )
{
return ezpwd::hexout( lhs, rhs.begin(), rhs.end() );
}
inline
std::ostream &operator<<(
std::ostream &lhs,
const std::vector<unsigned char>
&rhs )
{
return ezpwd::hexout( lhs, rhs.begin(), rhs.end() );
}
//
// <ostream&> << pair<T,U>
// <ostream&> << set<T> -- sorted by T
// <ostream&> << map<T,U> -- sorted by T (key)
// <ostream&> << vector<T>
//
// 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 <class T, class U>
std::ostream &operator<<(
std::ostream &lhs,
const std::pair<T,U> &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 <class T>
std::ostream &operator<<(
std::ostream &lhs,
const std::set<T> &rhs )
{
std::streamsize w = lhs.width(); // If width is set, use if for each item
for ( typename std::set<T>::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 <class T, class U>
std::ostream &operator<<(
std::ostream &lhs,
const std::map<T,U>&rhs )
{
std::streamsize w = lhs.width(); // If width is set, use if for each item
std::vector<T> key;
for ( typename std::map<T,U>::const_iterator
mi = rhs.begin()
; mi != rhs.end()
; ++mi )
key.push_back( mi->first );
std::sort( key.begin(), key.end() );
for ( typename std::vector<T>::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 <class T>
std::ostream &operator<<(
std::ostream &lhs,
const std::vector<T> &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
//
// <buf_t> << ... -- Copy the <string> into the C <char*,size_t> buffer, always NUL terminating
//
// Copies <string> contents into buffer, and always NUL-terminates. Returns advanced buf_t (NOT
// including the terminating NUL, suitable for repeating ... << <string> operations.
//
// std::ostream( &<streambuf_to_buf_t> ) << ...
//
// Use standard ostream operations to send output to a C buffer, always NUL
// terminating, and never exceeding capacity.
//
namespace ezpwd {
typedef std::pair<char *,size_t>
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.
}
//
// <std::string> << ...
//
// 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

View File

@ -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<SYMBOLS,PAYLOAD> -- 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<SYMBOLS, PAYLOAD> -- 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<PARITY> -- 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<int>&erasures, // original erasures positions
const std::vector<int>&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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,57 @@
//
// The encoder/decoder tables for all ezpwd::serialize::... base<N> 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<char,16>
ezpwd::serialize::hex<16>::encoder;
const constexpr std::array<char,127>
ezpwd::serialize::hex<16>::decoder;
//
// base<32> tables for RFC4864 standard, and the Hex32, EZPWD and Crockford codecs
//
const constexpr std::array<char,32>
ezpwd::serialize::hex<32>::encoder;
const constexpr std::array<char,127>
ezpwd::serialize::hex<32>::decoder;
const constexpr std::array<char,32>
ezpwd::serialize::standard<32>::encoder;
const constexpr std::array<char,127>
ezpwd::serialize::standard<32>::decoder;
const constexpr std::array<char,32>
ezpwd::serialize::ezpwd<32>::encoder;
const constexpr std::array<char,127>
ezpwd::serialize::ezpwd<32>::decoder;
const constexpr std::array<char,32>
ezpwd::serialize::crockford<32>::encoder;
const constexpr std::array<char,127>
ezpwd::serialize::crockford<32>::decoder;
//
// base<64> tables for RFC4864 standard (regular and url), and the EZPWD codecs
//
const constexpr std::array<char,64>
ezpwd::serialize::standard<64>::encoder;
const constexpr std::array<char,127>
ezpwd::serialize::standard<64>::decoder;
const constexpr std::array<char,64>
ezpwd::serialize::standard_url<64>::encoder;
const constexpr std::array<char,127>
ezpwd::serialize::standard_url<64>::decoder;
const constexpr std::array<char,64>
ezpwd::serialize::ezpwd<64>::encoder;
const constexpr std::array<char,127>
ezpwd::serialize::ezpwd<64>::decoder;
#endif // _EZPWD_SERIALIZE_DEFINITIONS

View File

@ -0,0 +1,73 @@
#ifndef _EZPWD_TIMEOFDAY
#define _EZPWD_TIMEOFDAY
#include <sys/time.h>
//
// ezpwd::timeofday -- Return current time.
// ezpwd::epoch -- The UNIX epoch.
// ezpwd::seconds -- convert timeval to a real-valued seconds
// <timeval> < <timeval>-- less-than comparison on timevals
// <timeval> - <timeval>-- 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