blob: 271a41fbc3904020aa3f76c88f22ebb3cc7be9db [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
Stephan Pleinesd99b1ee2024-02-02 20:56:44 -080014namespace frc971::jevois {
Brian Silverman41709732019-02-09 20:53:08 -080015
16constexpr size_t CobsMaxEncodedSize(size_t decoded_size) {
17 return decoded_size + ((decoded_size + 253) / 254);
18}
19
20// Encodes some data using COBS.
21// input is the data to encode. Its size may be at most max_decoded_size.
22// output_buffer is where to store the result.
23// Returns a span in output_buffer which has no 0 bytes.
24template <size_t max_decoded_size>
Tyler Chatowd0a49742022-02-25 22:06:19 -080025absl::Span<char> CobsEncode(absl::Span<const char> input,
26 absl::Span<char> output_buffer);
Brian Silverman41709732019-02-09 20:53:08 -080027
28// Decodes some COBS-encoded data.
29// input is the data to decide. Its size may be at most
30// CobsMaxEncodedSize(max_decoded_size), and it may not have any 0 bytes.
31// output_buffer is where to store the result.
32// Returns a span in output_buffer.
33// If the input data is invalid, this will simply stop when either the input or
34// output buffer is exhausted and return the result.
35template <size_t max_decoded_size>
Austin Schuhb72be802022-01-02 12:26:28 -080036absl::Span<char> CobsDecode(absl::Span<const char> input,
Austin Schuh7fe04492022-01-02 13:37:21 -080037 std::array<char, max_decoded_size> *output_buffer);
Brian Silverman41709732019-02-09 20:53:08 -080038
Brian Silvermanc64372e2019-02-17 18:07:40 -080039// Manages scanning a stream of bytes for 0s and exposing the resulting buffers.
40//
41// This will silently truncate packets longer than max_decoded_size, and ignore
42// empty packets.
43template <size_t max_decoded_size>
44class CobsPacketizer {
45 public:
46 CobsPacketizer() = default;
47 CobsPacketizer(const CobsPacketizer &) = delete;
48 CobsPacketizer &operator=(const CobsPacketizer &) = delete;
49
50 // Parses some new data. received_packet() will be filled out to the end of
51 // a packet if the end delimeters for any packets are present in new_data. If
52 // multiple end delimiters are present, received_packet() will be filled out
53 // to an arbitrary one of them.
Austin Schuhb72be802022-01-02 12:26:28 -080054 void ParseData(absl::Span<const char> new_data);
Brian Silvermanc64372e2019-02-17 18:07:40 -080055
56 // Returns the most-recently-parsed packet.
57 // If this is empty, it indicates no packet was received.
Austin Schuhb72be802022-01-02 12:26:28 -080058 absl::Span<const char> received_packet() const { return complete_packet_; }
59 void clear_received_packet() { complete_packet_ = absl::Span<char>(); }
Brian Silvermanc64372e2019-02-17 18:07:40 -080060
61 private:
62 using Buffer = std::array<char, CobsMaxEncodedSize(max_decoded_size)>;
63
Austin Schuhb72be802022-01-02 12:26:28 -080064 void CopyData(absl::Span<const char> input) {
Brian Silvermanc64372e2019-02-17 18:07:40 -080065 const size_t size = std::min(input.size(), remaining_active_.size());
66 for (size_t i = 0; i < size; ++i) {
67 remaining_active_[i] = input[i];
68 }
69 remaining_active_ = remaining_active_.subspan(size);
70 }
71
72 void FinishPacket() {
73 const Buffer &active_buffer = buffers_[active_index_];
74 complete_packet_ =
Austin Schuhb72be802022-01-02 12:26:28 -080075 absl::Span<const char>(active_buffer)
Brian Silvermanc64372e2019-02-17 18:07:40 -080076 .first(active_buffer.size() - remaining_active_.size());
77
78 active_index_ = 1 - active_index_;
Austin Schuhb72be802022-01-02 12:26:28 -080079 remaining_active_ = absl::Span<char>(buffers_[active_index_]);
Brian Silvermanc64372e2019-02-17 18:07:40 -080080 }
81
82 Buffer buffers_[2];
83 // The remaining space in the active buffer.
Austin Schuhb72be802022-01-02 12:26:28 -080084 absl::Span<char> remaining_active_{buffers_[0]};
Brian Silvermanc64372e2019-02-17 18:07:40 -080085 // The last complete packet we parsed.
Austin Schuhb72be802022-01-02 12:26:28 -080086 absl::Span<const char> complete_packet_;
Brian Silvermanc64372e2019-02-17 18:07:40 -080087 int active_index_ = 0;
88};
89
Brian Silverman41709732019-02-09 20:53:08 -080090template <size_t max_decoded_size>
Tyler Chatowd0a49742022-02-25 22:06:19 -080091absl::Span<char> CobsEncode(absl::Span<const char> input,
92 absl::Span<char> output_buffer) {
Brian Silverman41709732019-02-09 20:53:08 -080093 static_assert(max_decoded_size > 0, "Empty buffers not supported");
Brian Silverman58899fd2019-03-24 11:03:11 -070094 if (static_cast<size_t>(input.size()) > max_decoded_size) {
95 __builtin_trap();
96 }
Brian Silverman41709732019-02-09 20:53:08 -080097 auto input_pointer = input.begin();
Tyler Chatowd0a49742022-02-25 22:06:19 -080098 auto output_pointer = output_buffer.begin();
Brian Silverman41709732019-02-09 20:53:08 -080099 auto code_pointer = output_pointer;
100 ++output_pointer;
101 uint8_t code = 1;
102 while (input_pointer < input.end()) {
Tyler Chatowd0a49742022-02-25 22:06:19 -0800103 if (output_pointer >= output_buffer.end()) {
Brian Silverman58899fd2019-03-24 11:03:11 -0700104 __builtin_trap();
105 }
Brian Silverman41709732019-02-09 20:53:08 -0800106 if (*input_pointer == 0u) {
107 *code_pointer = code;
108 code_pointer = output_pointer;
109 ++output_pointer;
110 code = 1;
111 } else {
112 *output_pointer = *input_pointer;
113 ++output_pointer;
114 ++code;
115 if (code == 0xFFu) {
116 *code_pointer = 0xFF;
117 code_pointer = output_pointer;
118 ++output_pointer;
119 code = 1;
120 }
121 }
122 ++input_pointer;
123 }
124 *code_pointer = code;
Tyler Chatowd0a49742022-02-25 22:06:19 -0800125 if (output_pointer > output_buffer.end()) {
Brian Silverman58899fd2019-03-24 11:03:11 -0700126 __builtin_trap();
127 }
Tyler Chatowd0a49742022-02-25 22:06:19 -0800128 return absl::Span<char>(output_buffer)
129 .subspan(0, output_pointer - output_buffer.begin());
Brian Silverman41709732019-02-09 20:53:08 -0800130}
131
132template <size_t max_decoded_size>
Austin Schuhb72be802022-01-02 12:26:28 -0800133absl::Span<char> CobsDecode(absl::Span<const char> input,
Austin Schuh7fe04492022-01-02 13:37:21 -0800134 std::array<char, max_decoded_size> *output_buffer) {
Brian Silverman41709732019-02-09 20:53:08 -0800135 static_assert(max_decoded_size > 0, "Empty buffers not supported");
Brian Silverman58899fd2019-03-24 11:03:11 -0700136 if (static_cast<size_t>(input.size()) >
137 CobsMaxEncodedSize(max_decoded_size)) {
138 __builtin_trap();
139 }
Brian Silverman41709732019-02-09 20:53:08 -0800140 auto input_pointer = input.begin();
141 auto output_pointer = output_buffer->begin();
142 while (input_pointer < input.end()) {
143 const uint8_t code = *input_pointer;
144 ++input_pointer;
145 for (uint8_t i = 1; i < code; ++i) {
146 if (input_pointer == input.end()) {
147 break;
148 }
149 if (output_pointer == output_buffer->end()) {
Austin Schuhb72be802022-01-02 12:26:28 -0800150 return absl::Span<char>(*output_buffer);
Brian Silverman41709732019-02-09 20:53:08 -0800151 }
152 *output_pointer = *input_pointer;
153 ++output_pointer;
154 ++input_pointer;
155 }
156 if (output_pointer == output_buffer->end()) {
Austin Schuhb72be802022-01-02 12:26:28 -0800157 return absl::Span<char>(*output_buffer);
Brian Silverman41709732019-02-09 20:53:08 -0800158 }
159 if (code < 0xFFu) {
160 *output_pointer = 0;
161 ++output_pointer;
162 }
163 }
Austin Schuhb72be802022-01-02 12:26:28 -0800164 return absl::Span<char>(*output_buffer)
Brian Silverman41709732019-02-09 20:53:08 -0800165 .subspan(0, output_pointer - output_buffer->begin() - 1);
166}
167
Brian Silvermanc64372e2019-02-17 18:07:40 -0800168template <size_t max_decoded_size>
169void CobsPacketizer<max_decoded_size>::ParseData(
Austin Schuhb72be802022-01-02 12:26:28 -0800170 absl::Span<const char> new_data) {
Brian Silvermanc64372e2019-02-17 18:07:40 -0800171 // Find where the active packet ends.
172 const auto first_end = std::find(new_data.begin(), new_data.end(), 0);
173 if (first_end == new_data.end()) {
174 // This is the common case, where there's no packet end in new_data.
175 CopyData(new_data);
176 return;
177 }
178
179 // Copy any remaining data for the active packet, and then finish it.
180 const auto first_end_index = first_end - new_data.begin();
181 CopyData(new_data.subspan(0, first_end_index));
182 FinishPacket();
183
184 // Look for where the last packet end is.
185 const auto first_end_reverse = new_data.rend() - first_end_index - 1;
186 const auto last_end = std::find(new_data.rbegin(), first_end_reverse, 0);
187 if (last_end == first_end_reverse) {
188 // If we didn't find another zero afterwards, then copy the rest of the data
189 // into the new packet and we're done.
190 CopyData(new_data.subspan(first_end_index + 1));
191 return;
192 }
193
194 // Otherwise, find the second-to-the-end packet end, which is where the last
195 // packet starts.
196 auto new_start = last_end;
197 auto new_end = new_data.rbegin();
198 // If a second packet ends at the end of new_data, then we want to grab it
199 // instead of ignoring it.
200 if (new_start == new_end) {
201 ++new_end;
202 new_start = std::find(new_end, first_end_reverse, 0);
203 }
204
205 // Being here means we found the end of multiple packets in new_data. Only
206 // copy the data which is part of the last one.
207 const auto new_start_index = new_data.rend() - new_start;
208 CopyData(new_data.subspan(new_start_index, new_start - new_end));
209 if (last_end == new_data.rbegin()) {
210 // If we also found the end of a packet, then return it.
211 FinishPacket();
212 }
213}
214
Stephan Pleinesd99b1ee2024-02-02 20:56:44 -0800215} // namespace frc971::jevois
Brian Silverman41709732019-02-09 20:53:08 -0800216
217#endif // Y2019_JEVOIS_COBS_H_