pw_hdlc_lite: Update C++ decoder

- Update the C++ decoder to support an address byte and CRC-32 for the
  frame check sequence.
- Generate C++ tests from the Python decoder test data.
- Move common encode/decode constants to private protocol.h header.

Change-Id: If96e42b2c36b96a093a35600a47f474726056b49
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/18142
Commit-Queue: Wyatt Hepler <hepler@google.com>
Reviewed-by: Keir Mierle <keir@google.com>
diff --git a/pw_hdlc_lite/BUILD b/pw_hdlc_lite/BUILD
index 26bfad1..a4d0014 100644
--- a/pw_hdlc_lite/BUILD
+++ b/pw_hdlc_lite/BUILD
@@ -23,28 +23,29 @@
 
 pw_cc_library(
     name = "pw_hdlc_lite",
-    hdrs = [
-      "public/pw_hdlc_lite/decoder.h",
-      "public/pw_hdlc_lite/encoder.h",
-      "public/pw_hdlc_lite/rpc_server_packets.h",
-      "public/pw_hdlc_lite/sys_io_stream.h",
-      "public/pw_hdlc_lite/hdlc_channel.h",
-    ],
     srcs = [
-      "encoder.cc",
-      "decoder.cc",
-      "hdlc_channel.cc"
+        "decoder.cc",
+        "encoder.cc",
+        "hdlc_channel.cc",
+        "pw_hdlc_lite_private/protocol.h",
+    ],
+    hdrs = [
+        "public/pw_hdlc_lite/decoder.h",
+        "public/pw_hdlc_lite/encoder.h",
+        "public/pw_hdlc_lite/hdlc_channel.h",
+        "public/pw_hdlc_lite/rpc_server_packets.h",
+        "public/pw_hdlc_lite/sys_io_stream.h",
     ],
     includes = ["public"],
     deps = [
+        "//pw_bytes",
+        "//pw_checksum",
         "//pw_log",
+        "//pw_result",
+        "//pw_rpc",
         "//pw_span",
         "//pw_status",
         "//pw_stream",
-        "//pw_checksum",
-        "//pw_bytes",
-        "//pw_result",
-        "//pw_rpc",
     ],
 )
 
@@ -54,20 +55,20 @@
         "hdlc_server_example.cc",
     ],
     hdrs = [
-      "public/pw_hdlc_lite/decoder.h",
-      "public/pw_hdlc_lite/rpc_server_packets.h",
-      "public/pw_hdlc_lite/hdlc_channel.h",
+        "public/pw_hdlc_lite/decoder.h",
+        "public/pw_hdlc_lite/hdlc_channel.h",
+        "public/pw_hdlc_lite/rpc_server_packets.h",
     ],
     includes = ["public"],
     deps = [
+        "//pw_bytes",
+        "//pw_checksum",
         "//pw_log",
+        "//pw_result",
+        "//pw_rpc",
         "//pw_span",
         "//pw_status",
         "//pw_stream",
-        "//pw_checksum",
-        "//pw_bytes",
-        "//pw_result",
-        "//pw_rpc",
     ],
 )
 
@@ -86,9 +87,9 @@
     srcs = ["decoder_test.cc"],
     deps = [
         ":pw_hdlc_lite",
+        "//pw_result",
         "//pw_stream",
         "//pw_unit_test",
-        "//pw_result",
     ],
 )
 
diff --git a/pw_hdlc_lite/BUILD.gn b/pw_hdlc_lite/BUILD.gn
index 276f058..9e498c7 100644
--- a/pw_hdlc_lite/BUILD.gn
+++ b/pw_hdlc_lite/BUILD.gn
@@ -32,6 +32,7 @@
   sources = [
     "decoder.cc",
     "encoder.cc",
+    "pw_hdlc_lite_private/protocol.h",
   ]
   public_deps = [
     dir_pw_bytes,
@@ -44,6 +45,7 @@
     dir_pw_checksum,
     dir_pw_log,
   ]
+  friend = [ ":*" ]
 }
 
 pw_source_set("pw_rpc") {
@@ -94,9 +96,18 @@
   sources = [ "encoder_test.cc" ]
 }
 
+action("generate_decoder_test") {
+  outputs = [ "$target_gen_dir/generated_decoder_test.cc" ]
+  script = "py/decoder_test.py"
+  args = [ "--generate-cc-decode-test" ] + rebase_path(outputs)
+}
+
 pw_test("decoder_test") {
-  deps = [ ":pw_hdlc_lite" ]
-  sources = [ "decoder_test.cc" ]
+  deps = [
+    ":generate_decoder_test",
+    ":pw_hdlc_lite",
+  ]
+  sources = [ "decoder_test.cc" ] + get_target_outputs(":generate_decoder_test")
 }
 
 pw_test("hdlc_channel_test") {
diff --git a/pw_hdlc_lite/decoder.cc b/pw_hdlc_lite/decoder.cc
index fe2a419..37c0023 100644
--- a/pw_hdlc_lite/decoder.cc
+++ b/pw_hdlc_lite/decoder.cc
@@ -14,7 +14,10 @@
 
 #include "pw_hdlc_lite/decoder.h"
 
-#include "pw_checksum/crc16_ccitt.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;
@@ -22,112 +25,114 @@
 namespace pw::hdlc_lite {
 namespace {
 
-constexpr byte kHdlcFrameDelimiter = byte{0x7E};
-constexpr byte kHdlcEscape = byte{0x7D};
-constexpr byte kHdlcUnescapingConstant = byte{0x20};
+constexpr byte kUnescapeConstant = byte{0x20};
 
 }  // namespace
 
-Result<ConstByteSpan> Decoder::AddByte(const byte new_byte) {
+Result<Frame> Decoder::Process(const byte new_byte) {
   switch (state_) {
-    case DecoderState::kPacketActive: {
-      if (new_byte != kHdlcFrameDelimiter) {
-        // Packet active case
-        if (new_byte != kHdlcEscape) {
-          return Result<ConstByteSpan>(AddEscapedByte(new_byte));
+    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;
         }
-        state_ = DecoderState::kEscapeNextByte;
-        return Result<ConstByteSpan>(Status::UNAVAILABLE);
+      } else {
+        // Count bytes to track how many are discarded.
+        current_frame_size_ += 1;
       }
-      // Packet complete case
-      state_ = DecoderState::kNoPacket;
-      Status status = PacketStatus();
-      if (status.ok()) {
-        // Common case: Happy Packet
-        // Returning size_ - 2 is to trim off the 2-byte CRC value.
-        return Result<ConstByteSpan>(frame_buffer_.first(size_ - 2));
-      }
-      if (status == Status::DATA_LOSS && size_ == 0) {
-        // Uncommon case: Dropped an ending frame delimiter byte somewhere.
-        // This happens if a delimiter byte was lost or corrupted which causes
-        // the decoder to fall out of sync with the incoming flow of packets.
-        // Recovery is done by switching to active mode assuming that the frame
-        // delimiter "close" byte we just saw is actually a start delimiter.
-        PW_LOG_ERROR(
-            "Detected empty packet. Assuming out of sync; trying recovery");
-        clear();
-        state_ = DecoderState::kPacketActive;
-        return Result<ConstByteSpan>(Status::UNAVAILABLE);
-      }
-      // Otherwise, forward the status from PacketStatus().
-      return Result<ConstByteSpan>(status);
+      return Status::UNAVAILABLE;  // Report error when starting a new frame.
     }
+    case State::kFrame: {
+      if (new_byte == kFlag) {
+        const Status status = CheckFrame();
 
-    case DecoderState::kEscapeNextByte: {
-      byte escaped_byte = new_byte ^ kHdlcUnescapingConstant;
-      if (escaped_byte != kHdlcEscape && escaped_byte != kHdlcFrameDelimiter) {
-        PW_LOG_WARN(
-            "Suspicious escaped byte: 0x%02x; should only need to escape frame "
-            "delimiter and escape byte",
-            static_cast<int>(escaped_byte));
+        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;
       }
-      state_ = DecoderState::kPacketActive;
-      return Result<ConstByteSpan>(AddEscapedByte(escaped_byte));
+
+      if (new_byte == kEscape) {
+        state_ = State::kFrameEscape;
+      } else {
+        AppendByte(new_byte);
+      }
+      return Status::UNAVAILABLE;
     }
-
-    case DecoderState::kNoPacket: {
-      if (new_byte != kHdlcFrameDelimiter) {
-        PW_LOG_ERROR("Unexpected starting byte to the frame: 0x%02x",
-                     static_cast<int>(new_byte));
-        return Result<ConstByteSpan>(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;
       }
-      clear();
-      state_ = DecoderState::kPacketActive;
-      return Result<ConstByteSpan>(Status::UNAVAILABLE);
+
+      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;
     }
   }
-  return Result<ConstByteSpan>(Status::UNAVAILABLE);
+  PW_CRASH("Bad decoder state");
 }
 
-bool Decoder::CheckCrc() const {
-  uint16_t expected_crc =
-      checksum::Crc16Ccitt::Calculate(frame_buffer_.first(size_ - 2), 0xFFFF);
-  uint16_t actual_crc;
-  std::memcpy(&actual_crc, (frame_buffer_.data() + size_ - 2), 2);
-  return actual_crc == expected_crc;
-}
-
-Status Decoder::AddEscapedByte(const byte new_byte) {
-  if (size_ >= max_size()) {
-    // Increasing the size to flag the overflow case when the packet is complete
-    size_++;
-    return Status::RESOURCE_EXHAUSTED;
+void Decoder::AppendByte(byte new_byte) {
+  if (current_frame_size_ < max_size()) {
+    buffer_[current_frame_size_] = new_byte;
   }
-  frame_buffer_[size_++] = new_byte;
-  return Status::UNAVAILABLE;
+
+  // Always increase size: if it is larger than the buffer, overflow occurred.
+  current_frame_size_ += 1;
 }
 
-Status Decoder::PacketStatus() const {
-  if (size_ < 2) {
-    PW_LOG_ERROR(
-        "Received %d-byte packet; packets must at least have 2 CRC bytes",
-        static_cast<int>(size_));
+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 (size_ > max_size()) {
-    PW_LOG_ERROR("Packet size [%zu] exceeds the maximum buffer size [%zu]",
-                 size_,
-                 max_size());
+  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 (!CheckCrc()) {
-    PW_LOG_ERROR("CRC verification failed for packet");
+  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
diff --git a/pw_hdlc_lite/decoder_test.cc b/pw_hdlc_lite/decoder_test.cc
index 0373727..787fbf1 100644
--- a/pw_hdlc_lite/decoder_test.cc
+++ b/pw_hdlc_lite/decoder_test.cc
@@ -19,306 +19,109 @@
 
 #include "gtest/gtest.h"
 #include "pw_bytes/array.h"
-#include "pw_result/result.h"
-#include "pw_stream/memory_stream.h"
-
-using std::byte;
+#include "pw_hdlc_lite_private/protocol.h"
 
 namespace pw::hdlc_lite {
 namespace {
 
-TEST(DecoderBuffer, 1BytePayload) {
-  std::array<byte, 1> expected_payload = bytes::Array<0x41>();
-  std::array<byte, 5> data_frame = bytes::Array<0x7E, 0x41, 0x15, 0xB9, 0x7E>();
-  DecoderBuffer<10> decoder;
+using std::byte;
 
-  for (size_t i = 0; i < data_frame.size() - 1; i++) {
-    Result<ConstByteSpan> result = decoder.AddByte(data_frame[i]);
-    EXPECT_EQ(result.status(), Status::UNAVAILABLE);
-  }
-  Result<ConstByteSpan> result =
-      decoder.AddByte(data_frame[data_frame.size() - 1]);
-  EXPECT_TRUE(result.ok());
-  EXPECT_EQ(std::memcmp(result.value().data(),
-                        expected_payload.data(),
-                        expected_payload.size()),
-            0);
+TEST(Frame, Fields) {
+  static constexpr auto kFrameData = bytes::String("1234\xa3\xe0\xe3\x9b");
+  constexpr Frame frame(kFrameData);
+
+  static_assert(frame.address() == unsigned{'1'});
+  static_assert(frame.control() == byte{'2'});
+
+  static_assert(frame.data().size() == 2u);
+  static_assert(frame.data()[0] == byte{'3'});
+  static_assert(frame.data()[1] == byte{'4'});
 }
 
-TEST(DecoderBuffer, 0BytePayload) {
-  std::array<byte, 4> data_frame = bytes::Array<0x7E, 0xFF, 0xFF, 0x7E>();
-  DecoderBuffer<10> decoder;
+TEST(Decoder, Clear) {
+  DecoderBuffer<8> decoder;
 
-  for (size_t i = 0; i < data_frame.size() - 1; i++) {
-    Result<ConstByteSpan> result = decoder.AddByte(data_frame[i]);
-    EXPECT_EQ(result.status(), Status::UNAVAILABLE);
-  }
-  Result<ConstByteSpan> result =
-      decoder.AddByte(data_frame[data_frame.size() - 1]);
-  EXPECT_TRUE(result.ok());
-  EXPECT_TRUE(result.value().empty());
+  // Process a partial packet
+  decoder.Process(bytes::String("~1234abcd"),
+                  [](const Result<Frame>&) { FAIL(); });
+
+  decoder.clear();
+  Status status = Status::UNKNOWN;
+
+  decoder.Process(
+      bytes::String("~1234\xa3\xe0\xe3\x9b~"),
+      [&status](const Result<Frame>& result) { status = result.status(); });
+
+  EXPECT_EQ(Status::OK, status);
 }
 
-TEST(DecoderBuffer, 9BytePayload) {
-  std::array<byte, 9> expected_payload =
-      bytes::Array<0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39>();
-  std::array<byte, 13> data_frame = bytes::Array<0x7E,
-                                                 0x31,
-                                                 0x32,
-                                                 0x33,
-                                                 0x34,
-                                                 0x35,
-                                                 0x36,
-                                                 0x37,
-                                                 0x38,
-                                                 0x39,
-                                                 0xB1,
-                                                 0x29,
-                                                 0x7E>();
-  DecoderBuffer<15> decoder;
+TEST(Decoder, ExactFit) {
+  DecoderBuffer<8> decoder;
 
-  for (size_t i = 0; i < data_frame.size() - 1; i++) {
-    Result<ConstByteSpan> result = decoder.AddByte(data_frame[i]);
-    EXPECT_EQ(result.status(), Status::UNAVAILABLE);
+  for (byte b : bytes::String("~1234\xa3\xe0\xe3\x9b")) {
+    EXPECT_EQ(Status::UNAVAILABLE, decoder.Process(b).status());
   }
-  Result<ConstByteSpan> result =
-      decoder.AddByte(data_frame[data_frame.size() - 1]);
-  EXPECT_TRUE(result.ok());
-  EXPECT_EQ(std::memcmp(result.value().data(),
-                        expected_payload.data(),
-                        expected_payload.size()),
-            0);
+  auto result = decoder.Process(kFlag);
+  ASSERT_EQ(Status::OK, result.status());
+  ASSERT_EQ(result.value().data().size(), 2u);
+  ASSERT_EQ(result.value().data()[0], byte{'3'});
+  ASSERT_EQ(result.value().data()[1], byte{'4'});
 }
 
-TEST(DecoderBuffer, MultiFrameDecoding) {
-  std::array<byte, 1> expected_payload = bytes::Array<0x41>();
-  std::array<byte, 10> multiple_data_frames = bytes::
-      Array<0x7E, 0x41, 0x15, 0xB9, 0x7E, 0x7E, 0x41, 0x15, 0xB9, 0x7E>();
-  DecoderBuffer<12> decoder;
+TEST(Decoder, MinimumSizedBuffer) {
+  DecoderBuffer<6> decoder;
 
-  for (size_t i = 0; i < multiple_data_frames.size(); i++) {
-    Result<ConstByteSpan> result = decoder.AddByte(multiple_data_frames[i]);
-    if (i == 4u || i == 9u) {
-      EXPECT_EQ(std::memcmp(result.value().data(),
-                            expected_payload.data(),
-                            expected_payload.size()),
-                0);
-    } else {
-      EXPECT_EQ(result.status(), Status::UNAVAILABLE);
-    }
+  for (byte b : bytes::String("~12\xcd\x44\x53\x4f")) {
+    EXPECT_EQ(Status::UNAVAILABLE, decoder.Process(b).status());
   }
+
+  auto result = decoder.Process(kFlag);
+  ASSERT_EQ(Status::OK, result.status());
+  EXPECT_EQ(result.value().data().size(), 0u);
 }
 
-TEST(DecoderBuffer, UnescapingDataFrame_0x7D) {
-  std::array<byte, 1> expected_payload = bytes::Array<0x7D>();
-  std::array<byte, 6> data_frame =
-      bytes::Array<0x7E, 0x7D, 0x5D, 0xCA, 0x4E, 0x7E>();
-  DecoderBuffer<10> decoder;
+TEST(Decoder, TooLargeForBuffer_ReportsResourceExhausted) {
+  DecoderBuffer<8> decoder;
 
-  for (size_t i = 0; i < data_frame.size() - 1; i++) {
-    Result<ConstByteSpan> result = decoder.AddByte(data_frame[i]);
-    EXPECT_EQ(result.status(), Status::UNAVAILABLE);
+  for (byte b : bytes::String("~123456789")) {
+    EXPECT_EQ(Status::UNAVAILABLE, decoder.Process(b).status());
   }
-  Result<ConstByteSpan> result =
-      decoder.AddByte(data_frame[data_frame.size() - 1]);
-  EXPECT_TRUE(result.ok());
-  EXPECT_EQ(std::memcmp(result.value().data(),
-                        expected_payload.data(),
-                        expected_payload.size()),
-            0);
+  EXPECT_EQ(Status::RESOURCE_EXHAUSTED, decoder.Process(kFlag).status());
+
+  for (byte b : bytes::String("~123456789012345678901234567890")) {
+    EXPECT_EQ(Status::UNAVAILABLE, decoder.Process(b).status());
+  }
+  EXPECT_EQ(Status::RESOURCE_EXHAUSTED, decoder.Process(kFlag).status());
 }
 
-TEST(DecoderBuffer, UnescapingDataFrame_0x7E) {
-  std::array<byte, 1> expected_payload = bytes::Array<0x7E>();
-  std::array<byte, 7> data_frame =
-      bytes::Array<0x7E, 0x7D, 0x5E, 0xA9, 0x7D, 0x5E, 0x7E>();
-  DecoderBuffer<10> decoder;
+TEST(Decoder, TooLargeForBuffer_StaysWithinBufferBoundaries) {
+  std::array<byte, 16> buffer = bytes::Initialized<16>('?');
 
-  for (size_t i = 0; i < data_frame.size() - 1; i++) {
-    Result<ConstByteSpan> result = decoder.AddByte(data_frame[i]);
-    EXPECT_EQ(result.status(), Status::UNAVAILABLE);
+  Decoder decoder(std::span(buffer.data(), 8));
+
+  for (byte b : bytes::String("~1234567890123456789012345678901234567890")) {
+    EXPECT_EQ(Status::UNAVAILABLE, decoder.Process(b).status());
   }
-  Result<ConstByteSpan> result =
-      decoder.AddByte(data_frame[data_frame.size() - 1]);
-  EXPECT_TRUE(result.ok());
-  EXPECT_EQ(std::memcmp(result.value().data(),
-                        expected_payload.data(),
-                        expected_payload.size()),
-            0);
+
+  for (size_t i = 8; i < buffer.size(); ++i) {
+    ASSERT_EQ(byte{'?'}, buffer[i]);
+  }
+
+  EXPECT_EQ(Status::RESOURCE_EXHAUSTED, decoder.Process(kFlag).status());
 }
 
-TEST(DecoderBuffer, UnescapingDataFrame_Mix) {
-  std::array<byte, 7> expected_payload =
-      bytes::Array<0x7E, 0x7B, 0x61, 0x62, 0x63, 0x7D, 0x7E>();
-  ;
-  std::array<byte, 14> data_frame = bytes::Array<0x7E,
-                                                 0x7D,
-                                                 0x5E,
-                                                 0x7B,
-                                                 0x61,
-                                                 0x62,
-                                                 0x63,
-                                                 0x7D,
-                                                 0x5D,
-                                                 0x7D,
-                                                 0x5E,
-                                                 0x49,
-                                                 0xE5,
-                                                 0x7E>();
-  DecoderBuffer<15> decoder;
+TEST(Decoder, TooLargeForBuffer_DecodesNextFrame) {
+  DecoderBuffer<8> decoder;
 
-  for (size_t i = 0; i < data_frame.size() - 1; i++) {
-    Result<ConstByteSpan> result = decoder.AddByte(data_frame[i]);
-    EXPECT_EQ(result.status(), Status::UNAVAILABLE);
+  for (byte b : bytes::String("~123456789012345678901234567890")) {
+    EXPECT_EQ(Status::UNAVAILABLE, decoder.Process(b).status());
   }
-  Result<ConstByteSpan> result =
-      decoder.AddByte(data_frame[data_frame.size() - 1]);
-  EXPECT_TRUE(result.ok());
-  EXPECT_EQ(std::memcmp(result.value().data(),
-                        expected_payload.data(),
-                        expected_payload.size()),
-            0);
-}
+  EXPECT_EQ(Status::RESOURCE_EXHAUSTED, decoder.Process(kFlag).status());
 
-TEST(DecoderBuffer, IncorrectCRC) {
-  std::array<byte, 1> expected_payload = bytes::Array<0x41>();
-  std::array<byte, 5> incorrect_data_frame =
-      bytes::Array<0x7E, 0x41, 0x15, 0xB8, 0x7E>();
-  std::array<byte, 5> correct_data_frame =
-      bytes::Array<0x7E, 0x41, 0x15, 0xB9, 0x7E>();
-  DecoderBuffer<10> decoder;
-
-  for (size_t i = 0; i < incorrect_data_frame.size(); i++) {
-    Result<ConstByteSpan> result = decoder.AddByte(incorrect_data_frame[i]);
-    if (i == incorrect_data_frame.size() - 1) {
-      EXPECT_EQ(result.status(), Status::DATA_LOSS);
-    } else {
-      EXPECT_EQ(result.status(), Status::UNAVAILABLE);
-    }
+  for (byte b : bytes::String("1234\xa3\xe0\xe3\x9b")) {
+    EXPECT_EQ(Status::UNAVAILABLE, decoder.Process(b).status());
   }
-
-  for (size_t i = 0; i < correct_data_frame.size(); i++) {
-    Result<ConstByteSpan> result = decoder.AddByte(correct_data_frame[i]);
-    if (i == 4u) {
-      EXPECT_TRUE(result.ok());
-      EXPECT_EQ(std::memcmp(result.value().data(),
-                            expected_payload.data(),
-                            expected_payload.size()),
-                0);
-    } else {
-      EXPECT_EQ(result.status(), Status::UNAVAILABLE);
-    }
-  }
-}
-
-TEST(DecoderBuffer, BufferSizeLessThan2EdgeCase) {
-  std::array<byte, 3> data_frame = bytes::Array<0x7E, 0xFF, 0x7E>();
-  DecoderBuffer<10> decoder;
-
-  for (size_t i = 0; i < data_frame.size(); i++) {
-    Result<ConstByteSpan> result = decoder.AddByte(data_frame[i]);
-    if (i == data_frame.size() - 1) {
-      EXPECT_EQ(result.status(), Status::DATA_LOSS);
-    } else {
-      EXPECT_EQ(result.status(), Status::UNAVAILABLE);
-    }
-  }
-}
-
-TEST(DecoderBuffer, BufferOutOfSpace_SkipsRestOfFrame) {
-  std::array<byte, 5> data_frame = bytes::Array<0x7E, 0x41, 0x15, 0xB9, 0x7E>();
-  DecoderBuffer<2> decoder;
-
-  for (size_t i = 0; i < data_frame.size(); i++) {
-    Result<ConstByteSpan> result = decoder.AddByte(data_frame[i]);
-    if (i >= 3u) {
-      EXPECT_EQ(result.status(), Status::RESOURCE_EXHAUSTED);
-    } else {
-      EXPECT_EQ(result.status(), Status::UNAVAILABLE);
-    }
-  }
-}
-
-TEST(DecoderBuffer, BufferOutOfSpace_SkipsRestOfFrameAndDecodesNext) {
-  std::array<byte, 1> expected_payload = bytes::Array<0x41>();
-  std::array<byte, 11> data_frames = bytes::
-      Array<0x7E, 0x41, 0x42, 0x15, 0xB9, 0x7E, 0x7E, 0x41, 0x15, 0xB9, 0x7E>();
-  DecoderBuffer<3> decoder;
-
-  for (size_t i = 0; i < data_frames.size(); i++) {
-    Result<ConstByteSpan> result = decoder.AddByte(data_frames[i]);
-    if (i == 4u || i == 5u) {
-      EXPECT_EQ(result.status(), Status::RESOURCE_EXHAUSTED);
-    } else if (i == 10u) {
-      EXPECT_EQ(std::memcmp(result.value().data(),
-                            expected_payload.data(),
-                            expected_payload.size()),
-                0);
-    } else {
-      EXPECT_EQ(result.status(), Status::UNAVAILABLE);
-    }
-  }
-}
-
-TEST(DecoderBuffer, UnexpectedStartingByte) {
-  std::array<byte, 1> expected_payload = bytes::Array<0x41>();
-  std::array<byte, 12> data_frames = bytes::Array<0x7E,
-                                                  0x41,
-                                                  0x15,
-                                                  0xB9,
-                                                  0x7E,  // End of 1st Packet
-                                                  0xAA,  // Garbage bytes
-                                                  0xAA,  // Garbage bytes
-                                                  0x7E,
-                                                  0x41,
-                                                  0x15,
-                                                  0xB9,
-                                                  0x7E>();  // End of 2nd Packet
-  DecoderBuffer<10> decoder;
-
-  for (size_t i = 0; i < data_frames.size(); i++) {
-    Result<ConstByteSpan> result = decoder.AddByte(data_frames[i]);
-    if (i == 4u || i == 11u) {
-      EXPECT_TRUE(result.ok());
-      EXPECT_EQ(std::memcmp(result.value().data(),
-                            expected_payload.data(),
-                            expected_payload.size()),
-                0);
-    } else {
-      EXPECT_EQ(result.status(), Status::UNAVAILABLE);
-    }
-  }
-}
-
-TEST(DecoderBuffer, RecoveringMissingFrameDelimiterCase) {
-  std::array<byte, 1> expected_payload = bytes::Array<0x41>();
-  std::array<byte, 12> data_frames =
-      bytes::Array<0x7E,
-                   0x41,
-                   0x15,
-                   0xB9,
-                   0x7E,  // End of 1st Packet
-                   0x41,
-                   0x7E,  // End of Packet with missing start frame delimiter
-                   0x7E,
-                   0x41,
-                   0x15,
-                   0xB9,
-                   0x7E>();  // End of 2nd Packet
-  DecoderBuffer<10> decoder;
-
-  for (size_t i = 0; i < data_frames.size(); i++) {
-    Result<ConstByteSpan> result = decoder.AddByte(data_frames[i]);
-    if (i == 4u || i == 11u) {
-      EXPECT_TRUE(result.ok());
-      EXPECT_EQ(std::memcmp(result.value().data(),
-                            expected_payload.data(),
-                            expected_payload.size()),
-                0);
-    } else {
-      EXPECT_EQ(result.status(), Status::UNAVAILABLE);
-    }
-  }
+  EXPECT_EQ(Status::OK, decoder.Process(kFlag).status());
 }
 
 }  // namespace
diff --git a/pw_hdlc_lite/encoder.cc b/pw_hdlc_lite/encoder.cc
index d47fd04..dde2556 100644
--- a/pw_hdlc_lite/encoder.cc
+++ b/pw_hdlc_lite/encoder.cc
@@ -22,33 +22,26 @@
 
 #include "pw_bytes/endian.h"
 #include "pw_checksum/crc32.h"
+#include "pw_hdlc_lite_private/protocol.h"
 
 using std::byte;
 
 namespace pw::hdlc_lite {
 namespace {
 
-constexpr byte kHdlcFlag = byte{0x7E};
-constexpr byte kHdlcEscape = byte{0x7D};
-
 // Indicates this an information packet with sequence numbers set to 0.
 constexpr byte kUnusedControl = byte{0};
 
-constexpr std::array<byte, 2> kEscapedFlag = {byte{0x7D}, byte{0x5E}};
-constexpr std::array<byte, 2> kEscapedEscape = {byte{0x7D}, byte{0x5D}};
-
 Status EscapeAndWrite(const byte b, stream::Writer& writer) {
-  if (b == kHdlcFlag) {
+  if (b == kFlag) {
     return writer.Write(kEscapedFlag);
   }
-  if (b == kHdlcEscape) {
+  if (b == kEscape) {
     return writer.Write(kEscapedEscape);
   }
   return writer.Write(b);
 }
 
-bool NeedsEscaping(byte b) { return (b == kHdlcFlag || b == kHdlcEscape); }
-
 // Encodes and writes HDLC frames.
 class Encoder {
  public:
@@ -72,7 +65,7 @@
 
 Status Encoder::StartInformationFrame(uint8_t address) {
   fcs_.clear();
-  if (Status status = writer_.Write(kHdlcFlag); !status.ok()) {
+  if (Status status = writer_.Write(kFlag); !status.ok()) {
     return status;
   }
 
@@ -105,7 +98,7 @@
       !status.ok()) {
     return status;
   }
-  return writer_.Write(kHdlcFlag);
+  return writer_.Write(kFlag);
 }
 
 }  // namespace
diff --git a/pw_hdlc_lite/encoder_test.cc b/pw_hdlc_lite/encoder_test.cc
index d5d0848..0321c86 100644
--- a/pw_hdlc_lite/encoder_test.cc
+++ b/pw_hdlc_lite/encoder_test.cc
@@ -20,6 +20,7 @@
 
 #include "gtest/gtest.h"
 #include "pw_bytes/array.h"
+#include "pw_hdlc_lite_private/protocol.h"
 #include "pw_stream/memory_stream.h"
 
 using std::byte;
@@ -27,8 +28,6 @@
 namespace pw::hdlc_lite {
 namespace {
 
-constexpr byte kFlag = byte{0x7E};
-constexpr byte kEscape = byte{0x7D};
 constexpr uint8_t kAddress = 0x7B;  // 123
 constexpr byte kControl = byte{0};
 
diff --git a/pw_hdlc_lite/public/pw_hdlc_lite/decoder.h b/pw_hdlc_lite/public/pw_hdlc_lite/decoder.h
index 5c03977..1b88bb5 100644
--- a/pw_hdlc_lite/public/pw_hdlc_lite/decoder.h
+++ b/pw_hdlc_lite/public/pw_hdlc_lite/decoder.h
@@ -17,6 +17,7 @@
 #include <array>
 #include <cstddef>
 #include <cstring>
+#include <functional>  // std::invoke
 
 #include "pw_bytes/span.h"
 #include "pw_result/result.h"
@@ -24,6 +25,45 @@
 
 namespace pw::hdlc_lite {
 
+// 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 single-byte address and control fields and a
+// 32-bit frame check sequence (FCS).
+class Frame {
+ private:
+  static constexpr size_t kAddressSize = 1;
+  static constexpr size_t kControlSize = 1;
+  static constexpr size_t kFcsSize = sizeof(uint32_t);
+
+ public:
+  // The minimum size of a frame, excluding control bytes (flag or escape).
+  static constexpr size_t kMinSizeBytes =
+      kAddressSize + kControlSize + kFcsSize;
+
+  // Creates a Frame with the specified data. The data MUST be valid frame data
+  // with a verified frame check sequence.
+  explicit constexpr Frame(ConstByteSpan data) : frame_(data) {
+    // TODO(pwbug/246): Use PW_DASSERT when available.
+    // PW_DASSERT(data.size() >= kMinSizeBytes);
+  }
+
+  constexpr unsigned address() const {
+    return std::to_integer<unsigned>(frame_[0]);
+  }
+
+  constexpr std::byte control() const { return frame_[kAddressSize]; }
+
+  constexpr ConstByteSpan data() const {
+    return frame_.subspan(kAddressSize + kControlSize,
+                          frame_.size() - kMinSizeBytes);
+  }
+
+ private:
+  ConstByteSpan frame_;
+};
+
 // The Decoder class facilitates decoding of data frames using the HDLC-Lite
 // protocol, by returning packets as they are decoded and storing incomplete
 // data frames in a buffer.
@@ -34,80 +74,64 @@
 class Decoder {
  public:
   constexpr Decoder(ByteSpan buffer)
-      : frame_buffer_(buffer), state_(DecoderState::kNoPacket), size_(0) {}
+      : buffer_(buffer), current_frame_size_(0), state_(State::kInterFrame) {}
 
-  // Parse a single byte of a HDLC stream. Returns a result object with the
-  // complete packet if the latest byte finishes a frame, or a variety of
-  // statuses in other cases, as follows:
-  //
-  //     OK - If the end of the data-frame was found and the packet was decoded
-  //         successfully. The value of this Result<ConstByteSpan> object will
-  //         be the payload.
-  //     RESOURCE_EXHAUSTED - If the number of bytes added to the Decoder is
-  //         greater than the buffer size. This function call will clear the
-  //         decoder before returning.
-  //     UNAVAILABLE - If the byte has been successfully escaped and added to
-  //         the buffer, but we havent reached the end of the data-frame.
-  //     DATA_LOSS - If the CRC verification process fails after seeing the
-  //         ending Frame Delimiter byte (0x7E). Additionally happens when the
-  //         FCS is not in the payload or when the data-frame does not start
-  //         with the initial Frame Delimiter byte (0x7E).
-  //
-  Result<ConstByteSpan> AddByte(const std::byte b);
-
-  // Returns the number of bytes of the active packet that are added to the
-  // frame buffer.
-  size_t size() const { return size_; }
-
-  // Returns the maximum size of the Decoder's frame buffer.
-  size_t max_size() const { return frame_buffer_.size(); }
-
-  // Clears the frame buffer at the beginning of decoding the next packet.
-  void clear() { size_ = 0; };
-
-  // Indicates if the decoder is currently in the process of decoding a packet.
-  bool IsPacketActive() { return state_ != DecoderState::kNoPacket; }
-
- private:
-  // DecoderState enum class is used to make the Decoder a finite state machine.
-  enum class DecoderState {
-    kNoPacket,
-    kPacketActive,
-    kEscapeNextByte,
-  };
-
-  // Disallow Copy and Assign.
   Decoder(const Decoder&) = delete;
-
   Decoder& operator=(const Decoder&) = delete;
 
-  // Will return true if the CRC is successfully verified.
-  bool CheckCrc() const;
-
-  // Attempts to write the escaped byte to the buffer and returns a Status
-  // object accordingly:
+  // 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:
   //
-  //     RESOURCE_EXHAUSTED - If the buffer is out of space.
-  //     UNAVAILABLE - If the byte has been successfully added to the buffer.
+  //     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.
   //
-  Status AddEscapedByte(std::byte new_byte);
+  Result<Frame> Process(std::byte b);
 
-  // Ensures the packet is correctly decoded and returns a status object
-  // indicating if the packet can be returned. The three checks it does are:
-  //     1. Checks if there are packet meets the minimum size of a HDLC-Lite
-  //        packet.
-  //     2. Checks that the frame buffer wasnt overflowed.
-  //     3. Verifies if the CRC is correct
-  // Will log errors accordingly.
-  //
-  Status PacketStatus() const;
+  // 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::invoke(
+            std::forward<F>(callback), std::forward<Args>(args)..., result);
+      }
+    }
+  }
 
-  ByteSpan frame_buffer_;
-  DecoderState state_;
+  // Returns the maximum size of the Decoder's frame buffer.
+  size_t max_size() const { return buffer_.size(); }
 
-  // The size_ variable represents the number of decoded bytes of the current
-  // active packet.
-  size_t size_;
+  // Clears and resets the decoder.
+  void clear() {
+    current_frame_size_ = 0;
+    state_ = State::kInterFrame;
+  };
+
+ private:
+  // State enum class is used to make the Decoder a finite state machine.
+  enum class State {
+    kInterFrame,
+    kFrame,
+    kFrameEscape,
+  };
+
+  void AppendByte(std::byte new_byte);
+
+  Status CheckFrame() const;
+
+  bool VerifyFrameCheckSequence() const;
+
+  const ByteSpan buffer_;
+
+  size_t current_frame_size_;
+
+  State state_;
 };
 
 // DecoderBuffers declare a buffer along with a Decoder.
@@ -121,6 +145,8 @@
   static constexpr size_t max_size() { return size_bytes; }
 
  private:
+  static_assert(size_bytes >= Frame::kMinSizeBytes);
+
   std::array<std::byte, size_bytes> frame_buffer_;
 };
 
diff --git a/pw_hdlc_lite/pw_hdlc_lite_private/protocol.h b/pw_hdlc_lite/pw_hdlc_lite_private/protocol.h
new file mode 100644
index 0000000..25159dbc
--- /dev/null
+++ b/pw_hdlc_lite/pw_hdlc_lite_private/protocol.h
@@ -0,0 +1,32 @@
+// 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.
+#pragma once
+
+#include <cstddef>
+
+namespace pw::hdlc_lite {
+
+inline constexpr std::byte kFlag = std::byte{0x7E};
+inline constexpr std::byte kEscape = std::byte{0x7D};
+
+inline constexpr std::array<std::byte, 2> kEscapedFlag = {kEscape,
+                                                          std::byte{0x5E}};
+inline constexpr std::array<std::byte, 2> kEscapedEscape = {kEscape,
+                                                            std::byte{0x5D}};
+
+constexpr bool NeedsEscaping(std::byte b) {
+  return (b == kFlag || b == kEscape);
+}
+
+}  // namespace pw::hdlc_lite
diff --git a/pw_hdlc_lite/py/decoder_test.py b/pw_hdlc_lite/py/decoder_test.py
index df801eb..ec1e52c 100755
--- a/pw_hdlc_lite/py/decoder_test.py
+++ b/pw_hdlc_lite/py/decoder_test.py
@@ -15,10 +15,13 @@
 """Contains the Python decoder tests and generates C++ decoder tests."""
 
 from collections import defaultdict
-from typing import Any, Callable, Dict, List, NamedTuple, Tuple, Union
+from pathlib import Path
+from typing import Any, Callable, Dict, Iterator, List, NamedTuple, Union
+from typing import Optional, TextIO, Tuple
 import unittest
+import sys
 
-from pw_hdlc_lite.decoder import FrameDecoder, FrameStatus, NO_ADDRESS
+from pw_hdlc_lite.decoder import Frame, FrameDecoder, FrameStatus, NO_ADDRESS
 from pw_hdlc_lite.protocol import frame_check_sequence as fcs
 
 
@@ -54,7 +57,6 @@
 TestCase = Tuple[bytes, List[Expected]]
 TestCases = Tuple[Union[str, TestCase], ...]
 
-
 TEST_CASES: TestCases = (
     'Empty payload',
     (_encode(0, 0, b''), [Expected(0, b'\0', b'')]),
@@ -137,6 +139,15 @@
      [Expected(1, b'\2', b'', FrameStatus.INCOMPLETE)]),
     (b'\x7e\1\2abcd1234\x7d\x7e',
      [Expected(1, b'\2', b'abcd', FrameStatus.INCOMPLETE)]),
+    'Inter-frame data is only escapes',
+    (b'\x7e\x7d\x7e\x7d\x7e', [
+        Expected(NO_ADDRESS, b'', b'', FrameStatus.INCOMPLETE),
+        Expected(NO_ADDRESS, b'', b'', FrameStatus.INCOMPLETE),
+    ]),
+    (b'\x7e\x7d\x7d\x7e\x7d\x7d\x7e', [
+        Expected(NO_ADDRESS, b'', b'', FrameStatus.INVALID_ESCAPE),
+        Expected(NO_ADDRESS, b'', b'', FrameStatus.INVALID_ESCAPE),
+    ]),
     'Data before first flag',
     (b'\0\1' + fcs(b'\0\1'), []),
     (b'\0\1' + fcs(b'\0\1') + b'\x7e',
@@ -161,6 +172,129 @@
     return cases
 
 
+def _expected(frames: List[Frame]) -> Iterator[str]:
+    for i, frame in enumerate(frames, 1):
+        if frame.ok():
+            yield f'      Frame(kDecodedFrame{i:02}),'
+        else:
+            yield f'      Status::DATA_LOSS,  // Frame {i}'
+
+
+_CPP_HEADER = f"""\
+// 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.
+
+// AUTOGENERATED by {Path(__file__).name} - DO NOT EDIT
+
+#include "pw_hdlc_lite/decoder.h"
+
+#include <array>
+#include <cstddef>
+#include <variant>
+
+#include "gtest/gtest.h"
+#include "pw_bytes/array.h"
+
+namespace pw::hdlc_lite {{
+namespace {{
+
+// clang-format off
+"""
+
+_CPP_FOOTER = """\
+// clang-format on
+
+}  // namespace
+}  // namespace pw::hdlc_lite
+"""
+
+
+def _cpp_test(group: str, count: Optional[int], data: bytes) -> Iterator[str]:
+    """Generates a C++ test for the provided test data."""
+    frames = list(FrameDecoder().process(data))
+    data_bytes = ''.join(rf'\x{byte:02x}' for byte in data)
+
+    name = ''.join(w.capitalize() for w in group.replace('-', ' ').split(' '))
+    name = ''.join(c if c.isalnum() else '_' for c in name)
+    name = name if count is None else name + f'_{count}'
+
+    yield f'TEST(Decoder, {name}) {{'
+    yield f'  static constexpr auto kData = bytes::String("{data_bytes}");\n'
+
+    for i, frame in enumerate(frames, 1):
+        if frame.status is FrameStatus.OK:
+            frame_bytes = ''.join(rf'\x{byte:02x}' for byte in frame.raw)
+            yield (f'  static constexpr auto kDecodedFrame{i:02} = '
+                   f'bytes::String("{frame_bytes}");')
+        else:
+            yield f'  // Frame {i}: {frame.status.value}'
+
+    yield ''
+
+    expected = '\n'.join(_expected(frames)) or '      // No frames'
+    decoder_size = max(len(data), 8)  # Make sure large enough for a frame
+
+    yield f"""\
+  DecoderBuffer<{decoder_size}> decoder;
+
+  static constexpr std::array<std::variant<Frame, Status>, {len(frames)}> kExpected = {{
+{expected}
+  }};
+
+  size_t decoded_frames = 0;
+
+  decoder.Process(kData, [&](const Result<Frame>& result) {{
+    ASSERT_LT(decoded_frames++, kExpected.size());
+    auto& expected = kExpected[decoded_frames - 1];
+
+    if (std::holds_alternative<Status>(expected)) {{
+      EXPECT_EQ(Status::DATA_LOSS, result.status());
+    }} else {{
+      ASSERT_EQ(Status::OK, result.status());
+
+      const Frame& decoded_frame = result.value();
+      const Frame& expected_frame = std::get<Frame>(expected);
+      EXPECT_EQ(expected_frame.address(), decoded_frame.address());
+      EXPECT_EQ(expected_frame.control(), decoded_frame.control());
+      ASSERT_EQ(expected_frame.data().size(), decoded_frame.data().size());
+      EXPECT_EQ(std::memcmp(expected_frame.data().data(),
+                            decoded_frame.data().data(),
+                            expected_frame.data().size()),
+                0);
+    }}
+  }});
+
+  EXPECT_EQ(decoded_frames, kExpected.size());
+}}"""
+
+
+def _define_cc_tests(test_cases: TestCases, output: TextIO) -> None:
+    """Writes C++ tests for all test cases."""
+    output.write(_CPP_HEADER)
+
+    for group, test_list in _sort_test_cases(test_cases).items():
+        for i, (data, _) in enumerate(test_list, 1):
+            count = i if len(test_list) > 1 else None
+            for line in _cpp_test(group, count, data):
+                output.write(line)
+                output.write('\n')
+
+            output.write('\n')
+
+    output.write(_CPP_FOOTER)
+
+
 def _define_py_test(group: str,
                     data: bytes,
                     expected_frames: List[Expected],
@@ -205,4 +339,9 @@
 DecoderTest = type('DecoderTest', (unittest.TestCase, ), _define_py_tests())
 
 if __name__ == '__main__':
-    unittest.main()
+    # If --generate-cc-decode-test is provided, generate the C++ test file.
+    if len(sys.argv) >= 2 and sys.argv[1] == '--generate-cc-decode-test':
+        with Path(sys.argv[2]).open('w') as file:
+            _define_cc_tests(TEST_CASES, file)
+    else:  # Otherwise, run the unit tests.
+        unittest.main()