blob: 73e17e89f5ad56cb6f7c2652e5d7d40648f594d3 [file] [log] [blame]
// Copyright 2020 The Pigweed Authors
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#pragma once
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstring>
#include <functional> // std::invoke
#include "pw_assert/assert.h"
#include "pw_bytes/span.h"
#include "pw_checksum/crc32.h"
#include "pw_result/result.h"
#include "pw_status/status.h"
namespace pw::hdlc {
// Represents the contents of an HDLC frame -- the unescaped data between two
// flag bytes. Instances of Frame are only created when a full, valid frame has
// been read.
// For now, the Frame class assumes a single-byte control field and a 32-bit
// frame check sequence (FCS).
class Frame {
static constexpr size_t kMinimumAddressSize = 1;
static constexpr size_t kControlSize = 1;
static constexpr size_t kFcsSize = sizeof(uint32_t);
// The minimum size of a frame, excluding control bytes (flag or escape).
static constexpr size_t kMinSizeBytes =
kMinimumAddressSize + kControlSize + kFcsSize;
static Result<Frame> Parse(ConstByteSpan frame);
constexpr uint64_t address() const { return address_; }
constexpr std::byte control() const { return control_; }
constexpr ConstByteSpan data() const { return data_; }
// Creates a Frame with the specified data. The data MUST be valid frame data
// with a verified frame check sequence.
constexpr Frame(uint64_t address, std::byte control, ConstByteSpan data)
: data_(data), address_(address), control_(control) {}
ConstByteSpan data_;
uint64_t address_;
std::byte control_;
// The Decoder class facilitates decoding of data frames using the HDLC
// protocol, by returning packets as they are decoded and storing incomplete
// data frames in a buffer.
// The Decoder class does not own the buffer it writes to. It can be used to
// write bytes to any buffer. The DecoderBuffer template class, defined below,
// allocates a buffer.
class Decoder {
constexpr Decoder(ByteSpan buffer)
: buffer_(buffer),
state_(State::kInterFrame) {}
Decoder(const Decoder&) = delete;
Decoder& operator=(const Decoder&) = delete;
// Parses a single byte of an HDLC stream. Returns a Result with the complete
// frame if the byte completes a frame. The status is the following:
// OK - A frame was successfully decoded. The Result contains the Frame,
// which is invalidated by the next Process call.
// UNAVAILABLE - No frame is available.
// RESOURCE_EXHAUSTED - A frame completed, but it was too large to fit in
// the decoder's buffer.
// DATA_LOSS - A frame completed, but it was invalid. The frame was
// incomplete or the frame check sequence verification failed.
Result<Frame> Process(std::byte b);
// Processes a span of data and calls the provided callback with each frame or
// error.
template <typename F, typename... Args>
void Process(ConstByteSpan data, F&& callback, Args&&... args) {
for (std::byte b : data) {
auto result = Process(b);
if (result.status() != Status::Unavailable()) {
std::forward<F>(callback), std::forward<Args>(args)..., result);
// Returns the maximum size of the Decoder's frame buffer.
size_t max_size() const { return buffer_.size(); }
// Clears and resets the decoder.
void Clear() {
state_ = State::kInterFrame;
// State enum class is used to make the Decoder a finite state machine.
enum class State {
void Reset() {
current_frame_size_ = 0;
last_read_bytes_index_ = 0;
void AppendByte(std::byte new_byte);
Status CheckFrame() const;
bool VerifyFrameCheckSequence() const;
const ByteSpan buffer_;
// Ring buffer of the last four bytes read into the current frame, to allow
// calculating the frame's CRC incrementally. As data is evicted from this
// buffer, it is added to the running CRC. Once a frame is complete, the
// buffer contains the frame's FCS.
std::array<std::byte, sizeof(uint32_t)> last_read_bytes_;
size_t last_read_bytes_index_;
// Incremental checksum of the current frame.
checksum::Crc32 fcs_;
size_t current_frame_size_;
State state_;
// DecoderBuffers declare a buffer along with a Decoder.
template <size_t kSizeBytes>
class DecoderBuffer : public Decoder {
DecoderBuffer() : Decoder(frame_buffer_) {}
// Returns the maximum length of the bytes that can be inserted in the bytes
// buffer.
static constexpr size_t max_size() { return kSizeBytes; }
static_assert(kSizeBytes >= Frame::kMinSizeBytes);
std::array<std::byte, kSizeBytes> frame_buffer_;
} // namespace pw::hdlc