| 647 | }; |
| 648 | |
| 649 | static fec_t encode(const std::string_view &payload, size_t blocksize, size_t fecpercentage, size_t minparityshards, size_t prefixsize) { |
| 650 | auto payload_size = payload.size(); |
| 651 | |
| 652 | auto pad = payload_size % blocksize != 0; |
| 653 | |
| 654 | auto aligned_data_shards = payload_size / blocksize; |
| 655 | auto data_shards = aligned_data_shards + (pad ? 1 : 0); |
| 656 | auto parity_shards = (data_shards * fecpercentage + 99) / 100; |
| 657 | |
| 658 | // increase the FEC percentage for this frame if the parity shard minimum is not met |
| 659 | if (parity_shards < minparityshards && fecpercentage != 0) { |
| 660 | parity_shards = minparityshards; |
| 661 | fecpercentage = (100 * parity_shards) / data_shards; |
| 662 | |
| 663 | BOOST_LOG(verbose) << "Increasing FEC percentage to "sv << fecpercentage << " to meet parity shard minimum"sv << std::endl; |
| 664 | } |
| 665 | |
| 666 | auto nr_shards = data_shards + parity_shards; |
| 667 | |
| 668 | // If we need to store a zero-padded data shard, allocate that first to |
| 669 | // to keep the shards in order and reduce buffer fragmentation |
| 670 | auto parity_shard_offset = pad ? 1 : 0; |
| 671 | util::buffer_t<char> shards {(parity_shard_offset + parity_shards) * blocksize}; |
| 672 | util::buffer_t<uint8_t *> shards_p {nr_shards}; |
| 673 | std::vector<platf::buffer_descriptor_t> payload_buffers; |
| 674 | payload_buffers.reserve(2); |
| 675 | |
| 676 | // Point into the payload buffer for all except the final padded data shard |
| 677 | auto next = std::begin(payload); |
| 678 | for (auto x = 0; x < aligned_data_shards; ++x) { |
| 679 | shards_p[x] = (uint8_t *) next; |
| 680 | next += blocksize; |
| 681 | } |
| 682 | payload_buffers.emplace_back(std::begin(payload), aligned_data_shards * blocksize); |
| 683 | |
| 684 | // If the last data shard needs to be zero-padded, we must use the shards buffer |
| 685 | if (pad) { |
| 686 | shards_p[aligned_data_shards] = (uint8_t *) &shards[0]; |
| 687 | |
| 688 | // GCC doesn't figure out that std::copy_n() can be replaced with memcpy() here |
| 689 | // and ends up compiling a horribly slow element-by-element copy loop, so we |
| 690 | // help it by using memcpy()/memset() directly. |
| 691 | auto copy_len = std::min<size_t>(blocksize, std::end(payload) - next); |
| 692 | std::memcpy(shards_p[aligned_data_shards], next, copy_len); |
| 693 | if (copy_len < blocksize) { |
| 694 | // Zero any additional space after the end of the payload |
| 695 | std::memset(shards_p[aligned_data_shards] + copy_len, 0, blocksize - copy_len); |
| 696 | } |
| 697 | } |
| 698 | |
| 699 | // Add a payload buffer describing the shard buffer |
| 700 | payload_buffers.emplace_back(std::begin(shards), shards.size()); |
| 701 | |
| 702 | if (fecpercentage != 0) { |
| 703 | // Point into our allocated buffer for the parity shards |
| 704 | for (auto x = 0; x < parity_shards; ++x) { |
| 705 | shards_p[data_shards + x] = (uint8_t *) &shards[(parity_shard_offset + x) * blocksize]; |
| 706 | } |
no test coverage detected