280 lines
16 KiB
C++
280 lines
16 KiB
C++
//#include <cstddef>
|
|
//#include <cstdint>
|
|
#include <array>
|
|
#include <assert.h>
|
|
#include <string>
|
|
//#include <initializer_list> // for initializer_list
|
|
//#include <type_traits>
|
|
//#include <memory> // for shared_ptr, unique_ptr
|
|
#include <span>
|
|
#include "ILog.h"
|
|
#include "localization.h"
|
|
#include "slash6.h"
|
|
// This base64 uses the following characters as the numerals 0 to 63:
|
|
// Table to convert six bit to ascii
|
|
static constexpr uint8_t index2base64[]{ "0123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz!$*+-_" };
|
|
|
|
// Unlike regular baseINV, and incompatible with it. Intended to encode stuff small enough that it might be human typed and human transmitted, therefore maps 'o' and 'O' to '0', 'I' and 'l' to '1'
|
|
// uses six url safe additional characters !$*+-_ to bring it up to six bits
|
|
static_assert(index2base64[63] == '_', "surprise numeral at 63");
|
|
|
|
// Being intended for small bits of data, assumes no whitespace within an entity
|
|
// Encode and decode are called with a bit buffer, consisting of an unsigned byte pointer, a starting bit position relative to the pointer, and a bit count. We assume that there is room enough in the object pointed to to accommodate the bytes referenced by Bit Position+BitCount. We don't change bits outside the range.
|
|
// The bit buffer does not need to be aligned, nor does it need to be a multiple of six bits, eight bits, or twenty four bits.
|
|
// If the input bit buffer to be encoded to base sixty four is not a multiple of six bits, the last base sixty four numeral output will represent the bit buffer padded with trailing zeroes.
|
|
// If there is no room in the output span for all the base sixty four digits, the encode routine will return a number of bits less than the size of the input bit buffer, less than the bitcount it was given.
|
|
// Table to convert ascii to six bit.
|
|
static constexpr std::array<constexpr uint8_t, 0x100> ascii2six_ar{
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, //control characters
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, //control characters
|
|
0xff, 0x3a, 0xff, 0xff, 0x3b, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3c, 0x3d, 0xff, 0x3e, 0xff, 0xff, // ! $ * + - permitted The characters "#%&'(),. are stops
|
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // 0-9 permitted The characters :;<=>? are stops
|
|
0xff, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x01, 0x12, 0x13, 0x14, 0x15, 0x16, 0x00,
|
|
0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0xff, 0xff, 0xff, 0xff, 0x3f, // A-Z and _ permitted, @ and [\]^ are stops
|
|
0xff, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x01, 0x2d, 0x2e, 0x00,
|
|
0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0xff, 0xff, 0xff, 0xff, 0xff, //a-z the characters {|} ~` are stops, as is the mysterious control character 0x7F (del)
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff // utf8 multibyte characters, all stops.
|
|
};
|
|
|
|
// Table to convert ascii to six bit as a good old fashioned non owning naked pointer to const, whose storage is owned by a const static which exists until the program terminates.
|
|
static const uint8_t *ascii2six{ &ascii2six_ar[0] };
|
|
// Compile time execution is C++ is a pain, because expressions are apt to unpredictably lose their constexpr character for reasons that are far from clear.
|
|
// You really have to write compile time code in templates as a language, which is the totally obscure and hard to use language apt to generate remarkably voluminous error messages will little obvious connection to the actual problem, and surprising result that are very difficult to predict in advance or understand at all.
|
|
//In general, the better solution is to have a routine that is called once and only once at the beginning of the program, which initializes a bunch of static const values, if that solution is adequate, or to have a preproces routine written in python which generates the required C files and header files.
|
|
|
|
// After this experiment in compile time code, I swear off it.
|
|
|
|
// Decode does not have an input span of encoded characters, but a char *, because it assumes the string is always terminated by an invalid character, such as the trailing null at the end of string or a space, or any character that is not one of our base sixty four numerals
|
|
// If the there are not enough input base sixty four numerals, it returns a size less than requested.
|
|
// if the requested bit buffer is not a multiple of six bits, and the last base 64 numeral had trailing ones that would not fit in the buffer, rather than the expected trailing zeroes, then it returns a size larger than the buffer, as if it changed stuff outside the buffer but does not actually change bits outside the buffer. This is likely an error, because obviously we want to decode something from base sixty four that was originally decoded using the same sized buffer.
|
|
|
|
static const uint8_t INV{ UCHAR_MAX };
|
|
|
|
const uint8_t highBitMask[]{ 0x00, 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE, 0xFF };
|
|
static_assert(CHAR_BIT+1 == sizeof(highBitMask), "expecting eight bits per character");
|
|
const uint8_t lowBitMask[]{ 0xFF, 0x7F, 0x3F, 0x1F, 0x0F, 0x07, 0x03, 0x01, 0x00 };
|
|
static_assert(CHAR_BIT + 1 == sizeof(lowBitMask), "expecting eight bits per character");
|
|
|
|
|
|
// Converts bit a bit buffer into base 64 numerals
|
|
// Start does not need to be byte aligned, nor does the length need to be a multiple of eight, six, or twenty four.
|
|
// If insufficient room is provided for the base 64 value, throws exception.
|
|
void bits2base64(const uint8_t * bitBuffer, unsigned int start, unsigned int length, std::span<char>base64Numerals) {
|
|
assert(length <= 6 * base64Numerals.size() - 6);
|
|
// If you hit this assert you probably passed in a negative number, overflowing the string buffer
|
|
// which would cause you to write all over memory.
|
|
// Expects(length <= 6 * base64Numerals.size()-6);
|
|
if (length > 6 * base64Numerals.size() - 6)throw FatalException(sz_text_buffer_overflow);
|
|
auto e{ &base64Numerals[0] };
|
|
if (length) {
|
|
bitBuffer += start / CHAR_BIT;
|
|
start -= (start / CHAR_BIT) * CHAR_BIT;
|
|
assert(start == start % 8);
|
|
unsigned int zeropadding{ ((length + 5) / 6) * 6 - length };
|
|
assert((length + zeropadding) % 6 == 0);
|
|
assert(length + zeropadding <= 6 * (unsigned int)( base64Numerals.size()));
|
|
unsigned int bitAccumulator = (*bitBuffer++) & lowBitMask[(start)];
|
|
unsigned int bitsInAccumulator{ 8 - start };
|
|
assert(bitsInAccumulator < 9);
|
|
unsigned int count{ length - bitsInAccumulator }; //count is bits lifted out of the buffer.
|
|
// We need to keep track of count, because the last byte lifted out of the buffer may well have to be an incomplete byte, so when count gets down to below 8, we have to special case loading the bitAccumulator.
|
|
char * Outputsz{ &(base64Numerals[0]) };
|
|
auto endBaseNumeralBuffer{ Outputsz + length / 6 };
|
|
for (; e < endBaseNumeralBuffer; e++) {
|
|
assert(count + bitsInAccumulator + 6 * (unsigned int)(e - Outputsz) == length);
|
|
if (bitsInAccumulator < 6)
|
|
{
|
|
if (count > 7) {
|
|
bitAccumulator = (bitAccumulator << 8) | (*bitBuffer++);
|
|
bitsInAccumulator += 8;
|
|
count -= 8;
|
|
assert(count + bitsInAccumulator + 6 * (unsigned int)(e - Outputsz) == length);
|
|
}
|
|
else {
|
|
assert(count + bitsInAccumulator >= 6); // Should be enough bits in buffer for all numerals produced by this for loop.
|
|
bitAccumulator = (((bitAccumulator << 8) | (*bitBuffer++)) >> (8 - count));
|
|
bitsInAccumulator += count;
|
|
count = 0;
|
|
assert(count + bitsInAccumulator + 6 * (unsigned int)(e - &base64Numerals[0]) == length);
|
|
}
|
|
}
|
|
assert(bitsInAccumulator > 5);
|
|
*e = index2base64[(bitAccumulator >> (bitsInAccumulator - 6)) & 0x3F];
|
|
bitsInAccumulator -= 6;
|
|
}
|
|
// When we drop out of the for loop, we may have more than 0 bits left but less than six, in which case we then have to special case the last numeral by filling the bit accumulator with our zeropadding.
|
|
if (count) {
|
|
bitAccumulator = (((bitAccumulator << 8) | (*bitBuffer++)) >> (8 - count));
|
|
bitsInAccumulator += count;
|
|
count = 0;
|
|
assert(count + bitsInAccumulator + 6 * (unsigned int)(e - &base64Numerals[0]) == length);
|
|
}
|
|
if (bitsInAccumulator) {
|
|
// Going to issue one last numeral
|
|
assert(zeropadding + bitsInAccumulator == 6);
|
|
if (bitsInAccumulator < 6) {
|
|
bitAccumulator = bitAccumulator << zeropadding; // 0 pad accumulator
|
|
bitsInAccumulator = 6; //This breaks the invariant checked by the assert.
|
|
}
|
|
assert(bitsInAccumulator == 6);
|
|
*e++ = index2base64[bitAccumulator & 0x3F];
|
|
}
|
|
assert(count == 0);
|
|
assert(6 * (unsigned int)(e - Outputsz) == length + zeropadding);
|
|
assert((length + 5) / 6 == e - &base64Numerals[0]); //Ensures that base 64 representation is the right size to hold the bits.
|
|
}
|
|
*e = '\0';
|
|
}
|
|
|
|
// This may produce the runtime error that there are too few numerals to fill the bit buffer.
|
|
// Stops at the first invalid numeral - such as a space.
|
|
// This is intended for quite short bit fields, not for transmitting megabytes of data over lines that are not eight bit safe.
|
|
// Returns the actual size of the fill.
|
|
// If the bit buffer is not a multiple of six, the last numerals excess bits need to be zero
|
|
// If they are not zero will truncate the excess bits and and throw BadDataException.
|
|
unsigned int base64_to_bits(uint8_t * bitBuffer, unsigned int start, unsigned int length, const char * base64Numerals) {
|
|
bitBuffer += start / CHAR_BIT;
|
|
start -= (start / CHAR_BIT) * CHAR_BIT;
|
|
assert(start == start % 8);
|
|
unsigned int zeropadding{ ((length + 5) / 6) * 6 - length };
|
|
assert((length + zeropadding) % 6 == 0);
|
|
unsigned int bitAccumulator = (*bitBuffer)>>(8-start);
|
|
unsigned int bitsInAccumulator{ start };
|
|
unsigned int count{ length };
|
|
unsigned int numeral;
|
|
uint8_t overflowBits{ '\0' };
|
|
uint8_t * p{ bitBuffer };
|
|
for (const char * e = base64Numerals; ((numeral = ascii2six[static_cast<unsigned char>(*e)]), (numeral < INV)); e++) {
|
|
assert((e - base64Numerals) * 6 + count == length);
|
|
assert((p-bitBuffer)*8 + bitsInAccumulator == start + (e-base64Numerals)*6 );
|
|
bitAccumulator = (bitAccumulator << 6) | numeral;
|
|
bitsInAccumulator += 6;
|
|
if (count < 6) {
|
|
if (count > 0) {
|
|
const uint8_t lowBitMaskIn[]{ 0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F };
|
|
// Bit accumulator is here numeral aligned, rather than byte aligned
|
|
overflowBits = bitAccumulator & lowBitMaskIn[6 - count];
|
|
bitAccumulator = (bitAccumulator >> (6 - count));
|
|
bitsInAccumulator -= 6 - count;
|
|
// Now it is bit field aligned
|
|
count = 0;
|
|
}
|
|
}
|
|
else count -= 6;
|
|
if (bitsInAccumulator > 7) {
|
|
*p++ = (bitAccumulator >> (bitsInAccumulator % 8));
|
|
bitsInAccumulator -= 8;
|
|
}
|
|
if (count == 0) {
|
|
assert(bitsInAccumulator < 9);
|
|
if (bitsInAccumulator) {
|
|
*p = (bitAccumulator << (8 - bitsInAccumulator)) | (*p & lowBitMask[bitsInAccumulator]);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (overflowBits) throw BadDataException();
|
|
return overflowBits ? length + 1 : length - count;
|
|
}
|
|
|
|
/* Expects pointer to byte buffer and pointer to string.
|
|
Expects a string of exactly the correct number of numerals,
|
|
terminated by a non base64 character, such as null.
|
|
Throws exception if that is not what it gets.
|
|
Fills the byte buffer exactly.
|
|
Returns a uint8_t containing the excess bits of the last numeral in its low order part.*/
|
|
uint8_t base64_to_bytes(uint8_t* byteBuffer, uint_fast32_t byteCount, const char* base64Numerals) {
|
|
auto numeralsCount{ byteCount * 8 / 6 };
|
|
auto bitsCount{ numeralsCount * 6 };
|
|
auto length{ base64_to_bits(byteBuffer, 0, bitsCount, base64Numerals) };
|
|
if (length < bitsCount) throw BadDataException();
|
|
base64Numerals += numeralsCount;
|
|
auto leftoverBitsField{ byteCount * 8 - length };
|
|
auto leftoverBits{ 0 };
|
|
if (leftoverBitsField) {
|
|
// we cast to unsigned character, because otherwise it is likely to be sign
|
|
// extended resulting in indexing outside the range 0-0xFF
|
|
// with an indeterminate and unpredictable number of high
|
|
// order bits set.
|
|
uint8_t numeral{ ascii2six[static_cast<unsigned char>(*base64Numerals++)]};
|
|
if (numeral>63) throw BadDataException();
|
|
auto missingBitsField{8*byteCount-bitsCount};
|
|
assert(missingBitsField + leftoverBitsField == 6);
|
|
auto missingBitsMask{ (1 << missingBitsField) - 1 };
|
|
auto missingBits{ static_cast<uint8_t>(numeral >> leftoverBitsField) };
|
|
byteBuffer[byteCount - 1] = ((byteBuffer[byteCount - 1] | missingBitsMask) ^ missingBitsMask) | missingBits;
|
|
auto leftoverBits{ static_cast<uint8_t>( numeral ^ (missingBits << missingBitsField)) };
|
|
}
|
|
if (ascii2six[static_cast<unsigned char>(*base64Numerals)]<64) throw BadDataException();
|
|
return leftoverBits;
|
|
}
|
|
|
|
|
|
// Converts bit a bit buffer into base 2048 BIPS-39 words
|
|
// Using the array ar_sz_bip_0039_wordlist defined in localization.cpp
|
|
// The largest word in the array is eight characters, but other languages likely have longer words.
|
|
// Start does not need to be byte aligned, nor does the length need to be a multiple of eight or eleven.
|
|
void bits2base2048(const uint8_t* bitBuffer, int start, int length, std::span<char>szBipsWords) {
|
|
char* Outputsz{ &(szBipsWords[0]) };
|
|
if (szBipsWords.size() == 0)throw ;
|
|
char* end_of_Outputsz{ &(szBipsWords[szBipsWords.size() - 1]) };
|
|
if (length>0) {
|
|
bitBuffer += start / CHAR_BIT;
|
|
start -= (start / CHAR_BIT) * CHAR_BIT;
|
|
assert(start == start % 8);
|
|
uint_fast32_t bitAccumulator = (*bitBuffer++) & lowBitMask[(start)];
|
|
int bitsInAccumulator{ 8 - start };
|
|
assert(bitsInAccumulator < 9);
|
|
int count{ length - bitsInAccumulator }; //count is bits remaining in buffer, may go negative
|
|
// We need to keep track of count, because the last byte lifted out of the buffer may well have to be an incomplete byte, so when count gets down to below 8, we have to special case loading the bitAccumulator.
|
|
while (count) {
|
|
while (bitsInAccumulator < 11 && count >0)
|
|
{
|
|
bitAccumulator = (bitAccumulator << 8) | (*bitBuffer++);
|
|
bitsInAccumulator += 8;
|
|
count -= 8;
|
|
}
|
|
if (count < 0) {
|
|
// get rid of bad bits
|
|
bitAccumulator >>= (-count);
|
|
bitsInAccumulator += count;
|
|
if (bitsInAccumulator < 11) {
|
|
bitAccumulator <<= (11 - bitsInAccumulator);
|
|
bitsInAccumulator = 11;
|
|
}
|
|
count = 0;
|
|
}
|
|
uint_fast16_t wordnumber{ (bitAccumulator >> (bitsInAccumulator - 11)) & 2047 };
|
|
bitsInAccumulator -= 11;
|
|
const char* psz_Word{ ar_sz_bip_0039_wordlist[wordnumber] };
|
|
while (*psz_Word) {
|
|
if (Outputsz == end_of_Outputsz)throw FatalException("not enough room for BIPS-0039 passphrase");
|
|
*Outputsz++ = *psz_Word++;
|
|
}
|
|
if (bitsInAccumulator + count > 0) {
|
|
if (Outputsz == end_of_Outputsz)throw FatalException("not enough room for BIPS-0039 passphrase");
|
|
*Outputsz++ = ' ';
|
|
}
|
|
}
|
|
}
|
|
*Outputsz++ = 0;
|
|
}
|
|
/*
|
|
// Not cryptographically strong, not DoS induced collision resistant. Produces
|
|
// the same mapping on all machines, for all time.
|
|
uint32_t bernstein_hash(const uint8_t* key, unsigned int len) {
|
|
constexpr uint_fast32_t INITIAL_VALUE = 5381;
|
|
constexpr uint_fast32_t M = 33;
|
|
uint_fast32_t hash = INITIAL_VALUE;
|
|
for (uint_fast32_t i = 0; i < len; ++i)
|
|
hash = M * hash + key[i];
|
|
return hash;
|
|
}
|
|
*/ |