parent
74db47bd06
commit
0f2f040307
@ -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 |
@ -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 |
@ -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 |
@ -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 |
@ -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 |
@ -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 |