/* vim: set expandtab ts=4 sw=4: */
/*
* You may redistribute this program 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.
*
* This program 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include "benc/String.h"
#include "crypto/random/Random.h"
#include "switch/EncodingScheme.h"
#include "switch/NumberCompress.h"
#include "memory/Allocator.h"
#include "memory/MallocAllocator.h"
#include "util/Bits.h"
#define Order_TYPE struct EncodingScheme_Form
#define Order_NAME OfEncodingForms
#define Order_COMPARE compareEncodingForms
#include "util/Order.h"
static inline int compareEncodingForms(const struct EncodingScheme_Form* a,
const struct EncodingScheme_Form* b)
{
int diff = a->bitCount;
diff -= b->bitCount;
return diff == 0 ? 0 : diff > 0 ? 1 : -1;
}
static void randomForm(struct EncodingScheme_Form* form, struct Random* rand)
{
for (;;) {
Random_bytes(rand, (uint8_t*)form, sizeof(struct EncodingScheme_Form));
//Bits_memset(form, 0xff, sizeof(struct EncodingScheme_Form));
form->bitCount &= ((1<<5)-1);
if (!form->bitCount) {
form->bitCount++;
}
form->prefixLen &= ((1<<5)-1);
if (!form->prefixLen) {
form->prefixLen++;
}
if (EncodingScheme_formSize(form) > 59) { continue; }
if (form->prefixLen > 3 && (form->prefix & 0xf) == 1) { continue; }
break;
}
form->prefix &= ((((uint64_t)1)<prefixLen)-1);
}
static struct EncodingScheme* randomScheme(struct Random* rand, struct Allocator* alloc)
{
struct EncodingScheme* out =
Allocator_malloc(alloc, sizeof(struct EncodingScheme));
do {
out->count = Random_uint32(rand) % 32;
} while (out->count < 2);
out->forms = Allocator_malloc(alloc, sizeof(struct EncodingScheme_Form) * out->count);
for (int i = 0; i < (int)out->count; i++) {
randomForm(&out->forms[i], rand);
for (int j = 0; j < i; j++) {
if (out->forms[i].bitCount == out->forms[j].bitCount) {
i--;
break;
}
int minPfx = (out->forms[i].prefixLen < out->forms[j].prefixLen)
? out->forms[i].prefixLen : out->forms[j].prefixLen;
if (((out->forms[j].prefix ^ out->forms[i].prefix) & ((1<forms[j],
&out->forms[i-1],
sizeof(struct EncodingScheme_Form));
}
i -= 2;
break;
}
}
}
Order_OfEncodingForms_qsort(out->forms, out->count);
return out;
}
static void assertEqual(struct EncodingScheme* a,
struct EncodingScheme* b)
{
Assert_true(b);
Assert_true(a->count == b->count);
Assert_true(
!Bits_memcmp(a->forms, b->forms, sizeof(struct EncodingScheme_Form) * a->count));
}
static void randomTest(struct Allocator* parent, struct Random* rand)
{
struct Allocator* alloc = Allocator_child(parent);
struct EncodingScheme* control = randomScheme(rand, alloc);
String* data = EncodingScheme_serialize(control, alloc);
struct EncodingScheme* test = EncodingScheme_deserialize(data, alloc);
assertEqual(control, test);
Allocator_free(alloc);
}
// Just make sure random crap doesn't crash it.
static void fuzzTest(struct Allocator* parent, struct Random* rand)
{
struct Allocator* alloc = Allocator_child(parent);
String* data = String_newBinary(NULL, Random_uint32(rand) % 1024, alloc);
Random_bytes(rand, (uint8_t*)data->bytes, data->len);
EncodingScheme_deserialize(data, alloc);
Allocator_free(alloc);
}
/** Greatest possible number using x bits, all are set. */
#define Bits_maxBits64(x) ((((uint64_t)1)<<(x))-1)
static void encoding(struct Allocator* parent)
{
struct EncodingScheme_Form forms[3] = {
{
.prefixLen = 15,
.bitCount = 2,
.prefix = ((1<<15)-1) ^ (1<<1),
}, {
.prefixLen = 20,
.bitCount = 4,
.prefix = ((1<<20)-1) ^ (1<<2),
}, {
.prefixLen = 18,
.bitCount = 8,
.prefix = ((1<<18)-1) ^ (1<<3),
}
};
// should encode as prefixLen, bitCount, prefix
// 111111111111101 00010 01111 15/2/~0 -> 01001111 11110100 11111111 remainder 1
// 11111111111111111011 00100 10100 20/4/~0 -> 00101001 11011001 11111111 remainder 7f
// 111111111111110111 01000 10010 18/8/~0 -> 01111111 10001001 11101110 11111111 remainder 7
// 00000111 <-- appliation of remainder
//
// In bytes:
// 01001111 11110100 11111111 00101001 11011001 11111111
// 01111111 10001001 11101110 11111111 00000111
//
// or \x4f\xf4\xff\x29\xd9\xff\x7f\x89\xee\xff\x07
struct EncodingScheme list = {
.count = 3,
.forms = forms
};
struct Allocator* alloc = Allocator_child(parent);
String* data = EncodingScheme_serialize(&list, alloc);
Assert_true(data->len == 11);
Assert_true(!Bits_memcmp(data->bytes, "\x4f\xf4\xff\x29\xd9\xff\x7f\x89\xee\xff\x07", 11));
Allocator_free(alloc);
}
#define convertLabel_SELF_ROUTE 1
#define convertLabel_TOO_BIG 2
static int convertLabel(struct EncodingScheme_Form* iform,
struct EncodingScheme_Form* oform,
uint64_t label)
{
struct {
struct EncodingScheme scheme;
struct EncodingScheme_Form forms[2];
} s;
s.scheme.count = 2;
s.scheme.forms = s.forms;
Bits_memcpyConst(&s.forms[0], iform, sizeof(struct EncodingScheme_Form));
Bits_memcpyConst(&s.forms[1], oform, sizeof(struct EncodingScheme_Form));
int iformNum = 0;
int oformNum = 1;
struct EncodingScheme* scheme = &s.scheme;
Assert_true(EncodingScheme_getFormNum(scheme, label) == iformNum);
uint64_t label2 = EncodingScheme_convertLabel(scheme, label, oformNum);
if ((label & Bits_maxBits64(s.forms[0].prefixLen + s.forms[0].bitCount)) == 1) {
Assert_true(label2 == EncodingScheme_convertLabel_INVALID);
return convertLabel_SELF_ROUTE;
}
if (Bits_log2x64(label) + EncodingScheme_formSize(oform) -
EncodingScheme_formSize(iform) > 59)
{
Assert_true(label2 == EncodingScheme_convertLabel_INVALID);
return convertLabel_TOO_BIG;
}
uint64_t additional = label >> s.forms[0].prefixLen;
uint64_t director = additional & Bits_maxBits64(s.forms[0].bitCount);
additional = additional >> s.forms[0].bitCount;
// Conversions are necessary for two reasons.
// #1 ensure 0001 always references interface 1, the self interface.
// #2 reuse interface the binary encoding for interface 1 in other EncodingForms
// because interface 1 cannot be expressed as anything other than 0001
if ((s.forms[0].prefix & Bits_maxBits64(s.forms[0].prefixLen)) == 1) {
// Swap 0 and 1 because zero is represented as 1 and 1 is handled specially
if (director == 1) { director--; }
} else if (director) {
// Reuse the number 1 for 2 and 2 for 3 etc. to gain an extra slot in all other encodings.
director++;
}
if ((s.forms[1].prefix & Bits_maxBits64(s.forms[1].prefixLen)) == 1) {
// Swap 1 and 0 back if necessary.
if (director == 0) { director++; }
} else if (director) {
// Or move the numbers down by one.
director--;
}
uint64_t converted = (additional << s.forms[1].bitCount) | director;
converted = (converted << s.forms[1].prefixLen) | s.forms[1].prefix;
if ((converted & Bits_maxBits64(s.forms[1].prefixLen + s.forms[1].bitCount)) == 1) {
// looks like a self-route
Assert_true(label2 == EncodingScheme_convertLabel_INVALID);
return convertLabel_SELF_ROUTE;
}
Assert_true(label2 == converted);
uint64_t label3 = EncodingScheme_convertLabel(scheme, label2, iformNum);
Assert_true(label3 == label);
return 0;
}
static void convertLabelRand(struct Random* rand, struct EncodingScheme* scheme)
{
for (int i = 0; i < 100; i++) {
int oformNum;
do {
oformNum = Random_uint8(rand) % scheme->count;
} while (!oformNum);
int iformNum = Random_uint8(rand) % oformNum;
uint64_t label = Random_uint64(rand);
label &= (UINT64_MAX >> (Random_uint8(rand) % 63));
for (;;) {
// make sure the label has the right prefix
label <<= scheme->forms[iformNum].prefixLen;
label |= scheme->forms[iformNum].prefix;
Assert_true(EncodingScheme_getFormNum(scheme, label) == iformNum);
if (Bits_log2x64(label) > 59) {
// fall through
} else {
int ret = convertLabel(&scheme->forms[iformNum], &scheme->forms[oformNum], label);
if (ret == convertLabel_SELF_ROUTE) {
i--;
break;
} else if (ret == convertLabel_TOO_BIG) {
// fall through
} else if (!ret) {
// success
break;
} else {
Assert_true(0);
}
}
label >>= scheme->forms[iformNum].prefixLen + 1;
}
}
}
static void isOneHopScheme(struct Allocator* allocator)
{
struct Allocator* alloc = Allocator_child(allocator);
struct EncodingScheme* s4x8 = NumberCompress_v4x8_defineScheme(alloc);
Assert_true(!EncodingScheme_isOneHop(s4x8, 1));
Assert_true(EncodingScheme_isOneHop(s4x8, 0x21));
Assert_true(EncodingScheme_isOneHop(s4x8, 0x23));
Assert_true(!EncodingScheme_isOneHop(s4x8, 0x12));
Assert_true(EncodingScheme_isOneHop(s4x8, 0x220));
Assert_true(EncodingScheme_isOneHop(s4x8, 0x210));
Assert_true(!EncodingScheme_isOneHop(s4x8, 0x110));
struct EncodingScheme* s3x5x8 = NumberCompress_v3x5x8_defineScheme(alloc);
Assert_true(!EncodingScheme_isOneHop(s3x5x8, 1));
Assert_true(EncodingScheme_isOneHop(s3x5x8, 0x13));
Assert_true(EncodingScheme_isOneHop(s3x5x8, 0x15));
Assert_true(EncodingScheme_isOneHop(s3x5x8, 0x96));
Assert_true(EncodingScheme_isOneHop(s3x5x8, 0x400));
Assert_true(!EncodingScheme_isOneHop(s3x5x8, 0x115));
Assert_true(!EncodingScheme_isOneHop(s3x5x8, 0x166));
Assert_true(!EncodingScheme_isOneHop(s3x5x8, 0x1400));
Allocator_free(alloc);
}
int main()
{
struct Allocator* alloc = MallocAllocator_new(20000000);
struct Random* rand = Random_new(alloc, NULL, NULL);
encoding(alloc);
isOneHopScheme(alloc);
for (int i = 0; i < 1000; i++) {
randomTest(alloc, rand);
}
for (int i = 0; i < 1000; i++) {
fuzzTest(alloc, rand);
}
// for testing individual conversions in isolation.
/*convertLabel(
&(struct EncodingScheme_Form){.bitCount = 15, .prefixLen = 20, .prefix = 792003},
&(struct EncodingScheme_Form){.bitCount = 31, .prefixLen = 30, .prefix = 385462496},
11331704259
);*/
for (int i = 0; i < 10; i++) {
struct Allocator* tempAlloc = Allocator_child(alloc);
struct EncodingScheme* scheme = randomScheme(rand, tempAlloc);
convertLabelRand(rand, scheme);
Allocator_free(tempAlloc);
}
struct Allocator* tempAlloc = Allocator_child(alloc);
struct EncodingScheme* scheme = NumberCompress_v3x5x8_defineScheme(alloc);
for (int i = 0; i < NumberCompress_v3x5x8_INTERFACES; i++) {
int bits = NumberCompress_bitsUsedForNumber(i);
uint64_t expected = NumberCompress_getCompressed(i, bits);
Assert_true(expected == EncodingScheme_convertLabel(scheme, expected,
EncodingScheme_convertLabel_convertTo_CANNONICAL));
}
Allocator_free(tempAlloc);
return 0;
}