diff --git a/src/bytewords.cpp b/src/bytewords.cpp index d792856..b4b0378 100644 --- a/src/bytewords.cpp +++ b/src/bytewords.cpp @@ -65,152 +65,104 @@ static const int16_t _lookup[] = { 242, -1, -1, -1 }; -static bool decode_word(const string& word, size_t word_len, uint8_t& output) { - const size_t dim = 26; +static inline bool decode_word(const string& word, size_t word_len, uint8_t& output) { + constexpr size_t dim = 26; - // Sanity check - if(word.length() != word_len) { + if (word.length() != word_len) { return false; } - // If the coordinates generated by the first and last letters are out of bounds, - // or the lookup table contains -1 at the coordinates, then the word is not valid. int x = tolower(word[0]) - 'a'; int y = tolower(word[word_len == 4 ? 3 : 1]) - 'a'; - if(!(0 <= x && x < dim && 0 <= y && y < dim)) { + + if (static_cast(x) >= dim || static_cast(y) >= dim) { return false; } + size_t offset = y * dim + x; int16_t value = _lookup[offset]; - if(value == -1) { + if (value == -1) { return false; } - // If we're decoding a full four-letter word, verify that the two middle letters are correct. - if(word_len == 4) { + if (word_len == 4) { const char* byteword = bytewords + value * 4; - int c1 = tolower(word[1]); - int c2 = tolower(word[2]); - if(c1 != byteword[1] || c2 != byteword[2]) { + if (tolower(word[1]) != byteword[1] || tolower(word[2]) != byteword[2]) { return false; } } - // Successful decode. - output = value; + output = static_cast(value); return true; } -static string get_word(uint8_t index) { - const auto* p = &bytewords[index * 4]; - return {p, p + 4}; -} - -static string get_minimal_word(uint8_t index) { - string word; - word.reserve(2); - const auto* p = &bytewords[index * 4]; - word.push_back(*p); - word.push_back(*(p + 3)); - return word; -} - -static string encode(const ByteVector& buf, const string& separator) { - auto len = buf.size(); - StringVector words; - words.reserve(len); - for(int i = 0; i < len; i++) { - auto byte = buf[i]; - words.push_back(get_word(byte)); - } - return join(words, separator); -} - static inline ByteVector crc32_bytes(const ByteVector &buf) { - uint32_t checksum = __builtin_bswap32(esp_crc32_le(0, buf.data(), buf.size())); + const uint32_t checksum = __builtin_bswap32(esp_crc32_le(0, buf.data(), buf.size())); ByteVector result(sizeof(checksum)); - std::memcpy(result.data(), &checksum, sizeof(checksum)); + memcpy(result.data(), &checksum, sizeof(checksum)); return result; } -static ByteVector add_crc(const ByteVector& buf) { - auto crc_buf = crc32_bytes(buf); - auto result = buf; +string Bytewords::encode(style style, const ByteVector& bytes) { + auto crc_buf = crc32_bytes(bytes); + ByteVector result = bytes; append(result, crc_buf); - return result; -} -static string encode_with_separator(const ByteVector& buf, const string& separator) { - auto crc_buf = add_crc(buf); - return encode(crc_buf, separator); -} - -static string encode_minimal(const ByteVector& buf) { - string result; - auto crc_buf = add_crc(buf); - auto len = crc_buf.size(); - for(int i = 0; i < len; i++) { - auto byte = crc_buf[i]; - result.append(get_minimal_word(byte)); + if (style == minimal) { + std::string r; + r.reserve(result.size() * 2); + for (uint8_t byte : result) { + const char* p = &bytewords[byte * 4]; + r.push_back(p[0]); + r.push_back(p[3]); + } + return r; } - return result; -} -static ByteVector _decode(const string& s, char separator, size_t word_len) { + assert(style == standard || style == uri); + StringVector words; - if(word_len == 4) { - words = split(s, separator); - } else { - words = partition(s, 2); + words.reserve(result.size()); + for (uint8_t byte : result) { + words.emplace_back(&bytewords[byte * 4], 4); } + return join(words, style == standard ? " " : "-"); +} + +ByteVector Bytewords::decode(style style, const string& s) { + assert(style == standard || style == uri || style == minimal); + const size_t word_len = (style == minimal) ? 2 : 4; + const char separator = (style == standard) ? ' ' : (style == uri) ? '-' : 0; + + StringVector words = (word_len == 4) ? split(s, separator) : partition(s, 2); + + const size_t num_words = words.size(); + if (num_words < 5) return ByteVector(); + ByteVector buf; - buf.reserve(words.size()); - for (const auto &word : words) { + buf.reserve(num_words); + + for (const auto& word : words) { uint8_t output; if (!decode_word(word, word_len, output)) { - // Failed to decode word return ByteVector(); } buf.push_back(output); } - if(buf.size() < 5) { - return ByteVector(); - } - auto p = split(buf, buf.size() - 4); - auto body = p.first; - auto body_checksum = p.second; - auto checksum = crc32_bytes(body); - if(checksum != body_checksum) { - return ByteVector(); - } - return body; -} + if (buf.size() < 5) return ByteVector(); -string Bytewords::encode(style style, const ByteVector& bytes) { - switch(style) { - case standard: - return encode_with_separator(bytes, " "); - case uri: - return encode_with_separator(bytes, "-"); - case minimal: - return encode_minimal(bytes); - } - assert(false); - return string(); -} + const auto body_size = buf.size() - 4; + const ByteVector body(buf.begin(), buf.begin() + body_size); + const ByteVector body_checksum(buf.begin() + body_size, buf.end()); -ByteVector Bytewords::decode(style style, const string& string) { - switch(style) { - case standard: - return _decode(string, ' ', 4); - case uri: - return _decode(string, '-', 4); - case minimal: - return _decode(string, 0, 2); + const auto checksum = crc32_bytes(body); + + if (std::equal(body_checksum.begin(), body_checksum.end(), checksum.begin())) { + return body; } - assert(false); return ByteVector(); } + } diff --git a/src/fountain-decoder.cpp b/src/fountain-decoder.cpp index 22e936a..a8819fb 100644 --- a/src/fountain-decoder.cpp +++ b/src/fountain-decoder.cpp @@ -61,7 +61,7 @@ bool FountainDecoder::receive_part(FountainEncoder::Part& encoder_part) { } // Keep track of how many parts we've processed - processed_parts_count_ += 1; + ++processed_parts_count_; return true; } @@ -85,37 +85,41 @@ void FountainDecoder::process_queue_item() { } void FountainDecoder::reduce_mixed_by(const Part& p) { - // Reduce all the current mixed parts by the given part PartVector reduced_parts; - for(auto i = _mixed_parts.begin(); i != _mixed_parts.end(); i++) { - reduced_parts.push_back(reduce_part_by_part(i->second, p)); + reduced_parts.reserve(_mixed_parts.size()); + + for (auto it = _mixed_parts.begin(); it != _mixed_parts.end(); ++it) { + reduced_parts.push_back(reduce_part_by_part(it->second, p)); } - // Collect all the remaining mixed parts PartDict new_mixed; - for(auto reduced_part: reduced_parts) { - // If this reduced part is now simple - if(reduced_part.is_simple()) { - // Add it to the queue + + for (auto& reduced_part : reduced_parts) { + if (reduced_part.is_simple()) { enqueue(reduced_part); } else { - // Otherwise, add it to the list of current mixed parts - new_mixed.insert(pair(reduced_part.indexes(), reduced_part)); + new_mixed.emplace(reduced_part.indexes(), move(reduced_part)); } } - _mixed_parts = new_mixed; + + _mixed_parts = move(new_mixed); } + FountainDecoder::Part FountainDecoder::reduce_part_by_part(const Part& a, const Part& b) const { - // If the fragments mixed into `b` are a strict (proper) subset of those in `a`... - if(is_strict_subset(b.indexes(), a.indexes())) { - // The new fragments in the revised part are `a` - `b`. + if (is_strict_subset(b.indexes(), a.indexes())) { auto new_indexes = set_difference(a.indexes(), b.indexes()); - // The new data in the revised part are `a` XOR `b` - auto new_data = xor_with(a.data(), b.data()); + + ByteVector new_data = a.data(); + const auto& s = b.data(); + const size_t count = new_data.size(); + + for (size_t i = 0; i < count; ++i) { + new_data[i] ^= s[i]; + } + return Part(new_indexes, new_data); } else { - // `a` is not reducable by `b`, so return a return a; } } @@ -123,30 +127,39 @@ FountainDecoder::Part FountainDecoder::reduce_part_by_part(const Part& a, const void FountainDecoder::process_simple_part(Part& p) { // Don't process duplicate parts auto fragment_index = p.index(); - if(contains(received_part_indexes_, fragment_index)) return; + if (received_part_indexes_.find(fragment_index) != received_part_indexes_.end()) return; // Record this part - _simple_parts.insert(pair(p.indexes(), p)); + _simple_parts.emplace(p.indexes(), p); received_part_indexes_.insert(fragment_index); // If we've received all the parts - if(received_part_indexes_ == _expected_part_indexes) { + if (received_part_indexes_ == _expected_part_indexes) { // Reassemble the message from its fragments PartVector sorted_parts; - transform(_simple_parts.begin(), _simple_parts.end(), back_inserter(sorted_parts), [&](auto elem) { return elem.second; }); + sorted_parts.reserve(_simple_parts.size()); + for (const auto& elem : _simple_parts) { + sorted_parts.push_back(elem.second); + } + sort(sorted_parts.begin(), sorted_parts.end(), [](const Part& a, const Part& b) -> bool { return a.index() < b.index(); } ); + ByteVectorVector fragments; - transform(sorted_parts.begin(), sorted_parts.end(), back_inserter(fragments), [&](auto part) { return part.data(); }); + fragments.reserve(sorted_parts.size()); + for (const auto& part : sorted_parts) { + fragments.push_back(part.data()); + } + auto message = join_fragments(fragments, *_expected_message_len); // Verify the message checksum and note success or failure auto checksum = esp_crc32_le(0, message.data(), message.size()); - if(checksum == _expected_checksum) { - result_ = message; + if (checksum == _expected_checksum) { + result_ = move(message); } else { result_ = InvalidChecksum(); } @@ -158,23 +171,27 @@ void FountainDecoder::process_simple_part(Part& p) { void FountainDecoder::process_mixed_part(const Part& p) { // Don't process duplicate parts - if(any_of(_mixed_parts.begin(), _mixed_parts.end(), [&](auto r) { return r.first == p.indexes(); })) { + if (any_of(_mixed_parts.begin(), _mixed_parts.end(), [&](const auto& r) { return r.first == p.indexes(); })) { return; } - // Reduce this part by all the others - auto p2 = accumulate(_simple_parts.begin(), _simple_parts.end(), p, [&](auto p, auto r) { return reduce_part_by_part(p, r.second); }); - p2 = accumulate(_mixed_parts.begin(), _mixed_parts.end(), p2, [&](auto p, auto r) { return reduce_part_by_part(p, r.second); }); + Part p2 = p; + for (const auto& r : _simple_parts) { + p2 = reduce_part_by_part(p2, r.second); + } + for (const auto& r : _mixed_parts) { + p2 = reduce_part_by_part(p2, r.second); + } // If the part is now simple - if(p2.is_simple()) { + if (p2.is_simple()) { // Add it to the queue enqueue(p2); } else { // Reduce all the mixed parts by this one reduce_mixed_by(p2); // Record this new mixed part - _mixed_parts.insert(pair(p2.indexes(), p2)); + _mixed_parts.emplace(p2.indexes(), p2); } } @@ -185,7 +202,7 @@ bool FountainDecoder::validate_part(const FountainEncoder::Part& p) { if(!_expected_part_indexes.has_value()) { // Record the things that all the other parts we see will have to match to be valid. _expected_part_indexes = PartIndexes(); - for(size_t i = 0; i < p.seq_len(); i++) { _expected_part_indexes->insert(i); } + for(size_t i = 0; i < p.seq_len(); ++i) { _expected_part_indexes->insert(i); } _expected_message_len = p.message_len(); _expected_checksum = p.checksum(); _expected_fragment_len = p.data().size(); diff --git a/src/fountain-encoder.cpp b/src/fountain-encoder.cpp index c52e852..862b764 100644 --- a/src/fountain-encoder.cpp +++ b/src/fountain-encoder.cpp @@ -21,32 +21,45 @@ size_t FountainEncoder::find_nominal_fragment_length(size_t message_len, size_t assert(message_len > 0); assert(min_fragment_len > 0); assert(max_fragment_len >= min_fragment_len); - auto max_fragment_count = message_len / min_fragment_len; - optional fragment_len; - for(size_t fragment_count = 1; fragment_count <= max_fragment_count; fragment_count++) { - fragment_len = size_t(ceil(double(message_len) / fragment_count)); - if(fragment_len <= max_fragment_len) { - break; + + size_t min_fragment_count = (message_len + max_fragment_len - 1) / max_fragment_len; + size_t max_fragment_count = message_len / min_fragment_len; + + size_t fragment_len = (message_len + min_fragment_count - 1) / min_fragment_count; + if (fragment_len <= max_fragment_len) { + return fragment_len; + } + + size_t left = min_fragment_count, right = max_fragment_count; + while (left < right) { + size_t mid = (left + right) / 2; + fragment_len = (message_len + mid - 1) / mid; + if (fragment_len <= max_fragment_len) { + right = mid; + } else { + left = mid + 1; } } - assert(fragment_len.has_value()); - return *fragment_len; + + return (message_len + left - 1) / left; } ByteVectorVector FountainEncoder::partition_message(const ByteVector &message, size_t fragment_len) { - auto remaining = message; + size_t num_fragments = (message.size() + fragment_len - 1) / fragment_len; ByteVectorVector fragments; - while(!remaining.empty()) { - auto a = split(remaining, fragment_len); - auto fragment = a.first; - remaining = a.second; - auto padding = fragment_len - fragment.size(); - while(padding > 0) { - fragment.push_back(0); - padding--; - } - fragments.push_back(fragment); + fragments.reserve(num_fragments); + + size_t offset = 0; + while (offset < message.size()) { + size_t end = min(offset + fragment_len, message.size()); + ByteVector fragment(fragment_len, 0); + + copy(message.begin() + offset, message.begin() + end, fragment.begin()); + + fragments.push_back(move(fragment)); + offset += fragment_len; } + return fragments; } @@ -84,7 +97,7 @@ FountainEncoder::Part::Part(const ByteVector& cbor) if (cberr != CborNoError || !cbor_value_is_valid(&arrayItem) || !cbor_value_is_unsigned_integer(&arrayItem)) { return; } - if(n > std::numeric_limits::max()) { + if(n > numeric_limits::max()) { return; } seq_num_ = n; @@ -92,7 +105,7 @@ FountainEncoder::Part::Part(const ByteVector& cbor) if (cberr != CborNoError) { return; } - if(n > std::numeric_limits::max()) { + if(n > numeric_limits::max()) { return; } seq_len_ = n; @@ -109,7 +122,7 @@ FountainEncoder::Part::Part(const ByteVector& cbor) if (cberr != CborNoError || !cbor_value_is_valid(&arrayItem) || !cbor_value_is_unsigned_integer(&arrayItem)) { return; } - if(n > std::numeric_limits::max()) { + if(n > numeric_limits::max()) { return; } message_len_ = n; @@ -122,7 +135,7 @@ FountainEncoder::Part::Part(const ByteVector& cbor) return; } - if(n > std::numeric_limits::max()) { + if(n > numeric_limits::max()) { return; } checksum_ = n; @@ -146,8 +159,9 @@ ByteVector FountainEncoder::Part::cbor() const { ByteVector result; auto data_res = data(); // leave some extra headroom (13 minimum) - result.reserve(30 + data_res.size()); - result.resize(30 + data_res.size()); + const size_t estimated_size = 30 + data_res.size(); + result.reserve(estimated_size); + result.resize(estimated_size); CborEncoder root_encoder; cbor_encoder_init(&root_encoder, &result[0], result.size(), 0); @@ -176,7 +190,7 @@ ByteVector FountainEncoder::Part::cbor() const { } FountainEncoder::FountainEncoder(const ByteVector& message, size_t max_fragment_len, uint32_t first_seq_num, size_t min_fragment_len) { - assert(message.size() <= std::numeric_limits::max()); + assert(message.size() <= numeric_limits::max()); message_len_ = message.size(); checksum_ = esp_crc32_le(0, message.data(), message.size()); fragment_len_ = find_nominal_fragment_length(message_len_, min_fragment_len, max_fragment_len); @@ -186,19 +200,21 @@ FountainEncoder::FountainEncoder(const ByteVector& message, size_t max_fragment_ ByteVector FountainEncoder::mix(const PartIndexes& indexes) const { ByteVector result(fragment_len_, 0); - for(auto index: indexes) { xor_into(result, fragments_[index]); } + for(auto index: indexes) { + for(int i = 0; i < fragment_len_; ++i) { + result[i] ^= fragments_[index][i]; + } + } return result; } FountainEncoder::Part FountainEncoder::next_part() { - seq_num_ += 1; // wrap at period 2^32 + ++seq_num_; // wrap at period 2^32 + auto indexes = choose_fragments(seq_num_, seq_len(), checksum_); auto mixed = mix(indexes); - return Part(seq_num_, seq_len(), message_len_, checksum_, mixed); -} -string FountainEncoder::Part::description() const { - return "seqNum:" + to_string(seq_num_) + ", seqLen:" + to_string(seq_len_) + ", messageLen:" + to_string(message_len_) + ", checksum:" + to_string(checksum_) + ", data:" + data_to_hex(data_); + return Part(seq_num_, seq_len(), message_len_, checksum_, move(mixed)); } } diff --git a/src/fountain-encoder.hpp b/src/fountain-encoder.hpp index 48aa67a..f86bc94 100644 --- a/src/fountain-encoder.hpp +++ b/src/fountain-encoder.hpp @@ -38,7 +38,6 @@ class FountainEncoder final { const ByteVector& data() const { return data_; } ByteVector cbor() const; - std::string description() const; private: uint32_t seq_num_; diff --git a/src/fountain-utils.cpp b/src/fountain-utils.cpp index b8c9997..758c280 100644 --- a/src/fountain-utils.cpp +++ b/src/fountain-utils.cpp @@ -8,12 +8,12 @@ #include "fountain-utils.hpp" #include "random-sampler.hpp" #include "utils.hpp" - +#include using namespace std; namespace ur { -size_t choose_degree(size_t seq_len, Xoshiro256& rng) { +static inline size_t choose_degree(size_t seq_len, Xoshiro256& rng) { vector degree_probabilities; for(int i = 1; i <= seq_len; i++) { degree_probabilities.push_back(1.0 / i); @@ -22,21 +22,54 @@ size_t choose_degree(size_t seq_len, Xoshiro256& rng) { return degree_chooser.next([&]() { return rng.next_double(); }) + 1; } + +class Xoshiro256ShuffleWrapper { +public: + using result_type = uint32_t; + + Xoshiro256ShuffleWrapper(Xoshiro256& rng) : rng(rng) {} + + uint32_t operator()() { + return static_cast(rng.next()); + } + + static constexpr uint32_t min() { + return numeric_limits::min(); + } + + static constexpr uint32_t max() { + return numeric_limits::max(); + } + +private: + Xoshiro256& rng; +}; + PartIndexes choose_fragments(uint32_t seq_num, size_t seq_len, uint32_t checksum) { - // The first `seq_len` parts are the "pure" fragments, not mixed with any - // others. This means that if you only generate the first `seq_len` parts, - // then you have all the parts you need to decode the message. - if(seq_num <= seq_len) { + if (seq_num <= seq_len) { return PartIndexes({seq_num - 1}); } else { - auto seed = join(vector({int_to_bytes(seq_num), int_to_bytes(checksum)})); - auto rng = Xoshiro256(seed); + auto seq_num_bytes = int_to_bytes(seq_num); + auto checksum_bytes = int_to_bytes(checksum); + + vector seed; + seed.reserve(seq_num_bytes.size() + checksum_bytes.size()); + seed.insert(seed.end(), seq_num_bytes.begin(), seq_num_bytes.end()); + seed.insert(seed.end(), checksum_bytes.begin(), checksum_bytes.end()); + + ByteVector seed_bv(seed.begin(), seed.end()); + Xoshiro256 rng(seed_bv); + Xoshiro256ShuffleWrapper shuffle_rng(rng); + auto degree = choose_degree(seq_len, rng); - vector indexes; - indexes.reserve(seq_len); - for(int i = 0; i < seq_len; i++) { indexes.push_back(i); } - auto shuffled_indexes = shuffled(indexes, rng); - return PartIndexes(shuffled_indexes.begin(), shuffled_indexes.begin() + degree); + + vector indexes(seq_len); + for (size_t i = 0; i < seq_len; ++i) { + indexes[i] = i; + } + shuffle(indexes.begin(), indexes.end(), shuffle_rng); + + return PartIndexes(indexes.begin(), indexes.begin() + degree); } } diff --git a/src/fountain-utils.hpp b/src/fountain-utils.hpp index 2164d2b..f7def51 100644 --- a/src/fountain-utils.hpp +++ b/src/fountain-utils.hpp @@ -54,7 +54,6 @@ bool contains(const std::set& s, const T& v) { return s.find(v) != s.end(); } -size_t choose_degree(size_t seq_len, Xoshiro256& rng); PartIndexes choose_fragments(uint32_t seq_num, size_t seq_len, uint32_t checksum); } diff --git a/src/psram-allocator.hpp b/src/psram-allocator.hpp index c8ca4e2..a6a3129 100644 --- a/src/psram-allocator.hpp +++ b/src/psram-allocator.hpp @@ -20,16 +20,37 @@ class PSRAMAllocator { PSRAMAllocator() noexcept = default; template - PSRAMAllocator(const PSRAMAllocator& other) noexcept {}; + PSRAMAllocator(const PSRAMAllocator& other) noexcept {} + + PSRAMAllocator(const PSRAMAllocator&) noexcept = default; + PSRAMAllocator& operator=(const PSRAMAllocator&) noexcept = default; + + PSRAMAllocator(PSRAMAllocator&&) noexcept = default; + PSRAMAllocator& operator=(PSRAMAllocator&&) noexcept = default; T* allocate(std::size_t n) { - const size_t size = n * sizeof(T); - return static_cast(heap_caps_malloc_prefer(size, MALLOC_CAP_DEFAULT | MALLOC_CAP_SPIRAM, MALLOC_CAP_DEFAULT)); + const size_t size = n * sizeof(T); + return static_cast(heap_caps_malloc_prefer(size, MALLOC_CAP_DEFAULT | MALLOC_CAP_SPIRAM, MALLOC_CAP_DEFAULT)); } void deallocate(T* ptr, std::size_t n) noexcept { - std::free(ptr); + std::free(ptr); } + + template + void construct(U* p, Args&&... args) { + new (p) U(std::forward(args)...); + } + + template + void destroy(U* p) { + p->~U(); + } + + template + struct rebind { + using other = PSRAMAllocator; + }; }; template @@ -42,4 +63,4 @@ bool operator!=(const PSRAMAllocator& lhs, const PSRAMAllocator& rhs) { return !(lhs == rhs); } -#endif \ No newline at end of file +#endif diff --git a/src/random-sampler.cpp b/src/random-sampler.cpp index da1e154..bb30a14 100644 --- a/src/random-sampler.cpp +++ b/src/random-sampler.cpp @@ -15,25 +15,25 @@ using namespace std; namespace ur { RandomSampler::RandomSampler(std::vector probs) { - for(auto p: probs) { assert(p >= 0); } + for(const auto& p : probs) { + assert(p >= 0); + } // Normalize given probabilities - auto sum = accumulate(probs.begin(), probs.end(), 0.0); + double sum = std::accumulate(probs.begin(), probs.end(), 0.0); assert(sum > 0); - auto n = probs.size(); - - vector P; - P.reserve(n); - transform(probs.begin(), probs.end(), back_inserter(P), [&](double d) { return d * double(n) / sum; }); + size_t n = probs.size(); + std::vector P(n); + std::transform(probs.begin(), probs.end(), P.begin(), [&](double d) { return d * double(n) / sum; }); - vector S; + std::vector S; + std::vector L; S.reserve(n); - vector L; L.reserve(n); // Set separate index lists for small and large probabilities: - for(int i = n - 1; i >= 0; i--) { + for(size_t i = n; i-- > 0;) { // at variance from Schwarz, we reverse the index order if(P[i] < 1) { S.push_back(i); @@ -43,8 +43,8 @@ RandomSampler::RandomSampler(std::vector probs) { } // Work through index lists - vector _probs(n, 0); - vector _aliases(n, 0); + std::vector _probs(n, 0); + std::vector _aliases(n, 0); while(!S.empty() && !L.empty()) { auto a = S.back(); S.pop_back(); // Schwarz's l auto g = L.back(); L.pop_back(); // Schwarz's g @@ -69,8 +69,8 @@ RandomSampler::RandomSampler(std::vector probs) { S.pop_back(); } - this->probs_ = _probs; - this->aliases_ = _aliases; + this->probs_ = std::move(_probs); + this->aliases_ = std::move(_aliases); } int RandomSampler::next(std::function rng) { diff --git a/src/ur-decoder.cpp b/src/ur-decoder.cpp index edbc7b8..060fa00 100644 --- a/src/ur-decoder.cpp +++ b/src/ur-decoder.cpp @@ -9,11 +9,13 @@ #include "bytewords.hpp" #include "utils.hpp" +using namespace std; + extern "C" { void urcreate_decoder(void** const decoder) { assert(decoder && !*decoder); - *decoder = new(std::nothrow) ur::URDecoder(); + *decoder = new(nothrow) ur::URDecoder(); assert(*decoder); } @@ -36,10 +38,10 @@ void urfree_placement_decoder(void* const decoder) { urdecoder->~URDecoder(); } -bool urreceive_part_decoder(void* const decoder, const char* string) { +bool urreceive_part_decoder(void* const decoder, const char* s) { assert(decoder); ur::URDecoder* urdecoder = (ur::URDecoder*) decoder; - const std::string part(string); + const string part(s); return urdecoder->receive_part(part); } @@ -97,35 +99,34 @@ void urresult_ur_decoder(void* const decoder, uint8_t** result, size_t* result_l } -using namespace std; - namespace ur { UR URDecoder::decode(const string& s) { auto [type, components] = parse(s); - auto body = !components.empty() ? components.front() : std::string(); - return decode(type, body); + auto body = !components.empty() ? components.front() : string(); + return decode(type, move(body)); } URDecoder::URDecoder() { } -UR URDecoder::decode(const std::string& type, const std::string& body) { +UR URDecoder::decode(const string& type, const string& body) { auto cbor = Bytewords::decode(Bytewords::style::minimal, body); return UR(type, cbor); } pair URDecoder::parse(const string& s) { // Don't consider case - auto lowered = to_lowercase(s); + string lowered; + lowered.resize(s.size()); + transform(s.begin(), s.end(), lowered.begin(), [](unsigned char c){ return tolower(c); }); // Validate URI scheme - if(!has_prefix(lowered, "ur:")) { - return pair(string(), StringVector()); + if(!has_prefix(lowered, "ur:") || 3 >= lowered.length()) { + return {}; } - auto path = drop_first(lowered, 3); // Split the remainder into path components - auto components = split(path, '/'); + auto components = split(lowered.substr(3), '/'); // Make sure there are at least two path components if(components.size() < 2) { @@ -134,7 +135,7 @@ pair URDecoder::parse(const string& s) { // Validate the type auto type = components.front(); if(!is_ur_type(type)) { - return pair(string(), StringVector()); + return {}; } auto comps = StringVector(components.begin() + 1, components.end()); @@ -154,7 +155,7 @@ pair URDecoder::parse_sequence_component(const string& s) { return pair(seq_num, seq_len); } -bool URDecoder::validate_part(const std::string& type) { +bool URDecoder::validate_part(const string& type) { if(!expected_type_.has_value()) { if(!is_ur_type(type)) return false; expected_type_ = type; @@ -164,7 +165,7 @@ bool URDecoder::validate_part(const std::string& type) { } } -bool URDecoder::receive_part(const std::string& s) { +bool URDecoder::receive_part(const string& s) { // Don't process the part if we're already done if(result_.has_value()) return false; diff --git a/src/utils.cpp b/src/utils.cpp index 1c734a4..4d68130 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -12,29 +12,10 @@ #include #include - using namespace std; namespace ur { -ByteVector string_to_bytes(const string& s) { - return {s.begin(), s.end()}; -} - -string data_to_hex(const ByteVector& in) { - const string hex = "0123456789abcdef"; - string result; - for(auto c: in) { - result.append(1, hex[(c >> 4) & 0xF]); - result.append(1, hex[c & 0xF]); - } - return result; -} - -string data_to_hex(uint32_t n) { - return data_to_hex(int_to_bytes(n)); -} - ByteVector int_to_bytes(uint32_t n) { ByteVector b; b.reserve(4); @@ -45,18 +26,8 @@ ByteVector int_to_bytes(uint32_t n) { return b; } -uint32_t bytes_to_int(const ByteVector& in) { - assert(in.size() >= 4); - uint32_t result = 0; - result |= in[0] << 24; - result |= in[1] << 16; - result |= in[2] << 8; - result |= in[3]; - return result; -} - string join(const StringVector &strings, const string &separator) { - std::string result; + string result; size_t total_size = 0; for (const auto& s : strings) { total_size += s.size(); @@ -78,80 +49,51 @@ string join(const StringVector &strings, const string &separator) { } StringVector split(const string& s, char separator) { - StringVector result; - string buf; - - for(auto c: s) { - if(c != separator) { - buf += c; - } else if(buf.length() > 0) { - result.push_back(buf); - buf = ""; + StringVector result; + + size_t start = 0; + size_t end = 0; + + while ((end = s.find(separator, start)) != string::npos) { + if (end != start) { + result.emplace_back(s.substr(start, end - start)); } - } + start = end + 1; + } - if(!buf.empty()) { - result.push_back(buf); + if (start < s.size()) { + result.emplace_back(s.substr(start)); } - return result; + return result; } StringVector partition(const string& s, size_t size) { StringVector result; - auto remaining = s; - while(remaining.length() > 0) { - result.push_back(take_first(remaining, size)); - remaining = drop_first(remaining, size); - } - return result; -} + result.reserve((s.length() + size - 1) / size); -string take_first(const string &s, size_t count) { - auto first = s.begin(); - auto c = min(s.size(), count); - auto last = first + c; - return {first, last}; -} + auto start = s.begin(); + auto end = s.end(); -string drop_first(const string& s, size_t count) { - if(count >= s.length()) { return ""; } - return {s.begin() + count, s.end()}; -} - -void xor_into(ByteVector& target, const ByteVector& source) { - auto count = target.size(); - assert(count == source.size()); - for(int i = 0; i < count; i++) { - target[i] ^= source[i]; + while (start < end) { + auto next = (start + size < end) ? start + size : end; + result.emplace_back(start, next); + start = next; } -} -ByteVector xor_with(const ByteVector& a, const ByteVector& b) { - auto target = a; - xor_into(target, b); - return target; + return result; } bool is_ur_type(char c) { - if('a' <= c && c <= 'z') return true; - if('0' <= c && c <= '9') return true; - if(c == '-') return true; - return false; + return ('a' <= c && c <= 'z') || ('0' <= c && c <= '9') || (c == '-'); } bool is_ur_type(const string& s) { return none_of(s.begin(), s.end(), [](auto c) { return !is_ur_type(c); }); } -string to_lowercase(const string& s) { - string result; - transform(s.begin(), s.end(), back_inserter(result), [](char c){ return tolower(c); }); - return result; -} - bool has_prefix(const string& s, const string& prefix) { - return s.rfind(prefix, 0) == 0; + return s.compare(0, prefix.size(), prefix) == 0; } } diff --git a/src/utils.hpp b/src/utils.hpp index cfe5190..ab4b852 100644 --- a/src/utils.hpp +++ b/src/utils.hpp @@ -23,22 +23,13 @@ using ByteVector = std::vector>; using ByteVectorVector = std::vector>; using StringVector = std::vector>; -ByteVector string_to_bytes(const std::string& s); - -std::string data_to_hex(const ByteVector& in); -std::string data_to_hex(uint32_t n); - ByteVector int_to_bytes(uint32_t n); -uint32_t bytes_to_int(const ByteVector& in); std::string join(const StringVector &strings, const std::string &separator); StringVector split(const std::string& s, char separator); StringVector partition(const std::string& string, size_t size); -std::string take_first(const std::string &s, size_t count); -std::string drop_first(const std::string &s, size_t count); - template void append(std::vector& target, const std::vector& source) { target.insert(target.end(), source.begin(), source.end()); @@ -51,36 +42,39 @@ void append(std::vector& target, const std::array& source) { template std::vector join(const std::vector, A2>& parts) { + size_t total_size = 0; + for (const auto& part : parts) { + total_size += part.size(); + } + std::vector result; - for(auto part: parts) { append(result, part); } + result.reserve(total_size); + + for (const auto& part : parts) { + append(result, part); + } + return result; } template std::pair, std::vector> split(const std::vector& buf, size_t count) { - auto first = buf.begin(); - auto c = std::min(buf.size(), count); - auto last = first + c; - auto a = std::vector(first, last); - auto b = std::vector(last, buf.end()); - return std::make_pair(a, b); + const auto split_point = buf.begin() + std::min(buf.size(), count); + return { + std::vector(buf.begin(), split_point), + std::vector(split_point, buf.end()) + }; } template std::vector take_first(const std::vector &buf, size_t count) { - auto first = buf.begin(); - auto c = std::min(buf.size(), count); - auto last = first + c; - return std::vector(first, last); + const auto first = buf.begin(); + return std::vector(first, first + std::min(buf.size(), count)); } -void xor_into(ByteVector& target, const ByteVector& source); -ByteVector xor_with(const ByteVector& a, const ByteVector& b); - bool is_ur_type(char c); bool is_ur_type(const std::string& s); -std::string to_lowercase(const std::string& s); bool has_prefix(const std::string& s, const std::string& prefix); } diff --git a/src/xoshiro256.cpp b/src/xoshiro256.cpp index eb3b1fd..ad00f25 100644 --- a/src/xoshiro256.cpp +++ b/src/xoshiro256.cpp @@ -32,7 +32,7 @@ See . */ namespace ur { static inline uint64_t rotl(const uint64_t x, int k) { - return (x << k) | (x >> (64 - k)); + return (x << k) | (x >> (64 - k)); } Xoshiro256::Xoshiro256(const std::array& a) { @@ -79,12 +79,12 @@ Xoshiro256::Xoshiro256(const std::string& s) { } Xoshiro256::Xoshiro256(uint32_t crc32) { - auto bytes = int_to_bytes(crc32); + const auto bytes = int_to_bytes(crc32); hash_then_set_s(bytes); } double Xoshiro256::next_double() { - auto m = ((double)std::numeric_limits::max()) + 1; + const auto m = ((double)std::numeric_limits::max()) + 1; return next() / m; } diff --git a/test/test.cpp b/test/test.cpp deleted file mode 100644 index b68b0d2..0000000 --- a/test/test.cpp +++ /dev/null @@ -1,446 +0,0 @@ -// -// test.cpp -// -// Copyright © 2020 by Blockchain Commons, LLC -// Licensed under the "BSD-2-Clause Plus Patent License" -// - -#include -#include -#include -#include -#include - -#include "../src/bc-ur.hpp" -#include "test-utils.hpp" - -using namespace ur; -using namespace std; - -static bool _test_crc32(const string& input, const string& expected_hex) { - auto checksum = crc32_bytes(string_to_bytes(input)); - auto hex = data_to_hex(checksum); - return hex == expected_hex; -} - -static void test_crc32() { - assert(_test_crc32("Hello, world!", "ebe6c6e6")); - assert(_test_crc32("Wolf", "598c84dc")); -} - -static void test_bytewords_1() { - ByteVector input = {0, 1, 2, 128, 255}; - assert(Bytewords::encode(Bytewords::style::standard, input) == "able acid also lava zoom jade need echo taxi"); - assert(Bytewords::encode(Bytewords::style::uri, input) == "able-acid-also-lava-zoom-jade-need-echo-taxi"); - assert(Bytewords::encode(Bytewords::style::minimal, input) == "aeadaolazmjendeoti"); - - assert(Bytewords::decode(Bytewords::style::standard, "able acid also lava zoom jade need echo taxi") == input); - assert(Bytewords::decode(Bytewords::style::uri, "able-acid-also-lava-zoom-jade-need-echo-taxi") == input); - assert(Bytewords::decode(Bytewords::style::minimal, "aeadaolazmjendeoti") == input); - - // bad checksum - assert_throws(Bytewords::decode(Bytewords::style::standard, "able acid also lava zero jade need echo wolf")); - assert_throws(Bytewords::decode(Bytewords::style::uri, "able-acid-also-lava-zero-jade-need-echo-wolf")); - assert_throws(Bytewords::decode(Bytewords::style::minimal, "aeadaolazojendeowf")); - - // too short - assert_throws(Bytewords::decode(Bytewords::style::standard, "wolf")); - assert_throws(Bytewords::decode(Bytewords::style::standard, "")); -} - -static void test_bytewords_2() { - ByteVector input = { - 245, 215, 20, 198, 241, 235, 69, 59, 209, 205, - 165, 18, 150, 158, 116, 135, 229, 212, 19, 159, - 17, 37, 239, 240, 253, 11, 109, 191, 37, 242, - 38, 120, 223, 41, 156, 189, 242, 254, 147, 204, - 66, 163, 216, 175, 191, 72, 169, 54, 32, 60, - 144, 230, 210, 137, 184, 197, 33, 113, 88, 14, - 157, 31, 177, 46, 1, 115, 205, 69, 225, 150, - 65, 235, 58, 144, 65, 240, 133, 69, 113, 247, - 63, 53, 242, 165, 160, 144, 26, 13, 79, 237, - 133, 71, 82, 69, 254, 165, 138, 41, 85, 24 - }; - - string encoded = - "yank toys bulb skew when warm free fair tent swan " - "open brag mint noon jury list view tiny brew note " - "body data webs what zinc bald join runs data whiz " - "days keys user diet news ruby whiz zone menu surf " - "flew omit trip pose runs fund part even crux fern " - "math visa tied loud redo silk curl jugs hard beta " - "next cost puma drum acid junk swan free very mint " - "flap warm fact math flap what limp free jugs yell " - "fish epic whiz open numb math city belt glow wave " - "limp fuel grim free zone open love diet gyro cats " - "fizz holy city puff"; - - string encoded_minimal = - "yktsbbswwnwmfefrttsnonbgmtnnjyltvwtybwne" - "bydawswtzcbdjnrsdawzdsksurdtnsrywzzemusf" - "fwottppersfdptencxfnmhvatdldroskcljshdba" - "ntctpadmadjksnfevymtfpwmftmhfpwtlpfejsyl" - "fhecwzonnbmhcybtgwwelpflgmfezeonledtgocs" - "fzhycypf"; - - assert(Bytewords::encode(Bytewords::style::standard, input) == encoded); - assert(Bytewords::encode(Bytewords::style::minimal, input) == encoded_minimal); - assert(Bytewords::decode(Bytewords::style::standard, encoded) == input); - assert(Bytewords::decode(Bytewords::style::minimal, encoded_minimal) == input); -} - -static void test_rng_1() { - auto rng = Xoshiro256("Wolf"); - vector numbers; - for(int i = 0; i < 100; i++) { - numbers.push_back(rng.next() % 100); - } - vector expected_numbers = {42, 81, 85, 8, 82, 84, 76, 73, 70, 88, 2, 74, 40, 48, 77, 54, 88, 7, 5, 88, 37, 25, 82, 13, 69, 59, 30, 39, 11, 82, 19, 99, 45, 87, 30, 15, 32, 22, 89, 44, 92, 77, 29, 78, 4, 92, 44, 68, 92, 69, 1, 42, 89, 50, 37, 84, 63, 34, 32, 3, 17, 62, 40, 98, 82, 89, 24, 43, 85, 39, 15, 3, 99, 29, 20, 42, 27, 10, 85, 66, 50, 35, 69, 70, 70, 74, 30, 13, 72, 54, 11, 5, 70, 55, 91, 52, 10, 43, 43, 52}; - assert(numbers == expected_numbers); -} - -static void test_rng_2() { - auto checksum = bytes_to_int(crc32_bytes(string_to_bytes("Wolf"))); - auto rng = Xoshiro256(checksum); - vector numbers; - for(int i = 0; i < 100; i++) { - numbers.push_back(rng.next() % 100); - } - vector expected_numbers = {88, 44, 94, 74, 0, 99, 7, 77, 68, 35, 47, 78, 19, 21, 50, 15, 42, 36, 91, 11, 85, 39, 64, 22, 57, 11, 25, 12, 1, 91, 17, 75, 29, 47, 88, 11, 68, 58, 27, 65, 21, 54, 47, 54, 73, 83, 23, 58, 75, 27, 26, 15, 60, 36, 30, 21, 55, 57, 77, 76, 75, 47, 53, 76, 9, 91, 14, 69, 3, 95, 11, 73, 20, 99, 68, 61, 3, 98, 36, 98, 56, 65, 14, 80, 74, 57, 63, 68, 51, 56, 24, 39, 53, 80, 57, 51, 81, 3, 1, 30}; - assert(numbers == expected_numbers); -} - -static void test_rng_3() { - auto rng = Xoshiro256("Wolf"); - vector numbers; - for(int i = 0; i < 100; i++) { - numbers.push_back(rng.next_int(1, 10)); - } - vector expected_numbers = {6, 5, 8, 4, 10, 5, 7, 10, 4, 9, 10, 9, 7, 7, 1, 1, 2, 9, 9, 2, 6, 4, 5, 7, 8, 5, 4, 2, 3, 8, 7, 4, 5, 1, 10, 9, 3, 10, 2, 6, 8, 5, 7, 9, 3, 1, 5, 2, 7, 1, 4, 4, 4, 4, 9, 4, 5, 5, 6, 9, 5, 1, 2, 8, 3, 3, 2, 8, 4, 3, 2, 1, 10, 8, 9, 3, 10, 8, 5, 5, 6, 7, 10, 5, 8, 9, 4, 6, 4, 2, 10, 2, 1, 7, 9, 6, 7, 4, 2, 5}; - assert(numbers == expected_numbers); -} - -static void test_find_fragment_length() { - assert(FountainEncoder::find_nominal_fragment_length(12345, 1005, 1955) == 1764); - assert(FountainEncoder::find_nominal_fragment_length(12345, 1005, 30000) == 12345); -} - -static void test_random_sampler() { - vector probs = { 1, 2, 4, 8 }; - auto sampler = RandomSampler(probs); - auto rng = Xoshiro256("Wolf"); - vector samples; - auto f = [&](){ return rng.next_double(); }; - for(int i = 0; i < 500; i++) { - samples.push_back(sampler.next(f)); - } - vector expected_samples = {3, 3, 3, 3, 3, 3, 3, 0, 2, 3, 3, 3, 3, 1, 2, 2, 1, 3, 3, 2, 3, 3, 1, 1, 2, 1, 1, 3, 1, 3, 1, 2, 0, 2, 1, 0, 3, 3, 3, 1, 3, 3, 3, 3, 1, 3, 2, 3, 2, 2, 3, 3, 3, 3, 2, 3, 3, 0, 3, 3, 3, 3, 1, 2, 3, 3, 2, 2, 2, 1, 2, 2, 1, 2, 3, 1, 3, 0, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 2, 3, 1, 3, 3, 2, 0, 2, 2, 3, 1, 1, 2, 3, 2, 3, 3, 3, 3, 2, 3, 3, 3, 3, 3, 2, 3, 1, 2, 1, 1, 3, 1, 3, 2, 2, 3, 3, 3, 1, 3, 3, 3, 3, 3, 3, 3, 3, 2, 3, 2, 3, 3, 1, 2, 3, 3, 1, 3, 2, 3, 3, 3, 2, 3, 1, 3, 0, 3, 2, 1, 1, 3, 1, 3, 2, 3, 3, 3, 3, 2, 0, 3, 3, 1, 3, 0, 2, 1, 3, 3, 1, 1, 3, 1, 2, 3, 3, 3, 0, 2, 3, 2, 0, 1, 3, 3, 3, 2, 2, 2, 3, 3, 3, 3, 3, 2, 3, 3, 3, 3, 2, 3, 3, 2, 0, 2, 3, 3, 3, 3, 2, 1, 1, 1, 2, 1, 3, 3, 3, 2, 2, 3, 3, 1, 2, 3, 0, 3, 2, 3, 3, 3, 3, 0, 2, 2, 3, 2, 2, 3, 3, 3, 3, 1, 3, 2, 3, 3, 3, 3, 3, 2, 2, 3, 1, 3, 0, 2, 1, 3, 3, 3, 3, 3, 3, 3, 3, 1, 3, 3, 3, 3, 2, 2, 2, 3, 1, 1, 3, 2, 2, 0, 3, 2, 1, 2, 1, 0, 3, 3, 3, 2, 2, 3, 2, 1, 2, 0, 0, 3, 3, 2, 3, 3, 2, 3, 3, 3, 3, 3, 2, 2, 2, 3, 3, 3, 3, 3, 1, 1, 3, 2, 2, 3, 1, 1, 0, 1, 3, 2, 3, 3, 2, 3, 3, 2, 3, 3, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 1, 2, 3, 3, 2, 2, 2, 2, 3, 3, 2, 0, 2, 1, 3, 3, 3, 3, 0, 3, 3, 3, 3, 2, 2, 3, 1, 3, 3, 3, 2, 3, 3, 3, 2, 3, 3, 3, 3, 2, 3, 2, 1, 3, 3, 3, 3, 2, 2, 0, 1, 2, 3, 2, 0, 3, 3, 3, 3, 3, 3, 1, 3, 3, 2, 3, 2, 2, 3, 3, 3, 3, 3, 2, 2, 3, 3, 2, 2, 2, 1, 3, 3, 3, 3, 1, 2, 3, 2, 3, 3, 2, 3, 2, 3, 3, 3, 2, 3, 1, 2, 3, 2, 1, 1, 3, 3, 2, 3, 3, 2, 3, 3, 0, 0, 1, 3, 3, 2, 3, 3, 3, 3, 1, 3, 3, 0, 3, 2, 3, 3, 1, 3, 3, 3, 3, 3, 3, 3, 0, 3, 3, 2}; - assert(samples == expected_samples); -} - -static void test_shuffle() { - auto rng = Xoshiro256("Wolf"); - vector values = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; - vector> result; - for(int i = 0; i < 10; i++) { - result.push_back(shuffled(values, rng)); - } - vector> expectedResult = { - {6, 4, 9, 3, 10, 5, 7, 8, 1, 2}, - {10, 8, 6, 5, 1, 2, 3, 9, 7, 4}, - {6, 4, 5, 8, 9, 3, 2, 1, 7, 10}, - {7, 3, 5, 1, 10, 9, 4, 8, 2, 6}, - {8, 5, 7, 10, 2, 1, 4, 3, 9, 6}, - {4, 3, 5, 6, 10, 2, 7, 8, 9, 1}, - {5, 1, 3, 9, 4, 6, 2, 10, 7, 8}, - {2, 1, 10, 8, 9, 4, 7, 6, 3, 5}, - {6, 7, 10, 4, 8, 9, 2, 3, 1, 5}, - {10, 2, 1, 7, 9, 5, 6, 3, 4, 8} - }; - assert(result == expectedResult); -} - -static void test_partition_and_join() { - auto message = make_message(1024); - auto fragment_len = FountainEncoder::find_nominal_fragment_length(message.size(), 10, 100); - auto fragments = FountainEncoder::partition_message(message, fragment_len); - vector fragments_hex; - transform(fragments.begin(), fragments.end(), back_inserter(fragments_hex), [](const ByteVector& b) -> string { return data_to_hex(b); }); - vector expected_fragments = { - "916ec65cf77cadf55cd7f9cda1a1030026ddd42e905b77adc36e4f2d3ccba44f7f04f2de44f42d84c374a0e149136f25b01852545961d55f7f7a8cde6d0e2ec43f3b2dcb644a2209e8c9e34af5c4747984a5e873c9cf5f965e25ee29039f", - "df8ca74f1c769fc07eb7ebaec46e0695aea6cbd60b3ec4bbff1b9ffe8a9e7240129377b9d3711ed38d412fbb4442256f1e6f595e0fc57fed451fb0a0101fb76b1fb1e1b88cfdfdaa946294a47de8fff173f021c0e6f65b05c0a494e50791", - "270a0050a73ae69b6725505a2ec8a5791457c9876dd34aadd192a53aa0dc66b556c0c215c7ceb8248b717c22951e65305b56a3706e3e86eb01c803bbf915d80edcd64d4d41977fa6f78dc07eecd072aae5bc8a852397e06034dba6a0b570", - "797c3a89b16673c94838d884923b8186ee2db5c98407cab15e13678d072b43e406ad49477c2e45e85e52ca82a94f6df7bbbe7afbed3a3a830029f29090f25217e48d1f42993a640a67916aa7480177354cc7440215ae41e4d02eae9a1912", - "33a6d4922a792c1b7244aa879fefdb4628dc8b0923568869a983b8c661ffab9b2ed2c149e38d41fba090b94155adbed32f8b18142ff0d7de4eeef2b04adf26f2456b46775c6c20b37602df7da179e2332feba8329bbb8d727a138b4ba7a5", - "03215eda2ef1e953d89383a382c11d3f2cad37a4ee59a91236a3e56dcf89f6ac81dd4159989c317bd649d9cbc617f73fe10033bd288c60977481a09b343d3f676070e67da757b86de27bfca74392bac2996f7822a7d8f71a489ec6180390", - "089ea80a8fcd6526413ec6c9a339115f111d78ef21d456660aa85f790910ffa2dc58d6a5b93705caef1091474938bd312427021ad1eeafbd19e0d916ddb111fabd8dcab5ad6a6ec3a9c6973809580cb2c164e26686b5b98cfb017a337968", - "c7daaa14ae5152a067277b1b3902677d979f8e39cc2aafb3bc06fcf69160a853e6869dcc09a11b5009f91e6b89e5b927ab1527a735660faa6012b420dd926d940d742be6a64fb01cdc0cff9faa323f02ba41436871a0eab851e7f5782d10", - "fbefde2a7e9ae9dc1e5c2c48f74f6c824ce9ef3c89f68800d44587bedc4ab417cfb3e7447d90e1e417e6e05d30e87239d3a5d1d45993d4461e60a0192831640aa32dedde185a371ded2ae15f8a93dba8809482ce49225daadfbb0fec629e", - "23880789bdf9ed73be57fa84d555134630e8d0f7df48349f29869a477c13ccca9cd555ac42ad7f568416c3d61959d0ed568b2b81c7771e9088ad7fd55fd4386bafbf5a528c30f107139249357368ffa980de2c76ddd9ce4191376be0e6b5", - "170010067e2e75ebe2d2904aeb1f89d5dc98cd4a6f2faaa8be6d03354c990fd895a97feb54668473e9d942bb99e196d897e8f1b01625cf48a7b78d249bb4985c065aa8cd1402ed2ba1b6f908f63dcd84b66425df00000000000000000000" - }; - assert(fragments_hex == expected_fragments); - auto rejoined_message = FountainDecoder::join_fragments(fragments, message.size()); - assert(message == rejoined_message); -} - -static void test_choose_degree() { - auto message = make_message(1024); - auto fragment_len = FountainEncoder::find_nominal_fragment_length(message.size(), 10, 100); - auto fragments = FountainEncoder::partition_message(message, fragment_len); - vector degrees; - for(int nonce = 1; nonce <= 200; nonce++) { - auto part_rng = Xoshiro256("Wolf-" + to_string(nonce)); - degrees.push_back(choose_degree(fragments.size(), part_rng)); - } - vector expected_degrees = {11, 3, 6, 5, 2, 1, 2, 11, 1, 3, 9, 10, 10, 4, 2, 1, 1, 2, 1, 1, 5, 2, 4, 10, 3, 2, 1, 1, 3, 11, 2, 6, 2, 9, 9, 2, 6, 7, 2, 5, 2, 4, 3, 1, 6, 11, 2, 11, 3, 1, 6, 3, 1, 4, 5, 3, 6, 1, 1, 3, 1, 2, 2, 1, 4, 5, 1, 1, 9, 1, 1, 6, 4, 1, 5, 1, 2, 2, 3, 1, 1, 5, 2, 6, 1, 7, 11, 1, 8, 1, 5, 1, 1, 2, 2, 6, 4, 10, 1, 2, 5, 5, 5, 1, 1, 4, 1, 1, 1, 3, 5, 5, 5, 1, 4, 3, 3, 5, 1, 11, 3, 2, 8, 1, 2, 1, 1, 4, 5, 2, 1, 1, 1, 5, 6, 11, 10, 7, 4, 7, 1, 5, 3, 1, 1, 9, 1, 2, 5, 5, 2, 2, 3, 10, 1, 3, 2, 3, 3, 1, 1, 2, 1, 3, 2, 2, 1, 3, 8, 4, 1, 11, 6, 3, 1, 1, 1, 1, 1, 3, 1, 2, 1, 10, 1, 1, 8, 2, 7, 1, 2, 1, 9, 2, 10, 2, 1, 3, 4, 10}; - assert(degrees == expected_degrees); -} - -static void test_choose_fragments() { - auto message = make_message(1024); - auto checksum = crc32_int(message); - auto fragment_len = FountainEncoder::find_nominal_fragment_length(message.size(), 10, 100); - auto fragments = FountainEncoder::partition_message(message, fragment_len); - vector> fragment_indexes; - for(uint32_t seq_num = 1; seq_num <= 30; seq_num++) { - auto indexes_set = choose_fragments(seq_num, fragments.size(), checksum); - auto indexes = vector(indexes_set.begin(), indexes_set.end()); - sort(indexes.begin(), indexes.end()); - fragment_indexes.push_back(indexes); - } - vector> expected_fragment_indexes = { - {0}, - {1}, - {2}, - {3}, - {4}, - {5}, - {6}, - {7}, - {8}, - {9}, - {10}, - {9}, - {2, 5, 6, 8, 9, 10}, - {8}, - {1, 5}, - {1}, - {0, 2, 4, 5, 8, 10}, - {5}, - {2}, - {2}, - {0, 1, 3, 4, 5, 7, 9, 10}, - {0, 1, 2, 3, 5, 6, 8, 9, 10}, - {0, 2, 4, 5, 7, 8, 9, 10}, - {3, 5}, - {4}, - {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, - {0, 1, 3, 4, 5, 6, 7, 9, 10}, - {6}, - {5, 6}, - {7} - }; - assert(fragment_indexes == expected_fragment_indexes); -} - -static void test_xor() { - auto rng = Xoshiro256("Wolf"); - auto data1 = rng.next_data(10); - assert(data_to_hex(data1) == "916ec65cf77cadf55cd7"); - auto data2 = rng.next_data(10); - assert(data_to_hex(data2) == "f9cda1a1030026ddd42e"); - auto data3 = data1; - xor_into(data3, data2); - assert(data_to_hex(data3) == "68a367fdf47c8b2888f9"); - xor_into(data3, data1); - assert(data3 == data2); -} - -static void test_fountain_encoder() { - auto message = make_message(256); - auto encoder = FountainEncoder(message, 30); - StringVector parts; - for(int i = 0; i < 20; i++) { - parts.push_back(encoder.next_part().description()); - } - StringVector expected_parts = { - "seqNum:1, seqLen:9, messageLen:256, checksum:23570951, data:916ec65cf77cadf55cd7f9cda1a1030026ddd42e905b77adc36e4f2d3c", - "seqNum:2, seqLen:9, messageLen:256, checksum:23570951, data:cba44f7f04f2de44f42d84c374a0e149136f25b01852545961d55f7f7a", - "seqNum:3, seqLen:9, messageLen:256, checksum:23570951, data:8cde6d0e2ec43f3b2dcb644a2209e8c9e34af5c4747984a5e873c9cf5f", - "seqNum:4, seqLen:9, messageLen:256, checksum:23570951, data:965e25ee29039fdf8ca74f1c769fc07eb7ebaec46e0695aea6cbd60b3e", - "seqNum:5, seqLen:9, messageLen:256, checksum:23570951, data:c4bbff1b9ffe8a9e7240129377b9d3711ed38d412fbb4442256f1e6f59", - "seqNum:6, seqLen:9, messageLen:256, checksum:23570951, data:5e0fc57fed451fb0a0101fb76b1fb1e1b88cfdfdaa946294a47de8fff1", - "seqNum:7, seqLen:9, messageLen:256, checksum:23570951, data:73f021c0e6f65b05c0a494e50791270a0050a73ae69b6725505a2ec8a5", - "seqNum:8, seqLen:9, messageLen:256, checksum:23570951, data:791457c9876dd34aadd192a53aa0dc66b556c0c215c7ceb8248b717c22", - "seqNum:9, seqLen:9, messageLen:256, checksum:23570951, data:951e65305b56a3706e3e86eb01c803bbf915d80edcd64d4d0000000000", - "seqNum:10, seqLen:9, messageLen:256, checksum:23570951, data:330f0f33a05eead4f331df229871bee733b50de71afd2e5a79f196de09", - "seqNum:11, seqLen:9, messageLen:256, checksum:23570951, data:3b205ce5e52d8c24a52cffa34c564fa1af3fdffcd349dc4258ee4ee828", - "seqNum:12, seqLen:9, messageLen:256, checksum:23570951, data:dd7bf725ea6c16d531b5f03254783803048ca08b87148daacd1cd7a006", - "seqNum:13, seqLen:9, messageLen:256, checksum:23570951, data:760be7ad1c6187902bbc04f539b9ee5eb8ea6833222edea36031306c01", - "seqNum:14, seqLen:9, messageLen:256, checksum:23570951, data:5bf4031217d2c3254b088fa7553778b5003632f46e21db129416f65b55", - "seqNum:15, seqLen:9, messageLen:256, checksum:23570951, data:73f021c0e6f65b05c0a494e50791270a0050a73ae69b6725505a2ec8a5", - "seqNum:16, seqLen:9, messageLen:256, checksum:23570951, data:b8546ebfe2048541348910267331c643133f828afec9337c318f71b7df", - "seqNum:17, seqLen:9, messageLen:256, checksum:23570951, data:23dedeea74e3a0fb052befabefa13e2f80e4315c9dceed4c8630612e64", - "seqNum:18, seqLen:9, messageLen:256, checksum:23570951, data:d01a8daee769ce34b6b35d3ca0005302724abddae405bdb419c0a6b208", - "seqNum:19, seqLen:9, messageLen:256, checksum:23570951, data:3171c5dc365766eff25ae47c6f10e7de48cfb8474e050e5fe997a6dc24", - "seqNum:20, seqLen:9, messageLen:256, checksum:23570951, data:e055c2433562184fa71b4be94f262e200f01c6f74c284b0dc6fae6673f" - }; - assert(parts == expected_parts); -} - -static void test_fountain_encoder_cbor() { - auto message = make_message(256); - auto encoder = FountainEncoder(message, 30); - StringVector parts; - for(int i = 0; i < 20; i++) { - parts.push_back(data_to_hex(encoder.next_part().cbor())); - } - StringVector expected_parts = { - "8501091901001a0167aa07581d916ec65cf77cadf55cd7f9cda1a1030026ddd42e905b77adc36e4f2d3c", - "8502091901001a0167aa07581dcba44f7f04f2de44f42d84c374a0e149136f25b01852545961d55f7f7a", - "8503091901001a0167aa07581d8cde6d0e2ec43f3b2dcb644a2209e8c9e34af5c4747984a5e873c9cf5f", - "8504091901001a0167aa07581d965e25ee29039fdf8ca74f1c769fc07eb7ebaec46e0695aea6cbd60b3e", - "8505091901001a0167aa07581dc4bbff1b9ffe8a9e7240129377b9d3711ed38d412fbb4442256f1e6f59", - "8506091901001a0167aa07581d5e0fc57fed451fb0a0101fb76b1fb1e1b88cfdfdaa946294a47de8fff1", - "8507091901001a0167aa07581d73f021c0e6f65b05c0a494e50791270a0050a73ae69b6725505a2ec8a5", - "8508091901001a0167aa07581d791457c9876dd34aadd192a53aa0dc66b556c0c215c7ceb8248b717c22", - "8509091901001a0167aa07581d951e65305b56a3706e3e86eb01c803bbf915d80edcd64d4d0000000000", - "850a091901001a0167aa07581d330f0f33a05eead4f331df229871bee733b50de71afd2e5a79f196de09", - "850b091901001a0167aa07581d3b205ce5e52d8c24a52cffa34c564fa1af3fdffcd349dc4258ee4ee828", - "850c091901001a0167aa07581ddd7bf725ea6c16d531b5f03254783803048ca08b87148daacd1cd7a006", - "850d091901001a0167aa07581d760be7ad1c6187902bbc04f539b9ee5eb8ea6833222edea36031306c01", - "850e091901001a0167aa07581d5bf4031217d2c3254b088fa7553778b5003632f46e21db129416f65b55", - "850f091901001a0167aa07581d73f021c0e6f65b05c0a494e50791270a0050a73ae69b6725505a2ec8a5", - "8510091901001a0167aa07581db8546ebfe2048541348910267331c643133f828afec9337c318f71b7df", - "8511091901001a0167aa07581d23dedeea74e3a0fb052befabefa13e2f80e4315c9dceed4c8630612e64", - "8512091901001a0167aa07581dd01a8daee769ce34b6b35d3ca0005302724abddae405bdb419c0a6b208", - "8513091901001a0167aa07581d3171c5dc365766eff25ae47c6f10e7de48cfb8474e050e5fe997a6dc24", - "8514091901001a0167aa07581de055c2433562184fa71b4be94f262e200f01c6f74c284b0dc6fae6673f" - }; - assert(parts == expected_parts); -} - -static void test_fountain_encoder_is_complete() { - auto message = make_message(256); - auto encoder = FountainEncoder(message, 30); - size_t generated_parts_count = 0; - while(!encoder.is_complete()) { - encoder.next_part(); - generated_parts_count += 1; - } - assert(encoder.seq_len() == generated_parts_count); -} - -static void test_fountain_decoder() { - string message_seed = "Wolf"; - size_t message_size = 32767; - size_t max_fragment_len = 1000; - - auto message = make_message(message_size, message_seed); - auto encoder = FountainEncoder(message, max_fragment_len, 100); - auto decoder = FountainDecoder(); - do { - auto part = encoder.next_part(); - decoder.receive_part(part); - } while(!decoder.is_complete()); - - if(decoder.is_success()) { - assert(decoder.result_message() == message); - } else { - cout << decoder.result_error().what() << endl; - assert(false); - } -} - -static void test_fountain_cbor() { - auto part = FountainEncoder::Part(12, 8, 100, 0x12345678, {1, 5, 3, 3 ,5}); - auto cbor = part.cbor(); - auto part2 = FountainEncoder::Part(cbor); - auto cbor2 = part2.cbor(); - assert(cbor == cbor2); -} - -static void test_single_part_ur() { - auto ur = make_message_ur(50); - auto encoded = UREncoder::encode(ur); - string expected = "ur:bytes/hdeymejtswhhylkepmykhhtsytsnoyoyaxaedsuttydmmhhpktpmsrjtgwdpfnsboxgwlbaawzuefywkdplrsrjynbvygabwjldapfcsdwkbrkch"; - assert(encoded == expected); - auto decoded = URDecoder::decode(encoded); - assert(ur == decoded); -} - -static void test_ur_encoder() { - auto ur = make_message_ur(256); - auto encoder = UREncoder(ur, 30); - StringVector parts; - for(int i = 0; i < 20; i++) { - parts.push_back(encoder.next_part()); - } - StringVector expected_parts = { - "ur:bytes/1-9/lpadascfadaxcywenbpljkhdcahkadaemejtswhhylkepmykhhtsytsnoyoyaxaedsuttydmmhhpktpmsrjtdkgslpgh", - "ur:bytes/2-9/lpaoascfadaxcywenbpljkhdcagwdpfnsboxgwlbaawzuefywkdplrsrjynbvygabwjldapfcsgmghhkhstlrdcxaefz", - "ur:bytes/3-9/lpaxascfadaxcywenbpljkhdcahelbknlkuejnbadmssfhfrdpsbiegecpasvssovlgeykssjykklronvsjksopdzmol", - "ur:bytes/4-9/lpaaascfadaxcywenbpljkhdcasotkhemthydawydtaxneurlkosgwcekonertkbrlwmplssjtammdplolsbrdzcrtas", - "ur:bytes/5-9/lpahascfadaxcywenbpljkhdcatbbdfmssrkzmcwnezelennjpfzbgmuktrhtejscktelgfpdlrkfyfwdajldejokbwf", - "ur:bytes/6-9/lpamascfadaxcywenbpljkhdcackjlhkhybssklbwefectpfnbbectrljectpavyrolkzczcpkmwidmwoxkilghdsowp", - "ur:bytes/7-9/lpatascfadaxcywenbpljkhdcavszmwnjkwtclrtvaynhpahrtoxmwvwatmedibkaegdosftvandiodagdhthtrlnnhy", - "ur:bytes/8-9/lpayascfadaxcywenbpljkhdcadmsponkkbbhgsoltjntegepmttmoonftnbuoiyrehfrtsabzsttorodklubbuyaetk", - "ur:bytes/9-9/lpasascfadaxcywenbpljkhdcajskecpmdckihdyhphfotjojtfmlnwmadspaxrkytbztpbauotbgtgtaeaevtgavtny", - "ur:bytes/10-9/lpbkascfadaxcywenbpljkhdcahkadaemejtswhhylkepmykhhtsytsnoyoyaxaedsuttydmmhhpktpmsrjtwdkiplzs", - "ur:bytes/11-9/lpbdascfadaxcywenbpljkhdcahelbknlkuejnbadmssfhfrdpsbiegecpasvssovlgeykssjykklronvsjkvetiiapk", - "ur:bytes/12-9/lpbnascfadaxcywenbpljkhdcarllaluzmdmgstospeyiefmwejlwtpedamktksrvlcygmzemovovllarodtmtbnptrs", - "ur:bytes/13-9/lpbtascfadaxcywenbpljkhdcamtkgtpknghchchyketwsvwgwfdhpgmgtylctotzopdrpayoschcmhplffziachrfgd", - "ur:bytes/14-9/lpbaascfadaxcywenbpljkhdcapazewnvonnvdnsbyleynwtnsjkjndeoldydkbkdslgjkbbkortbelomueekgvstegt", - "ur:bytes/15-9/lpbsascfadaxcywenbpljkhdcaynmhpddpzmversbdqdfyrehnqzlugmjzmnmtwmrouohtstgsbsahpawkditkckynwt", - "ur:bytes/16-9/lpbeascfadaxcywenbpljkhdcawygekobamwtlihsnpalnsghenskkiynthdzotsimtojetprsttmukirlrsbtamjtpd", - "ur:bytes/17-9/lpbyascfadaxcywenbpljkhdcamklgftaxykpewyrtqzhydntpnytyisincxmhtbceaykolduortotiaiaiafhiaoyce", - "ur:bytes/18-9/lpbgascfadaxcywenbpljkhdcahkadaemejtswhhylkepmykhhtsytsnoyoyaxaedsuttydmmhhpktpmsrjtntwkbkwy", - "ur:bytes/19-9/lpbwascfadaxcywenbpljkhdcadekicpaajootjzpsdrbalpeywllbdsnbinaerkurspbncxgslgftvtsrjtksplcpeo", - "ur:bytes/20-9/lpbbascfadaxcywenbpljkhdcayapmrleeleaxpasfrtrdkncffwjyjzgyetdmlewtkpktgllepfrltataztksmhkbot" - }; - assert(parts == expected_parts); -} - -static void test_multipart_ur() { - auto ur = make_message_ur(32767); - size_t max_fragment_len = 1000; - uint32_t first_seq_num = 100; - auto encoder = UREncoder(ur, max_fragment_len, first_seq_num); - auto decoder = URDecoder(); - do { - auto part = encoder.next_part(); - decoder.receive_part(part); - } while(!decoder.is_complete()); - - if(decoder.is_success()) { - assert(decoder.result_ur() == ur); - } else { - cout << decoder.result_error().what() << endl; - assert(false); - } -} - -int main() { - test_crc32(); - test_bytewords_1(); - test_bytewords_2(); - test_rng_1(); - test_rng_2(); - test_rng_3(); - test_find_fragment_length(); - test_random_sampler(); - test_shuffle(); - test_partition_and_join(); - test_choose_degree(); - test_choose_fragments(); - test_xor(); - test_fountain_encoder(); - test_fountain_encoder_cbor(); - test_fountain_encoder_is_complete(); - test_fountain_decoder(); - test_fountain_cbor(); - test_single_part_ur(); - test_ur_encoder(); - test_multipart_ur(); -}