blob: 0e92ed6c5d77984cb16622c9243ca3b6524537e3 [file] [log] [blame]
#ifndef Y2019_JEVOIS_COBS_H_
#define Y2019_JEVOIS_COBS_H_
#include <stdint.h>
#include <algorithm>
#include <array>
#include "third_party/GSL/include/gsl/gsl"
// This file contains code for encoding and decoding Consistent Overhead Byte
// Stuffing data. <http://www.stuartcheshire.org/papers/cobsforton.pdf> has
// details on what this entails and why it's a good idea.
namespace frc971 {
namespace jevois {
constexpr size_t CobsMaxEncodedSize(size_t decoded_size) {
return decoded_size + ((decoded_size + 253) / 254);
}
// Encodes some data using COBS.
// input is the data to encode. Its size may be at most max_decoded_size.
// output_buffer is where to store the result.
// Returns a span in output_buffer which has no 0 bytes.
template <size_t max_decoded_size>
gsl::span<char> CobsEncode(
gsl::span<const char> input,
std::array<char, CobsMaxEncodedSize(max_decoded_size)> *output_buffer);
// Decodes some COBS-encoded data.
// input is the data to decide. Its size may be at most
// CobsMaxEncodedSize(max_decoded_size), and it may not have any 0 bytes.
// output_buffer is where to store the result.
// Returns a span in output_buffer.
// If the input data is invalid, this will simply stop when either the input or
// output buffer is exhausted and return the result.
template <size_t max_decoded_size>
gsl::span<char> CobsDecode(gsl::span<const char> input,
std::array<char, max_decoded_size> *output_buffer);
// Manages scanning a stream of bytes for 0s and exposing the resulting buffers.
//
// This will silently truncate packets longer than max_decoded_size, and ignore
// empty packets.
template <size_t max_decoded_size>
class CobsPacketizer {
public:
CobsPacketizer() = default;
CobsPacketizer(const CobsPacketizer &) = delete;
CobsPacketizer &operator=(const CobsPacketizer &) = delete;
// Parses some new data. received_packet() will be filled out to the end of
// a packet if the end delimeters for any packets are present in new_data. If
// multiple end delimiters are present, received_packet() will be filled out
// to an arbitrary one of them.
void ParseData(gsl::span<const char> new_data);
// Returns the most-recently-parsed packet.
// If this is empty, it indicates no packet was received.
gsl::span<const char> received_packet() const { return complete_packet_; }
void clear_received_packet() { complete_packet_ = gsl::span<char>(); }
private:
using Buffer = std::array<char, CobsMaxEncodedSize(max_decoded_size)>;
void CopyData(gsl::span<const char> input) {
const size_t size = std::min(input.size(), remaining_active_.size());
for (size_t i = 0; i < size; ++i) {
remaining_active_[i] = input[i];
}
remaining_active_ = remaining_active_.subspan(size);
}
void FinishPacket() {
const Buffer &active_buffer = buffers_[active_index_];
complete_packet_ =
gsl::span<const char>(active_buffer)
.first(active_buffer.size() - remaining_active_.size());
active_index_ = 1 - active_index_;
remaining_active_ = buffers_[active_index_];
}
Buffer buffers_[2];
// The remaining space in the active buffer.
gsl::span<char> remaining_active_ = buffers_[0];
// The last complete packet we parsed.
gsl::span<const char> complete_packet_;
int active_index_ = 0;
};
template <size_t max_decoded_size>
gsl::span<char> CobsEncode(
gsl::span<const char> input,
std::array<char, CobsMaxEncodedSize(max_decoded_size)> *output_buffer) {
static_assert(max_decoded_size > 0, "Empty buffers not supported");
if (static_cast<size_t>(input.size()) > max_decoded_size) {
__builtin_trap();
}
auto input_pointer = input.begin();
auto output_pointer = output_buffer->begin();
auto code_pointer = output_pointer;
++output_pointer;
uint8_t code = 1;
while (input_pointer < input.end()) {
if (output_pointer >= output_buffer->end()) {
__builtin_trap();
}
if (*input_pointer == 0u) {
*code_pointer = code;
code_pointer = output_pointer;
++output_pointer;
code = 1;
} else {
*output_pointer = *input_pointer;
++output_pointer;
++code;
if (code == 0xFFu) {
*code_pointer = 0xFF;
code_pointer = output_pointer;
++output_pointer;
code = 1;
}
}
++input_pointer;
}
*code_pointer = code;
if (output_pointer > output_buffer->end()) {
__builtin_trap();
}
return gsl::span<char>(*output_buffer)
.subspan(0, output_pointer - output_buffer->begin());
}
template <size_t max_decoded_size>
gsl::span<char> CobsDecode(gsl::span<const char> input,
std::array<char, max_decoded_size> *output_buffer) {
static_assert(max_decoded_size > 0, "Empty buffers not supported");
if (static_cast<size_t>(input.size()) >
CobsMaxEncodedSize(max_decoded_size)) {
__builtin_trap();
}
auto input_pointer = input.begin();
auto output_pointer = output_buffer->begin();
while (input_pointer < input.end()) {
const uint8_t code = *input_pointer;
++input_pointer;
for (uint8_t i = 1; i < code; ++i) {
if (input_pointer == input.end()) {
break;
}
if (output_pointer == output_buffer->end()) {
return gsl::span<char>(*output_buffer);
}
*output_pointer = *input_pointer;
++output_pointer;
++input_pointer;
}
if (output_pointer == output_buffer->end()) {
return gsl::span<char>(*output_buffer);
}
if (code < 0xFFu) {
*output_pointer = 0;
++output_pointer;
}
}
return gsl::span<char>(*output_buffer)
.subspan(0, output_pointer - output_buffer->begin() - 1);
}
template <size_t max_decoded_size>
void CobsPacketizer<max_decoded_size>::ParseData(
gsl::span<const char> new_data) {
// Find where the active packet ends.
const auto first_end = std::find(new_data.begin(), new_data.end(), 0);
if (first_end == new_data.end()) {
// This is the common case, where there's no packet end in new_data.
CopyData(new_data);
return;
}
// Copy any remaining data for the active packet, and then finish it.
const auto first_end_index = first_end - new_data.begin();
CopyData(new_data.subspan(0, first_end_index));
FinishPacket();
// Look for where the last packet end is.
const auto first_end_reverse = new_data.rend() - first_end_index - 1;
const auto last_end = std::find(new_data.rbegin(), first_end_reverse, 0);
if (last_end == first_end_reverse) {
// If we didn't find another zero afterwards, then copy the rest of the data
// into the new packet and we're done.
CopyData(new_data.subspan(first_end_index + 1));
return;
}
// Otherwise, find the second-to-the-end packet end, which is where the last
// packet starts.
auto new_start = last_end;
auto new_end = new_data.rbegin();
// If a second packet ends at the end of new_data, then we want to grab it
// instead of ignoring it.
if (new_start == new_end) {
++new_end;
new_start = std::find(new_end, first_end_reverse, 0);
}
// Being here means we found the end of multiple packets in new_data. Only
// copy the data which is part of the last one.
const auto new_start_index = new_data.rend() - new_start;
CopyData(new_data.subspan(new_start_index, new_start - new_end));
if (last_end == new_data.rbegin()) {
// If we also found the end of a packet, then return it.
FinishPacket();
}
}
} // namespace jevois
} // namespace frc971
#endif // Y2019_JEVOIS_COBS_H_