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