blob: 979cc151e0a799a070e080868bebe20255602c5c [file] [log] [blame]
Brian Silverman41709732019-02-09 20:53:08 -08001#ifndef Y2019_JEVOIS_COBS_H_
2#define Y2019_JEVOIS_COBS_H_
3
Brian Silvermanc64372e2019-02-17 18:07:40 -08004#include <algorithm>
Brian Silverman41709732019-02-09 20:53:08 -08005#include <array>
Tyler Chatowbf0609c2021-07-31 16:13:27 -07006#include <cstdint>
Brian Silverman41709732019-02-09 20:53:08 -08007
Austin Schuhb72be802022-01-02 12:26:28 -08008#include "absl/types/span.h"
Brian Silverman41709732019-02-09 20:53:08 -08009
10// This file contains code for encoding and decoding Consistent Overhead Byte
11// Stuffing data. <http://www.stuartcheshire.org/papers/cobsforton.pdf> has
12// details on what this entails and why it's a good idea.
13
14namespace frc971 {
15namespace jevois {
16
17constexpr size_t CobsMaxEncodedSize(size_t decoded_size) {
18 return decoded_size + ((decoded_size + 253) / 254);
19}
20
21// Encodes some data using COBS.
22// input is the data to encode. Its size may be at most max_decoded_size.
23// output_buffer is where to store the result.
24// Returns a span in output_buffer which has no 0 bytes.
25template <size_t max_decoded_size>
Austin Schuhb72be802022-01-02 12:26:28 -080026absl::Span<char> CobsEncode(
27 absl::Span<const char> input,
Brian Silverman41709732019-02-09 20:53:08 -080028 std::array<char, CobsMaxEncodedSize(max_decoded_size)> *output_buffer);
29
30// Decodes some COBS-encoded data.
31// input is the data to decide. Its size may be at most
32// CobsMaxEncodedSize(max_decoded_size), and it may not have any 0 bytes.
33// output_buffer is where to store the result.
34// Returns a span in output_buffer.
35// If the input data is invalid, this will simply stop when either the input or
36// output buffer is exhausted and return the result.
37template <size_t max_decoded_size>
Austin Schuhb72be802022-01-02 12:26:28 -080038absl::Span<char> CobsDecode(absl::Span<const char> input,
Austin Schuh7fe04492022-01-02 13:37:21 -080039 std::array<char, max_decoded_size> *output_buffer);
Brian Silverman41709732019-02-09 20:53:08 -080040
Brian Silvermanc64372e2019-02-17 18:07:40 -080041// Manages scanning a stream of bytes for 0s and exposing the resulting buffers.
42//
43// This will silently truncate packets longer than max_decoded_size, and ignore
44// empty packets.
45template <size_t max_decoded_size>
46class CobsPacketizer {
47 public:
48 CobsPacketizer() = default;
49 CobsPacketizer(const CobsPacketizer &) = delete;
50 CobsPacketizer &operator=(const CobsPacketizer &) = delete;
51
52 // Parses some new data. received_packet() will be filled out to the end of
53 // a packet if the end delimeters for any packets are present in new_data. If
54 // multiple end delimiters are present, received_packet() will be filled out
55 // to an arbitrary one of them.
Austin Schuhb72be802022-01-02 12:26:28 -080056 void ParseData(absl::Span<const char> new_data);
Brian Silvermanc64372e2019-02-17 18:07:40 -080057
58 // Returns the most-recently-parsed packet.
59 // If this is empty, it indicates no packet was received.
Austin Schuhb72be802022-01-02 12:26:28 -080060 absl::Span<const char> received_packet() const { return complete_packet_; }
61 void clear_received_packet() { complete_packet_ = absl::Span<char>(); }
Brian Silvermanc64372e2019-02-17 18:07:40 -080062
63 private:
64 using Buffer = std::array<char, CobsMaxEncodedSize(max_decoded_size)>;
65
Austin Schuhb72be802022-01-02 12:26:28 -080066 void CopyData(absl::Span<const char> input) {
Brian Silvermanc64372e2019-02-17 18:07:40 -080067 const size_t size = std::min(input.size(), remaining_active_.size());
68 for (size_t i = 0; i < size; ++i) {
69 remaining_active_[i] = input[i];
70 }
71 remaining_active_ = remaining_active_.subspan(size);
72 }
73
74 void FinishPacket() {
75 const Buffer &active_buffer = buffers_[active_index_];
76 complete_packet_ =
Austin Schuhb72be802022-01-02 12:26:28 -080077 absl::Span<const char>(active_buffer)
Brian Silvermanc64372e2019-02-17 18:07:40 -080078 .first(active_buffer.size() - remaining_active_.size());
79
80 active_index_ = 1 - active_index_;
Austin Schuhb72be802022-01-02 12:26:28 -080081 remaining_active_ = absl::Span<char>(buffers_[active_index_]);
Brian Silvermanc64372e2019-02-17 18:07:40 -080082 }
83
84 Buffer buffers_[2];
85 // The remaining space in the active buffer.
Austin Schuhb72be802022-01-02 12:26:28 -080086 absl::Span<char> remaining_active_{buffers_[0]};
Brian Silvermanc64372e2019-02-17 18:07:40 -080087 // The last complete packet we parsed.
Austin Schuhb72be802022-01-02 12:26:28 -080088 absl::Span<const char> complete_packet_;
Brian Silvermanc64372e2019-02-17 18:07:40 -080089 int active_index_ = 0;
90};
91
Brian Silverman41709732019-02-09 20:53:08 -080092template <size_t max_decoded_size>
Austin Schuhb72be802022-01-02 12:26:28 -080093absl::Span<char> CobsEncode(
94 absl::Span<const char> input,
Brian Silverman41709732019-02-09 20:53:08 -080095 std::array<char, CobsMaxEncodedSize(max_decoded_size)> *output_buffer) {
96 static_assert(max_decoded_size > 0, "Empty buffers not supported");
Brian Silverman58899fd2019-03-24 11:03:11 -070097 if (static_cast<size_t>(input.size()) > max_decoded_size) {
98 __builtin_trap();
99 }
Brian Silverman41709732019-02-09 20:53:08 -0800100 auto input_pointer = input.begin();
101 auto output_pointer = output_buffer->begin();
102 auto code_pointer = output_pointer;
103 ++output_pointer;
104 uint8_t code = 1;
105 while (input_pointer < input.end()) {
Brian Silverman58899fd2019-03-24 11:03:11 -0700106 if (output_pointer >= output_buffer->end()) {
107 __builtin_trap();
108 }
Brian Silverman41709732019-02-09 20:53:08 -0800109 if (*input_pointer == 0u) {
110 *code_pointer = code;
111 code_pointer = output_pointer;
112 ++output_pointer;
113 code = 1;
114 } else {
115 *output_pointer = *input_pointer;
116 ++output_pointer;
117 ++code;
118 if (code == 0xFFu) {
119 *code_pointer = 0xFF;
120 code_pointer = output_pointer;
121 ++output_pointer;
122 code = 1;
123 }
124 }
125 ++input_pointer;
126 }
127 *code_pointer = code;
Brian Silverman58899fd2019-03-24 11:03:11 -0700128 if (output_pointer > output_buffer->end()) {
129 __builtin_trap();
130 }
Austin Schuhb72be802022-01-02 12:26:28 -0800131 return absl::Span<char>(*output_buffer)
Brian Silverman41709732019-02-09 20:53:08 -0800132 .subspan(0, output_pointer - output_buffer->begin());
133}
134
135template <size_t max_decoded_size>
Austin Schuhb72be802022-01-02 12:26:28 -0800136absl::Span<char> CobsDecode(absl::Span<const char> input,
Austin Schuh7fe04492022-01-02 13:37:21 -0800137 std::array<char, max_decoded_size> *output_buffer) {
Brian Silverman41709732019-02-09 20:53:08 -0800138 static_assert(max_decoded_size > 0, "Empty buffers not supported");
Brian Silverman58899fd2019-03-24 11:03:11 -0700139 if (static_cast<size_t>(input.size()) >
140 CobsMaxEncodedSize(max_decoded_size)) {
141 __builtin_trap();
142 }
Brian Silverman41709732019-02-09 20:53:08 -0800143 auto input_pointer = input.begin();
144 auto output_pointer = output_buffer->begin();
145 while (input_pointer < input.end()) {
146 const uint8_t code = *input_pointer;
147 ++input_pointer;
148 for (uint8_t i = 1; i < code; ++i) {
149 if (input_pointer == input.end()) {
150 break;
151 }
152 if (output_pointer == output_buffer->end()) {
Austin Schuhb72be802022-01-02 12:26:28 -0800153 return absl::Span<char>(*output_buffer);
Brian Silverman41709732019-02-09 20:53:08 -0800154 }
155 *output_pointer = *input_pointer;
156 ++output_pointer;
157 ++input_pointer;
158 }
159 if (output_pointer == output_buffer->end()) {
Austin Schuhb72be802022-01-02 12:26:28 -0800160 return absl::Span<char>(*output_buffer);
Brian Silverman41709732019-02-09 20:53:08 -0800161 }
162 if (code < 0xFFu) {
163 *output_pointer = 0;
164 ++output_pointer;
165 }
166 }
Austin Schuhb72be802022-01-02 12:26:28 -0800167 return absl::Span<char>(*output_buffer)
Brian Silverman41709732019-02-09 20:53:08 -0800168 .subspan(0, output_pointer - output_buffer->begin() - 1);
169}
170
Brian Silvermanc64372e2019-02-17 18:07:40 -0800171template <size_t max_decoded_size>
172void CobsPacketizer<max_decoded_size>::ParseData(
Austin Schuhb72be802022-01-02 12:26:28 -0800173 absl::Span<const char> new_data) {
Brian Silvermanc64372e2019-02-17 18:07:40 -0800174 // Find where the active packet ends.
175 const auto first_end = std::find(new_data.begin(), new_data.end(), 0);
176 if (first_end == new_data.end()) {
177 // This is the common case, where there's no packet end in new_data.
178 CopyData(new_data);
179 return;
180 }
181
182 // Copy any remaining data for the active packet, and then finish it.
183 const auto first_end_index = first_end - new_data.begin();
184 CopyData(new_data.subspan(0, first_end_index));
185 FinishPacket();
186
187 // Look for where the last packet end is.
188 const auto first_end_reverse = new_data.rend() - first_end_index - 1;
189 const auto last_end = std::find(new_data.rbegin(), first_end_reverse, 0);
190 if (last_end == first_end_reverse) {
191 // If we didn't find another zero afterwards, then copy the rest of the data
192 // into the new packet and we're done.
193 CopyData(new_data.subspan(first_end_index + 1));
194 return;
195 }
196
197 // Otherwise, find the second-to-the-end packet end, which is where the last
198 // packet starts.
199 auto new_start = last_end;
200 auto new_end = new_data.rbegin();
201 // If a second packet ends at the end of new_data, then we want to grab it
202 // instead of ignoring it.
203 if (new_start == new_end) {
204 ++new_end;
205 new_start = std::find(new_end, first_end_reverse, 0);
206 }
207
208 // Being here means we found the end of multiple packets in new_data. Only
209 // copy the data which is part of the last one.
210 const auto new_start_index = new_data.rend() - new_start;
211 CopyData(new_data.subspan(new_start_index, new_start - new_end));
212 if (last_end == new_data.rbegin()) {
213 // If we also found the end of a packet, then return it.
214 FinishPacket();
215 }
216}
217
Brian Silverman41709732019-02-09 20:53:08 -0800218} // namespace jevois
219} // namespace frc971
220
221#endif // Y2019_JEVOIS_COBS_H_