blob: 0e92ed6c5d77984cb16622c9243ca3b6524537e3 [file] [log] [blame]
Brian Silverman41709732019-02-09 20:53:08 -08001#ifndef Y2019_JEVOIS_COBS_H_
2#define Y2019_JEVOIS_COBS_H_
3
4#include <stdint.h>
5
Brian Silvermanc64372e2019-02-17 18:07:40 -08006#include <algorithm>
Brian Silverman41709732019-02-09 20:53:08 -08007#include <array>
8
Brian Silverman41709732019-02-09 20:53:08 -08009#include "third_party/GSL/include/gsl/gsl"
10
11// This file contains code for encoding and decoding Consistent Overhead Byte
12// Stuffing data. <http://www.stuartcheshire.org/papers/cobsforton.pdf> has
13// details on what this entails and why it's a good idea.
14
15namespace frc971 {
16namespace jevois {
17
18constexpr size_t CobsMaxEncodedSize(size_t decoded_size) {
19 return decoded_size + ((decoded_size + 253) / 254);
20}
21
22// Encodes some data using COBS.
23// input is the data to encode. Its size may be at most max_decoded_size.
24// output_buffer is where to store the result.
25// Returns a span in output_buffer which has no 0 bytes.
26template <size_t max_decoded_size>
27gsl::span<char> CobsEncode(
28 gsl::span<const char> input,
29 std::array<char, CobsMaxEncodedSize(max_decoded_size)> *output_buffer);
30
31// Decodes some COBS-encoded data.
32// input is the data to decide. Its size may be at most
33// CobsMaxEncodedSize(max_decoded_size), and it may not have any 0 bytes.
34// output_buffer is where to store the result.
35// Returns a span in output_buffer.
36// If the input data is invalid, this will simply stop when either the input or
37// output buffer is exhausted and return the result.
38template <size_t max_decoded_size>
39gsl::span<char> CobsDecode(gsl::span<const char> input,
40 std::array<char, max_decoded_size> *output_buffer);
41
Brian Silvermanc64372e2019-02-17 18:07:40 -080042// Manages scanning a stream of bytes for 0s and exposing the resulting buffers.
43//
44// This will silently truncate packets longer than max_decoded_size, and ignore
45// empty packets.
46template <size_t max_decoded_size>
47class CobsPacketizer {
48 public:
49 CobsPacketizer() = default;
50 CobsPacketizer(const CobsPacketizer &) = delete;
51 CobsPacketizer &operator=(const CobsPacketizer &) = delete;
52
53 // Parses some new data. received_packet() will be filled out to the end of
54 // a packet if the end delimeters for any packets are present in new_data. If
55 // multiple end delimiters are present, received_packet() will be filled out
56 // to an arbitrary one of them.
57 void ParseData(gsl::span<const char> new_data);
58
59 // Returns the most-recently-parsed packet.
60 // If this is empty, it indicates no packet was received.
61 gsl::span<const char> received_packet() const { return complete_packet_; }
62 void clear_received_packet() { complete_packet_ = gsl::span<char>(); }
63
64 private:
65 using Buffer = std::array<char, CobsMaxEncodedSize(max_decoded_size)>;
66
67 void CopyData(gsl::span<const char> input) {
68 const size_t size = std::min(input.size(), remaining_active_.size());
69 for (size_t i = 0; i < size; ++i) {
70 remaining_active_[i] = input[i];
71 }
72 remaining_active_ = remaining_active_.subspan(size);
73 }
74
75 void FinishPacket() {
76 const Buffer &active_buffer = buffers_[active_index_];
77 complete_packet_ =
78 gsl::span<const char>(active_buffer)
79 .first(active_buffer.size() - remaining_active_.size());
80
81 active_index_ = 1 - active_index_;
82 remaining_active_ = buffers_[active_index_];
83 }
84
85 Buffer buffers_[2];
86 // The remaining space in the active buffer.
87 gsl::span<char> remaining_active_ = buffers_[0];
88 // The last complete packet we parsed.
89 gsl::span<const char> complete_packet_;
90 int active_index_ = 0;
91};
92
Brian Silverman41709732019-02-09 20:53:08 -080093template <size_t max_decoded_size>
94gsl::span<char> CobsEncode(
95 gsl::span<const char> input,
96 std::array<char, CobsMaxEncodedSize(max_decoded_size)> *output_buffer) {
97 static_assert(max_decoded_size > 0, "Empty buffers not supported");
Brian Silverman58899fd2019-03-24 11:03:11 -070098 if (static_cast<size_t>(input.size()) > max_decoded_size) {
99 __builtin_trap();
100 }
Brian Silverman41709732019-02-09 20:53:08 -0800101 auto input_pointer = input.begin();
102 auto output_pointer = output_buffer->begin();
103 auto code_pointer = output_pointer;
104 ++output_pointer;
105 uint8_t code = 1;
106 while (input_pointer < input.end()) {
Brian Silverman58899fd2019-03-24 11:03:11 -0700107 if (output_pointer >= output_buffer->end()) {
108 __builtin_trap();
109 }
Brian Silverman41709732019-02-09 20:53:08 -0800110 if (*input_pointer == 0u) {
111 *code_pointer = code;
112 code_pointer = output_pointer;
113 ++output_pointer;
114 code = 1;
115 } else {
116 *output_pointer = *input_pointer;
117 ++output_pointer;
118 ++code;
119 if (code == 0xFFu) {
120 *code_pointer = 0xFF;
121 code_pointer = output_pointer;
122 ++output_pointer;
123 code = 1;
124 }
125 }
126 ++input_pointer;
127 }
128 *code_pointer = code;
Brian Silverman58899fd2019-03-24 11:03:11 -0700129 if (output_pointer > output_buffer->end()) {
130 __builtin_trap();
131 }
Brian Silverman41709732019-02-09 20:53:08 -0800132 return gsl::span<char>(*output_buffer)
133 .subspan(0, output_pointer - output_buffer->begin());
134}
135
136template <size_t max_decoded_size>
137gsl::span<char> CobsDecode(gsl::span<const char> input,
138 std::array<char, max_decoded_size> *output_buffer) {
139 static_assert(max_decoded_size > 0, "Empty buffers not supported");
Brian Silverman58899fd2019-03-24 11:03:11 -0700140 if (static_cast<size_t>(input.size()) >
141 CobsMaxEncodedSize(max_decoded_size)) {
142 __builtin_trap();
143 }
Brian Silverman41709732019-02-09 20:53:08 -0800144 auto input_pointer = input.begin();
145 auto output_pointer = output_buffer->begin();
146 while (input_pointer < input.end()) {
147 const uint8_t code = *input_pointer;
148 ++input_pointer;
149 for (uint8_t i = 1; i < code; ++i) {
150 if (input_pointer == input.end()) {
151 break;
152 }
153 if (output_pointer == output_buffer->end()) {
154 return gsl::span<char>(*output_buffer);
155 }
156 *output_pointer = *input_pointer;
157 ++output_pointer;
158 ++input_pointer;
159 }
160 if (output_pointer == output_buffer->end()) {
161 return gsl::span<char>(*output_buffer);
162 }
163 if (code < 0xFFu) {
164 *output_pointer = 0;
165 ++output_pointer;
166 }
167 }
168 return gsl::span<char>(*output_buffer)
169 .subspan(0, output_pointer - output_buffer->begin() - 1);
170}
171
Brian Silvermanc64372e2019-02-17 18:07:40 -0800172template <size_t max_decoded_size>
173void CobsPacketizer<max_decoded_size>::ParseData(
174 gsl::span<const char> new_data) {
175 // Find where the active packet ends.
176 const auto first_end = std::find(new_data.begin(), new_data.end(), 0);
177 if (first_end == new_data.end()) {
178 // This is the common case, where there's no packet end in new_data.
179 CopyData(new_data);
180 return;
181 }
182
183 // Copy any remaining data for the active packet, and then finish it.
184 const auto first_end_index = first_end - new_data.begin();
185 CopyData(new_data.subspan(0, first_end_index));
186 FinishPacket();
187
188 // Look for where the last packet end is.
189 const auto first_end_reverse = new_data.rend() - first_end_index - 1;
190 const auto last_end = std::find(new_data.rbegin(), first_end_reverse, 0);
191 if (last_end == first_end_reverse) {
192 // If we didn't find another zero afterwards, then copy the rest of the data
193 // into the new packet and we're done.
194 CopyData(new_data.subspan(first_end_index + 1));
195 return;
196 }
197
198 // Otherwise, find the second-to-the-end packet end, which is where the last
199 // packet starts.
200 auto new_start = last_end;
201 auto new_end = new_data.rbegin();
202 // If a second packet ends at the end of new_data, then we want to grab it
203 // instead of ignoring it.
204 if (new_start == new_end) {
205 ++new_end;
206 new_start = std::find(new_end, first_end_reverse, 0);
207 }
208
209 // Being here means we found the end of multiple packets in new_data. Only
210 // copy the data which is part of the last one.
211 const auto new_start_index = new_data.rend() - new_start;
212 CopyData(new_data.subspan(new_start_index, new_start - new_end));
213 if (last_end == new_data.rbegin()) {
214 // If we also found the end of a packet, then return it.
215 FinishPacket();
216 }
217}
218
Brian Silverman41709732019-02-09 20:53:08 -0800219} // namespace jevois
220} // namespace frc971
221
222#endif // Y2019_JEVOIS_COBS_H_