This is a binary-to-text encoder that is based on Base64, but it works by converting data into an almost pronounceable string (consonant-vowel-consonant-vowel for each 3 bytes). Any improvements, suggestions, etc. are welcome.
bitspeak.hpp
#ifndef BITSPEAK_HPP
#define BITSPEAK_HPP
/*
Encoding:
******** ******** ********
CCCCCCCV VVVVCCCC CCCVVVVV
-CCCCCCC ---VVVVV -CCCCCCC ---VVVVV
C: Consonant
V: Vowel
Decoding:
-******* ---***** -******* ---*****
-xxxxxxx ---xyyyy -yyyyzzz ---zzzzz
xxxxxxxx yyyyyyyy zzzzzzzz
*/
#include <iostream>
#include <cstdlib>
#include <cstdint>
#include <cstring>
#include <string>
#include <sstream>
#include <vector>
namespace bitspeak
{
static const std::string vows[32] = {"a","e","i","o","u","w","y","ae","ai","ao","au","aw","ay","ea","ee","ei","eo","eu","ey","ia","io","iu","oa","oi","ou","ow","oy","ui","uo","uy","we","wi"};
static const std::string cons[128] = {"b","c","d","f","g","h","j","k","l","m","n","p","q","r","s","t","v","x","z","bc","bl","br","bs","ch","chs","cht","ck","cl","cr","cs","ct","fl","fr","fs","ft","gl","gm","gr","gs","gz","hd","hf","hr","hs","ht","hv","jr","js","jt","kl","kr","kt","lb","lc","ld","lg","lm","ln","lp","ls","lt","lv","mb","mn","mp","mr","nd","nk","nm","np","ns","nt","nv","nz","pl","pr","ps","pt","pz","rb","rc","rch","rd","rf","rg","rk","rl","rm","rn","rp","rr","rs","rt","rv","rz","sb","sc","sch","sd","sf","sh","sl","sm","sn","sp","sq","sr","st","sth","str","sv","sz","tch","th","tl","tn","tr","ts","vl","vr","vs","vz","xc","xz","zd","zl","zp","zt"};
static const std::string vow_l = "aeiouwy";
static const std::string con_l = "bcdfghjklmnpqrstvxz";
std::vector<std::string> split(const char* str,char delim)
{
std::stringstream ss;
std::string str1 = str;
std::vector<std::string> splitted;
std::string rfs;
while (str1[0] == delim) { str1.erase(0,1); }
int se = str1.length();
while (str1[se-1] == delim) { str1.erase(se-1,se); se = str1.length(); }
for (int i=0;i<str1.length()-1;i++)
{
if (str1[i] == delim && str1[i+1] == delim)
continue;
else
rfs += str1[i];
}
rfs += str1[str1.length()-1];
for (int i=0;i<rfs.length()+1;i++)
{
if (rfs[i] != delim && i != rfs.length())
ss << rfs[i];
else
{
splitted.push_back(ss.str());
ss.str(std::string());
}
}
return splitted;
}
void ca3_to_ca4(uint8_t _a3[3],uint8_t _a4[4])
{
_a4[0] = (_a3[0]&0xFE) >> 1;
_a4[1] = ((_a3[0]&0x01) << 4) | ((_a3[1]&0xF0) >> 4);
_a4[2] = ((_a3[1]&0x0F) << 3) | ((_a3[2]&0xE0) >> 5);
_a4[3] = _a3[2]&0x1F;
}
void ca4_to_ca3(uint8_t _a4[4],uint8_t _a3[3])
{
_a3[0] = ((_a4[0]&0x7F) << 1) | ((_a4[1]&0x10) >> 4);
_a3[1] = ((_a4[1]&0x0F) << 4) | ((_a4[2]&0x78) >> 3);
_a3[2] = ((_a4[2]&0x07) << 5) | (_a4[3]&0x1F);
}
bool is_vowel(char x) { bool o = false; int i; for (i=0;i<vow_l.size();i++) { if (x == vow_l[i]) { o = true; break; } } return o; }
bool is_consonant(char x) { bool o = false; int i; for (i=0;i<con_l.size();i++) { if (x == con_l[i]) { o = true; break; } } return o; }
uint32_t string_find(const std::string *arr,std::string x,uint32_t size)
{
uint32_t idx = -1, i = 0;
for (i=0;i<size;i++)
{
if (x == arr[i])
{
idx = i;
break;
}
}
return idx;
}
void encode(uint8_t *data,uint32_t length,std::string& out)
{
uint32_t i = 0, j = 0;
uint8_t ca3[3];
uint8_t ca4[4];
while (length--)
{
ca3[i++] = *(data++);
if (i == 3)
{
ca3_to_ca4(ca3,ca4);
// Put consonants and vowels (form is CVCV for each 3 bytes of data)
for (i=0;i<4;i++)
{
if (i == 0) out += cons[ca4[0]];
if (i == 1) out += vows[ca4[1]];
if (i == 2) out += cons[ca4[2]];
if (i == 3) out += vows[ca4[3]];
}
i = 0;
}
}
if (i)
{
for (j=i;j<3;j++)
ca3[j] = '\0';
ca3_to_ca4(ca3,ca4);
for (j=0;j<(i+1);j++)
{
if (j == 0) out += cons[ca4[0]];
if (j == 1) out += vows[ca4[1]];
if (j == 2) out += cons[ca4[2]];
if (j == 3) out += vows[ca4[3]];
}
// Put padding
while ((i++ < 3))
out += "?";
}
}
void decode(const char *data,uint32_t length,std::string& out)
{
uint32_t i = 0, j = 0, si = 0;
uint8_t ca3[3];
uint8_t ca4[4];
std::string sa4[4];
uint32_t slen = length;
std::vector<std::string> vow_v; // Vowels
std::vector<std::string> con_v; // Consonants
std::vector<std::string> all_v; // All data to decode
std::string vow_tmp, con_tmp, pad_tmp; // Placeholders for consonants, vowels and padding
uint32_t padc = 0;
while ((i < slen))
{
if (is_consonant(data[i]))
{
con_tmp += data[i];
vow_tmp += '*'; pad_tmp += '*';
}
if (is_vowel(data[i]))
{
vow_tmp += data[i];
con_tmp += '*'; pad_tmp += '*';
}
if (data[i] == '?')
{
pad_tmp += data[i];
con_tmp += '*'; vow_tmp += '*';
padc++;
}
i++;
}
// Split
vow_v = split(vow_tmp.c_str(),'*');
con_v = split(con_tmp.c_str(),'*');
// Put consonants and vowels (form is CVCV for each 3 bytes of data)
uint32_t np = vow_v.size()+con_v.size();
for (i=0;i<np;i++)
{
if (i%2 == 0) all_v.push_back(con_v[i/2]);
if (i%2 == 1) all_v.push_back(vow_v[i/2]);
}
// Put padding
for (i=0;i<padc;i++)
all_v.push_back("?");
uint32_t vlen = all_v.size();
i = 0;
while (vlen-- && (all_v[si] != "?"))
{
// Find which position is the one that corresponds to cons. and vow.
if (si%2 == 0) ca4[i++] = (uint8_t)string_find(cons,con_v[si/2],128);
if (si%2 == 1) ca4[i++] = (uint8_t)string_find(vows,vow_v[si/2], 32);
si++;
if (i == 4)
{
ca4_to_ca3(ca4,ca3);
for (i=0;i<3;i++)
{
if (i == 0) out += ca3[0];
if (i == 1) out += ca3[1];
if (i == 2) out += ca3[2];
}
i = 0;
}
}
if (i)
{
for (j=i;j<4;j++)
ca4[j] = 0;
ca4_to_ca3(ca4,ca3);
for (j=0;j<(i-1);j++)
{
if (j == 0) out += ca3[0];
if (j == 1) out += ca3[1];
if (j == 2) out += ca3[2];
}
}
}
} // end of "bitspeak" namespace
#endif
bitspeak_test.cpp
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <string>
#include "bitspeak.hpp"
using namespace std;
int main(int argc,char **argv)
{
string tests[4] = {
"Technological progress is like an axe in the hands of a pathological criminal.",
"Bitspeak test",
"123456789abcdef",
" ((( d(O_o)b ))) "
};
cout << ".....--------============= Bitspeak test =============--------.....\n";
for (int i=0;i<4;i++)
{
string encoded, decoded;
cout << "Test string " << (i+1) << ": " << tests[i] << "\n";
bitspeak::encode(
(uint8_t*)tests[i].c_str(),
tests[i].length(),
encoded
);
cout << "Encoded: " << encoded << "\n";
bitspeak::decode(
encoded.c_str(),
encoded.length(),
decoded
);
cout << "Decoded: " << decoded << "\n\n";
}
}
Here is the output of the test program:
.....--------============= Bitspeak test =============--------..... Test string 1: Technological progress is like an axe in the hands of a pathological criminal. Encoded: hryhsolbytneildyxzaelboacleldifeolnyxzaelnyhsialneyfaolneyfaylboarswvypeevypoukreyfaolgifiolbyhfalbypeekraechtalgoaklajteyfeojtoiglailgoasfeiktoaprojtoaschakloibcaoldoapreejtoaschee Decoded: Technological progress is like an axe in the hands of a pathological criminal. Test string 2: Bitspeak test Encoded: fsypriolnoifwjtoarpalpyhsialpa?? Decoded: Bitspeak test Test string 3: 123456789abcdef Encoded: chsiaxiackohfoacliamrowjtoabcokryhsy Decoded: 123456789abcdef Test string 4: ((( d(O_o)b ))) Encoded: vimraiblifubluxcwilgeyprivinzaobleyb? Decoded: ((( d(O_o)b )))