blob: c4296222ee45e2654be2b5729c0eb98db1c74ea4 [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>
Tyler Chatowd0a49742022-02-25 22:06:19 -080026absl::Span<char> CobsEncode(absl::Span<const char> input,
27 absl::Span<char> output_buffer);
Brian Silverman41709732019-02-09 20:53:08 -080028
29// Decodes some COBS-encoded data.
30// input is the data to decide. Its size may be at most
31// CobsMaxEncodedSize(max_decoded_size), and it may not have any 0 bytes.
32// output_buffer is where to store the result.
33// Returns a span in output_buffer.
34// If the input data is invalid, this will simply stop when either the input or
35// output buffer is exhausted and return the result.
36template <size_t max_decoded_size>
Austin Schuhb72be802022-01-02 12:26:28 -080037absl::Span<char> CobsDecode(absl::Span<const char> input,
Austin Schuh7fe04492022-01-02 13:37:21 -080038 std::array<char, max_decoded_size> *output_buffer);
Brian Silverman41709732019-02-09 20:53:08 -080039
Brian Silvermanc64372e2019-02-17 18:07:40 -080040// Manages scanning a stream of bytes for 0s and exposing the resulting buffers.
41//
42// This will silently truncate packets longer than max_decoded_size, and ignore
43// empty packets.
44template <size_t max_decoded_size>
45class CobsPacketizer {
46 public:
47 CobsPacketizer() = default;
48 CobsPacketizer(const CobsPacketizer &) = delete;
49 CobsPacketizer &operator=(const CobsPacketizer &) = delete;
50
51 // Parses some new data. received_packet() will be filled out to the end of
52 // a packet if the end delimeters for any packets are present in new_data. If
53 // multiple end delimiters are present, received_packet() will be filled out
54 // to an arbitrary one of them.
Austin Schuhb72be802022-01-02 12:26:28 -080055 void ParseData(absl::Span<const char> new_data);
Brian Silvermanc64372e2019-02-17 18:07:40 -080056
57 // Returns the most-recently-parsed packet.
58 // If this is empty, it indicates no packet was received.
Austin Schuhb72be802022-01-02 12:26:28 -080059 absl::Span<const char> received_packet() const { return complete_packet_; }
60 void clear_received_packet() { complete_packet_ = absl::Span<char>(); }
Brian Silvermanc64372e2019-02-17 18:07:40 -080061
62 private:
63 using Buffer = std::array<char, CobsMaxEncodedSize(max_decoded_size)>;
64
Austin Schuhb72be802022-01-02 12:26:28 -080065 void CopyData(absl::Span<const char> input) {
Brian Silvermanc64372e2019-02-17 18:07:40 -080066 const size_t size = std::min(input.size(), remaining_active_.size());
67 for (size_t i = 0; i < size; ++i) {
68 remaining_active_[i] = input[i];
69 }
70 remaining_active_ = remaining_active_.subspan(size);
71 }
72
73 void FinishPacket() {
74 const Buffer &active_buffer = buffers_[active_index_];
75 complete_packet_ =
Austin Schuhb72be802022-01-02 12:26:28 -080076 absl::Span<const char>(active_buffer)
Brian Silvermanc64372e2019-02-17 18:07:40 -080077 .first(active_buffer.size() - remaining_active_.size());
78
79 active_index_ = 1 - active_index_;
Austin Schuhb72be802022-01-02 12:26:28 -080080 remaining_active_ = absl::Span<char>(buffers_[active_index_]);
Brian Silvermanc64372e2019-02-17 18:07:40 -080081 }
82
83 Buffer buffers_[2];
84 // The remaining space in the active buffer.
Austin Schuhb72be802022-01-02 12:26:28 -080085 absl::Span<char> remaining_active_{buffers_[0]};
Brian Silvermanc64372e2019-02-17 18:07:40 -080086 // The last complete packet we parsed.
Austin Schuhb72be802022-01-02 12:26:28 -080087 absl::Span<const char> complete_packet_;
Brian Silvermanc64372e2019-02-17 18:07:40 -080088 int active_index_ = 0;
89};
90
Brian Silverman41709732019-02-09 20:53:08 -080091template <size_t max_decoded_size>
Tyler Chatowd0a49742022-02-25 22:06:19 -080092absl::Span<char> CobsEncode(absl::Span<const char> input,
93 absl::Span<char> output_buffer) {
Brian Silverman41709732019-02-09 20:53:08 -080094 static_assert(max_decoded_size > 0, "Empty buffers not supported");
Brian Silverman58899fd2019-03-24 11:03:11 -070095 if (static_cast<size_t>(input.size()) > max_decoded_size) {
96 __builtin_trap();
97 }
Brian Silverman41709732019-02-09 20:53:08 -080098 auto input_pointer = input.begin();
Tyler Chatowd0a49742022-02-25 22:06:19 -080099 auto output_pointer = output_buffer.begin();
Brian Silverman41709732019-02-09 20:53:08 -0800100 auto code_pointer = output_pointer;
101 ++output_pointer;
102 uint8_t code = 1;
103 while (input_pointer < input.end()) {
Tyler Chatowd0a49742022-02-25 22:06:19 -0800104 if (output_pointer >= output_buffer.end()) {
Brian Silverman58899fd2019-03-24 11:03:11 -0700105 __builtin_trap();
106 }
Brian Silverman41709732019-02-09 20:53:08 -0800107 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;
Tyler Chatowd0a49742022-02-25 22:06:19 -0800126 if (output_pointer > output_buffer.end()) {
Brian Silverman58899fd2019-03-24 11:03:11 -0700127 __builtin_trap();
128 }
Tyler Chatowd0a49742022-02-25 22:06:19 -0800129 return absl::Span<char>(output_buffer)
130 .subspan(0, output_pointer - output_buffer.begin());
Brian Silverman41709732019-02-09 20:53:08 -0800131}
132
133template <size_t max_decoded_size>
Austin Schuhb72be802022-01-02 12:26:28 -0800134absl::Span<char> CobsDecode(absl::Span<const char> input,
Austin Schuh7fe04492022-01-02 13:37:21 -0800135 std::array<char, max_decoded_size> *output_buffer) {
Brian Silverman41709732019-02-09 20:53:08 -0800136 static_assert(max_decoded_size > 0, "Empty buffers not supported");
Brian Silverman58899fd2019-03-24 11:03:11 -0700137 if (static_cast<size_t>(input.size()) >
138 CobsMaxEncodedSize(max_decoded_size)) {
139 __builtin_trap();
140 }
Brian Silverman41709732019-02-09 20:53:08 -0800141 auto input_pointer = input.begin();
142 auto output_pointer = output_buffer->begin();
143 while (input_pointer < input.end()) {
144 const uint8_t code = *input_pointer;
145 ++input_pointer;
146 for (uint8_t i = 1; i < code; ++i) {
147 if (input_pointer == input.end()) {
148 break;
149 }
150 if (output_pointer == output_buffer->end()) {
Austin Schuhb72be802022-01-02 12:26:28 -0800151 return absl::Span<char>(*output_buffer);
Brian Silverman41709732019-02-09 20:53:08 -0800152 }
153 *output_pointer = *input_pointer;
154 ++output_pointer;
155 ++input_pointer;
156 }
157 if (output_pointer == output_buffer->end()) {
Austin Schuhb72be802022-01-02 12:26:28 -0800158 return absl::Span<char>(*output_buffer);
Brian Silverman41709732019-02-09 20:53:08 -0800159 }
160 if (code < 0xFFu) {
161 *output_pointer = 0;
162 ++output_pointer;
163 }
164 }
Austin Schuhb72be802022-01-02 12:26:28 -0800165 return absl::Span<char>(*output_buffer)
Brian Silverman41709732019-02-09 20:53:08 -0800166 .subspan(0, output_pointer - output_buffer->begin() - 1);
167}
168
Brian Silvermanc64372e2019-02-17 18:07:40 -0800169template <size_t max_decoded_size>
170void CobsPacketizer<max_decoded_size>::ParseData(
Austin Schuhb72be802022-01-02 12:26:28 -0800171 absl::Span<const char> new_data) {
Brian Silvermanc64372e2019-02-17 18:07:40 -0800172 // Find where the active packet ends.
173 const auto first_end = std::find(new_data.begin(), new_data.end(), 0);
174 if (first_end == new_data.end()) {
175 // This is the common case, where there's no packet end in new_data.
176 CopyData(new_data);
177 return;
178 }
179
180 // Copy any remaining data for the active packet, and then finish it.
181 const auto first_end_index = first_end - new_data.begin();
182 CopyData(new_data.subspan(0, first_end_index));
183 FinishPacket();
184
185 // Look for where the last packet end is.
186 const auto first_end_reverse = new_data.rend() - first_end_index - 1;
187 const auto last_end = std::find(new_data.rbegin(), first_end_reverse, 0);
188 if (last_end == first_end_reverse) {
189 // If we didn't find another zero afterwards, then copy the rest of the data
190 // into the new packet and we're done.
191 CopyData(new_data.subspan(first_end_index + 1));
192 return;
193 }
194
195 // Otherwise, find the second-to-the-end packet end, which is where the last
196 // packet starts.
197 auto new_start = last_end;
198 auto new_end = new_data.rbegin();
199 // If a second packet ends at the end of new_data, then we want to grab it
200 // instead of ignoring it.
201 if (new_start == new_end) {
202 ++new_end;
203 new_start = std::find(new_end, first_end_reverse, 0);
204 }
205
206 // Being here means we found the end of multiple packets in new_data. Only
207 // copy the data which is part of the last one.
208 const auto new_start_index = new_data.rend() - new_start;
209 CopyData(new_data.subspan(new_start_index, new_start - new_end));
210 if (last_end == new_data.rbegin()) {
211 // If we also found the end of a packet, then return it.
212 FinishPacket();
213 }
214}
215
Brian Silverman41709732019-02-09 20:53:08 -0800216} // namespace jevois
217} // namespace frc971
218
219#endif // Y2019_JEVOIS_COBS_H_