// 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/encoder.h" #include #include #include #include "gtest/gtest.h" #include "pw_bytes/array.h" #include "pw_hdlc/encoded_size.h" #include "pw_hdlc/internal/encoder.h" #include "pw_hdlc/internal/protocol.h" #include "pw_stream/memory_stream.h" using std::byte; namespace pw::hdlc { namespace { constexpr uint8_t kAddress = 0x7B; // 123 constexpr uint8_t kEncodedAddress = (kAddress << 1) | 1; #define EXPECT_ENCODER_WROTE(...) \ do { \ constexpr auto expected_data = (__VA_ARGS__); \ EXPECT_EQ(writer_.bytes_written(), expected_data.size()); \ EXPECT_EQ( \ std::memcmp( \ writer_.data(), expected_data.data(), writer_.bytes_written()), \ 0); \ } while (0) class WriteUnnumberedFrame : public ::testing::Test { protected: WriteUnnumberedFrame() : writer_(buffer_) {} stream::MemoryWriter writer_; // Allocate a buffer that will fit any 7-byte payload. std::array buffer_; }; constexpr byte kUnnumberedControl = byte{0x3}; TEST_F(WriteUnnumberedFrame, EmptyPayload) { ASSERT_EQ(OkStatus(), WriteUIFrame(kAddress, span(), writer_)); EXPECT_ENCODER_WROTE(bytes::Concat( kFlag, kEncodedAddress, kUnnumberedControl, uint32_t{0x832d343f}, kFlag)); } TEST_F(WriteUnnumberedFrame, OneBytePayload) { ASSERT_EQ(OkStatus(), WriteUIFrame(kAddress, bytes::String("A"), writer_)); EXPECT_ENCODER_WROTE(bytes::Concat(kFlag, kEncodedAddress, kUnnumberedControl, 'A', uint32_t{0x653c9e82}, kFlag)); } TEST_F(WriteUnnumberedFrame, OneBytePayload_Escape0x7d) { ASSERT_EQ(OkStatus(), WriteUIFrame(kAddress, bytes::Array<0x7d>(), writer_)); EXPECT_ENCODER_WROTE(bytes::Concat(kFlag, kEncodedAddress, kUnnumberedControl, kEscape, byte{0x7d} ^ byte{0x20}, uint32_t{0x4a53e205}, kFlag)); } TEST_F(WriteUnnumberedFrame, OneBytePayload_Escape0x7E) { ASSERT_EQ(OkStatus(), WriteUIFrame(kAddress, bytes::Array<0x7e>(), writer_)); EXPECT_ENCODER_WROTE(bytes::Concat(kFlag, kEncodedAddress, kUnnumberedControl, kEscape, byte{0x7e} ^ byte{0x20}, uint32_t{0xd35ab3bf}, kFlag)); } TEST_F(WriteUnnumberedFrame, AddressNeedsEscaping) { // Becomes 0x7d when encoded. constexpr uint8_t kEscapeRequiredAddress = 0x7d >> 1; ASSERT_EQ(OkStatus(), WriteUIFrame(kEscapeRequiredAddress, bytes::String("A"), writer_)); EXPECT_ENCODER_WROTE(bytes::Concat(kFlag, kEscape, byte{0x5d}, kUnnumberedControl, 'A', uint32_t{0x899E00D4}, kFlag)); } TEST_F(WriteUnnumberedFrame, Crc32NeedsEscaping) { ASSERT_EQ(OkStatus(), WriteUIFrame(kAddress, bytes::String("aa"), writer_)); // The CRC-32 of {kEncodedAddress, kUnnumberedControl, "aa"} is 0x7ee04473, so // the 0x7e must be escaped. constexpr auto expected_crc32 = bytes::Array<0x73, 0x44, 0xe0, 0x7d, 0x5e>(); EXPECT_ENCODER_WROTE(bytes::Concat(kFlag, kEncodedAddress, kUnnumberedControl, bytes::String("aa"), expected_crc32, kFlag)); } TEST_F(WriteUnnumberedFrame, MultiplePayloads) { ASSERT_EQ(OkStatus(), WriteUIFrame(kAddress, bytes::String("ABC"), writer_)); ASSERT_EQ(OkStatus(), WriteUIFrame(kAddress, bytes::String("DEF"), writer_)); EXPECT_ENCODER_WROTE(bytes::Concat(kFlag, kEncodedAddress, kUnnumberedControl, bytes::String("ABC"), uint32_t{0x72410ee4}, kFlag, kFlag, kEncodedAddress, kUnnumberedControl, bytes::String("DEF"), uint32_t{0x4ba1ae47}, kFlag)); } TEST_F(WriteUnnumberedFrame, PayloadWithNoEscapes) { ASSERT_EQ( OkStatus(), WriteUIFrame(kAddress, bytes::String("1995 toyota corolla"), writer_)); EXPECT_ENCODER_WROTE(bytes::Concat(kFlag, kEncodedAddress, kUnnumberedControl, bytes::String("1995 toyota corolla"), uint32_t{0x53ee911c}, kFlag)); } TEST_F(WriteUnnumberedFrame, MultibyteAddress) { ASSERT_EQ(OkStatus(), WriteUIFrame(0x3fff, bytes::String("abc"), writer_)); EXPECT_ENCODER_WROTE(bytes::Concat(kFlag, bytes::String("\xfe\xff"), kUnnumberedControl, bytes::String("abc"), uint32_t{0x8cee2978}, kFlag)); } TEST_F(WriteUnnumberedFrame, PayloadWithMultipleEscapes) { ASSERT_EQ( OkStatus(), WriteUIFrame(kAddress, bytes::Array<0x7E, 0x7B, 0x61, 0x62, 0x63, 0x7D, 0x7E>(), writer_)); EXPECT_ENCODER_WROTE(bytes::Concat( kFlag, kEncodedAddress, kUnnumberedControl, bytes:: Array<0x7D, 0x5E, 0x7B, 0x61, 0x62, 0x63, 0x7D, 0x5D, 0x7D, 0x5E>(), uint32_t{0x1563a4e6}, kFlag)); } TEST_F(WriteUnnumberedFrame, PayloadTooLarge_WritesNothing) { constexpr auto data = bytes::Initialized(0x7e); EXPECT_EQ(Status::ResourceExhausted(), WriteUIFrame(kAddress, data, writer_)); EXPECT_EQ(0u, writer_.bytes_written()); } class ErrorWriter : public stream::NonSeekableWriter { private: Status DoWrite(ConstByteSpan) override { return Status::Unimplemented(); } }; TEST(WriteUIFrame, WriterError) { ErrorWriter writer; EXPECT_EQ(Status::Unimplemented(), WriteUIFrame(kAddress, bytes::Array<0x01>(), writer)); } } // namespace } // namespace pw::hdlc