| // 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 |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // 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. |
| |
| #include "pw_hdlc_lite/decoder.h" |
| |
| #include "pw_assert/assert.h" |
| #include "pw_bytes/endian.h" |
| #include "pw_checksum/crc32.h" |
| #include "pw_hdlc_lite_private/protocol.h" |
| #include "pw_log/log.h" |
| |
| using std::byte; |
| |
| namespace pw::hdlc_lite { |
| namespace { |
| |
| constexpr byte kUnescapeConstant = byte{0x20}; |
| |
| } // namespace |
| |
| Result<Frame> Decoder::Process(const byte new_byte) { |
| switch (state_) { |
| case State::kInterFrame: { |
| if (new_byte == kFlag) { |
| state_ = State::kFrame; |
| |
| // Report an error if non-flag bytes were read between frames. |
| if (current_frame_size_ != 0u) { |
| current_frame_size_ = 0; |
| return Status::DATA_LOSS; |
| } |
| } else { |
| // Count bytes to track how many are discarded. |
| current_frame_size_ += 1; |
| } |
| return Status::UNAVAILABLE; // Report error when starting a new frame. |
| } |
| case State::kFrame: { |
| if (new_byte == kFlag) { |
| const Status status = CheckFrame(); |
| |
| state_ = State::kFrame; |
| const size_t completed_frame_size = current_frame_size_; |
| current_frame_size_ = 0; |
| |
| if (status.ok()) { |
| return Frame(buffer_.first(completed_frame_size)); |
| } |
| return status; |
| } |
| |
| if (new_byte == kEscape) { |
| state_ = State::kFrameEscape; |
| } else { |
| AppendByte(new_byte); |
| } |
| return Status::UNAVAILABLE; |
| } |
| case State::kFrameEscape: { |
| // The flag character cannot be escaped; return an error. |
| if (new_byte == kFlag) { |
| state_ = State::kFrame; |
| current_frame_size_ = 0; |
| return Status::DATA_LOSS; |
| } |
| |
| if (new_byte == kEscape) { |
| // Two escape characters in a row is illegal -- invalidate this frame. |
| // The frame is reported abandoned when the next flag byte appears. |
| state_ = State::kInterFrame; |
| |
| // Count the escape byte so that the inter-frame state detects an error. |
| current_frame_size_ += 1; |
| } else { |
| state_ = State::kFrame; |
| AppendByte(new_byte ^ kUnescapeConstant); |
| } |
| return Status::UNAVAILABLE; |
| } |
| } |
| PW_CRASH("Bad decoder state"); |
| } |
| |
| void Decoder::AppendByte(byte new_byte) { |
| if (current_frame_size_ < max_size()) { |
| buffer_[current_frame_size_] = new_byte; |
| } |
| |
| // Always increase size: if it is larger than the buffer, overflow occurred. |
| current_frame_size_ += 1; |
| } |
| |
| Status Decoder::CheckFrame() const { |
| // Empty frames are not an error; repeated flag characters are okay. |
| if (current_frame_size_ == 0u) { |
| return Status::UNAVAILABLE; |
| } |
| |
| if (current_frame_size_ < Frame::kMinSizeBytes) { |
| PW_LOG_ERROR("Received %lu-byte frame; frame must be at least 6 bytes", |
| static_cast<unsigned long>(current_frame_size_)); |
| return Status::DATA_LOSS; |
| } |
| |
| if (current_frame_size_ > max_size()) { |
| PW_LOG_ERROR("Frame size [%lu] exceeds the maximum buffer size [%lu]", |
| static_cast<unsigned long>(current_frame_size_), |
| static_cast<unsigned long>(max_size())); |
| return Status::RESOURCE_EXHAUSTED; |
| } |
| |
| if (!VerifyFrameCheckSequence()) { |
| PW_LOG_ERROR("Frame check sequence verification failed"); |
| return Status::DATA_LOSS; |
| } |
| |
| return Status::OK; |
| } |
| |
| bool Decoder::VerifyFrameCheckSequence() const { |
| uint32_t fcs = bytes::ReadInOrder<uint32_t>( |
| std::endian::little, buffer_.data() + current_frame_size_ - sizeof(fcs)); |
| return fcs == checksum::Crc32::Calculate( |
| buffer_.first(current_frame_size_ - sizeof(fcs))); |
| } |
| |
| } // namespace pw::hdlc_lite |