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