Export balsa_frame_test.cc and balsa_headers_test.cc publicly.

PiperOrigin-RevId: 445194714
diff --git a/quiche/common/balsa/balsa_frame_test.cc b/quiche/common/balsa/balsa_frame_test.cc
new file mode 100644
index 0000000..e99d6f6
--- /dev/null
+++ b/quiche/common/balsa/balsa_frame_test.cc
@@ -0,0 +1,3703 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quiche/common/balsa/balsa_frame.h"
+
+#include <stdlib.h>
+
+#include <cstdint>
+#include <limits>
+#include <map>
+#include <random>
+#include <sstream>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "absl/flags/flag.h"
+#include "absl/strings/escaping.h"
+#include "absl/strings/numbers.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/str_format.h"
+#include "absl/strings/str_replace.h"
+#include "absl/strings/string_view.h"
+#include "quiche/common/balsa/balsa_enums.h"
+#include "quiche/common/balsa/balsa_headers.h"
+#include "quiche/common/balsa/balsa_visitor_interface.h"
+#include "quiche/common/balsa/http_validation_policy.h"
+#include "quiche/common/balsa/noop_balsa_visitor.h"
+#include "quiche/common/balsa/simple_buffer.h"
+#include "quiche/common/platform/api/quiche_expect_bug.h"
+#include "quiche/common/platform/api/quiche_logging.h"
+#include "quiche/common/platform/api/quiche_test.h"
+
+using testing::_;
+using testing::AnyNumber;
+using testing::AtLeast;
+using testing::InSequence;
+using testing::IsEmpty;
+using testing::Mock;
+using testing::NiceMock;
+using testing::Range;
+using testing::StrEq;
+using testing::StrictMock;
+
+ABSL_FLAG(std::string, randseed, "",
+          "This is the seed for Pseudo-random number"
+          " generator used when generating random messages for unittests");
+
+namespace quiche {
+
+namespace test {
+
+// This random engine from the standard library supports initialization with a
+// seed, which is helpful for reproducing any unit test failures that are due to
+// random sequence variation.
+using RandomEngine = std::mt19937;
+
+class BalsaFrameTestPeer {
+ public:
+  static int32_t HeaderFramingFound(BalsaFrame* balsa_frame, char c) {
+    return balsa_frame->HeaderFramingFound(c);
+  }
+
+  static void FindColonsAndParseIntoKeyValue(BalsaFrame* balsa_frame,
+                                             const BalsaFrame::Lines& lines,
+                                             bool is_trailer,
+                                             BalsaHeaders* headers) {
+    balsa_frame->FindColonsAndParseIntoKeyValue(lines, is_trailer, headers);
+  }
+};
+
+class BalsaHeadersTestPeer {
+ public:
+  static void WriteFromFramer(BalsaHeaders* headers, const char* ptr,
+                              size_t size) {
+    headers->WriteFromFramer(ptr, size);
+  }
+};
+
+namespace {
+
+// This class encapsulates the policy of seed selection. If user supplies a
+// valid use via the --randseed flag, GetSeed will only return the user
+// supplied seed value. This is useful in reproducing bugs reported by the
+// test. If an invalid seed value is supplied (likely due to bad numeric
+// format), the test will abort (since this mode tend to be used for debugging,
+// it is better to die early so the user knows a bad value is supplied). If no
+// seed is supplied, the value supplied by ACMRandom::HostnamePidTimeSeed() is
+// used. This class is supposed to be a singleton, but there is no ill-effect if
+// multiple instances are created (although that tends not to be what the user
+// wants).
+class TestSeed {
+ public:
+  TestSeed() : test_seed_(0), user_supplied_seed_(false) {}
+
+  void Initialize(const std::string& seed_flag) {
+    if (!seed_flag.empty()) {
+      ASSERT_TRUE(absl::SimpleAtoi(seed_flag, &test_seed_));
+      user_supplied_seed_ = true;
+    }
+  }
+
+  int GetSeed() const {
+    int seed =
+        (user_supplied_seed_ ? test_seed_
+                             : testing::UnitTest::GetInstance()->random_seed());
+    QUICHE_LOG(INFO) << "**** The current seed is " << seed << " ****";
+    return seed;
+  }
+
+ private:
+  int test_seed_;
+  bool user_supplied_seed_;
+};
+
+static bool RandomBool(RandomEngine& rng) { return rng() % 2 != 0; }
+
+std::string EscapeString(absl::string_view message) {
+  return absl::StrReplaceAll(
+      message, {{"\n", "\\\\n\n"}, {"\\r", "\\\\r"}, {"\\t", "\\\\t"}});
+}
+
+char random_lws(RandomEngine& rng) {
+  if (RandomBool(rng)) {
+    return '\t';
+  }
+  return ' ';
+}
+
+const char* random_line_term(RandomEngine& rng) {
+  if (RandomBool(rng)) {
+    return "\r\n";
+  }
+  return "\n";
+}
+
+void AppendRandomWhitespace(RandomEngine& rng, std::stringstream* s) {
+  // Appending a random amount of whitespace to the unparsed value. There is a
+  // max of 1000 pieces of whitespace that will be attached, however, it is
+  // extremely unlikely (1 in 2^1000) that we'll hit this limit, as we have a
+  // 50% probability of exiting the loop at any point in time.
+  for (int i = 0; i < 1000 && RandomBool(rng); ++i) {
+    *s << random_lws(rng);
+  }
+}
+
+// Creates an HTTP message firstline from the given inputs.
+//
+// tokens - The list of nonwhitespace tokens (which should later be parsed out
+//          from the firstline).
+// whitespace - the whitespace that occurs before, between, and
+//              after the tokens. Note that the last whitespace
+//              character should -not- include any '\n'.
+// line_ending - one of "\n" or "\r\n"
+//
+// whitespace[0] occurs before the first token.
+// whitespace[1] occurs between the first and second token
+// whitespace[2] occurs between the second and third token
+// whitespace[3] occurs between the third token and the line_ending.
+//
+// This code:
+//   const char tokens[3] = {"GET", "/", "HTTP/1.0"};
+//   const char whitespace[4] = { "\n\n", " ", "\t", "\t"};
+//   const char line_ending = "\r\n";
+//   CreateFirstLine(tokens, whitespace, line_ending) ->
+// Would yield the following string:
+//   string(
+//     "\n"
+//     "\n"
+//     "GET /\tHTTP/1.0\t\r\n"
+//   );
+//
+std::string CreateFirstLine(const char* tokens[3], const char* whitespace[4],
+                            const char* line_ending) {
+  QUICHE_CHECK(tokens != nullptr);
+  QUICHE_CHECK(whitespace != nullptr);
+  QUICHE_CHECK(line_ending != nullptr);
+  QUICHE_CHECK(std::string(line_ending) == "\n" ||
+               std::string(line_ending) == "\r\n")
+      << "line_ending: " << EscapeString(line_ending);
+  SimpleBuffer firstline_buffer;
+  firstline_buffer.WriteString(whitespace[0]);
+  for (int i = 0; i < 3; ++i) {
+    firstline_buffer.WriteString(tokens[i]);
+    firstline_buffer.WriteString(whitespace[i + 1]);
+  }
+  firstline_buffer.WriteString(line_ending);
+  return std::string(firstline_buffer.GetReadableRegion());
+}
+
+// Creates a string (ostensibly an entire HTTP message) from the given input
+// arguments.
+//
+// firstline - the first line of the request or response.
+//     The firstline should already have a line-ending on it.  If you use the
+//     CreateFirstLine function, you'll get a valid firstline string for this
+//     function.  This may include 'extraneous' whitespace before the first
+//     nonwhitespace character, including '\n's
+// headers - a list of the -interpreted- key, value pairs.
+//     In other words, the value should be what you expect to get out of the
+//     headers after framing has occurred (and should include no whitespace
+//     before or after the first and list nonwhitespace characters,
+//     respectively).  While this function will succeed if you don't follow
+//     these guidelines, the VerifyHeaderLines function will likely not agree
+//     with that input.
+// headers_len - the number of key value pairs
+// colon - the string that exists between the key and value pairs.
+//     It MUST include EXACTLY one colon, and may include any amount of either
+//     ' ' or '\t'. Note that for certain key strings, this value will be
+//     modified to exclude any leading whitespace. See the body of the function
+//     for more details.
+// line_ending - one of "\r\n", or "\n\n"
+// body - the appropriate body.
+//     The CreateMessage function does not do any checking that the headers
+//     agree with the present of any body, so the input must be correct given
+//     the set of headers.
+std::string CreateMessage(const char* firstline,
+                          const std::pair<std::string, std::string>* headers,
+                          size_t headers_len, const char* colon,
+                          const char* line_ending, const char* body) {
+  SimpleBuffer request_buffer;
+  request_buffer.WriteString(firstline);
+  if (headers_len > 0) {
+    QUICHE_CHECK(headers != nullptr);
+    QUICHE_CHECK(colon != nullptr);
+  }
+  QUICHE_CHECK(line_ending != nullptr);
+  QUICHE_CHECK(std::string(line_ending) == "\n" ||
+               std::string(line_ending) == "\r\n")
+      << "line_ending: " << EscapeString(line_ending);
+  QUICHE_CHECK(body != nullptr);
+  for (size_t i = 0; i < headers_len; ++i) {
+    bool only_whitespace_in_key = true;
+    {
+      // If the 'key' part includes no non-whitespace characters, then we need
+      // to be sure that the 'colon' part includes no whitespace before the
+      // ':'. If it did, then the line would be (correctly!) interpreted as a
+      // continuation, and the test would not work properly.
+      const char* tmp_key = headers[i].first.c_str();
+      while (*tmp_key != '\0') {
+        if (*tmp_key > ' ') {
+          only_whitespace_in_key = false;
+          break;
+        }
+        ++tmp_key;
+      }
+    }
+    const char* tmp_colon = colon;
+    if (only_whitespace_in_key) {
+      while (*tmp_colon != ':') {
+        ++tmp_colon;
+      }
+    }
+    request_buffer.WriteString(headers[i].first);
+    request_buffer.WriteString(tmp_colon);
+    request_buffer.WriteString(headers[i].second);
+    request_buffer.WriteString(line_ending);
+  }
+  request_buffer.WriteString(line_ending);
+  request_buffer.WriteString(body);
+  return std::string(request_buffer.GetReadableRegion());
+}
+
+void VerifyRequestFirstLine(const char* tokens[3],
+                            const BalsaHeaders& headers) {
+  EXPECT_EQ(tokens[0], headers.request_method());
+  EXPECT_EQ(tokens[1], headers.request_uri());
+  EXPECT_EQ(0u, headers.parsed_response_code());
+  EXPECT_EQ(tokens[2], headers.request_version());
+}
+
+void VerifyResponseFirstLine(const char* tokens[3],
+                             size_t expected_response_code,
+                             const BalsaHeaders& headers) {
+  EXPECT_EQ(tokens[0], headers.response_version());
+  EXPECT_EQ(tokens[1], headers.response_code());
+  EXPECT_EQ(expected_response_code, headers.parsed_response_code());
+  EXPECT_EQ(tokens[2], headers.response_reason_phrase());
+}
+
+// This function verifies that the expected_headers key and values
+// are exactly equal to that returned by an iterator to a BalsaHeader
+// object.
+//
+// expected_headers - key, value pairs, in the order in which they're
+//                    expected to be returned from the iterator.
+// headers_len - as expected, the number of expected key-value pairs.
+// headers - the BalsaHeaders from which we'll examine the actual
+//           headers.
+void VerifyHeaderLines(
+    const std::pair<std::string, std::string>* expected_headers,
+    size_t headers_len, const BalsaHeaders& headers) {
+  BalsaHeaders::const_header_lines_iterator it = headers.lines().begin();
+  for (size_t i = 0; it != headers.lines().end(); ++it, ++i) {
+    ASSERT_GT(headers_len, i);
+    std::string actual_key;
+    std::string actual_value;
+    if (!it->first.empty()) {
+      actual_key = std::string(it->first);
+    }
+    if (!it->second.empty()) {
+      actual_value = std::string(it->second);
+    }
+    EXPECT_THAT(actual_key, StrEq(expected_headers[i].first));
+    EXPECT_THAT(actual_value, StrEq(expected_headers[i].second));
+  }
+  EXPECT_TRUE(headers.lines().end() == it);
+}
+
+void FirstLineParsedCorrectlyHelper(const char* tokens[3],
+                                    size_t expected_response_code,
+                                    bool is_request, const char* whitespace) {
+  BalsaHeaders headers;
+  BalsaFrame framer;
+  framer.set_is_request(is_request);
+  framer.set_balsa_headers(&headers);
+  const char* tmp_tokens[3] = {tokens[0], tokens[1], tokens[2]};
+  const char* tmp_whitespace[4] = {"", whitespace, whitespace, ""};
+  for (int j = 2; j >= 0; --j) {
+    framer.Reset();
+    std::string firstline = CreateFirstLine(tmp_tokens, tmp_whitespace, "\n");
+    std::string message =
+        CreateMessage(firstline.c_str(), nullptr, 0, nullptr, "\n", "");
+    SCOPED_TRACE(absl::StrFormat("input: \n%s", EscapeString(message)));
+    EXPECT_GE(message.size(),
+              framer.ProcessInput(message.data(), message.size()));
+    // If this is a request then we don't expect a framer error (as we'll be
+    // getting back warnings that fields are missing). If, however, this is
+    // a response, and it is missing anything other than the reason phrase,
+    // the framer will signal an error instead.
+    if (is_request || j >= 1) {
+      EXPECT_FALSE(framer.Error());
+      if (is_request) {
+        EXPECT_TRUE(framer.MessageFullyRead());
+      }
+      if (j == 0) {
+        expected_response_code = 0;
+      }
+      if (is_request) {
+        VerifyRequestFirstLine(tmp_tokens, *framer.headers());
+      } else {
+        VerifyResponseFirstLine(tmp_tokens, expected_response_code,
+                                *framer.headers());
+      }
+    } else {
+      EXPECT_TRUE(framer.Error());
+    }
+    tmp_tokens[j] = "";
+    tmp_whitespace[j] = "";
+  }
+}
+
+TEST(HTTPBalsaFrame, ParseStateToString) {
+  EXPECT_STREQ("ERROR",
+               BalsaFrameEnums::ParseStateToString(BalsaFrameEnums::ERROR));
+  EXPECT_STREQ("READING_HEADER_AND_FIRSTLINE",
+               BalsaFrameEnums::ParseStateToString(
+                   BalsaFrameEnums::READING_HEADER_AND_FIRSTLINE));
+  EXPECT_STREQ("READING_CHUNK_LENGTH",
+               BalsaFrameEnums::ParseStateToString(
+                   BalsaFrameEnums::READING_CHUNK_LENGTH));
+  EXPECT_STREQ("READING_CHUNK_EXTENSION",
+               BalsaFrameEnums::ParseStateToString(
+                   BalsaFrameEnums::READING_CHUNK_EXTENSION));
+  EXPECT_STREQ("READING_CHUNK_DATA", BalsaFrameEnums::ParseStateToString(
+                                         BalsaFrameEnums::READING_CHUNK_DATA));
+  EXPECT_STREQ("READING_CHUNK_TERM", BalsaFrameEnums::ParseStateToString(
+                                         BalsaFrameEnums::READING_CHUNK_TERM));
+  EXPECT_STREQ("READING_LAST_CHUNK_TERM",
+               BalsaFrameEnums::ParseStateToString(
+                   BalsaFrameEnums::READING_LAST_CHUNK_TERM));
+  EXPECT_STREQ("READING_TRAILER", BalsaFrameEnums::ParseStateToString(
+                                      BalsaFrameEnums::READING_TRAILER));
+  EXPECT_STREQ("READING_UNTIL_CLOSE",
+               BalsaFrameEnums::ParseStateToString(
+                   BalsaFrameEnums::READING_UNTIL_CLOSE));
+  EXPECT_STREQ("READING_CONTENT", BalsaFrameEnums::ParseStateToString(
+                                      BalsaFrameEnums::READING_CONTENT));
+  EXPECT_STREQ("MESSAGE_FULLY_READ", BalsaFrameEnums::ParseStateToString(
+                                         BalsaFrameEnums::MESSAGE_FULLY_READ));
+
+  EXPECT_STREQ("UNKNOWN_STATE", BalsaFrameEnums::ParseStateToString(
+                                    BalsaFrameEnums::NUM_STATES));
+  EXPECT_STREQ("UNKNOWN_STATE",
+               BalsaFrameEnums::ParseStateToString(
+                   static_cast<BalsaFrameEnums::ParseState>(-1)));
+
+  for (int i = 0; i < BalsaFrameEnums::NUM_STATES; ++i) {
+    EXPECT_STRNE("UNKNOWN_STATE",
+                 BalsaFrameEnums::ParseStateToString(
+                     static_cast<BalsaFrameEnums::ParseState>(i)));
+  }
+}
+
+TEST(HTTPBalsaFrame, ErrorCodeToString) {
+  EXPECT_STREQ("NO_STATUS_LINE_IN_RESPONSE",
+               BalsaFrameEnums::ErrorCodeToString(
+                   BalsaFrameEnums::NO_STATUS_LINE_IN_RESPONSE));
+  EXPECT_STREQ("NO_REQUEST_LINE_IN_REQUEST",
+               BalsaFrameEnums::ErrorCodeToString(
+                   BalsaFrameEnums::NO_REQUEST_LINE_IN_REQUEST));
+  EXPECT_STREQ("FAILED_TO_FIND_WS_AFTER_RESPONSE_VERSION",
+               BalsaFrameEnums::ErrorCodeToString(
+                   BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_RESPONSE_VERSION));
+  EXPECT_STREQ("FAILED_TO_FIND_WS_AFTER_REQUEST_METHOD",
+               BalsaFrameEnums::ErrorCodeToString(
+                   BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_REQUEST_METHOD));
+  EXPECT_STREQ(
+      "FAILED_TO_FIND_WS_AFTER_RESPONSE_STATUSCODE",
+      BalsaFrameEnums::ErrorCodeToString(
+          BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_RESPONSE_STATUSCODE));
+  EXPECT_STREQ(
+      "FAILED_TO_FIND_WS_AFTER_REQUEST_REQUEST_URI",
+      BalsaFrameEnums::ErrorCodeToString(
+          BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_REQUEST_REQUEST_URI));
+  EXPECT_STREQ(
+      "FAILED_TO_FIND_NL_AFTER_RESPONSE_REASON_PHRASE",
+      BalsaFrameEnums::ErrorCodeToString(
+          BalsaFrameEnums::FAILED_TO_FIND_NL_AFTER_RESPONSE_REASON_PHRASE));
+  EXPECT_STREQ(
+      "FAILED_TO_FIND_NL_AFTER_REQUEST_HTTP_VERSION",
+      BalsaFrameEnums::ErrorCodeToString(
+          BalsaFrameEnums::FAILED_TO_FIND_NL_AFTER_REQUEST_HTTP_VERSION));
+  EXPECT_STREQ("FAILED_CONVERTING_STATUS_CODE_TO_INT",
+               BalsaFrameEnums::ErrorCodeToString(
+                   BalsaFrameEnums::FAILED_CONVERTING_STATUS_CODE_TO_INT));
+  EXPECT_STREQ("HEADERS_TOO_LONG", BalsaFrameEnums::ErrorCodeToString(
+                                       BalsaFrameEnums::HEADERS_TOO_LONG));
+  EXPECT_STREQ("UNPARSABLE_CONTENT_LENGTH",
+               BalsaFrameEnums::ErrorCodeToString(
+                   BalsaFrameEnums::UNPARSABLE_CONTENT_LENGTH));
+  EXPECT_STREQ("MAYBE_BODY_BUT_NO_CONTENT_LENGTH",
+               BalsaFrameEnums::ErrorCodeToString(
+                   BalsaFrameEnums::MAYBE_BODY_BUT_NO_CONTENT_LENGTH));
+  EXPECT_STREQ("HEADER_MISSING_COLON",
+               BalsaFrameEnums::ErrorCodeToString(
+                   BalsaFrameEnums::HEADER_MISSING_COLON));
+  EXPECT_STREQ("INVALID_CHUNK_LENGTH",
+               BalsaFrameEnums::ErrorCodeToString(
+                   BalsaFrameEnums::INVALID_CHUNK_LENGTH));
+  EXPECT_STREQ("CHUNK_LENGTH_OVERFLOW",
+               BalsaFrameEnums::ErrorCodeToString(
+                   BalsaFrameEnums::CHUNK_LENGTH_OVERFLOW));
+  EXPECT_STREQ("CALLED_BYTES_SPLICED_WHEN_UNSAFE_TO_DO_SO",
+               BalsaFrameEnums::ErrorCodeToString(
+                   BalsaFrameEnums::CALLED_BYTES_SPLICED_WHEN_UNSAFE_TO_DO_SO));
+  EXPECT_STREQ("CALLED_BYTES_SPLICED_AND_EXCEEDED_SAFE_SPLICE_AMOUNT",
+               BalsaFrameEnums::ErrorCodeToString(
+                   BalsaFrameEnums::
+                       CALLED_BYTES_SPLICED_AND_EXCEEDED_SAFE_SPLICE_AMOUNT));
+  EXPECT_STREQ("MULTIPLE_CONTENT_LENGTH_KEYS",
+               BalsaFrameEnums::ErrorCodeToString(
+                   BalsaFrameEnums::MULTIPLE_CONTENT_LENGTH_KEYS));
+  EXPECT_STREQ("MULTIPLE_TRANSFER_ENCODING_KEYS",
+               BalsaFrameEnums::ErrorCodeToString(
+                   BalsaFrameEnums::MULTIPLE_TRANSFER_ENCODING_KEYS));
+  EXPECT_STREQ("INVALID_HEADER_FORMAT",
+               BalsaFrameEnums::ErrorCodeToString(
+                   BalsaFrameEnums::INVALID_HEADER_FORMAT));
+  EXPECT_STREQ("INVALID_TRAILER_FORMAT",
+               BalsaFrameEnums::ErrorCodeToString(
+                   BalsaFrameEnums::INVALID_TRAILER_FORMAT));
+  EXPECT_STREQ("TRAILER_TOO_LONG", BalsaFrameEnums::ErrorCodeToString(
+                                       BalsaFrameEnums::TRAILER_TOO_LONG));
+  EXPECT_STREQ("TRAILER_MISSING_COLON",
+               BalsaFrameEnums::ErrorCodeToString(
+                   BalsaFrameEnums::TRAILER_MISSING_COLON));
+  EXPECT_STREQ("INTERNAL_LOGIC_ERROR",
+               BalsaFrameEnums::ErrorCodeToString(
+                   BalsaFrameEnums::INTERNAL_LOGIC_ERROR));
+  EXPECT_STREQ("INVALID_HEADER_CHARACTER",
+               BalsaFrameEnums::ErrorCodeToString(
+                   BalsaFrameEnums::INVALID_HEADER_CHARACTER));
+
+  EXPECT_STREQ("UNKNOWN_ERROR", BalsaFrameEnums::ErrorCodeToString(
+                                    BalsaFrameEnums::NUM_ERROR_CODES));
+  EXPECT_STREQ("UNKNOWN_ERROR",
+               BalsaFrameEnums::ErrorCodeToString(
+                   static_cast<BalsaFrameEnums::ErrorCode>(-1)));
+
+  for (int i = 0; i < BalsaFrameEnums::NUM_ERROR_CODES; ++i) {
+    EXPECT_STRNE("UNKNOWN_ERROR",
+                 BalsaFrameEnums::ErrorCodeToString(
+                     static_cast<BalsaFrameEnums::ErrorCode>(i)));
+  }
+}
+
+class FakeHeaders {
+ public:
+  struct KeyValuePair {
+    KeyValuePair(const std::string& key, const std::string& value)
+        : key(key), value(value) {}
+    KeyValuePair() {}
+
+    std::string key;
+    std::string value;
+  };
+  typedef std::vector<KeyValuePair> KeyValuePairs;
+  KeyValuePairs key_value_pairs_;
+
+  bool operator==(const FakeHeaders& other) const {
+    if (key_value_pairs_.size() != other.key_value_pairs_.size()) {
+      return false;
+    }
+    for (KeyValuePairs::size_type i = 0; i < key_value_pairs_.size(); ++i) {
+      if (key_value_pairs_[i].key != other.key_value_pairs_[i].key) {
+        return false;
+      }
+      if (key_value_pairs_[i].value != other.key_value_pairs_[i].value) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  void AddKeyValue(const std::string& key, const std::string& value) {
+    key_value_pairs_.push_back(KeyValuePair(key, value));
+  }
+};
+
+class BalsaVisitorMock : public BalsaVisitorInterface {
+ public:
+  ~BalsaVisitorMock() override = default;
+
+  void ProcessHeaders(const BalsaHeaders& headers) override {
+    FakeHeaders fake_headers;
+    GenerateFakeHeaders(headers, &fake_headers);
+    ProcessHeaders(fake_headers);
+  }
+  void ProcessTrailers(const BalsaHeaders& trailer) override {
+    FakeHeaders fake_headers;
+    GenerateFakeHeaders(trailer, &fake_headers);
+    ProcessTrailers(fake_headers);
+  }
+
+  MOCK_METHOD(void, OnRawBodyInput, (absl::string_view input), (override));
+  MOCK_METHOD(void, OnBodyChunkInput, (absl::string_view input), (override));
+  MOCK_METHOD(void, OnHeaderInput, (absl::string_view input), (override));
+  MOCK_METHOD(void, OnTrailerInput, (absl::string_view input), (override));
+  MOCK_METHOD(void, ProcessHeaders, (const FakeHeaders& headers));
+  MOCK_METHOD(void, ProcessTrailers, (const FakeHeaders& headers));
+  MOCK_METHOD(void, OnRequestFirstLineInput,
+              (absl::string_view line_input, absl::string_view method_input,
+               absl::string_view request_uri, absl::string_view version_input),
+              (override));
+  MOCK_METHOD(void, OnResponseFirstLineInput,
+              (absl::string_view line_input, absl::string_view version_input,
+               absl::string_view status_input, absl::string_view reason_input),
+              (override));
+  MOCK_METHOD(void, OnChunkLength, (size_t length), (override));
+  MOCK_METHOD(void, OnChunkExtensionInput, (absl::string_view input),
+              (override));
+  MOCK_METHOD(void, ContinueHeaderDone, (), (override));
+  MOCK_METHOD(void, HeaderDone, (), (override));
+  MOCK_METHOD(void, MessageDone, (), (override));
+  MOCK_METHOD(void, HandleError, (BalsaFrameEnums::ErrorCode error_code),
+              (override));
+  MOCK_METHOD(void, HandleWarning, (BalsaFrameEnums::ErrorCode error_code),
+              (override));
+
+ private:
+  static void GenerateFakeHeaders(const BalsaHeaders& headers,
+                                  FakeHeaders* fake_headers) {
+    for (const auto& line : headers.lines()) {
+      fake_headers->AddKeyValue(std::string(line.first),
+                                std::string(line.second));
+    }
+  }
+};
+
+class HTTPBalsaFrameTest : public QuicheTest {
+ protected:
+  void SetUp() override {
+    balsa_frame_.set_http_validation_policy(
+        HttpValidationPolicy::CreateDefault());
+    balsa_frame_.set_balsa_headers(&headers_);
+    balsa_frame_.set_balsa_visitor(&visitor_mock_);
+    balsa_frame_.set_is_request(true);
+  }
+
+  void VerifyFirstLineParsing(const std::string& firstline,
+                              BalsaFrameEnums::ErrorCode error_code) {
+    balsa_frame_.ProcessInput(firstline.data(), firstline.size());
+    EXPECT_EQ(error_code, balsa_frame_.ErrorCode());
+  }
+
+  BalsaHeaders headers_;
+  BalsaHeaders trailer_;
+  BalsaFrame balsa_frame_;
+  NiceMock<BalsaVisitorMock> visitor_mock_;
+};
+
+// Test correct return value for HeaderFramingFound.
+TEST_F(HTTPBalsaFrameTest, TestHeaderFramingFound) {
+  // Pattern \r\n\r\n should match kValidTerm1.
+  EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, ' '));
+  EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\r'));
+  EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\n'));
+  EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\r'));
+  EXPECT_EQ(BalsaFrame::kValidTerm1,
+            BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\n'));
+
+  // Pattern \n\r\n should match kValidTerm1.
+  EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\t'));
+  EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\n'));
+  EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\r'));
+  EXPECT_EQ(BalsaFrame::kValidTerm1,
+            BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\n'));
+
+  // Pattern \r\n\n should match kValidTerm2.
+  EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, 'a'));
+  EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\r'));
+  EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\n'));
+  EXPECT_EQ(BalsaFrame::kValidTerm2,
+            BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\n'));
+
+  // Pattern \n\n should match kValidTerm2.
+  EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '1'));
+  EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\n'));
+  EXPECT_EQ(BalsaFrame::kValidTerm2,
+            BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\n'));
+
+  // Other patterns should not match.
+  EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, ':'));
+  EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\r'));
+  EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\r'));
+  EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\n'));
+}
+
+TEST_F(HTTPBalsaFrameTest, MissingColonInTrailer) {
+  const absl::string_view trailer = "kv\r\n\r\n";
+
+  BalsaFrame::Lines lines;
+  lines.push_back({0, 4});
+  lines.push_back({4, trailer.length()});
+  BalsaHeadersTestPeer::WriteFromFramer(&trailer_, trailer.data(),
+                                        trailer.length());
+  BalsaFrameTestPeer::FindColonsAndParseIntoKeyValue(
+      &balsa_frame_, lines, true /*is_trailer*/, &trailer_);
+  // Note missing colon is not an error, just a warning.
+  EXPECT_FALSE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::TRAILER_MISSING_COLON, balsa_frame_.ErrorCode());
+}
+
+// Correctness of FindColonsAndParseIntoKeyValue is already verified for
+// headers, so trailer related test is light.
+TEST_F(HTTPBalsaFrameTest, FindColonsAndParseIntoKeyValueInTrailer) {
+  const absl::string_view trailer_line1 = "Fraction: 0.23\r\n";
+  const absl::string_view trailer_line2 = "Some:junk \r\n";
+  const absl::string_view trailer_line3 = "\r\n";
+  const std::string trailer =
+      absl::StrCat(trailer_line1, trailer_line2, trailer_line3);
+
+  BalsaFrame::Lines lines;
+  lines.push_back({0, trailer_line1.length()});
+  lines.push_back({trailer_line1.length(),
+                   trailer_line1.length() + trailer_line2.length()});
+  lines.push_back(
+      {trailer_line1.length() + trailer_line2.length(), trailer.length()});
+  BalsaHeadersTestPeer::WriteFromFramer(&trailer_, trailer.data(),
+                                        trailer.length());
+  BalsaFrameTestPeer::FindColonsAndParseIntoKeyValue(
+      &balsa_frame_, lines, true /*is_trailer*/, &trailer_);
+  EXPECT_FALSE(balsa_frame_.Error());
+  absl::string_view fraction = trailer_.GetHeader("Fraction");
+  EXPECT_EQ("0.23", fraction);
+  absl::string_view some = trailer_.GetHeader("Some");
+  EXPECT_EQ("junk", some);
+}
+
+TEST_F(HTTPBalsaFrameTest, InvalidTrailer) {
+  const absl::string_view trailer_line1 = "Fraction : 0.23\r\n";
+  const absl::string_view trailer_line2 = "Some\t  :junk \r\n";
+  const absl::string_view trailer_line3 = "\r\n";
+  const std::string trailer =
+      absl::StrCat(trailer_line1, trailer_line2, trailer_line3);
+
+  BalsaFrame::Lines lines;
+  lines.push_back({0, trailer_line1.length()});
+  lines.push_back({trailer_line1.length(),
+                   trailer_line1.length() + trailer_line2.length()});
+  lines.push_back(
+      {trailer_line1.length() + trailer_line2.length(), trailer.length()});
+  BalsaHeadersTestPeer::WriteFromFramer(&trailer_, trailer.data(),
+                                        trailer.length());
+  BalsaFrameTestPeer::FindColonsAndParseIntoKeyValue(
+      &balsa_frame_, lines, true /*is_trailer*/, &trailer_);
+  EXPECT_TRUE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::INVALID_TRAILER_NAME_CHARACTER,
+            balsa_frame_.ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest, OneCharacterFirstLineParsedAsExpected) {
+  VerifyFirstLineParsing(
+      "a\r\n\r\n", BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_REQUEST_METHOD);
+}
+
+TEST_F(HTTPBalsaFrameTest,
+       OneCharacterFirstLineWithWhitespaceParsedAsExpected) {
+  VerifyFirstLineParsing(
+      "a   \r\n\r\n", BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_REQUEST_METHOD);
+}
+
+TEST_F(HTTPBalsaFrameTest, WhitespaceOnlyFirstLineIsNotACompleteHeader) {
+  VerifyFirstLineParsing(" \n\n", BalsaFrameEnums::NO_REQUEST_LINE_IN_REQUEST);
+}
+
+TEST(HTTPBalsaFrame, RequestFirstLineParsedCorrectly) {
+  const char* request_tokens[3] = {"GET", "/jjsdjrqk", "HTTP/1.0"};
+  FirstLineParsedCorrectlyHelper(request_tokens, 0, true, " ");
+  FirstLineParsedCorrectlyHelper(request_tokens, 0, true, "\t");
+  FirstLineParsedCorrectlyHelper(request_tokens, 0, true, "\t    ");
+  FirstLineParsedCorrectlyHelper(request_tokens, 0, true, "   \t");
+  FirstLineParsedCorrectlyHelper(request_tokens, 0, true, "   \t \t  ");
+}
+
+TEST_F(HTTPBalsaFrameTest, NonnumericResponseCode) {
+  balsa_frame_.set_is_request(false);
+
+  VerifyFirstLineParsing("HTTP/1.1 0x3 Digits only\r\n\r\n",
+                         BalsaFrameEnums::FAILED_CONVERTING_STATUS_CODE_TO_INT);
+
+  EXPECT_EQ("HTTP/1.1 0x3 Digits only", headers_.first_line());
+}
+
+TEST_F(HTTPBalsaFrameTest, NegativeResponseCode) {
+  balsa_frame_.set_is_request(false);
+
+  VerifyFirstLineParsing("HTTP/1.1 -11 No sign allowed\r\n\r\n",
+                         BalsaFrameEnums::FAILED_CONVERTING_STATUS_CODE_TO_INT);
+
+  EXPECT_EQ("HTTP/1.1 -11 No sign allowed", headers_.first_line());
+}
+
+TEST_F(HTTPBalsaFrameTest, WithoutTrailingWhitespace) {
+  balsa_frame_.set_is_request(false);
+
+  VerifyFirstLineParsing(
+      "HTTP/1.1 101\r\n\r\n",
+      BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_RESPONSE_STATUSCODE);
+
+  EXPECT_EQ("HTTP/1.1 101", headers_.first_line());
+}
+
+TEST_F(HTTPBalsaFrameTest, TrailingWhitespace) {
+  balsa_frame_.set_is_request(false);
+
+  // b/69446061
+  std::string firstline = "HTTP/1.1 101 \r\n\r\n";
+  balsa_frame_.ProcessInput(firstline.data(), firstline.size());
+
+  EXPECT_EQ("HTTP/1.1 101 ", headers_.first_line());
+}
+
+TEST(HTTPBalsaFrame, ResponseFirstLineParsedCorrectly) {
+  const char* response_tokens[3] = {"HTTP/1.1", "200", "A reason\tphrase"};
+  FirstLineParsedCorrectlyHelper(response_tokens, 200, false, " ");
+  FirstLineParsedCorrectlyHelper(response_tokens, 200, false, "\t");
+  FirstLineParsedCorrectlyHelper(response_tokens, 200, false, "\t    ");
+  FirstLineParsedCorrectlyHelper(response_tokens, 200, false, "   \t");
+  FirstLineParsedCorrectlyHelper(response_tokens, 200, false, "   \t \t  ");
+
+  response_tokens[1] = "312";
+  FirstLineParsedCorrectlyHelper(response_tokens, 312, false, " ");
+  FirstLineParsedCorrectlyHelper(response_tokens, 312, false, "\t");
+  FirstLineParsedCorrectlyHelper(response_tokens, 312, false, "\t    ");
+  FirstLineParsedCorrectlyHelper(response_tokens, 312, false, "   \t");
+  FirstLineParsedCorrectlyHelper(response_tokens, 312, false, "   \t \t  ");
+
+  // Who knows what the future may hold w.r.t. response codes?!
+  response_tokens[1] = "4242";
+  FirstLineParsedCorrectlyHelper(response_tokens, 4242, false, " ");
+  FirstLineParsedCorrectlyHelper(response_tokens, 4242, false, "\t");
+  FirstLineParsedCorrectlyHelper(response_tokens, 4242, false, "\t    ");
+  FirstLineParsedCorrectlyHelper(response_tokens, 4242, false, "   \t");
+  FirstLineParsedCorrectlyHelper(response_tokens, 4242, false, "   \t \t  ");
+}
+
+void HeaderLineTestHelper(const char* firstline, bool is_request,
+                          const std::pair<std::string, std::string>* headers,
+                          size_t headers_len, const char* colon,
+                          const char* line_ending) {
+  BalsaHeaders balsa_headers;
+  BalsaFrame framer;
+  framer.set_is_request(is_request);
+  framer.set_balsa_headers(&balsa_headers);
+  std::string message =
+      CreateMessage(firstline, headers, headers_len, colon, line_ending, "");
+  SCOPED_TRACE(EscapeString(message));
+  size_t bytes_consumed = framer.ProcessInput(message.data(), message.size());
+  EXPECT_EQ(message.size(), bytes_consumed);
+  VerifyHeaderLines(headers, headers_len, *framer.headers());
+}
+
+TEST(HTTPBalsaFrame, RequestLinesParsedProperly) {
+  SCOPED_TRACE("Testing that lines are properly parsed.");
+  const char firstline[] = "GET / HTTP/1.1\r\n";
+  const std::pair<std::string, std::string> headers[] = {
+      std::pair<std::string, std::string>("foo", "bar"),
+      std::pair<std::string, std::string>("duck", "water"),
+      std::pair<std::string, std::string>("goose", "neck"),
+      std::pair<std::string, std::string>("key_is_fine",
+                                          "value:includes:colons"),
+      std::pair<std::string, std::string>("trucks",
+                                          "along\rvalue\rincluding\rslash\rrs"),
+      std::pair<std::string, std::string>("monster", "truck"),
+      std::pair<std::string, std::string>("another_key", ":colons in value"),
+      std::pair<std::string, std::string>("another_key", "colons in value:"),
+      std::pair<std::string, std::string>("another_key",
+                                          "value includes\r\n continuation"),
+      std::pair<std::string, std::string>("key_without_continuations",
+                                          "multiple\n in\r\n the\n value"),
+      std::pair<std::string, std::string>("key_without_value",
+                                          ""),  // empty value
+      std::pair<std::string, std::string>("",
+                                          "value without key"),  // empty key
+      std::pair<std::string, std::string>("", ""),  // both key and value empty
+      std::pair<std::string, std::string>("normal_key", "normal_value"),
+  };
+  const size_t headers_len = ABSL_ARRAYSIZE(headers);
+  HeaderLineTestHelper(firstline, true, headers, headers_len, ": ", "\n");
+  HeaderLineTestHelper(firstline, true, headers, headers_len, ": ", "\r\n");
+  HeaderLineTestHelper(firstline, true, headers, headers_len, ":\t", "\n");
+  HeaderLineTestHelper(firstline, true, headers, headers_len, ":\t", "\r\n");
+  HeaderLineTestHelper(firstline, true, headers, headers_len, ":\t ", "\n");
+  HeaderLineTestHelper(firstline, true, headers, headers_len, ":\t ", "\r\n");
+  HeaderLineTestHelper(firstline, true, headers, headers_len, ":\t\t", "\n");
+  HeaderLineTestHelper(firstline, true, headers, headers_len, ":\t\t", "\r\n");
+  HeaderLineTestHelper(firstline, true, headers, headers_len, ":\t \t", "\n");
+  HeaderLineTestHelper(firstline, true, headers, headers_len, ":\t \t", "\r\n");
+}
+
+TEST(HTTPBalsaFrame, ResponseLinesParsedProperly) {
+  SCOPED_TRACE("ResponseLineParsedProperly");
+  const char firstline[] = "HTTP/1.0 200 A reason\tphrase\r\n";
+  const std::pair<std::string, std::string> headers[] = {
+      std::pair<std::string, std::string>("foo", "bar"),
+      std::pair<std::string, std::string>("duck", "water"),
+      std::pair<std::string, std::string>("goose", "neck"),
+      std::pair<std::string, std::string>("key_is_fine",
+                                          "value:includes:colons"),
+      std::pair<std::string, std::string>("trucks",
+                                          "along\rvalue\rincluding\rslash\rrs"),
+      std::pair<std::string, std::string>("monster", "truck"),
+      std::pair<std::string, std::string>("another_key", ":colons in value"),
+      std::pair<std::string, std::string>("another_key", "colons in value:"),
+      std::pair<std::string, std::string>("another_key",
+                                          "value includes\r\n continuation"),
+      std::pair<std::string, std::string>("key_includes_no_continuations",
+                                          "multiple\n in\r\n the\n value"),
+      std::pair<std::string, std::string>("key_without_value",
+                                          ""),  // empty value
+      std::pair<std::string, std::string>("",
+                                          "value without key"),  // empty key
+      std::pair<std::string, std::string>("", ""),  // both key and value empty
+      std::pair<std::string, std::string>("normal_key", "normal_value"),
+  };
+  const size_t headers_len = ABSL_ARRAYSIZE(headers);
+  HeaderLineTestHelper(firstline, false, headers, headers_len, ": ", "\n");
+  HeaderLineTestHelper(firstline, false, headers, headers_len, ": ", "\r\n");
+  HeaderLineTestHelper(firstline, false, headers, headers_len, ":\t", "\n");
+  HeaderLineTestHelper(firstline, false, headers, headers_len, ":\t", "\r\n");
+  HeaderLineTestHelper(firstline, false, headers, headers_len, ":\t ", "\n");
+  HeaderLineTestHelper(firstline, false, headers, headers_len, ":\t ", "\r\n");
+  HeaderLineTestHelper(firstline, false, headers, headers_len, ":\t\t", "\n");
+  HeaderLineTestHelper(firstline, false, headers, headers_len, ":\t\t", "\r\n");
+  HeaderLineTestHelper(firstline, false, headers, headers_len, ":\t \t", "\n");
+  HeaderLineTestHelper(firstline, false, headers, headers_len, ":\t \t",
+                       "\r\n");
+}
+
+void WhitespaceHeaderTestHelper(
+    const std::string& message, bool is_request,
+    BalsaFrameEnums::ErrorCode expected_error_code) {
+  BalsaHeaders balsa_headers;
+  BalsaFrame framer;
+  framer.set_is_request(is_request);
+  framer.set_balsa_headers(&balsa_headers);
+  SCOPED_TRACE(EscapeString(message));
+  size_t bytes_consumed = framer.ProcessInput(message.data(), message.size());
+  EXPECT_EQ(message.size(), bytes_consumed);
+  if (expected_error_code == BalsaFrameEnums::NO_ERROR) {
+    EXPECT_EQ(false, framer.Error());
+  } else {
+    EXPECT_EQ(true, framer.Error());
+  }
+  EXPECT_EQ(expected_error_code, framer.ErrorCode());
+}
+
+TEST(HTTPBalsaFrame, WhitespaceInRequestsProcessedProperly) {
+  SCOPED_TRACE(
+      "Test that a request header with a line with spaces and no "
+      "data generates an error.");
+  WhitespaceHeaderTestHelper(
+      "GET / HTTP/1.1\r\n"
+      " \r\n"
+      "\r\n",
+      true, BalsaFrameEnums::INVALID_HEADER_NAME_CHARACTER);
+  WhitespaceHeaderTestHelper(
+      "GET / HTTP/1.1\r\n"
+      "   \r\n"
+      "test: test\r\n"
+      "\r\n",
+      true, BalsaFrameEnums::INVALID_HEADER_NAME_CHARACTER);
+
+  SCOPED_TRACE("Test proper handling for line continuation in requests.");
+  WhitespaceHeaderTestHelper(
+      "GET / HTTP/1.1\r\n"
+      "test: test\r\n"
+      " continued\r\n"
+      "\r\n",
+      true, BalsaFrameEnums::NO_ERROR);
+  WhitespaceHeaderTestHelper(
+      "GET / HTTP/1.1\r\n"
+      "test: test\r\n"
+      " \r\n"
+      "\r\n",
+      true, BalsaFrameEnums::NO_ERROR);
+}
+
+TEST(HTTPBalsaFrame, WhitespaceInResponsesProcessedProperly) {
+  SCOPED_TRACE(
+      "Test that a response header with a line with spaces and no "
+      "data generates an error.");
+  WhitespaceHeaderTestHelper(
+      "HTTP/1.0 200 Reason\r\n"
+      "  \r\nContent-Length: 0\r\n"
+      "\r\n",
+      false, BalsaFrameEnums::INVALID_HEADER_NAME_CHARACTER);
+
+  SCOPED_TRACE("Test proper handling for line continuation in responses.");
+  WhitespaceHeaderTestHelper(
+      "HTTP/1.0 200 Reason\r\n"
+      "test: test\r\n"
+      " continued\r\n"
+      "Content-Length: 0\r\n"
+      "\r\n",
+      false, BalsaFrameEnums::NO_ERROR);
+  WhitespaceHeaderTestHelper(
+      "HTTP/1.0 200 Reason\r\n"
+      "test: test\r\n"
+      " \r\n"
+      "Content-Length: 0\r\n"
+      "\r\n",
+      false, BalsaFrameEnums::NO_ERROR);
+}
+
+TEST_F(HTTPBalsaFrameTest, VisitorInvokedProperlyForTrivialRequest) {
+  std::string message = "GET /foobar HTTP/1.0\r\n\n";
+
+  FakeHeaders fake_headers;
+
+  {
+    InSequence s;
+
+    EXPECT_CALL(visitor_mock_,
+                OnRequestFirstLineInput("GET /foobar HTTP/1.0", "GET",
+                                        "/foobar", "HTTP/1.0"));
+    EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
+    EXPECT_CALL(visitor_mock_, HeaderDone());
+    EXPECT_CALL(visitor_mock_, MessageDone());
+  }
+  EXPECT_CALL(visitor_mock_, OnHeaderInput(message));
+
+  ASSERT_EQ(message.size(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+}
+
+TEST_F(HTTPBalsaFrameTest, VisitorInvokedProperlyForRequestWithBlankLines) {
+  std::string message = "\n\n\r\n\nGET /foobar HTTP/1.0\r\n\n";
+
+  FakeHeaders fake_headers;
+
+  {
+    InSequence s1;
+    // Yes, that is correct-- the framer 'eats' the blank-lines at the beginning
+    // and never notifies the visitor.
+
+    EXPECT_CALL(visitor_mock_,
+                OnRequestFirstLineInput("GET /foobar HTTP/1.0", "GET",
+                                        "/foobar", "HTTP/1.0"));
+    EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
+    EXPECT_CALL(visitor_mock_, HeaderDone());
+    EXPECT_CALL(visitor_mock_, MessageDone());
+  }
+  EXPECT_CALL(visitor_mock_, OnHeaderInput("GET /foobar HTTP/1.0\r\n\n"));
+
+  ASSERT_EQ(message.size(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+}
+
+TEST_F(HTTPBalsaFrameTest,
+       VisitorInvokedProperlyForRequestWitSplithBlankLines) {
+  std::string blanks =
+      "\n"
+      "\n"
+      "\r\n"
+      "\n";
+  std::string header_input = "GET /foobar HTTP/1.0\r\n\n";
+
+  FakeHeaders fake_headers;
+
+  {
+    InSequence s1;
+    // Yes, that is correct-- the framer 'eats' the blank-lines at the beginning
+    // and never notifies the visitor.
+
+    EXPECT_CALL(visitor_mock_,
+                OnRequestFirstLineInput("GET /foobar HTTP/1.0", "GET",
+                                        "/foobar", "HTTP/1.0"));
+    EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
+    EXPECT_CALL(visitor_mock_, HeaderDone());
+    EXPECT_CALL(visitor_mock_, MessageDone());
+  }
+  EXPECT_CALL(visitor_mock_, OnHeaderInput("GET /foobar HTTP/1.0\r\n\n"));
+
+  ASSERT_EQ(blanks.size(),
+            balsa_frame_.ProcessInput(blanks.data(), blanks.size()));
+  ASSERT_EQ(header_input.size(), balsa_frame_.ProcessInput(
+                                     header_input.data(), header_input.size()));
+}
+
+TEST_F(HTTPBalsaFrameTest,
+       VisitorInvokedProperlyForRequestWithZeroContentLength) {
+  std::string message =
+      "PUT /search?q=fo HTTP/1.1\n"
+      "content-length:      0  \n"
+      "\n";
+
+  FakeHeaders fake_headers;
+  fake_headers.AddKeyValue("content-length", "0");
+
+  {
+    InSequence s1;
+
+    EXPECT_CALL(visitor_mock_,
+                OnRequestFirstLineInput("PUT /search?q=fo HTTP/1.1", "PUT",
+                                        "/search?q=fo", "HTTP/1.1"));
+    EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
+    EXPECT_CALL(visitor_mock_, HeaderDone());
+    EXPECT_CALL(visitor_mock_, MessageDone());
+  }
+  EXPECT_CALL(visitor_mock_, OnHeaderInput(message));
+
+  ASSERT_EQ(message.size(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+}
+
+TEST_F(HTTPBalsaFrameTest,
+       VisitorInvokedProperlyForRequestWithMissingContentLength) {
+  std::string message =
+      "PUT /search?q=fo HTTP/1.1\n"
+      "\n";
+
+  auto error_code =
+      BalsaFrameEnums::BalsaFrameEnums::REQUIRED_BODY_BUT_NO_CONTENT_LENGTH;
+  EXPECT_CALL(visitor_mock_, HandleError(error_code));
+
+  balsa_frame_.ProcessInput(message.data(), message.size());
+  EXPECT_FALSE(balsa_frame_.MessageFullyRead());
+  EXPECT_TRUE(balsa_frame_.Error());
+  EXPECT_EQ(error_code, balsa_frame_.ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest,
+       VisitorInvokedProperlyForPermittedMissingContentLength) {
+  std::string message =
+      "PUT /search?q=fo HTTP/1.1\n"
+      "\n";
+
+  FakeHeaders fake_headers;
+
+  {
+    InSequence s1;
+
+    EXPECT_CALL(visitor_mock_,
+                OnRequestFirstLineInput("PUT /search?q=fo HTTP/1.1", "PUT",
+                                        "/search?q=fo", "HTTP/1.1"));
+  }
+  ASSERT_EQ(message.size(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+}
+
+TEST_F(HTTPBalsaFrameTest, NothingBadHappensWhenNothingInConnectionLine) {
+  // This is similar to the test above, but we use different whitespace
+  // throughout.
+  std::string message =
+      "PUT \t /search?q=fo \t HTTP/1.1 \t \r\n"
+      "Connection:\r\n"
+      "content-length: 0\r\n"
+      "\r\n";
+
+  FakeHeaders fake_headers;
+  fake_headers.AddKeyValue("Connection", "");
+  fake_headers.AddKeyValue("content-length", "0");
+
+  {
+    InSequence s1;
+
+    EXPECT_CALL(visitor_mock_,
+                OnRequestFirstLineInput("PUT \t /search?q=fo \t HTTP/1.1",
+                                        "PUT", "/search?q=fo", "HTTP/1.1"));
+    EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
+    EXPECT_CALL(visitor_mock_, HeaderDone());
+    EXPECT_CALL(visitor_mock_, MessageDone());
+  }
+  EXPECT_CALL(visitor_mock_, OnHeaderInput(message));
+
+  ASSERT_EQ(message.size(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+}
+
+TEST_F(HTTPBalsaFrameTest, NothingBadHappensWhenOnlyCommentsInConnectionLine) {
+  // This is similar to the test above, but we use different whitespace
+  // throughout.
+  std::string message =
+      "PUT \t /search?q=fo \t HTTP/1.1 \t \r\n"
+      "Connection: ,,,,,,,,\r\n"
+      "content-length: 0\r\n"
+      "\r\n";
+
+  FakeHeaders fake_headers;
+  fake_headers.AddKeyValue("Connection", ",,,,,,,,");
+  fake_headers.AddKeyValue("content-length", "0");
+
+  {
+    InSequence s1;
+
+    EXPECT_CALL(visitor_mock_,
+                OnRequestFirstLineInput("PUT \t /search?q=fo \t HTTP/1.1",
+                                        "PUT", "/search?q=fo", "HTTP/1.1"));
+    EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
+    EXPECT_CALL(visitor_mock_, HeaderDone());
+    EXPECT_CALL(visitor_mock_, MessageDone());
+  }
+  EXPECT_CALL(visitor_mock_, OnHeaderInput(message));
+
+  ASSERT_EQ(message.size(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+}
+
+TEST_F(HTTPBalsaFrameTest,
+       VisitorInvokedProperlyForRequestWithZeroContentLengthMk2) {
+  // This is similar to the test above, but we use different whitespace
+  // throughout.
+  std::string message =
+      "PUT \t /search?q=fo \t HTTP/1.1 \t \r\n"
+      "Connection:      \t close      \t\r\n"
+      "content-length:  \t\t   0 \t\t  \r\n"
+      "\r\n";
+
+  FakeHeaders fake_headers;
+  fake_headers.AddKeyValue("Connection", "close");
+  fake_headers.AddKeyValue("content-length", "0");
+
+  {
+    InSequence s1;
+
+    EXPECT_CALL(visitor_mock_,
+                OnRequestFirstLineInput("PUT \t /search?q=fo \t HTTP/1.1",
+                                        "PUT", "/search?q=fo", "HTTP/1.1"));
+    EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
+    EXPECT_CALL(visitor_mock_, HeaderDone());
+    EXPECT_CALL(visitor_mock_, MessageDone());
+  }
+  EXPECT_CALL(visitor_mock_, OnHeaderInput(message));
+
+  ASSERT_EQ(message.size(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+}
+
+TEST_F(HTTPBalsaFrameTest, NothingBadHappensWhenNoVisitorIsAssigned) {
+  std::string headers =
+      "GET / HTTP/1.1\r\n"
+      "Connection: close\r\n"
+      "transfer-encoding: chunked\r\n"
+      "\r\n";
+
+  std::string chunks =
+      "3\r\n"
+      "123\r\n"
+      "0\r\n";
+  std::string trailer =
+      "crass: monkeys\r\n"
+      "funky: monkeys\r\n"
+      "\r\n";
+
+  balsa_frame_.set_balsa_visitor(nullptr);
+  ASSERT_EQ(headers.size(),
+            balsa_frame_.ProcessInput(headers.data(), headers.size()));
+  ASSERT_EQ(chunks.size(),
+            balsa_frame_.ProcessInput(chunks.data(), chunks.size()));
+  EXPECT_EQ(trailer.size(),
+            balsa_frame_.ProcessInput(trailer.data(), trailer.size()));
+  EXPECT_TRUE(balsa_frame_.MessageFullyRead());
+  EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest, NothingBadHappensWhenNoVisitorIsAssignedInResponse) {
+  std::string headers =
+      "HTTP/1.1 502 Bad Gateway\r\n"
+      "Connection: close\r\n"
+      "transfer-encoding: chunked\r\n"
+      "\r\n";
+
+  std::string chunks =
+      "3\r\n"
+      "123\r\n"
+      "0\r\n";
+  std::string trailer =
+      "crass: monkeys\r\n"
+      "funky: monkeys\r\n"
+      "\r\n";
+  balsa_frame_.set_is_request(false);
+  balsa_frame_.set_balsa_trailer(&trailer_);
+  balsa_frame_.set_balsa_visitor(nullptr);
+
+  ASSERT_EQ(headers.size(),
+            balsa_frame_.ProcessInput(headers.data(), headers.size()));
+  ASSERT_EQ(chunks.size(),
+            balsa_frame_.ProcessInput(chunks.data(), chunks.size()));
+  EXPECT_EQ(trailer.size(),
+            balsa_frame_.ProcessInput(trailer.data(), trailer.size()));
+  EXPECT_TRUE(balsa_frame_.MessageFullyRead());
+  EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
+  const absl::string_view crass = trailer_.GetHeader("crass");
+  EXPECT_EQ("monkeys", crass);
+  const absl::string_view funky = trailer_.GetHeader("funky");
+  EXPECT_EQ("monkeys", funky);
+}
+
+TEST_F(HTTPBalsaFrameTest, TransferEncodingIdentityIsIgnored) {
+  std::string headers =
+      "GET / HTTP/1.1\r\n"
+      "Connection: close\r\n"
+      "transfer-encoding: identity\r\n"
+      "content-length: 10\r\n"
+      "\r\n";
+
+  std::string body = "1234567890";
+  std::string message = (headers + body);
+
+  ASSERT_EQ(headers.size(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+  EXPECT_FALSE(balsa_frame_.MessageFullyRead());
+  ASSERT_EQ(body.size(), balsa_frame_.ProcessInput(body.data(), body.size()));
+  EXPECT_TRUE(balsa_frame_.MessageFullyRead());
+  EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest,
+       NothingBadHappensWhenAVisitorIsChangedToNULLInMidParsing) {
+  std::string headers =
+      "GET / HTTP/1.1\r\n"
+      "Connection: close\r\n"
+      "transfer-encoding: chunked\r\n"
+      "\r\n";
+
+  std::string chunks =
+      "3\r\n"
+      "123\r\n"
+      "0\r\n";
+  std::string trailer =
+      "crass: monkeys\r\n"
+      "funky: monkeys\r\n"
+      "\n";
+
+  ASSERT_EQ(headers.size(),
+            balsa_frame_.ProcessInput(headers.data(), headers.size()));
+  balsa_frame_.set_balsa_visitor(nullptr);
+  ASSERT_EQ(chunks.size(),
+            balsa_frame_.ProcessInput(chunks.data(), chunks.size()));
+  ASSERT_EQ(trailer.size(),
+            balsa_frame_.ProcessInput(trailer.data(), trailer.size()));
+  EXPECT_TRUE(balsa_frame_.MessageFullyRead());
+  EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest,
+       NothingBadHappensWhenAVisitorIsChangedToNULLInMidParsingInTrailer) {
+  std::string headers =
+      "HTTP/1.1 503 Server Not Available\r\n"
+      "Connection: close\r\n"
+      "transfer-encoding: chunked\r\n"
+      "\r\n";
+
+  std::string chunks =
+      "3\r\n"
+      "123\r\n"
+      "0\r\n";
+  std::string trailer =
+      "crass: monkeys\r\n"
+      "funky: monkeys\r\n"
+      "\n";
+
+  balsa_frame_.set_is_request(false);
+  balsa_frame_.set_balsa_trailer(&trailer_);
+
+  ASSERT_EQ(headers.size(),
+            balsa_frame_.ProcessInput(headers.data(), headers.size()));
+  balsa_frame_.set_balsa_visitor(nullptr);
+  ASSERT_EQ(chunks.size(),
+            balsa_frame_.ProcessInput(chunks.data(), chunks.size()));
+  ASSERT_EQ(trailer.size(),
+            balsa_frame_.ProcessInput(trailer.data(), trailer.size()));
+  EXPECT_TRUE(balsa_frame_.MessageFullyRead());
+  EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
+  const absl::string_view crass = trailer_.GetHeader("crass");
+  EXPECT_EQ("monkeys", crass);
+  const absl::string_view funky = trailer_.GetHeader("funky");
+  EXPECT_EQ("monkeys", funky);
+}
+
+TEST_F(HTTPBalsaFrameTest,
+       NothingBadHappensWhenNoVisitorAssignedAndChunkingErrorOccurs) {
+  std::string headers =
+      "GET / HTTP/1.1\r\n"
+      "Connection: close\r\n"
+      "transfer-encoding: chunked\r\n"
+      "\r\n";
+
+  std::string chunks =
+      "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF\r\n"  // should overflow
+      "0\r\n";
+  std::string trailer =
+      "crass: monkeys\r\n"
+      "funky: monkeys\r\n"
+      "\n";
+
+  ASSERT_EQ(headers.size(),
+            balsa_frame_.ProcessInput(headers.data(), headers.size()));
+  balsa_frame_.set_balsa_visitor(nullptr);
+  EXPECT_GE(chunks.size(),
+            balsa_frame_.ProcessInput(chunks.data(), chunks.size()));
+  EXPECT_FALSE(balsa_frame_.MessageFullyRead());
+  EXPECT_TRUE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::CHUNK_LENGTH_OVERFLOW, balsa_frame_.ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest, FramerRecognizesSemicolonAsChunkSizeDelimiter) {
+  std::string headers =
+      "GET / HTTP/1.1\r\n"
+      "Connection: close\r\n"
+      "transfer-encoding: chunked\r\n"
+      "\r\n";
+
+  std::string chunks =
+      "8; foo=bar\r\n"
+      "deadbeef\r\n"
+      "0\r\n"
+      "\r\n";
+
+  ASSERT_EQ(headers.size(),
+            balsa_frame_.ProcessInput(headers.data(), headers.size()));
+
+  balsa_frame_.set_balsa_visitor(&visitor_mock_);
+  EXPECT_CALL(visitor_mock_, OnChunkLength(8));
+  EXPECT_CALL(visitor_mock_, OnChunkLength(0));
+  EXPECT_CALL(visitor_mock_, OnChunkExtensionInput("; foo=bar"));
+  EXPECT_CALL(visitor_mock_, OnChunkExtensionInput(""));
+
+  EXPECT_EQ(chunks.size(),
+            balsa_frame_.ProcessInput(chunks.data(), chunks.size()));
+  EXPECT_TRUE(balsa_frame_.MessageFullyRead());
+  EXPECT_FALSE(balsa_frame_.Error());
+}
+
+TEST_F(HTTPBalsaFrameTest, NonAsciiCharacterInChunkLength) {
+  std::string headers =
+      "GET / HTTP/1.1\r\n"
+      "Connection: close\r\n"
+      "transfer-encoding: chunked\r\n"
+      "\r\n";
+
+  std::string chunks =
+      "555\xAB\r\n"  // Character overflowing 7 bits, see b/20238315
+      "0\r\n";
+  std::string trailer =
+      "crass: monkeys\r\n"
+      "funky: monkeys\r\n"
+      "\n";
+
+  FakeHeaders fake_headers;
+  fake_headers.AddKeyValue("Connection", "close");
+  fake_headers.AddKeyValue("transfer-encoding", "chunked");
+
+  auto error_code = BalsaFrameEnums::INVALID_CHUNK_LENGTH;
+  {
+    InSequence s1;
+    EXPECT_CALL(visitor_mock_, OnRequestFirstLineInput("GET / HTTP/1.1", "GET",
+                                                       "/", "HTTP/1.1"));
+    EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
+    EXPECT_CALL(visitor_mock_, HeaderDone());
+    EXPECT_CALL(visitor_mock_, OnRawBodyInput("555\xAB"));
+    EXPECT_CALL(visitor_mock_, HandleError(error_code));
+  }
+
+  ASSERT_EQ(headers.size(),
+            balsa_frame_.ProcessInput(headers.data(), headers.size()));
+  EXPECT_EQ(strlen("555\xAB"),
+            balsa_frame_.ProcessInput(chunks.data(), chunks.size()));
+  EXPECT_FALSE(balsa_frame_.MessageFullyRead());
+  EXPECT_TRUE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::INVALID_CHUNK_LENGTH, balsa_frame_.ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest, VisitorCalledAsExpectedWhenChunkingOverflowOccurs) {
+  std::string headers =
+      "GET / HTTP/1.1\r\n"
+      "Connection: close\r\n"
+      "transfer-encoding: chunked\r\n"
+      "\r\n";
+
+  std::string chunks =
+      "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF\r\n"  // should overflow
+      "0\r\n";
+  std::string trailer =
+      "crass: monkeys\r\n"
+      "funky: monkeys\r\n"
+      "\n";
+
+  const char* chunk_read_before_overflow = "FFFFFFFFFFFFFFFFF";
+
+  FakeHeaders fake_headers;
+  fake_headers.AddKeyValue("Connection", "close");
+  fake_headers.AddKeyValue("transfer-encoding", "chunked");
+
+  auto error_code = BalsaFrameEnums::CHUNK_LENGTH_OVERFLOW;
+  {
+    InSequence s1;
+    EXPECT_CALL(visitor_mock_, OnRequestFirstLineInput("GET / HTTP/1.1", "GET",
+                                                       "/", "HTTP/1.1"));
+    EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
+    EXPECT_CALL(visitor_mock_, HeaderDone());
+    EXPECT_CALL(visitor_mock_, OnRawBodyInput(chunk_read_before_overflow));
+    EXPECT_CALL(visitor_mock_, HandleError(error_code));
+  }
+
+  ASSERT_EQ(headers.size(),
+            balsa_frame_.ProcessInput(headers.data(), headers.size()));
+  EXPECT_EQ(strlen(chunk_read_before_overflow),
+            balsa_frame_.ProcessInput(chunks.data(), chunks.size()));
+  EXPECT_FALSE(balsa_frame_.MessageFullyRead());
+  EXPECT_TRUE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::CHUNK_LENGTH_OVERFLOW, balsa_frame_.ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest,
+       VisitorCalledAsExpectedWhenInvalidChunkLengthOccurs) {
+  std::string headers =
+      "GET / HTTP/1.1\r\n"
+      "Connection: close\r\n"
+      "transfer-encoding: chunked\r\n"
+      "\r\n";
+
+  std::string chunks =
+      "12z123 \r\n"  // invalid chunk length
+      "0\r\n";
+  std::string trailer =
+      "crass: monkeys\r\n"
+      "funky: monkeys\r\n"
+      "\n";
+
+  FakeHeaders fake_headers;
+  fake_headers.AddKeyValue("Connection", "close");
+  fake_headers.AddKeyValue("transfer-encoding", "chunked");
+
+  auto error_code = BalsaFrameEnums::INVALID_CHUNK_LENGTH;
+  {
+    InSequence s1;
+    EXPECT_CALL(visitor_mock_, OnRequestFirstLineInput("GET / HTTP/1.1", "GET",
+                                                       "/", "HTTP/1.1"));
+    EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
+    EXPECT_CALL(visitor_mock_, HeaderDone());
+    EXPECT_CALL(visitor_mock_, OnRawBodyInput("12z"));
+    EXPECT_CALL(visitor_mock_, HandleError(error_code));
+  }
+
+  ASSERT_EQ(headers.size(),
+            balsa_frame_.ProcessInput(headers.data(), headers.size()));
+  EXPECT_EQ(3u, balsa_frame_.ProcessInput(chunks.data(), chunks.size()));
+  EXPECT_FALSE(balsa_frame_.MessageFullyRead());
+  EXPECT_TRUE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::INVALID_CHUNK_LENGTH, balsa_frame_.ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest, VisitorInvokedProperlyForRequestWithContentLength) {
+  std::string message_headers =
+      "PUT \t /search?q=fo \t HTTP/1.1 \t \r\n"
+      "content-length:  \t\t   20 \t\t  \r\n"
+      "\r\n";
+  std::string message_body = "12345678901234567890";
+  std::string message =
+      std::string(message_headers) + std::string(message_body);
+
+  FakeHeaders fake_headers;
+  fake_headers.AddKeyValue("content-length", "20");
+
+  {
+    InSequence s1;
+    EXPECT_CALL(visitor_mock_,
+                OnRequestFirstLineInput("PUT \t /search?q=fo \t HTTP/1.1",
+                                        "PUT", "/search?q=fo", "HTTP/1.1"));
+    EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
+    EXPECT_CALL(visitor_mock_, HeaderDone());
+    EXPECT_CALL(visitor_mock_, OnRawBodyInput(message_body));
+    EXPECT_CALL(visitor_mock_, OnBodyChunkInput(message_body));
+    EXPECT_CALL(visitor_mock_, MessageDone());
+  }
+  EXPECT_CALL(visitor_mock_, OnHeaderInput(message_headers));
+
+  ASSERT_EQ(message_headers.size(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+  ASSERT_EQ(message_body.size(),
+            balsa_frame_.ProcessInput(message.data() + message_headers.size(),
+                                      message.size()));
+  EXPECT_TRUE(balsa_frame_.MessageFullyRead());
+  EXPECT_FALSE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest,
+       VisitorInvokedProperlyForRequestWithOneCharContentLength) {
+  std::string message_headers =
+      "PUT \t /search?q=fo \t HTTP/1.1 \t \r\n"
+      "content-length:  \t\t   2 \t\t  \r\n"
+      "\r\n";
+  std::string message_body = "12";
+  std::string message =
+      std::string(message_headers) + std::string(message_body);
+
+  FakeHeaders fake_headers;
+  fake_headers.AddKeyValue("content-length", "2");
+
+  {
+    InSequence s1;
+    EXPECT_CALL(visitor_mock_,
+                OnRequestFirstLineInput("PUT \t /search?q=fo \t HTTP/1.1",
+                                        "PUT", "/search?q=fo", "HTTP/1.1"));
+    EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
+    EXPECT_CALL(visitor_mock_, HeaderDone());
+    EXPECT_CALL(visitor_mock_, OnRawBodyInput(message_body));
+    EXPECT_CALL(visitor_mock_, OnBodyChunkInput(message_body));
+    EXPECT_CALL(visitor_mock_, MessageDone());
+  }
+  EXPECT_CALL(visitor_mock_, OnHeaderInput(message_headers));
+
+  ASSERT_EQ(message_headers.size(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+  ASSERT_EQ(message_body.size(),
+            balsa_frame_.ProcessInput(message.data() + message_headers.size(),
+                                      message.size()));
+  EXPECT_TRUE(balsa_frame_.MessageFullyRead());
+  EXPECT_FALSE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest,
+       VisitorInvokedProperlyForRequestWithTransferEncoding) {
+  std::string message_headers =
+      "DELETE /search?q=fo \t HTTP/1.1 \t \r\n"
+      "trAnsfer-eNcoding:  chunked\r\n"
+      "\r\n";
+  std::string message_body =
+      "A            chunkjed extension  \r\n"
+      "01234567890            more crud including numbers 123123\r\n"
+      "3f\n"
+      "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"
+      "0 last one\r\n"
+      "\r\n";
+  std::string message_body_data =
+      "0123456789"
+      "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
+
+  std::string message =
+      std::string(message_headers) + std::string(message_body);
+
+  FakeHeaders fake_headers;
+  fake_headers.AddKeyValue("trAnsfer-eNcoding", "chunked");
+
+  {
+    InSequence s1;
+    EXPECT_CALL(visitor_mock_,
+                OnRequestFirstLineInput("DELETE /search?q=fo \t HTTP/1.1",
+                                        "DELETE", "/search?q=fo", "HTTP/1.1"));
+    EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
+    EXPECT_CALL(visitor_mock_, HeaderDone());
+    EXPECT_CALL(visitor_mock_, OnChunkLength(10));
+    EXPECT_CALL(visitor_mock_,
+                OnChunkExtensionInput("            chunkjed extension  "));
+    EXPECT_CALL(visitor_mock_, OnChunkLength(63));
+    EXPECT_CALL(visitor_mock_, OnChunkExtensionInput(""));
+    EXPECT_CALL(visitor_mock_, OnChunkLength(0));
+    EXPECT_CALL(visitor_mock_, OnChunkExtensionInput(" last one"));
+    EXPECT_CALL(visitor_mock_, MessageDone());
+  }
+  EXPECT_CALL(visitor_mock_, OnHeaderInput(message_headers));
+  std::string body_input;
+  EXPECT_CALL(visitor_mock_, OnRawBodyInput(_))
+      .WillRepeatedly([&body_input](absl::string_view input) {
+        absl::StrAppend(&body_input, input);
+      });
+  std::string body_data;
+  EXPECT_CALL(visitor_mock_, OnBodyChunkInput(_))
+      .WillRepeatedly([&body_data](absl::string_view input) {
+        absl::StrAppend(&body_data, input);
+      });
+  EXPECT_CALL(visitor_mock_, OnTrailerInput(_)).Times(0);
+
+  ASSERT_EQ(message_headers.size(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+  EXPECT_EQ(message_body.size(),
+            balsa_frame_.ProcessInput(message.data() + message_headers.size(),
+                                      message.size()));
+  EXPECT_TRUE(balsa_frame_.MessageFullyRead());
+  EXPECT_FALSE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
+
+  EXPECT_EQ(message_body, body_input);
+  EXPECT_EQ(message_body_data, body_data);
+}
+
+TEST_F(HTTPBalsaFrameTest,
+       VisitorInvokedProperlyForRequestWithTransferEncodingAndTrailers) {
+  std::string message_headers =
+      "DELETE /search?q=fo \t HTTP/1.1 \t \r\n"
+      "trAnsfer-eNcoding:  chunked\r\n"
+      "another_random_header:  \r\n"
+      "  \t \n"
+      "  \t includes a continuation\n"
+      "\r\n";
+  std::string message_body =
+      "A            chunkjed extension  \r\n"
+      "01234567890            more crud including numbers 123123\r\n"
+      "3f\n"
+      "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"
+      "1  \r\n"
+      "x   \r\n"
+      "0 last one\r\n";
+  std::string trailer_data =
+      "a_trailer_key: and a trailer value\r\n"
+      "\r\n";
+  std::string message_body_data =
+      "0123456789"
+      "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
+
+  std::string message = (std::string(message_headers) +
+                         std::string(message_body) + std::string(trailer_data));
+
+  FakeHeaders fake_headers;
+  fake_headers.AddKeyValue("trAnsfer-eNcoding", "chunked");
+  fake_headers.AddKeyValue("another_random_header", "includes a continuation");
+
+  {
+    InSequence s1;
+
+    EXPECT_CALL(visitor_mock_,
+                OnRequestFirstLineInput("DELETE /search?q=fo \t HTTP/1.1",
+                                        "DELETE", "/search?q=fo", "HTTP/1.1"));
+    EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
+    EXPECT_CALL(visitor_mock_, HeaderDone());
+    EXPECT_CALL(visitor_mock_, OnChunkLength(10));
+    EXPECT_CALL(visitor_mock_, OnChunkLength(63));
+    EXPECT_CALL(visitor_mock_, OnChunkLength(1));
+    EXPECT_CALL(visitor_mock_, OnChunkLength(0));
+    EXPECT_CALL(visitor_mock_, MessageDone());
+  }
+  EXPECT_CALL(visitor_mock_, OnHeaderInput(message_headers));
+  std::string body_input;
+  EXPECT_CALL(visitor_mock_, OnRawBodyInput(_))
+      .WillRepeatedly([&body_input](absl::string_view input) {
+        absl::StrAppend(&body_input, input);
+      });
+  std::string body_data;
+  EXPECT_CALL(visitor_mock_, OnBodyChunkInput(_))
+      .WillRepeatedly([&body_data](absl::string_view input) {
+        absl::StrAppend(&body_data, input);
+      });
+  EXPECT_CALL(visitor_mock_, OnTrailerInput(trailer_data));
+  EXPECT_CALL(visitor_mock_, OnChunkExtensionInput(_)).Times(AnyNumber());
+
+  ASSERT_EQ(message_headers.size(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+  EXPECT_EQ(message_body.size() + trailer_data.size(),
+            balsa_frame_.ProcessInput(message.data() + message_headers.size(),
+                                      message.size()));
+  EXPECT_TRUE(balsa_frame_.MessageFullyRead());
+  EXPECT_FALSE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
+
+  EXPECT_EQ(message_body, body_input);
+  EXPECT_EQ(message_body_data, body_data);
+}
+
+TEST_F(HTTPBalsaFrameTest,
+       VisitorInvokedProperlyWithRequestFirstLineWarningWithOnlyMethod) {
+  std::string message = "GET\n";
+
+  FakeHeaders fake_headers;
+
+  auto error_code = BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_REQUEST_METHOD;
+  {
+    InSequence s;
+    EXPECT_CALL(visitor_mock_, HandleWarning(error_code));
+    EXPECT_CALL(visitor_mock_, OnRequestFirstLineInput("GET", "GET", "", ""));
+    EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
+    EXPECT_CALL(visitor_mock_, HeaderDone());
+    EXPECT_CALL(visitor_mock_, MessageDone());
+  }
+  EXPECT_CALL(visitor_mock_, OnHeaderInput(message));
+
+  EXPECT_EQ(message.size(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+  EXPECT_TRUE(balsa_frame_.MessageFullyRead());
+  EXPECT_FALSE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_REQUEST_METHOD,
+            balsa_frame_.ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest,
+       VisitorInvokedProperlyWithRequestFirstLineWarningWithOnlyMethodAndWS) {
+  std::string message = "GET  \n";
+
+  FakeHeaders fake_headers;
+
+  auto error_code = BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_REQUEST_METHOD;
+  {
+    InSequence s;
+    EXPECT_CALL(visitor_mock_, HandleWarning(error_code));
+    // The flag setting here intentionally alters the framer's behavior with
+    // trailing whitespace.
+    EXPECT_CALL(visitor_mock_, OnRequestFirstLineInput("GET  ", "GET", "", ""));
+    EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
+    EXPECT_CALL(visitor_mock_, HeaderDone());
+    EXPECT_CALL(visitor_mock_, MessageDone());
+  }
+  EXPECT_CALL(visitor_mock_, OnHeaderInput(message));
+
+  EXPECT_EQ(message.size(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+  EXPECT_TRUE(balsa_frame_.MessageFullyRead());
+  EXPECT_FALSE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_REQUEST_METHOD,
+            balsa_frame_.ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest,
+       VisitorInvokedProperlyWithRequestFirstLineWarningWithMethodAndURI) {
+  std::string message = "GET /uri\n";
+
+  FakeHeaders fake_headers;
+
+  auto error_code =
+      BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_REQUEST_REQUEST_URI;
+  {
+    InSequence s;
+    EXPECT_CALL(visitor_mock_, HandleWarning(error_code));
+    EXPECT_CALL(visitor_mock_,
+                OnRequestFirstLineInput("GET /uri", "GET", "/uri", ""));
+    EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
+    EXPECT_CALL(visitor_mock_, HeaderDone());
+    EXPECT_CALL(visitor_mock_, MessageDone());
+  }
+  EXPECT_CALL(visitor_mock_, OnHeaderInput(message));
+
+  EXPECT_EQ(message.size(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+  EXPECT_TRUE(balsa_frame_.MessageFullyRead());
+  EXPECT_FALSE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_REQUEST_REQUEST_URI,
+            balsa_frame_.ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest, VisitorInvokedProperlyWithResponseFirstLineError) {
+  std::string message = "HTTP/1.1\n\n";
+
+  FakeHeaders fake_headers;
+
+  balsa_frame_.set_is_request(false);
+  auto error_code = BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_RESPONSE_VERSION;
+  {
+    InSequence s;
+    EXPECT_CALL(visitor_mock_, HandleError(error_code));
+    // The function returns before any of the following is called.
+    EXPECT_CALL(visitor_mock_, OnRequestFirstLineInput).Times(0);
+    EXPECT_CALL(visitor_mock_, ProcessHeaders(_)).Times(0);
+    EXPECT_CALL(visitor_mock_, HeaderDone()).Times(0);
+    EXPECT_CALL(visitor_mock_, MessageDone()).Times(0);
+  }
+  EXPECT_CALL(visitor_mock_, OnHeaderInput(_)).Times(0);
+
+  EXPECT_GE(message.size(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+  EXPECT_FALSE(balsa_frame_.MessageFullyRead());
+  EXPECT_TRUE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_RESPONSE_VERSION,
+            balsa_frame_.ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest, FlagsErrorWithContentLengthOverflow) {
+  std::string message =
+      "HTTP/1.0 200 OK\r\n"
+      "content-length: 9999999999999999999999999999999999999999\n"
+      "\n";
+
+  balsa_frame_.set_is_request(false);
+  auto error_code = BalsaFrameEnums::UNPARSABLE_CONTENT_LENGTH;
+  EXPECT_CALL(visitor_mock_, HandleError(error_code));
+
+  EXPECT_EQ(message.size(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+  EXPECT_FALSE(balsa_frame_.MessageFullyRead());
+  EXPECT_TRUE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::UNPARSABLE_CONTENT_LENGTH,
+            balsa_frame_.ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest, FlagsErrorWithInvalidResponseCode) {
+  std::string message =
+      "HTTP/1.0 x OK\r\n"
+      "\n";
+
+  balsa_frame_.set_is_request(false);
+  auto error_code = BalsaFrameEnums::FAILED_CONVERTING_STATUS_CODE_TO_INT;
+  EXPECT_CALL(visitor_mock_, HandleError(error_code));
+
+  EXPECT_GE(message.size(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+  EXPECT_FALSE(balsa_frame_.MessageFullyRead());
+  EXPECT_TRUE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::FAILED_CONVERTING_STATUS_CODE_TO_INT,
+            balsa_frame_.ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest, FlagsErrorWithOverflowingResponseCode) {
+  std::string message =
+      "HTTP/1.0 999999999999999999999999999999999999999 OK\r\n"
+      "\n";
+
+  balsa_frame_.set_is_request(false);
+  auto error_code = BalsaFrameEnums::FAILED_CONVERTING_STATUS_CODE_TO_INT;
+  EXPECT_CALL(visitor_mock_, HandleError(error_code));
+
+  EXPECT_GE(message.size(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+  EXPECT_FALSE(balsa_frame_.MessageFullyRead());
+  EXPECT_TRUE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::FAILED_CONVERTING_STATUS_CODE_TO_INT,
+            balsa_frame_.ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest, FlagsErrorWithInvalidContentLength) {
+  std::string message =
+      "HTTP/1.0 200 OK\r\n"
+      "content-length: xxx\n"
+      "\n";
+
+  balsa_frame_.set_is_request(false);
+  auto error_code = BalsaFrameEnums::UNPARSABLE_CONTENT_LENGTH;
+  EXPECT_CALL(visitor_mock_, HandleError(error_code));
+
+  EXPECT_EQ(message.size(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+  EXPECT_FALSE(balsa_frame_.MessageFullyRead());
+  EXPECT_TRUE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::UNPARSABLE_CONTENT_LENGTH,
+            balsa_frame_.ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest, FlagsErrorWithNegativeContentLengthValue) {
+  std::string message =
+      "HTTP/1.0 200 OK\r\n"
+      "content-length: -20\n"
+      "\n";
+
+  balsa_frame_.set_is_request(false);
+  auto error_code = BalsaFrameEnums::UNPARSABLE_CONTENT_LENGTH;
+  EXPECT_CALL(visitor_mock_, HandleError(error_code));
+
+  EXPECT_EQ(message.size(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+  EXPECT_FALSE(balsa_frame_.MessageFullyRead());
+  EXPECT_TRUE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::UNPARSABLE_CONTENT_LENGTH,
+            balsa_frame_.ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest, FlagsErrorWithEmptyContentLengthValue) {
+  std::string message =
+      "HTTP/1.0 200 OK\r\n"
+      "content-length: \n"
+      "\n";
+
+  balsa_frame_.set_is_request(false);
+  auto error_code = BalsaFrameEnums::UNPARSABLE_CONTENT_LENGTH;
+  EXPECT_CALL(visitor_mock_, HandleError(error_code));
+
+  EXPECT_EQ(message.size(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+  EXPECT_FALSE(balsa_frame_.MessageFullyRead());
+  EXPECT_TRUE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::UNPARSABLE_CONTENT_LENGTH,
+            balsa_frame_.ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest, VisitorInvokedProperlyForTrivialResponse) {
+  std::string message =
+      "HTTP/1.0 200 OK\r\n"
+      "content-length: 0\n"
+      "\n";
+
+  FakeHeaders fake_headers;
+  fake_headers.AddKeyValue("content-length", "0");
+
+  balsa_frame_.set_is_request(false);
+  {
+    InSequence s;
+    EXPECT_CALL(visitor_mock_, OnResponseFirstLineInput(
+                                   "HTTP/1.0 200 OK", "HTTP/1.0", "200", "OK"));
+    EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
+    EXPECT_CALL(visitor_mock_, HeaderDone());
+    EXPECT_CALL(visitor_mock_, MessageDone());
+  }
+  EXPECT_CALL(visitor_mock_, OnHeaderInput(message));
+
+  EXPECT_EQ(message.size(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+  EXPECT_TRUE(balsa_frame_.MessageFullyRead());
+  EXPECT_FALSE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest,
+       VisitorInvokedProperlyForResponseWithSplitBlankLines) {
+  std::string blanks =
+      "\n"
+      "\r\n"
+      "\r\n";
+  std::string header_input =
+      "HTTP/1.0 200 OK\r\n"
+      "content-length: 0\n"
+      "\n";
+  FakeHeaders fake_headers;
+  fake_headers.AddKeyValue("content-length", "0");
+
+  balsa_frame_.set_is_request(false);
+  {
+    InSequence s;
+    EXPECT_CALL(visitor_mock_, OnResponseFirstLineInput(
+                                   "HTTP/1.0 200 OK", "HTTP/1.0", "200", "OK"));
+    EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
+    EXPECT_CALL(visitor_mock_, HeaderDone());
+    EXPECT_CALL(visitor_mock_, MessageDone());
+  }
+  EXPECT_CALL(visitor_mock_, OnHeaderInput(header_input));
+
+  EXPECT_EQ(blanks.size(),
+            balsa_frame_.ProcessInput(blanks.data(), blanks.size()));
+  EXPECT_EQ(header_input.size(), balsa_frame_.ProcessInput(
+                                     header_input.data(), header_input.size()));
+  EXPECT_TRUE(balsa_frame_.MessageFullyRead());
+  EXPECT_FALSE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest, VisitorInvokedProperlyForResponseWithBlankLines) {
+  std::string blanks =
+      "\n"
+      "\r\n"
+      "\n"
+      "\n"
+      "\r\n"
+      "\r\n";
+  std::string header_input =
+      "HTTP/1.0 200 OK\r\n"
+      "content-length: 0\n"
+      "\n";
+  std::string message = blanks + header_input;
+
+  FakeHeaders fake_headers;
+  fake_headers.AddKeyValue("content-length", "0");
+
+  balsa_frame_.set_is_request(false);
+  {
+    InSequence s;
+    EXPECT_CALL(visitor_mock_, OnResponseFirstLineInput(
+                                   "HTTP/1.0 200 OK", "HTTP/1.0", "200", "OK"));
+    EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
+    EXPECT_CALL(visitor_mock_, HeaderDone());
+    EXPECT_CALL(visitor_mock_, MessageDone());
+  }
+  EXPECT_CALL(visitor_mock_, OnHeaderInput(header_input));
+
+  EXPECT_EQ(message.size(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+  EXPECT_TRUE(balsa_frame_.MessageFullyRead());
+  EXPECT_FALSE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest, VisitorInvokedProperlyForResponseWithContentLength) {
+  std::string message_headers =
+      "HTTP/1.1  \t 200 Ok all is well\r\n"
+      "content-length:  \t\t   20 \t\t  \r\n"
+      "\r\n";
+  std::string message_body = "12345678901234567890";
+  std::string message =
+      std::string(message_headers) + std::string(message_body);
+
+  FakeHeaders fake_headers;
+  fake_headers.AddKeyValue("content-length", "20");
+
+  balsa_frame_.set_is_request(false);
+  {
+    InSequence s1;
+    EXPECT_CALL(visitor_mock_,
+                OnResponseFirstLineInput("HTTP/1.1  \t 200 Ok all is well",
+                                         "HTTP/1.1", "200", "Ok all is well"));
+    EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
+    EXPECT_CALL(visitor_mock_, HeaderDone());
+    EXPECT_CALL(visitor_mock_, OnRawBodyInput(message_body));
+    EXPECT_CALL(visitor_mock_, OnBodyChunkInput(message_body));
+    EXPECT_CALL(visitor_mock_, MessageDone());
+  }
+  EXPECT_CALL(visitor_mock_, OnHeaderInput(message_headers));
+
+  ASSERT_EQ(message_headers.size(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+  EXPECT_EQ(message_body.size(),
+            balsa_frame_.ProcessInput(message.data() + message_headers.size(),
+                                      message.size()));
+  EXPECT_TRUE(balsa_frame_.MessageFullyRead());
+  EXPECT_FALSE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest,
+       VisitorInvokedProperlyForResponseWithTransferEncoding) {
+  std::string message_headers =
+      "HTTP/1.1  \t 200 Ok all is well\r\n"
+      "trAnsfer-eNcoding:  chunked\r\n"
+      "\r\n";
+  std::string message_body =
+      "A            chunkjed extension  \r\n"
+      "01234567890            more crud including numbers 123123\r\n"
+      "3f\n"
+      "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"
+      "0 last one\r\n"
+      "\r\n";
+  std::string message_body_data =
+      "0123456789"
+      "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
+
+  std::string message =
+      std::string(message_headers) + std::string(message_body);
+
+  FakeHeaders fake_headers;
+  fake_headers.AddKeyValue("trAnsfer-eNcoding", "chunked");
+
+  balsa_frame_.set_is_request(false);
+  {
+    InSequence s1;
+    EXPECT_CALL(visitor_mock_,
+                OnResponseFirstLineInput("HTTP/1.1  \t 200 Ok all is well",
+                                         "HTTP/1.1", "200", "Ok all is well"));
+    EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
+    EXPECT_CALL(visitor_mock_, HeaderDone());
+    EXPECT_CALL(visitor_mock_, OnChunkLength(10));
+    EXPECT_CALL(visitor_mock_, OnChunkLength(63));
+    EXPECT_CALL(visitor_mock_, OnChunkLength(0));
+    EXPECT_CALL(visitor_mock_, MessageDone());
+  }
+  EXPECT_CALL(visitor_mock_, OnHeaderInput(message_headers));
+  std::string body_input;
+  EXPECT_CALL(visitor_mock_, OnRawBodyInput(_))
+      .WillRepeatedly([&body_input](absl::string_view input) {
+        absl::StrAppend(&body_input, input);
+      });
+  std::string body_data;
+  EXPECT_CALL(visitor_mock_, OnBodyChunkInput(_))
+      .WillRepeatedly([&body_data](absl::string_view input) {
+        absl::StrAppend(&body_data, input);
+      });
+  EXPECT_CALL(visitor_mock_, OnTrailerInput(_)).Times(0);
+
+  ASSERT_EQ(message_headers.size(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+  EXPECT_EQ(message_body.size(),
+            balsa_frame_.ProcessInput(message.data() + message_headers.size(),
+                                      message.size()));
+  EXPECT_TRUE(balsa_frame_.MessageFullyRead());
+  EXPECT_FALSE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
+
+  EXPECT_EQ(message_body, body_input);
+  EXPECT_EQ(message_body_data, body_data);
+}
+
+TEST_F(HTTPBalsaFrameTest,
+       VisitorInvokedProperlyForResponseWithTransferEncodingAndTrailers) {
+  std::string message_headers =
+      "HTTP/1.1  \t 200 Ok all is well\r\n"
+      "trAnsfer-eNcoding:  chunked\r\n"
+      "\r\n";
+  std::string message_body =
+      "A            chunkjed extension  \r\n"
+      "01234567890            more crud including numbers 123123\r\n"
+      "3f\n"
+      "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"
+      "0 last one\r\n";
+  std::string trailer_data =
+      "a_trailer_key: and a trailer value\r\n"
+      "\r\n";
+  std::string message_body_data =
+      "0123456789"
+      "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
+
+  std::string message = (std::string(message_headers) +
+                         std::string(message_body) + std::string(trailer_data));
+
+  FakeHeaders fake_headers;
+  fake_headers.AddKeyValue("trAnsfer-eNcoding", "chunked");
+
+  FakeHeaders fake_headers_in_trailer;
+  fake_headers_in_trailer.AddKeyValue("a_trailer_key", "and a trailer value");
+
+  balsa_frame_.set_is_request(false);
+  balsa_frame_.set_balsa_trailer(&trailer_);
+
+  {
+    InSequence s1;
+    EXPECT_CALL(visitor_mock_,
+                OnResponseFirstLineInput("HTTP/1.1  \t 200 Ok all is well",
+                                         "HTTP/1.1", "200", "Ok all is well"));
+    EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
+    EXPECT_CALL(visitor_mock_, HeaderDone());
+    EXPECT_CALL(visitor_mock_, OnChunkLength(10));
+    EXPECT_CALL(visitor_mock_, OnChunkLength(63));
+    EXPECT_CALL(visitor_mock_, OnChunkLength(0));
+    EXPECT_CALL(visitor_mock_, ProcessTrailers(fake_headers_in_trailer));
+    EXPECT_CALL(visitor_mock_, MessageDone());
+  }
+  EXPECT_CALL(visitor_mock_, OnHeaderInput(message_headers));
+  std::string body_input;
+  EXPECT_CALL(visitor_mock_, OnRawBodyInput(_))
+      .WillRepeatedly([&body_input](absl::string_view input) {
+        absl::StrAppend(&body_input, input);
+      });
+  std::string body_data;
+  EXPECT_CALL(visitor_mock_, OnBodyChunkInput(_))
+      .WillRepeatedly([&body_data](absl::string_view input) {
+        absl::StrAppend(&body_data, input);
+      });
+  EXPECT_CALL(visitor_mock_, OnTrailerInput(trailer_data));
+
+  ASSERT_EQ(message_headers.size(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+  EXPECT_EQ(message_body.size() + trailer_data.size(),
+            balsa_frame_.ProcessInput(message.data() + message_headers.size(),
+                                      message.size()));
+  EXPECT_TRUE(balsa_frame_.MessageFullyRead());
+  EXPECT_FALSE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
+
+  EXPECT_EQ(message_body, body_input);
+  EXPECT_EQ(message_body_data, body_data);
+
+  const absl::string_view a_trailer_key = trailer_.GetHeader("a_trailer_key");
+  EXPECT_EQ("and a trailer value", a_trailer_key);
+}
+
+TEST_F(
+    HTTPBalsaFrameTest,
+    VisitorInvokedProperlyForResponseWithTransferEncodingAndTrailersBytePer) {
+  std::string message_headers =
+      "HTTP/1.1  \t 200 Ok all is well\r\n"
+      "trAnsfer-eNcoding:  chunked\r\n"
+      "\r\n";
+  std::string message_body =
+      "A            chunkjed extension  \r\n"
+      "01234567890            more crud including numbers 123123\r\n"
+      "3f\n"
+      "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"
+      "0 last one\r\n";
+  std::string trailer_data =
+      "a_trailer_key: and a trailer value\r\n"
+      "\r\n";
+  std::string message_body_data =
+      "0123456789"
+      "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
+
+  std::string message = (std::string(message_headers) +
+                         std::string(message_body) + std::string(trailer_data));
+
+  FakeHeaders fake_headers;
+  fake_headers.AddKeyValue("trAnsfer-eNcoding", "chunked");
+  FakeHeaders fake_headers_in_trailer;
+  fake_headers_in_trailer.AddKeyValue("a_trailer_key", "and a trailer value");
+
+  balsa_frame_.set_is_request(false);
+  balsa_frame_.set_balsa_trailer(&trailer_);
+
+  {
+    InSequence s1;
+    EXPECT_CALL(visitor_mock_,
+                OnResponseFirstLineInput("HTTP/1.1  \t 200 Ok all is well",
+                                         "HTTP/1.1", "200", "Ok all is well"));
+    EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
+    EXPECT_CALL(visitor_mock_, HeaderDone());
+    EXPECT_CALL(visitor_mock_, OnChunkLength(10));
+    EXPECT_CALL(visitor_mock_, OnChunkLength(63));
+    EXPECT_CALL(visitor_mock_, OnChunkLength(0));
+    EXPECT_CALL(visitor_mock_, ProcessTrailers(fake_headers_in_trailer));
+    EXPECT_CALL(visitor_mock_, MessageDone());
+  }
+  EXPECT_CALL(visitor_mock_, OnHeaderInput(message_headers));
+  std::string body_input;
+  EXPECT_CALL(visitor_mock_, OnRawBodyInput(_))
+      .WillRepeatedly([&body_input](absl::string_view input) {
+        absl::StrAppend(&body_input, input);
+      });
+  std::string body_data;
+  EXPECT_CALL(visitor_mock_, OnBodyChunkInput(_))
+      .WillRepeatedly([&body_data](absl::string_view input) {
+        absl::StrAppend(&body_data, input);
+      });
+  std::string trailer_input;
+  EXPECT_CALL(visitor_mock_, OnTrailerInput(_))
+      .WillRepeatedly([&trailer_input](absl::string_view input) {
+        absl::StrAppend(&trailer_input, input);
+      });
+
+  for (size_t i = 0; i < message.size(); ++i) {
+    ASSERT_EQ(1u, balsa_frame_.ProcessInput(message.data() + i, 1));
+  }
+  EXPECT_TRUE(balsa_frame_.MessageFullyRead());
+  EXPECT_FALSE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
+
+  EXPECT_EQ(message_body, body_input);
+  EXPECT_EQ(message_body_data, body_data);
+  EXPECT_EQ(trailer_data, trailer_input);
+
+  const absl::string_view a_trailer_key = trailer_.GetHeader("a_trailer_key");
+  EXPECT_EQ("and a trailer value", a_trailer_key);
+}
+
+TEST(HTTPBalsaFrame,
+     VisitorInvokedProperlyForResponseWithTransferEncodingAndTrailersRandom) {
+  TestSeed seed;
+  seed.Initialize(absl::GetFlag(FLAGS_randseed));
+  RandomEngine rng;
+  rng.seed(seed.GetSeed());
+  for (int i = 0; i < 1000; ++i) {
+    std::string message_headers =
+        "HTTP/1.1  \t 200 Ok all is well\r\n"
+        "trAnsfer-eNcoding:  chunked\r\n"
+        "\r\n";
+    std::string message_body =
+        "A            chunkjed extension  \r\n"
+        "01234567890            more crud including numbers 123123\r\n"
+        "3f\n"
+        "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"
+        "0 last one\r\n";
+    std::string trailer_data =
+        "a_trailer_key: and a trailer value\r\n"
+        "\r\n";
+    std::string message_body_data =
+        "0123456789"
+        "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
+
+    std::string message =
+        (std::string(message_headers) + std::string(message_body) +
+         std::string(trailer_data));
+
+    FakeHeaders fake_headers;
+    fake_headers.AddKeyValue("trAnsfer-eNcoding", "chunked");
+    FakeHeaders fake_headers_in_trailer;
+    fake_headers_in_trailer.AddKeyValue("a_trailer_key", "and a trailer value");
+
+    StrictMock<BalsaVisitorMock> visitor_mock;
+
+    BalsaHeaders headers;
+    BalsaHeaders trailer;
+    BalsaFrame balsa_frame;
+    balsa_frame.set_is_request(false);
+    balsa_frame.set_balsa_headers(&headers);
+    balsa_frame.set_balsa_trailer(&trailer);
+    balsa_frame.set_balsa_visitor(&visitor_mock);
+
+    {
+      InSequence s1;
+      EXPECT_CALL(visitor_mock, OnResponseFirstLineInput(
+                                    "HTTP/1.1  \t 200 Ok all is well",
+                                    "HTTP/1.1", "200", "Ok all is well"));
+      EXPECT_CALL(visitor_mock, ProcessHeaders(fake_headers));
+      EXPECT_CALL(visitor_mock, HeaderDone());
+      EXPECT_CALL(visitor_mock, ProcessTrailers(fake_headers_in_trailer));
+      EXPECT_CALL(visitor_mock, MessageDone());
+    }
+    EXPECT_CALL(visitor_mock, OnHeaderInput(message_headers));
+    std::string body_input;
+    EXPECT_CALL(visitor_mock, OnRawBodyInput(_))
+        .WillRepeatedly([&body_input](absl::string_view input) {
+          absl::StrAppend(&body_input, input);
+        });
+    std::string body_data;
+    EXPECT_CALL(visitor_mock, OnBodyChunkInput(_))
+        .WillRepeatedly([&body_data](absl::string_view input) {
+          absl::StrAppend(&body_data, input);
+        });
+    std::string trailer_input;
+    EXPECT_CALL(visitor_mock, OnTrailerInput(_))
+        .WillRepeatedly([&trailer_input](absl::string_view input) {
+          absl::StrAppend(&trailer_input, input);
+        });
+    EXPECT_CALL(visitor_mock, OnChunkLength(_)).Times(AtLeast(1));
+    EXPECT_CALL(visitor_mock, OnChunkExtensionInput(_)).Times(AtLeast(1));
+
+    size_t count = 0;
+    size_t total_processed = 0;
+    for (size_t i = 0; i < message.size();) {
+      auto dist = std::uniform_int_distribution<>(0, message.size() - i + 1);
+      count = dist(rng);
+      size_t processed = balsa_frame.ProcessInput(message.data() + i, count);
+      ASSERT_GE(count, processed);
+      total_processed += processed;
+      i += processed;
+    }
+    EXPECT_EQ(message.size(), total_processed);
+    EXPECT_TRUE(balsa_frame.MessageFullyRead());
+    EXPECT_FALSE(balsa_frame.Error());
+    EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame.ErrorCode());
+
+    EXPECT_EQ(message_body, body_input);
+    EXPECT_EQ(message_body_data, body_data);
+    EXPECT_EQ(trailer_data, trailer_input);
+
+    const absl::string_view a_trailer_key = trailer.GetHeader("a_trailer_key");
+    EXPECT_EQ("and a trailer value", a_trailer_key);
+  }
+}
+
+TEST_F(HTTPBalsaFrameTest,
+       AppropriateActionTakenWhenHeadersTooLongWithTooMuchInput) {
+  std::string message =
+      "GET /asflkasfdhjsafdkljhasfdlkjhasdflkjhsafdlkjhh HTTP/1.1\r\n"
+      "\r\n";
+  const size_t kAmountLessThanHeaderLen = 10;
+  ASSERT_LE(kAmountLessThanHeaderLen, message.size());
+
+  auto error_code = BalsaFrameEnums::HEADERS_TOO_LONG;
+  EXPECT_CALL(visitor_mock_, HandleError(error_code));
+
+  balsa_frame_.set_max_header_length(message.size() - kAmountLessThanHeaderLen);
+
+  ASSERT_EQ(balsa_frame_.max_header_length(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+  EXPECT_TRUE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::HEADERS_TOO_LONG, balsa_frame_.ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest, AppropriateActionTakenWhenHeadersTooLongWithBody) {
+  std::string message =
+      "PUT /foo HTTP/1.1\r\n"
+      "Content-Length: 4\r\n"
+      "header: xxxxxxxxx\r\n\r\n"
+      "B";  // body begin
+
+  auto error_code = BalsaFrameEnums::HEADERS_TOO_LONG;
+  EXPECT_CALL(visitor_mock_, HandleError(error_code));
+
+  // -2 because we have 1 byte of body, and we want to refuse
+  // this.
+  balsa_frame_.set_max_header_length(message.size() - 2);
+
+  ASSERT_EQ(balsa_frame_.max_header_length(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+  EXPECT_TRUE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::HEADERS_TOO_LONG, balsa_frame_.ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest, AppropriateActionTakenWhenHeadersTooLongWhenReset) {
+  std::string message =
+      "GET /asflkasfdhjsafdkljhasfdlkjhasdflkjhsafdlkjhh HTTP/1.1\r\n"
+      "\r\n";
+  const size_t kAmountLessThanHeaderLen = 10;
+  ASSERT_LE(kAmountLessThanHeaderLen, message.size());
+
+  auto error_code = BalsaFrameEnums::HEADERS_TOO_LONG;
+
+  ASSERT_EQ(message.size() - 2,
+            balsa_frame_.ProcessInput(message.data(), message.size() - 2));
+
+  // Now set max header length to something smaller.
+  balsa_frame_.set_max_header_length(message.size() - kAmountLessThanHeaderLen);
+  EXPECT_CALL(visitor_mock_, HandleError(error_code));
+
+  ASSERT_EQ(0u,
+            balsa_frame_.ProcessInput(message.data() + message.size() - 2, 2));
+  EXPECT_TRUE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::HEADERS_TOO_LONG, balsa_frame_.ErrorCode());
+}
+
+class BalsaFrameParsingTest : public testing::Test {
+ protected:
+  void SetUp() override {
+    balsa_frame_.set_http_validation_policy(
+        HttpValidationPolicy::CreateDefault());
+    balsa_frame_.set_is_request(true);
+    balsa_frame_.set_balsa_headers(&headers_);
+    balsa_frame_.set_balsa_visitor(&visitor_mock_);
+  }
+
+  void TestEmptyHeaderKeyHelper(const std::string& message) {
+    InSequence s;
+    EXPECT_CALL(visitor_mock_, OnRequestFirstLineInput("GET / HTTP/1.1", "GET",
+                                                       "/", "HTTP/1.1"));
+    EXPECT_CALL(visitor_mock_, OnHeaderInput(_));
+    EXPECT_CALL(visitor_mock_,
+                HandleError(BalsaFrameEnums::INVALID_HEADER_FORMAT));
+
+    ASSERT_EQ(message.size(),
+              balsa_frame_.ProcessInput(message.data(), message.size()));
+    EXPECT_TRUE(balsa_frame_.Error());
+    Mock::VerifyAndClearExpectations(&visitor_mock_);
+  }
+
+  void TestInvalidTrailerFormat(const std::string& trailer,
+                                bool invalid_name_char) {
+    balsa_frame_.set_is_request(false);
+    balsa_frame_.set_balsa_trailer(&trailer_);
+
+    std::string headers =
+        "HTTP/1.0 200 ok\r\n"
+        "transfer-encoding: chunked\r\n"
+        "\r\n";
+
+    std::string chunks =
+        "3\r\n"
+        "123\r\n"
+        "0\r\n";
+
+    InSequence s;
+
+    EXPECT_CALL(visitor_mock_, OnResponseFirstLineInput);
+    EXPECT_CALL(visitor_mock_, OnHeaderInput);
+    EXPECT_CALL(visitor_mock_, ProcessHeaders);
+    EXPECT_CALL(visitor_mock_, HeaderDone);
+    EXPECT_CALL(visitor_mock_, OnChunkLength(3));
+    EXPECT_CALL(visitor_mock_, OnChunkExtensionInput);
+    EXPECT_CALL(visitor_mock_, OnRawBodyInput);
+    EXPECT_CALL(visitor_mock_, OnBodyChunkInput);
+    EXPECT_CALL(visitor_mock_, OnChunkLength(0));
+    EXPECT_CALL(visitor_mock_, OnChunkExtensionInput);
+    EXPECT_CALL(visitor_mock_, OnRawBodyInput);
+    EXPECT_CALL(visitor_mock_, OnRawBodyInput);
+    const auto expected_error =
+        invalid_name_char ? BalsaFrameEnums::INVALID_TRAILER_NAME_CHARACTER
+                          : BalsaFrameEnums::INVALID_TRAILER_FORMAT;
+    EXPECT_CALL(visitor_mock_, HandleError(expected_error)).Times(1);
+
+    EXPECT_CALL(visitor_mock_, ProcessTrailers(_)).Times(0);
+    EXPECT_CALL(visitor_mock_, MessageDone()).Times(0);
+
+    ASSERT_EQ(headers.size(),
+              balsa_frame_.ProcessInput(headers.data(), headers.size()));
+    ASSERT_EQ(chunks.size(),
+              balsa_frame_.ProcessInput(chunks.data(), chunks.size()));
+    EXPECT_EQ(trailer.size(),
+              balsa_frame_.ProcessInput(trailer.data(), trailer.size()));
+    EXPECT_FALSE(balsa_frame_.MessageFullyRead());
+    EXPECT_TRUE(balsa_frame_.Error());
+    EXPECT_EQ(expected_error, balsa_frame_.ErrorCode());
+
+    Mock::VerifyAndClearExpectations(&visitor_mock_);
+  }
+
+  BalsaHeaders headers_;
+  BalsaHeaders trailer_;
+  BalsaFrame balsa_frame_;
+  StrictMock<BalsaVisitorMock> visitor_mock_;
+};
+
+TEST_F(BalsaFrameParsingTest, AppropriateActionTakenWhenHeaderColonsAreFunny) {
+  // Believe it or not, the following message is not structured willy-nilly.
+  // It is structured so that both codepaths in both SSE2 and non SSE2 paths
+  // for finding colons are exersized.
+  std::string message =
+      "GET / HTTP/1.1\r\n"
+      "a\r\n"
+      "b\r\n"
+      "c\r\n"
+      "d\r\n"
+      "e\r\n"
+      "f\r\n"
+      "g\r\n"
+      "h\r\n"
+      "i:\r\n"
+      "j\r\n"
+      "k\r\n"
+      "l\r\n"
+      "m\r\n"
+      "n\r\n"
+      "o\r\n"
+      "p\r\n"
+      "q\r\n"
+      "r\r\n"
+      "s\r\n"
+      "t\r\n"
+      "u\r\n"
+      "v\r\n"
+      "w\r\n"
+      "x\r\n"
+      "y\r\n"
+      "z\r\n"
+      "A\r\n"
+      "B\r\n"
+      ": val\r\n"
+      "\r\n";
+
+  EXPECT_CALL(visitor_mock_, OnRequestFirstLineInput("GET / HTTP/1.1", "GET",
+                                                     "/", "HTTP/1.1"));
+  EXPECT_CALL(visitor_mock_, OnHeaderInput(_));
+  EXPECT_CALL(visitor_mock_,
+              HandleWarning(BalsaFrameEnums::HEADER_MISSING_COLON))
+      .Times(27);
+  EXPECT_CALL(visitor_mock_,
+              HandleError(BalsaFrameEnums::INVALID_HEADER_FORMAT));
+
+  ASSERT_EQ(message.size(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+
+  EXPECT_TRUE(balsa_frame_.Error());
+}
+
+TEST_F(BalsaFrameParsingTest, ErrorWhenHeaderKeyIsEmpty) {
+  std::string firstKeyIsEmpty =
+      "GET / HTTP/1.1\r\n"
+      ": \r\n"
+      "a:b\r\n"
+      "c:d\r\n"
+      "\r\n";
+  TestEmptyHeaderKeyHelper(firstKeyIsEmpty);
+
+  balsa_frame_.Reset();
+
+  std::string laterKeyIsEmpty =
+      "GET / HTTP/1.1\r\n"
+      "a:b\r\n"
+      ": \r\n"
+      "c:d\r\n"
+      "\r\n";
+  TestEmptyHeaderKeyHelper(laterKeyIsEmpty);
+}
+
+TEST_F(BalsaFrameParsingTest, InvalidTrailerFormat) {
+  std::string trailer =
+      ":monkeys\n"
+      "\r\n";
+  TestInvalidTrailerFormat(trailer, false);
+
+  balsa_frame_.Reset();
+
+  std::string trailer2 =
+      "   \r\n"
+      "test: test\r\n"
+      "\r\n";
+  TestInvalidTrailerFormat(trailer2, true);
+
+  balsa_frame_.Reset();
+
+  std::string trailer3 =
+      "a: b\r\n"
+      ": test\r\n"
+      "\r\n";
+  TestInvalidTrailerFormat(trailer3, false);
+}
+
+TEST_F(HTTPBalsaFrameTest,
+       EnsureHeaderFramingFoundWithVariousCombinationsOfRN_RN) {
+  const std::string message =
+      "GET / HTTP/1.1\r\n"
+      "content-length: 0\r\n"
+      "a\r\n"
+      "b\r\n"
+      "c\r\n"
+      "d\r\n"
+      "e\r\n"
+      "f\r\n"
+      "g\r\n"
+      "h\r\n"
+      "i\r\n"
+      "\r\n";
+  EXPECT_EQ(message.size(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+  EXPECT_FALSE(balsa_frame_.Error())
+      << BalsaFrameEnums::ErrorCodeToString(balsa_frame_.ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest,
+       EnsureHeaderFramingFoundWithVariousCombinationsOfRN_N) {
+  const std::string message =
+      "GET / HTTP/1.1\n"
+      "content-length: 0\n"
+      "a\n"
+      "b\n"
+      "c\n"
+      "d\n"
+      "e\n"
+      "f\n"
+      "g\n"
+      "h\n"
+      "i\n"
+      "\n";
+  EXPECT_EQ(message.size(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+  EXPECT_FALSE(balsa_frame_.Error())
+      << BalsaFrameEnums::ErrorCodeToString(balsa_frame_.ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest,
+       EnsureHeaderFramingFoundWithVariousCombinationsOfRN_RN_N) {
+  const std::string message =
+      "GET / HTTP/1.1\n"
+      "content-length: 0\r\n"
+      "a\r\n"
+      "b\n"
+      "c\r\n"
+      "d\n"
+      "e\r\n"
+      "f\n"
+      "g\r\n"
+      "h\n"
+      "i\r\n"
+      "\n";
+  EXPECT_EQ(message.size(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+  EXPECT_FALSE(balsa_frame_.Error())
+      << BalsaFrameEnums::ErrorCodeToString(balsa_frame_.ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest,
+       EnsureHeaderFramingFoundWithVariousCombinationsOfRN_N_RN) {
+  const std::string message =
+      "GET / HTTP/1.1\n"
+      "content-length: 0\r\n"
+      "a\n"
+      "b\r\n"
+      "c\n"
+      "d\r\n"
+      "e\n"
+      "f\r\n"
+      "g\n"
+      "h\r\n"
+      "i\n"
+      "\r\n";
+  EXPECT_EQ(message.size(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+  EXPECT_FALSE(balsa_frame_.Error())
+      << BalsaFrameEnums::ErrorCodeToString(balsa_frame_.ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest, ReadUntilCloseStateEnteredAsExpectedAndNotExited) {
+  std::string message =
+      "HTTP/1.1 200 OK\r\n"
+      "\r\n";
+  balsa_frame_.set_is_request(false);
+  EXPECT_EQ(message.size(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+  EXPECT_FALSE(balsa_frame_.Error())
+      << BalsaFrameEnums::ErrorCodeToString(balsa_frame_.ErrorCode());
+  EXPECT_EQ(BalsaFrameEnums::READING_UNTIL_CLOSE, balsa_frame_.ParseState());
+
+  std::string gobldygook = "-198324-9182-43981-23498-98342-jasldfn-1294hj";
+  for (int i = 0; i < 1000; ++i) {
+    EXPECT_EQ(gobldygook.size(),
+              balsa_frame_.ProcessInput(gobldygook.data(), gobldygook.size()));
+    EXPECT_FALSE(balsa_frame_.Error())
+        << BalsaFrameEnums::ErrorCodeToString(balsa_frame_.ErrorCode());
+    EXPECT_EQ(BalsaFrameEnums::READING_UNTIL_CLOSE, balsa_frame_.ParseState());
+  }
+}
+
+TEST_F(HTTPBalsaFrameTest,
+       BytesSafeToSpliceAndBytesSplicedWorksWithContentLength) {
+  std::string header =
+      "HTTP/1.1 200 OK\r\n"
+      "content-length: 1000\r\n"
+      "\r\n";
+  balsa_frame_.set_is_request(false);
+  size_t bytes_safe_to_splice = 1000;
+  EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice());
+  EXPECT_EQ(header.size(),
+            balsa_frame_.ProcessInput(header.data(), header.size()));
+  EXPECT_EQ(bytes_safe_to_splice, balsa_frame_.BytesSafeToSplice());
+  while (bytes_safe_to_splice > 0) {
+    balsa_frame_.BytesSpliced(1);
+    bytes_safe_to_splice -= 1;
+    ASSERT_FALSE(balsa_frame_.Error())
+        << BalsaFrameEnums::ParseStateToString(balsa_frame_.ParseState()) << " "
+        << BalsaFrameEnums::ErrorCodeToString(balsa_frame_.ErrorCode())
+        << " with bytes_safe_to_splice: " << bytes_safe_to_splice
+        << " and BytesSafeToSplice(): " << balsa_frame_.BytesSafeToSplice();
+  }
+  EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice());
+  EXPECT_FALSE(balsa_frame_.Error());
+  EXPECT_TRUE(balsa_frame_.MessageFullyRead());
+}
+
+TEST_F(HTTPBalsaFrameTest, BytesSplicedFlagsErrorsWhenNotInProperState) {
+  balsa_frame_.set_is_request(false);
+  balsa_frame_.BytesSpliced(1);
+  EXPECT_TRUE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::CALLED_BYTES_SPLICED_WHEN_UNSAFE_TO_DO_SO,
+            balsa_frame_.ErrorCode());
+  EXPECT_FALSE(balsa_frame_.MessageFullyRead());
+}
+
+TEST_F(HTTPBalsaFrameTest,
+       BytesSplicedFlagsErrorsWhenTooMuchSplicedForContentLen) {
+  std::string header =
+      "HTTP/1.1 200 OK\r\n"
+      "content-length: 1000\r\n"
+      "\r\n";
+  balsa_frame_.set_is_request(false);
+  EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice());
+  EXPECT_EQ(header.size(),
+            balsa_frame_.ProcessInput(header.data(), header.size()));
+  EXPECT_EQ(1000u, balsa_frame_.BytesSafeToSplice());
+  balsa_frame_.BytesSpliced(1001);
+  EXPECT_TRUE(balsa_frame_.Error());
+  EXPECT_EQ(
+      BalsaFrameEnums::CALLED_BYTES_SPLICED_AND_EXCEEDED_SAFE_SPLICE_AMOUNT,
+      balsa_frame_.ErrorCode());
+  EXPECT_FALSE(balsa_frame_.MessageFullyRead());
+}
+
+TEST_F(HTTPBalsaFrameTest, BytesSplicedWorksAsExpectedForReadUntilClose) {
+  std::string header =
+      "HTTP/1.1 200 OK\r\n"
+      "\r\n";
+  balsa_frame_.set_is_request(false);
+  EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice());
+  EXPECT_EQ(header.size(),
+            balsa_frame_.ProcessInput(header.data(), header.size()));
+  EXPECT_EQ(BalsaFrameEnums::READING_UNTIL_CLOSE, balsa_frame_.ParseState());
+  EXPECT_EQ(std::numeric_limits<size_t>::max(),
+            balsa_frame_.BytesSafeToSplice());
+  for (int i = 0; i < 1000; ++i) {
+    EXPECT_EQ(std::numeric_limits<size_t>::max(),
+              balsa_frame_.BytesSafeToSplice());
+    balsa_frame_.BytesSpliced(12312312);
+    EXPECT_FALSE(balsa_frame_.Error());
+    EXPECT_FALSE(balsa_frame_.MessageFullyRead());
+  }
+  EXPECT_EQ(std::numeric_limits<size_t>::max(),
+            balsa_frame_.BytesSafeToSplice());
+}
+
+TEST_F(HTTPBalsaFrameTest,
+       BytesSplicedFlagsErrorsWhenTooMuchSplicedForChunked) {
+  std::string header =
+      "HTTP/1.1 200 OK\r\n"
+      "transfer-encoding: chunked\r\n"
+      "\r\n";
+  std::string body_fragment = "a\r\n";
+  balsa_frame_.set_is_request(false);
+  EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice());
+  EXPECT_EQ(header.size(),
+            balsa_frame_.ProcessInput(header.data(), header.size()));
+  EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice());
+  EXPECT_EQ(
+      body_fragment.size(),
+      balsa_frame_.ProcessInput(body_fragment.data(), body_fragment.size()));
+  EXPECT_EQ(10u, balsa_frame_.BytesSafeToSplice());
+  balsa_frame_.BytesSpliced(11);
+  EXPECT_TRUE(balsa_frame_.Error());
+  EXPECT_EQ(
+      BalsaFrameEnums::CALLED_BYTES_SPLICED_AND_EXCEEDED_SAFE_SPLICE_AMOUNT,
+      balsa_frame_.ErrorCode());
+  EXPECT_FALSE(balsa_frame_.MessageFullyRead());
+}
+
+TEST_F(HTTPBalsaFrameTest, BytesSafeToSpliceAndBytesSplicedWorksWithChunks) {
+  std::string header =
+      "HTTP/1.1 200 OK\r\n"
+      "transfer-encoding: chunked\r\n"
+      "\r\n";
+  balsa_frame_.set_is_request(false);
+  EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice());
+  EXPECT_EQ(header.size(),
+            balsa_frame_.ProcessInput(header.data(), header.size()));
+
+  {
+    std::string body_fragment = "3e8\r\n";
+    EXPECT_FALSE(balsa_frame_.MessageFullyRead());
+    size_t bytes_safe_to_splice = 1000;
+    EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice());
+    EXPECT_EQ(
+        body_fragment.size(),
+        balsa_frame_.ProcessInput(body_fragment.data(), body_fragment.size()));
+    EXPECT_EQ(bytes_safe_to_splice, balsa_frame_.BytesSafeToSplice());
+    while (bytes_safe_to_splice > 0) {
+      balsa_frame_.BytesSpliced(1);
+      bytes_safe_to_splice -= 1;
+      ASSERT_FALSE(balsa_frame_.Error());
+    }
+    EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice());
+    EXPECT_FALSE(balsa_frame_.Error());
+  }
+  {
+    std::string body_fragment = "\r\n7d0\r\n";
+    EXPECT_FALSE(balsa_frame_.MessageFullyRead());
+    size_t bytes_safe_to_splice = 2000;
+    EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice());
+    EXPECT_EQ(
+        body_fragment.size(),
+        balsa_frame_.ProcessInput(body_fragment.data(), body_fragment.size()));
+    EXPECT_EQ(bytes_safe_to_splice, balsa_frame_.BytesSafeToSplice());
+    while (bytes_safe_to_splice > 0) {
+      balsa_frame_.BytesSpliced(1);
+      bytes_safe_to_splice -= 1;
+      ASSERT_FALSE(balsa_frame_.Error());
+    }
+    EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice());
+    EXPECT_FALSE(balsa_frame_.Error());
+  }
+  {
+    std::string body_fragment = "\r\n1\r\n";
+    EXPECT_FALSE(balsa_frame_.MessageFullyRead());
+    size_t bytes_safe_to_splice = 1;
+    EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice());
+    EXPECT_EQ(
+        body_fragment.size(),
+        balsa_frame_.ProcessInput(body_fragment.data(), body_fragment.size()));
+    EXPECT_EQ(bytes_safe_to_splice, balsa_frame_.BytesSafeToSplice());
+    while (bytes_safe_to_splice > 0) {
+      balsa_frame_.BytesSpliced(1);
+      bytes_safe_to_splice -= 1;
+      ASSERT_FALSE(balsa_frame_.Error());
+    }
+    EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice());
+    EXPECT_FALSE(balsa_frame_.Error());
+  }
+  {
+    std::string body_fragment = "\r\n0\r\n\r\n";
+    EXPECT_FALSE(balsa_frame_.MessageFullyRead());
+    EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice());
+    EXPECT_EQ(
+        body_fragment.size(),
+        balsa_frame_.ProcessInput(body_fragment.data(), body_fragment.size()));
+    EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice());
+    EXPECT_FALSE(balsa_frame_.Error());
+  }
+  EXPECT_TRUE(balsa_frame_.MessageFullyRead());
+}
+
+TEST_F(HTTPBalsaFrameTest, TwoDifferentContentHeadersIsAnError) {
+  std::string header =
+      "HTTP/1.1 200 OK\r\n"
+      "content-length: 12\r\n"
+      "content-length: 14\r\n"
+      "\r\n";
+  balsa_frame_.set_is_request(false);
+  balsa_frame_.ProcessInput(header.data(), header.size());
+  EXPECT_TRUE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::MULTIPLE_CONTENT_LENGTH_KEYS,
+            balsa_frame_.ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest, TwoSameContentHeadersIsNotAnError) {
+  std::string header =
+      "POST / HTTP/1.1\r\n"
+      "content-length: 1\r\n"
+      "content-length: 1\r\n"
+      "\r\n"
+      "1";
+  balsa_frame_.ProcessInput(header.data(), header.size());
+  EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
+  EXPECT_FALSE(balsa_frame_.Error());
+  balsa_frame_.ProcessInput(header.data(), header.size());
+  EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
+  EXPECT_FALSE(balsa_frame_.Error());
+  EXPECT_TRUE(balsa_frame_.MessageFullyRead());
+}
+
+TEST_F(HTTPBalsaFrameTest, TwoTransferEncodingHeadersIsAnError) {
+  std::string header =
+      "HTTP/1.1 200 OK\r\n"
+      "transfer-encoding: chunked\r\n"
+      "transfer-encoding: identity\r\n"
+      "\r\n";
+  balsa_frame_.set_is_request(false);
+  balsa_frame_.ProcessInput(header.data(), header.size());
+  EXPECT_TRUE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::MULTIPLE_TRANSFER_ENCODING_KEYS,
+            balsa_frame_.ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest, TwoTransferEncodingTokensIsAnError) {
+  std::string header =
+      "HTTP/1.1 200 OK\r\n"
+      "transfer-encoding: chunked, identity\r\n"
+      "\r\n";
+  balsa_frame_.set_is_request(false);
+  balsa_frame_.ProcessInput(header.data(), header.size());
+  EXPECT_TRUE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::UNKNOWN_TRANSFER_ENCODING,
+            balsa_frame_.ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest, UnknownTransferEncodingTokenIsAnError) {
+  std::string header =
+      "HTTP/1.1 200 OK\r\n"
+      "transfer-encoding: chunked-identity\r\n"
+      "\r\n";
+  balsa_frame_.set_is_request(false);
+  balsa_frame_.ProcessInput(header.data(), header.size());
+  EXPECT_TRUE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::UNKNOWN_TRANSFER_ENCODING,
+            balsa_frame_.ErrorCode());
+}
+
+class DetachOnDoneFramer : public NoOpBalsaVisitor {
+ public:
+  DetachOnDoneFramer() {
+    framer_.set_balsa_headers(&headers_);
+    framer_.set_balsa_visitor(this);
+  }
+
+  void MessageDone() override { framer_.set_balsa_headers(nullptr); }
+
+  BalsaFrame* framer() { return &framer_; }
+
+ protected:
+  BalsaFrame framer_;
+  BalsaHeaders headers_;
+};
+
+TEST(HTTPBalsaFrame, TestDetachOnDone) {
+  DetachOnDoneFramer framer;
+  const char* message = "GET HTTP/1.1\r\n\r\n";
+  // Frame the whole message.  The framer will call MessageDone which will set
+  // the headers to nullptr.
+  framer.framer()->ProcessInput(message, strlen(message));
+  EXPECT_TRUE(framer.framer()->MessageFullyRead());
+  EXPECT_FALSE(framer.framer()->Error());
+}
+
+// We simply extend DetachOnDoneFramer so that we do not have
+// to provide trivial implementation for various functions.
+class ModifyMaxHeaderLengthFramerInFirstLine : public DetachOnDoneFramer {
+ public:
+  void MessageDone() override {}
+  // This sets to max_header_length to a low number and
+  // this would cause us to reject the query. Even though
+  // our original headers length was acceptable.
+  void OnRequestFirstLineInput(absl::string_view /*line_input*/,
+                               absl::string_view /*method_input*/,
+                               absl::string_view /*request_uri*/,
+                               absl::string_view /*version_input*/
+                               ) override {
+    framer_.set_max_header_length(1);
+  }
+};
+
+// In this case we have already processed the headers and called on
+// the visitor HeadersDone and hence its too late to reduce the
+// max_header_length here.
+class ModifyMaxHeaderLengthFramerInHeaderDone : public DetachOnDoneFramer {
+ public:
+  void MessageDone() override {}
+  void HeaderDone() override { framer_.set_max_header_length(1); }
+};
+
+TEST(HTTPBalsaFrame, ChangeMaxHeadersLengthOnFirstLine) {
+  std::string message =
+      "PUT /foo HTTP/1.1\r\n"
+      "Content-Length: 2\r\n"
+      "header: xxxxxxxxx\r\n\r\n"
+      "B";  // body begin
+
+  ModifyMaxHeaderLengthFramerInFirstLine balsa_frame;
+  balsa_frame.framer()->set_is_request(true);
+  balsa_frame.framer()->set_max_header_length(message.size() - 1);
+
+  balsa_frame.framer()->ProcessInput(message.data(), message.size());
+  EXPECT_EQ(BalsaFrameEnums::HEADERS_TOO_LONG,
+            balsa_frame.framer()->ErrorCode());
+}
+
+TEST(HTTPBalsaFrame, ChangeMaxHeadersLengthOnHeaderDone) {
+  std::string message =
+      "PUT /foo HTTP/1.1\r\n"
+      "Content-Length: 2\r\n"
+      "header: xxxxxxxxx\r\n\r\n"
+      "B";  // body begin
+
+  ModifyMaxHeaderLengthFramerInHeaderDone balsa_frame;
+  balsa_frame.framer()->set_is_request(true);
+  balsa_frame.framer()->set_max_header_length(message.size() - 1);
+
+  balsa_frame.framer()->ProcessInput(message.data(), message.size());
+  EXPECT_EQ(0, balsa_frame.framer()->ErrorCode());
+}
+
+// This is a simple test to ensure the simple case that we accept
+// a query which has headers size same as the max_header_length.
+// (i.e., there is no off by one error).
+TEST(HTTPBalsaFrame, HeadersSizeSameAsMaxLengthIsAccepted) {
+  std::string message =
+      "GET /foo HTTP/1.1\r\n"
+      "header: xxxxxxxxx\r\n\r\n";
+
+  ModifyMaxHeaderLengthFramerInHeaderDone balsa_frame;
+  balsa_frame.framer()->set_is_request(true);
+  balsa_frame.framer()->set_max_header_length(message.size());
+  balsa_frame.framer()->ProcessInput(message.data(), message.size());
+  EXPECT_EQ(0, balsa_frame.framer()->ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest, KeyHasSpaces) {
+  const std::string message =
+      "GET / HTTP/1.1\r\n"
+      "key has spaces: lock\r\n"
+      "\r\n";
+  EXPECT_EQ(message.size(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+  EXPECT_TRUE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::INVALID_HEADER_NAME_CHARACTER,
+            balsa_frame_.ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest, SpaceBeforeColon) {
+  const std::string message =
+      "GET / HTTP/1.1\r\n"
+      "key : lock\r\n"
+      "\r\n";
+  EXPECT_EQ(message.size(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+  EXPECT_TRUE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::INVALID_HEADER_NAME_CHARACTER,
+            balsa_frame_.ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest, SpaceBeforeColonNotAfter) {
+  const std::string message =
+      "GET / HTTP/1.1\r\n"
+      "key :lock\r\n"
+      "\r\n";
+  EXPECT_EQ(message.size(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+  EXPECT_TRUE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::INVALID_HEADER_NAME_CHARACTER,
+            balsa_frame_.ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest, KeyHasTabs) {
+  const std::string message =
+      "GET / HTTP/1.1\r\n"
+      "key\thas\ttabs: lock\r\n"
+      "\r\n";
+  EXPECT_EQ(message.size(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+  EXPECT_TRUE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::INVALID_HEADER_NAME_CHARACTER,
+            balsa_frame_.ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest, TabBeforeColon) {
+  const std::string message =
+      "GET / HTTP/1.1\r\n"
+      "key\t: lock\r\n"
+      "\r\n";
+  EXPECT_EQ(message.size(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+  EXPECT_TRUE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::INVALID_HEADER_NAME_CHARACTER,
+            balsa_frame_.ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest, KeyHasContinuation) {
+  const std::string message =
+      "GET / HTTP/1.1\r\n"
+      "key\n includes continuation: but not value\r\n"
+      "\r\n";
+  EXPECT_EQ(message.size(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+  EXPECT_TRUE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::INVALID_HEADER_NAME_CHARACTER,
+            balsa_frame_.ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest, KeyHasMultipleContinuations) {
+  const std::string message =
+      "GET / HTTP/1.1\r\n"
+      "key\n includes\r\n multiple\n continuations: but not value\r\n"
+      "\r\n";
+  EXPECT_EQ(message.size(),
+            balsa_frame_.ProcessInput(message.data(), message.size()));
+  EXPECT_TRUE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::INVALID_HEADER_NAME_CHARACTER,
+            balsa_frame_.ErrorCode());
+}
+
+// Missing colon is a warning, not an error.
+TEST_F(HTTPBalsaFrameTest, TrailerMissingColon) {
+  std::string headers =
+      "HTTP/1.0 302 Redirect\r\n"
+      "transfer-encoding: chunked\r\n"
+      "\r\n";
+
+  std::string chunks =
+      "3\r\n"
+      "123\r\n"
+      "0\r\n";
+  std::string trailer =
+      "crass_monkeys\n"
+      "\r\n";
+
+  balsa_frame_.set_is_request(false);
+  balsa_frame_.set_balsa_trailer(&trailer_);
+  EXPECT_CALL(visitor_mock_,
+              HandleWarning(BalsaFrameEnums::TRAILER_MISSING_COLON));
+  ASSERT_EQ(headers.size(),
+            balsa_frame_.ProcessInput(headers.data(), headers.size()));
+  ASSERT_EQ(chunks.size(),
+            balsa_frame_.ProcessInput(chunks.data(), chunks.size()));
+  EXPECT_EQ(trailer.size(),
+            balsa_frame_.ProcessInput(trailer.data(), trailer.size()));
+  EXPECT_TRUE(balsa_frame_.MessageFullyRead());
+  EXPECT_FALSE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::TRAILER_MISSING_COLON, balsa_frame_.ErrorCode());
+  EXPECT_FALSE(trailer_.HasHeader("crass"));
+  EXPECT_TRUE(trailer_.HasHeader("crass_monkeys"));
+  const absl::string_view crass_monkeys = trailer_.GetHeader("crass_monkeys");
+  EXPECT_TRUE(crass_monkeys.empty());
+}
+
+// This tests multiple headers in trailer. We currently do not and have no plan
+// to support Trailer field in headers to limit valid field-name in trailer.
+// Test that we aren't confused by the non-alphanumeric characters in the
+// trailer, especially ':'.
+TEST_F(HTTPBalsaFrameTest, MultipleHeadersInTrailer) {
+  std::string headers =
+      "HTTP/1.1 200 OK\r\n"
+      "transfer-encoding: chunked\r\n"
+      "\r\n";
+
+  std::string chunks =
+      "3\r\n"
+      "123\n"
+      "0\n";
+  std::map<std::string, std::string> trailer;
+  trailer["X-Trace"] =
+      "http://trace.example.com/trace?host="
+      "foobar.example.com&start=2012-06-03_15:59:06&rpc_duration=0.243349";
+  trailer["Date"] = "Sun, 03 Jun 2012 22:59:06 GMT";
+  trailer["Content-Type"] = "text/html";
+  trailer["X-Backends"] = "127.0.0.1_0,foo.example.com:39359";
+  trailer["X-Request-Trace"] =
+      "foo.example.com:39359,127.0.0.1_1,"
+      "foo.example.com:39359,127.0.0.1_0,"
+      "foo.example.com:39359";
+  trailer["X-Service-Trace"] = "default";
+  trailer["X-Service"] = "default";
+
+  std::map<std::string, std::string>::const_iterator iter;
+  std::string trailer_data;
+  TestSeed seed;
+  seed.Initialize(absl::GetFlag(FLAGS_randseed));
+  RandomEngine rng;
+  rng.seed(seed.GetSeed());
+  FakeHeaders fake_headers_in_trailer;
+  for (iter = trailer.begin(); iter != trailer.end(); ++iter) {
+    trailer_data += iter->first;
+    trailer_data += ":";
+    std::stringstream leading_whitespace_for_value;
+    AppendRandomWhitespace(rng, &leading_whitespace_for_value);
+    trailer_data += leading_whitespace_for_value.str();
+    trailer_data += iter->second;
+    std::stringstream trailing_whitespace_for_value;
+    AppendRandomWhitespace(rng, &trailing_whitespace_for_value);
+    trailer_data += trailing_whitespace_for_value.str();
+    trailer_data += random_line_term(rng);
+    fake_headers_in_trailer.AddKeyValue(iter->first, iter->second);
+  }
+  trailer_data += random_line_term(rng);
+
+  FakeHeaders fake_headers;
+  fake_headers.AddKeyValue("transfer-encoding", "chunked");
+
+  {
+    InSequence s1;
+    EXPECT_CALL(visitor_mock_, OnResponseFirstLineInput(
+                                   "HTTP/1.1 200 OK", "HTTP/1.1", "200", "OK"));
+    EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
+    EXPECT_CALL(visitor_mock_, HeaderDone());
+    EXPECT_CALL(visitor_mock_, OnChunkLength(3));
+    EXPECT_CALL(visitor_mock_, OnChunkLength(0));
+    EXPECT_CALL(visitor_mock_, ProcessTrailers(fake_headers_in_trailer));
+    EXPECT_CALL(visitor_mock_, OnTrailerInput(trailer_data));
+    EXPECT_CALL(visitor_mock_, MessageDone());
+  }
+  EXPECT_CALL(visitor_mock_, OnHeaderInput(headers));
+  std::string body_input;
+  EXPECT_CALL(visitor_mock_, OnRawBodyInput(_))
+      .WillRepeatedly([&body_input](absl::string_view input) {
+        absl::StrAppend(&body_input, input);
+      });
+  EXPECT_CALL(visitor_mock_, OnBodyChunkInput("123"));
+
+  balsa_frame_.set_is_request(false);
+  balsa_frame_.set_balsa_trailer(&trailer_);
+
+  ASSERT_EQ(headers.size(),
+            balsa_frame_.ProcessInput(headers.data(), headers.size()));
+  ASSERT_EQ(chunks.size(),
+            balsa_frame_.ProcessInput(chunks.data(), chunks.size()));
+  EXPECT_EQ(trailer_data.size(), balsa_frame_.ProcessInput(
+                                     trailer_data.data(), trailer_data.size()));
+  EXPECT_TRUE(balsa_frame_.MessageFullyRead());
+  EXPECT_FALSE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
+
+  EXPECT_EQ(chunks, body_input);
+
+  for (iter = trailer.begin(); iter != trailer.end(); ++iter) {
+    const absl::string_view value = trailer_.GetHeader(iter->first);
+    EXPECT_EQ(iter->second, value);
+  }
+}
+
+// Test if trailer is not set (the common case), everything will be fine.
+TEST_F(HTTPBalsaFrameTest, NothingBadHappensWithNULLTrailer) {
+  std::string headers =
+      "HTTP/1.1 200 OK\r\n"
+      "transfer-encoding: chunked\r\n"
+      "\r\n";
+
+  std::string chunks =
+      "3\r\n"
+      "123\r\n"
+      "0\r\n";
+  std::string trailer =
+      "crass: monkeys\r\n"
+      "funky: monkeys\r\n"
+      "\n";
+
+  balsa_frame_.set_is_request(false);
+  balsa_frame_.set_balsa_visitor(nullptr);
+  balsa_frame_.set_balsa_trailer(nullptr);
+
+  ASSERT_EQ(headers.size(),
+            balsa_frame_.ProcessInput(headers.data(), headers.size()));
+  ASSERT_EQ(chunks.size(),
+            balsa_frame_.ProcessInput(chunks.data(), chunks.size()));
+  ASSERT_EQ(trailer.size(),
+            balsa_frame_.ProcessInput(trailer.data(), trailer.size()));
+  EXPECT_TRUE(balsa_frame_.MessageFullyRead());
+  EXPECT_FALSE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
+}
+
+// Test Reset() correctly resets trailer related states.
+TEST_F(HTTPBalsaFrameTest, FrameAndResetAndFrameAgain) {
+  std::string headers =
+      "HTTP/1.1 200 OK\r\n"
+      "transfer-encoding: chunked\r\n"
+      "\r\n";
+
+  std::string chunks =
+      "3\r\n"
+      "123\r\n"
+      "0\r\n";
+  std::string trailer =
+      "k: v\n"
+      "\n";
+
+  balsa_frame_.set_is_request(false);
+  balsa_frame_.set_balsa_trailer(&trailer_);
+  balsa_frame_.set_balsa_visitor(nullptr);
+
+  ASSERT_EQ(headers.size(),
+            balsa_frame_.ProcessInput(headers.data(), headers.size()));
+  ASSERT_EQ(chunks.size(),
+            balsa_frame_.ProcessInput(chunks.data(), chunks.size()));
+  ASSERT_EQ(trailer.size(),
+            balsa_frame_.ProcessInput(trailer.data(), trailer.size()));
+  EXPECT_TRUE(balsa_frame_.MessageFullyRead());
+  EXPECT_FALSE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
+
+  absl::string_view value = trailer_.GetHeader("k");
+  EXPECT_EQ("v", value);
+
+  balsa_frame_.Reset();
+
+  headers =
+      "HTTP/1.1 404 Error\r\n"
+      "transfer-encoding: chunked\r\n"
+      "\r\n";
+
+  chunks =
+      "4\r\n"
+      "1234\r\n"
+      "0\r\n";
+  trailer =
+      "nk: nv\n"
+      "\n";
+
+  ASSERT_EQ(headers.size(),
+            balsa_frame_.ProcessInput(headers.data(), headers.size()));
+  ASSERT_EQ(chunks.size(),
+            balsa_frame_.ProcessInput(chunks.data(), chunks.size()));
+  ASSERT_EQ(trailer.size(),
+            balsa_frame_.ProcessInput(trailer.data(), trailer.size()));
+  EXPECT_TRUE(balsa_frame_.MessageFullyRead());
+  EXPECT_FALSE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
+
+  value = trailer_.GetHeader("k");
+  EXPECT_TRUE(value.empty());
+  value = trailer_.GetHeader("nk");
+  EXPECT_EQ("nv", value);
+}
+
+TEST_F(HTTPBalsaFrameTest, TrackInvalidChars) {
+  EXPECT_FALSE(balsa_frame_.track_invalid_chars());
+}
+
+// valid chars are 9 (tab), 10 (LF), 13(CR), and 32-255
+TEST_F(HTTPBalsaFrameTest, InvalidCharsInHeaderValueWarning) {
+  balsa_frame_.set_invalid_chars_level(BalsaFrame::InvalidCharsLevel::kWarning);
+  // nulls are double escaped since otherwise this initialized wrong
+  const std::string kEscapedInvalid1 =
+      "GET /foo HTTP/1.1\r\n"
+      "Bogus-Head: val\\x00\r\n"
+      "More-Invalid: \\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0E\x0F\r\n"
+      "And-More: \x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D"
+      "\x1E\x1F\r\n\r\n";
+  std::string message;
+  // now we convert to real embedded nulls
+  absl::CUnescape(kEscapedInvalid1, &message);
+
+  EXPECT_CALL(visitor_mock_,
+              HandleWarning(BalsaFrameEnums::INVALID_HEADER_CHARACTER));
+
+  balsa_frame_.ProcessInput(message.data(), message.size());
+  EXPECT_FALSE(balsa_frame_.Error());
+  EXPECT_TRUE(balsa_frame_.MessageFullyRead());
+}
+
+// Header names reject invalid chars even at the warning level.
+TEST_F(HTTPBalsaFrameTest, InvalidCharsInHeaderKeyError) {
+  balsa_frame_.set_invalid_chars_level(BalsaFrame::InvalidCharsLevel::kWarning);
+  // nulls are double escaped since otherwise this initialized wrong
+  const std::string kEscapedInvalid1 =
+      "GET /foo HTTP/1.1\r\n"
+      "Bogus\\x00-Head: val\r\n\r\n";
+  std::string message;
+  // now we convert to real embedded nulls
+  absl::CUnescape(kEscapedInvalid1, &message);
+
+  EXPECT_CALL(visitor_mock_,
+              HandleError(BalsaFrameEnums::INVALID_HEADER_NAME_CHARACTER));
+
+  balsa_frame_.ProcessInput(message.data(), message.size());
+  EXPECT_TRUE(balsa_frame_.Error());
+  EXPECT_FALSE(balsa_frame_.MessageFullyRead());
+}
+
+TEST_F(HTTPBalsaFrameTest, InvalidCharsInHeaderError) {
+  balsa_frame_.set_invalid_chars_level(BalsaFrame::InvalidCharsLevel::kError);
+  const std::string kEscapedInvalid =
+      "GET /foo HTTP/1.1\r\n"
+      "Smuggle-Me: \\x00GET /bar HTTP/1.1\r\n"
+      "Another-Header: value\r\n\r\n";
+  std::string message;
+  absl::CUnescape(kEscapedInvalid, &message);
+
+  EXPECT_CALL(visitor_mock_,
+              HandleError(BalsaFrameEnums::INVALID_HEADER_CHARACTER));
+
+  balsa_frame_.ProcessInput(message.data(), message.size());
+  EXPECT_TRUE(balsa_frame_.Error());
+  EXPECT_FALSE(balsa_frame_.MessageFullyRead());
+}
+
+class HTTPBalsaFrameTestOneChar : public HTTPBalsaFrameTest,
+                                  public testing::WithParamInterface<char> {
+ public:
+  char GetCharUnderTest() { return GetParam(); }
+};
+
+TEST_P(HTTPBalsaFrameTestOneChar, InvalidCharsWarningSet) {
+  balsa_frame_.set_invalid_chars_level(BalsaFrame::InvalidCharsLevel::kWarning);
+  const std::string kRequest =
+      "GET /foo HTTP/1.1\r\n"
+      "Bogus-Char-Goes-Here: ";
+  const std::string kEnding = "\r\n\r\n";
+  std::string message = kRequest;
+  const char c = GetCharUnderTest();
+  message.append(1, c);
+  message.append(kEnding);
+  if (c == 9 || c == 10 || c == 13) {
+    // valid char
+    EXPECT_CALL(visitor_mock_,
+                HandleWarning(BalsaFrameEnums::INVALID_HEADER_CHARACTER))
+        .Times(0);
+    balsa_frame_.ProcessInput(message.data(), message.size());
+    EXPECT_THAT(balsa_frame_.get_invalid_chars(), IsEmpty());
+  } else {
+    // invalid char
+    absl::flat_hash_map<char, int> expected_count = {{c, 1}};
+    EXPECT_CALL(visitor_mock_,
+                HandleWarning(BalsaFrameEnums::INVALID_HEADER_CHARACTER));
+    balsa_frame_.ProcessInput(message.data(), message.size());
+    EXPECT_EQ(balsa_frame_.get_invalid_chars(), expected_count);
+  }
+  EXPECT_FALSE(balsa_frame_.Error());
+  EXPECT_TRUE(balsa_frame_.MessageFullyRead());
+}
+
+INSTANTIATE_TEST_SUITE_P(TestInvalidCharSet, HTTPBalsaFrameTestOneChar,
+                         Range((char)0, (char)32));
+
+TEST_F(HTTPBalsaFrameTest, InvalidCharEndOfLine) {
+  balsa_frame_.set_invalid_chars_level(BalsaFrame::InvalidCharsLevel::kWarning);
+  const std::string kInvalid1 =
+      "GET /foo HTTP/1.1\r\n"
+      "Header-Key: headervalue\\x00\r\n"
+      "Legit-Header: legitvalue\r\n\r\n";
+  std::string message;
+  absl::CUnescape(kInvalid1, &message);
+
+  EXPECT_CALL(visitor_mock_,
+              HandleWarning(BalsaFrameEnums::INVALID_HEADER_CHARACTER));
+  balsa_frame_.ProcessInput(message.data(), message.size());
+  EXPECT_FALSE(balsa_frame_.Error());
+  EXPECT_TRUE(balsa_frame_.MessageFullyRead());
+}
+
+TEST_F(HTTPBalsaFrameTest, InvalidCharInFirstLine) {
+  balsa_frame_.set_invalid_chars_level(BalsaFrame::InvalidCharsLevel::kWarning);
+  const std::string kInvalid1 =
+      "GET /foo \\x00HTTP/1.1\r\n"
+      "Legit-Header: legitvalue\r\n\r\n";
+  std::string message;
+  absl::CUnescape(kInvalid1, &message);
+
+  EXPECT_CALL(visitor_mock_,
+              HandleWarning(BalsaFrameEnums::INVALID_HEADER_CHARACTER));
+  balsa_frame_.ProcessInput(message.data(), message.size());
+  EXPECT_FALSE(balsa_frame_.Error());
+  EXPECT_TRUE(balsa_frame_.MessageFullyRead());
+}
+
+TEST_F(HTTPBalsaFrameTest, InvalidCharsAreCounted) {
+  balsa_frame_.set_invalid_chars_level(BalsaFrame::InvalidCharsLevel::kWarning);
+  const std::string kInvalid1 =
+      "GET /foo \\x00\\x00\\x00HTTP/1.1\r\n"
+      "Bogus-Header: \\x00\\x04\\x04value\r\n\r\n";
+  std::string message;
+  absl::CUnescape(kInvalid1, &message);
+
+  EXPECT_CALL(visitor_mock_,
+              HandleWarning(BalsaFrameEnums::INVALID_HEADER_CHARACTER));
+  balsa_frame_.ProcessInput(message.data(), message.size());
+  absl::flat_hash_map<char, int> expected_count = {{'\0', 4}, {'\4', 2}};
+  EXPECT_FALSE(balsa_frame_.Error());
+  EXPECT_TRUE(balsa_frame_.MessageFullyRead());
+  EXPECT_EQ(balsa_frame_.get_invalid_chars(), expected_count);
+
+  absl::flat_hash_map<char, int> empty_count;
+  balsa_frame_.Reset();
+  EXPECT_EQ(balsa_frame_.get_invalid_chars(), empty_count);
+}
+
+// Test gibberish in headers and trailer. GFE does not crash but garbage in
+// garbage out.
+TEST_F(HTTPBalsaFrameTest, GibberishInHeadersAndTrailer) {
+  const char kGibberish1[] = {138, 175, 233, 0};
+  const char kGibberish2[] = {'?', '?', 128, 255, 129, 254, 0};
+  const char kGibberish3[] = "foo: bar : eeep : baz";
+
+  std::string gibberish_headers;
+  gibberish_headers += kGibberish1;
+  gibberish_headers += ":";
+  gibberish_headers += kGibberish2;
+  gibberish_headers += "\r\n";
+  gibberish_headers += kGibberish3;
+  gibberish_headers += "\r\n";
+
+  std::string headers =
+      "HTTP/1.1 200 OK\r\n"
+      "transfer-encoding: chunked\r\n";
+  headers += gibberish_headers;
+  headers += "\r\n";
+
+  std::string chunks =
+      "3\r\n"
+      "123\r\n"
+      "0\r\n";
+  std::string trailer = "k: v\n";
+  trailer += gibberish_headers;
+  trailer += "\n";
+
+  balsa_frame_.set_is_request(false);
+  balsa_frame_.set_balsa_trailer(&trailer_);
+  balsa_frame_.set_balsa_visitor(nullptr);
+
+  ASSERT_EQ(headers.size(),
+            balsa_frame_.ProcessInput(headers.data(), headers.size()));
+  ASSERT_EQ(chunks.size(),
+            balsa_frame_.ProcessInput(chunks.data(), chunks.size()));
+  ASSERT_EQ(trailer.size(),
+            balsa_frame_.ProcessInput(trailer.data(), trailer.size()));
+  EXPECT_TRUE(balsa_frame_.MessageFullyRead());
+  EXPECT_FALSE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
+
+  // Transfer-encoding can be multi-valued, so GetHeader does not work.
+  EXPECT_TRUE(headers_.transfer_encoding_is_chunked());
+  absl::string_view field_value = headers_.GetHeader(kGibberish1);
+  EXPECT_EQ(kGibberish2, field_value);
+  field_value = headers_.GetHeader("foo");
+  EXPECT_EQ("bar : eeep : baz", field_value);
+
+  field_value = trailer_.GetHeader("k");
+  EXPECT_EQ("v", field_value);
+  field_value = trailer_.GetHeader(kGibberish1);
+  EXPECT_EQ(kGibberish2, field_value);
+  field_value = trailer_.GetHeader("foo");
+  EXPECT_EQ("bar : eeep : baz", field_value);
+}
+
+// Test that trailer is not allowed for request.
+TEST_F(HTTPBalsaFrameTest, TrailerIsNotAllowedInRequest) {
+  EXPECT_QUICHE_BUG(balsa_frame_.set_balsa_trailer(&trailer_),
+                    "Trailer in request is not allowed");
+}
+
+// Note we reuse the header length limit because trailer is just multiple
+// headers.
+TEST_F(HTTPBalsaFrameTest, TrailerTooLong) {
+  std::string headers =
+      "HTTP/1.0 200 ok\r\n"
+      "transfer-encoding: chunked\r\n"
+      "\r\n";
+
+  std::string chunks =
+      "3\r\n"
+      "123\r\n"
+      "0\r\n";
+  std::string trailer =
+      "very : long trailer\n"
+      "should:cause\r\n"
+      "trailer :too long error\n"
+      "\r\n";
+
+  balsa_frame_.set_is_request(false);
+  balsa_frame_.set_balsa_trailer(&trailer_);
+  ASSERT_LT(headers.size(), trailer.size());
+  balsa_frame_.set_max_header_length(headers.size());
+
+  EXPECT_CALL(visitor_mock_, HandleError(BalsaFrameEnums::TRAILER_TOO_LONG));
+  EXPECT_CALL(visitor_mock_, ProcessTrailers(_)).Times(0);
+  EXPECT_CALL(visitor_mock_, MessageDone()).Times(0);
+  ASSERT_EQ(headers.size(),
+            balsa_frame_.ProcessInput(headers.data(), headers.size()));
+  ASSERT_EQ(chunks.size(),
+            balsa_frame_.ProcessInput(chunks.data(), chunks.size()));
+  EXPECT_EQ(balsa_frame_.max_header_length(),
+            balsa_frame_.ProcessInput(trailer.data(), trailer.size()));
+  EXPECT_FALSE(balsa_frame_.MessageFullyRead());
+  EXPECT_TRUE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::TRAILER_TOO_LONG, balsa_frame_.ErrorCode());
+}
+
+// If the trailer_ object in the framer is not set, ProcessTrailers won't be
+// called.
+TEST_F(HTTPBalsaFrameTest,
+       NoProcessTrailersCallWhenFramerHasNullTrailerObject) {
+  std::string headers =
+      "HTTP/1.0 200 ok\r\n"
+      "transfer-encoding: chunked\r\n"
+      "\r\n";
+
+  std::string chunks =
+      "3\r\n"
+      "123\r\n"
+      "0\r\n";
+  std::string trailer =
+      "trailer_key : trailer_value\n"
+      "\r\n";
+
+  balsa_frame_.set_is_request(false);
+
+  EXPECT_CALL(visitor_mock_, ProcessTrailers(_)).Times(0);
+  ASSERT_EQ(headers.size(),
+            balsa_frame_.ProcessInput(headers.data(), headers.size()));
+  ASSERT_EQ(chunks.size(),
+            balsa_frame_.ProcessInput(chunks.data(), chunks.size()));
+  EXPECT_EQ(trailer.size(),
+            balsa_frame_.ProcessInput(trailer.data(), trailer.size()));
+  EXPECT_TRUE(balsa_frame_.MessageFullyRead());
+  EXPECT_FALSE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
+}
+
+// Handle two sets of headers when set up properly and the first is 100
+// Continue.
+TEST_F(HTTPBalsaFrameTest, Support100Continue) {
+  std::string initial_headers =
+      "HTTP/1.1 100 Continue\r\n"
+      "\r\n";
+  std::string real_headers =
+      "HTTP/1.1 200 OK\r\n"
+      "content-length: 3\r\n"
+      "\r\n";
+  std::string body = "foo";
+
+  balsa_frame_.set_is_request(false);
+  BalsaHeaders continue_headers;
+  balsa_frame_.set_continue_headers(&continue_headers);
+
+  ASSERT_EQ(initial_headers.size(),
+            balsa_frame_.ProcessInput(initial_headers.data(),
+                                      initial_headers.size()));
+  ASSERT_EQ(real_headers.size(),
+            balsa_frame_.ProcessInput(real_headers.data(), real_headers.size()))
+      << balsa_frame_.ErrorCode();
+  ASSERT_EQ(body.size(), balsa_frame_.ProcessInput(body.data(), body.size()));
+  EXPECT_TRUE(balsa_frame_.MessageFullyRead());
+  EXPECT_FALSE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
+}
+
+// Handle two sets of headers when set up properly and the first is 100
+// Continue and it meets the conditions for b/62408297
+TEST_F(HTTPBalsaFrameTest, Support100Continue401Unauthorized) {
+  std::string initial_headers =
+      "HTTP/1.1 100 Continue\r\n"
+      "\r\n";
+  std::string real_headers =
+      "HTTP/1.1 401 Unauthorized\r\n"
+      "content-length: 3\r\n"
+      "\r\n";
+  std::string body = "foo";
+
+  balsa_frame_.set_is_request(false);
+  BalsaHeaders continue_headers;
+  balsa_frame_.set_continue_headers(&continue_headers);
+
+  ASSERT_EQ(initial_headers.size(),
+            balsa_frame_.ProcessInput(initial_headers.data(),
+                                      initial_headers.size()));
+  ASSERT_EQ(real_headers.size(),
+            balsa_frame_.ProcessInput(real_headers.data(), real_headers.size()))
+      << balsa_frame_.ErrorCode();
+  ASSERT_EQ(body.size(), balsa_frame_.ProcessInput(body.data(), body.size()));
+  EXPECT_TRUE(balsa_frame_.MessageFullyRead());
+  EXPECT_FALSE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
+}
+
+TEST_F(HTTPBalsaFrameTest, Support100ContinueRunTogether) {
+  std::string both_headers =
+      "HTTP/1.1 100 Continue\r\n"
+      "\r\n"
+      "HTTP/1.1 200 OK\r\n"
+      "content-length: 3\r\n"
+      "\r\n";
+  std::string body = "foo";
+
+  {
+    InSequence s;
+    EXPECT_CALL(visitor_mock_, ContinueHeaderDone());
+    EXPECT_CALL(visitor_mock_, HeaderDone());
+    EXPECT_CALL(visitor_mock_, MessageDone());
+  }
+
+  balsa_frame_.set_is_request(false);
+  BalsaHeaders continue_headers;
+  balsa_frame_.set_continue_headers(&continue_headers);
+
+  ASSERT_EQ(both_headers.size(),
+            balsa_frame_.ProcessInput(both_headers.data(), both_headers.size()))
+      << balsa_frame_.ErrorCode();
+  ASSERT_EQ(body.size(), balsa_frame_.ProcessInput(body.data(), body.size()));
+  EXPECT_TRUE(balsa_frame_.MessageFullyRead());
+  EXPECT_FALSE(balsa_frame_.Error());
+  EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
+}
+
+}  // namespace
+
+}  // namespace test
+
+}  // namespace quiche
diff --git a/quiche/common/balsa/balsa_headers_test.cc b/quiche/common/balsa/balsa_headers_test.cc
new file mode 100644
index 0000000..216ab07
--- /dev/null
+++ b/quiche/common/balsa/balsa_headers_test.cc
@@ -0,0 +1,3743 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Note that several of the BalsaHeaders functions are
+// tested in the balsa_frame_test as the BalsaFrame and
+// BalsaHeaders classes are fairly related.
+
+#include "quiche/common/balsa/balsa_headers.h"
+
+#include <cstring>
+#include <limits>
+#include <memory>
+#include <sstream>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "absl/base/macros.h"
+#include "absl/memory/memory.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/str_format.h"
+#include "absl/strings/string_view.h"
+#include "quiche/common/balsa/balsa_enums.h"
+#include "quiche/common/balsa/balsa_frame.h"
+#include "quiche/common/balsa/simple_buffer.h"
+#include "quiche/common/platform/api/quiche_logging.h"
+#include "quiche/common/platform/api/quiche_test.h"
+#include "quiche/common/platform/api/quiche_test_helpers.h"
+
+using absl::make_unique;
+using testing::AnyOf;
+using testing::Combine;
+using testing::ElementsAre;
+using testing::Eq;
+using testing::StrEq;
+using testing::ValuesIn;
+
+namespace quiche {
+
+namespace test {
+
+class BalsaHeadersTestPeer {
+ public:
+  static void WriteFromFramer(BalsaHeaders* headers, const char* ptr,
+                              size_t size) {
+    headers->WriteFromFramer(ptr, size);
+  }
+};
+
+namespace {
+
+class BalsaBufferTest : public QuicheTest {
+ public:
+  void CreateBuffer(size_t blocksize) {
+    buffer_ = std::make_unique<BalsaBuffer>(blocksize);
+  }
+  void CreateBuffer() { buffer_ = std::make_unique<BalsaBuffer>(); }
+  static std::unique_ptr<BalsaBuffer> CreateUnmanagedBuffer(size_t blocksize) {
+    return std::make_unique<BalsaBuffer>(blocksize);
+  }
+  absl::string_view Write(absl::string_view sp, size_t* block_buffer_idx) {
+    if (sp.empty()) {
+      return sp;
+    }
+    char* storage = buffer_->Reserve(sp.size(), block_buffer_idx);
+    memcpy(storage, sp.data(), sp.size());
+    return absl::string_view(storage, sp.size());
+  }
+
+ protected:
+  std::unique_ptr<BalsaBuffer> buffer_;
+};
+
+using BufferBlock = BalsaBuffer::BufferBlock;
+
+BufferBlock MakeBufferBlock(const std::string& s) {
+  // Make the buffer twice the size needed to verify that CopyFrom copies our
+  // buffer_size (as opposed to shrinking to fit or reusing an old buffer).
+  BufferBlock block{make_unique<char[]>(s.size()), s.size() * 2, s.size()};
+  std::memcpy(block.buffer.get(), s.data(), s.size());
+  return block;
+}
+
+BalsaHeaders CreateHTTPHeaders(bool request, absl::string_view s) {
+  BalsaHeaders headers;
+  BalsaFrame framer;
+  framer.set_is_request(request);
+  framer.set_balsa_headers(&headers);
+  QUICHE_CHECK_EQ(s.size(), framer.ProcessInput(s.data(), s.size()));
+  QUICHE_CHECK(framer.MessageFullyRead());
+  return headers;
+}
+
+class BufferBlockTest
+    : public QuicheTestWithParam<std::tuple<const char*, const char*>> {};
+
+TEST_P(BufferBlockTest, CopyFrom) {
+  const std::string s1 = std::get<0>(GetParam());
+  const std::string s2 = std::get<1>(GetParam());
+  BufferBlock block;
+  block.CopyFrom(MakeBufferBlock(s1));
+  EXPECT_EQ(s1.size(), block.bytes_free);
+  ASSERT_EQ(2 * s1.size(), block.buffer_size);
+  EXPECT_EQ(0, memcmp(s1.data(), block.buffer.get(), s1.size()));
+  block.CopyFrom(MakeBufferBlock(s2));
+  EXPECT_EQ(s2.size(), block.bytes_free);
+  ASSERT_EQ(2 * s2.size(), block.buffer_size);
+  EXPECT_EQ(0, memcmp(s2.data(), block.buffer.get(), s2.size()));
+}
+
+const char* block_strings[] = {"short string", "longer than the other string"};
+INSTANTIATE_TEST_SUITE_P(VariousSizes, BufferBlockTest,
+                         Combine(ValuesIn(block_strings),
+                                 ValuesIn(block_strings)));
+
+TEST_F(BalsaBufferTest, BlocksizeSet) {
+  CreateBuffer();
+  EXPECT_EQ(BalsaBuffer::kDefaultBlocksize, buffer_->blocksize());
+  CreateBuffer(1024);
+  EXPECT_EQ(1024u, buffer_->blocksize());
+}
+
+TEST_F(BalsaBufferTest, GetMemorySize) {
+  CreateBuffer(10);
+  EXPECT_EQ(0u, buffer_->GetTotalBytesUsed());
+  EXPECT_EQ(0u, buffer_->GetTotalBufferBlockSize());
+  BalsaBuffer::Blocks::size_type index;
+  buffer_->Reserve(1024, &index);
+  EXPECT_EQ(10u + 1024u, buffer_->GetTotalBufferBlockSize());
+  EXPECT_EQ(1024u, buffer_->GetTotalBytesUsed());
+}
+
+TEST_F(BalsaBufferTest, ManyWritesToContiguousBuffer) {
+  CreateBuffer(0);
+  // The test is that the process completes.  If it needs to do a resize on
+  // every write, it will timeout or run out of memory.
+  // ( 10 + 20 + 30 + ... + 1.2e6 bytes => ~1e11 bytes )
+  std::string data = "0123456789";
+  for (int i = 0; i < 120 * 1000; ++i) {
+    buffer_->WriteToContiguousBuffer(data);
+  }
+}
+
+TEST_F(BalsaBufferTest, CopyFrom) {
+  CreateBuffer(10);
+  std::unique_ptr<BalsaBuffer> ptr = CreateUnmanagedBuffer(1024);
+  ASSERT_EQ(1024u, ptr->blocksize());
+  EXPECT_EQ(0u, ptr->num_blocks());
+
+  std::string data1 = "foobarbaz01";
+  buffer_->WriteToContiguousBuffer(data1);
+  buffer_->NoMoreWriteToContiguousBuffer();
+  std::string data2 = "12345";
+  Write(data2, nullptr);
+  std::string data3 = "6789";
+  Write(data3, nullptr);
+  std::string data4 = "123456789012345";
+  Write(data3, nullptr);
+
+  ptr->CopyFrom(*buffer_);
+
+  EXPECT_EQ(ptr->can_write_to_contiguous_buffer(),
+            buffer_->can_write_to_contiguous_buffer());
+  ASSERT_EQ(ptr->num_blocks(), buffer_->num_blocks());
+  for (size_t i = 0; i < buffer_->num_blocks(); ++i) {
+    ASSERT_EQ(ptr->bytes_used(i), buffer_->bytes_used(i));
+    ASSERT_EQ(ptr->buffer_size(i), buffer_->buffer_size(i));
+    EXPECT_EQ(0,
+              memcmp(ptr->GetPtr(i), buffer_->GetPtr(i), ptr->bytes_used(i)));
+  }
+}
+
+TEST_F(BalsaBufferTest, ClearWorks) {
+  CreateBuffer(10);
+
+  std::string data1 = "foobarbaz01";
+  buffer_->WriteToContiguousBuffer(data1);
+  buffer_->NoMoreWriteToContiguousBuffer();
+  std::string data2 = "12345";
+  Write(data2, nullptr);
+  std::string data3 = "6789";
+  Write(data3, nullptr);
+  std::string data4 = "123456789012345";
+  Write(data3, nullptr);
+
+  buffer_->Clear();
+
+  EXPECT_TRUE(buffer_->can_write_to_contiguous_buffer());
+  EXPECT_EQ(10u, buffer_->blocksize());
+  EXPECT_EQ(0u, buffer_->num_blocks());
+}
+
+TEST_F(BalsaBufferTest, ClearWorksWhenLargerThanBlocksize) {
+  CreateBuffer(10);
+
+  std::string data1 = "foobarbaz01lkjasdlkjasdlkjasd";
+  buffer_->WriteToContiguousBuffer(data1);
+  buffer_->NoMoreWriteToContiguousBuffer();
+  std::string data2 = "12345";
+  Write(data2, nullptr);
+  std::string data3 = "6789";
+  Write(data3, nullptr);
+  std::string data4 = "123456789012345";
+  Write(data3, nullptr);
+
+  buffer_->Clear();
+
+  EXPECT_TRUE(buffer_->can_write_to_contiguous_buffer());
+  EXPECT_EQ(10u, buffer_->blocksize());
+  EXPECT_EQ(0u, buffer_->num_blocks());
+}
+
+TEST_F(BalsaBufferTest, ContiguousWriteSmallerThanBlocksize) {
+  CreateBuffer(1024);
+
+  std::string data1 = "foo";
+  buffer_->WriteToContiguousBuffer(data1);
+  std::string composite = data1;
+  const char* buf_ptr = buffer_->GetPtr(0);
+  ASSERT_LE(composite.size(), buffer_->buffer_size(0));
+  EXPECT_EQ(0, memcmp(composite.data(), buf_ptr, composite.size()));
+
+  std::string data2 = "barbaz";
+  buffer_->WriteToContiguousBuffer(data2);
+  composite += data2;
+  buf_ptr = buffer_->GetPtr(0);
+  ASSERT_LE(composite.size(), buffer_->buffer_size(0));
+  EXPECT_EQ(0, memcmp(composite.data(), buf_ptr, composite.size()));
+}
+
+TEST_F(BalsaBufferTest, SingleContiguousWriteLargerThanBlocksize) {
+  CreateBuffer(10);
+
+  std::string data1 = "abracadabrawords";
+  buffer_->WriteToContiguousBuffer(data1);
+  std::string composite = data1;
+  const char* buf_ptr = buffer_->GetPtr(0);
+  ASSERT_LE(data1.size(), buffer_->buffer_size(0));
+  EXPECT_EQ(0, memcmp(composite.data(), buf_ptr, composite.size()))
+      << composite << "\n"
+      << absl::string_view(buf_ptr, buffer_->bytes_used(0));
+}
+
+TEST_F(BalsaBufferTest, ContiguousWriteLargerThanBlocksize) {
+  CreateBuffer(10);
+
+  std::string data1 = "123456789";
+  buffer_->WriteToContiguousBuffer(data1);
+  std::string composite = data1;
+  ASSERT_LE(10u, buffer_->buffer_size(0));
+
+  std::string data2 = "0123456789";
+  buffer_->WriteToContiguousBuffer(data2);
+  composite += data2;
+
+  const char* buf_ptr = buffer_->GetPtr(0);
+  ASSERT_LE(composite.size(), buffer_->buffer_size(0));
+  EXPECT_EQ(0, memcmp(composite.data(), buf_ptr, composite.size()))
+      << "composite: " << composite << "\n"
+      << "   actual: " << absl::string_view(buf_ptr, buffer_->bytes_used(0));
+}
+
+TEST_F(BalsaBufferTest, TwoContiguousWritesLargerThanBlocksize) {
+  CreateBuffer(5);
+
+  std::string data1 = "123456";
+  buffer_->WriteToContiguousBuffer(data1);
+  std::string composite = data1;
+  ASSERT_LE(composite.size(), buffer_->buffer_size(0));
+
+  std::string data2 = "7890123";
+  buffer_->WriteToContiguousBuffer(data2);
+  composite += data2;
+
+  const char* buf_ptr = buffer_->GetPtr(0);
+  ASSERT_LE(composite.size(), buffer_->buffer_size(0));
+  EXPECT_EQ(0, memcmp(composite.data(), buf_ptr, composite.size()))
+      << "composite: " << composite << "\n"
+      << "   actual: " << absl::string_view(buf_ptr, buffer_->bytes_used(0));
+}
+
+TEST_F(BalsaBufferTest, WriteSmallerThanBlocksize) {
+  CreateBuffer(5);
+  std::string data1 = "1234";
+  size_t block_idx = 0;
+  absl::string_view write_result = Write(data1, &block_idx);
+  ASSERT_EQ(1u, block_idx);
+  EXPECT_THAT(write_result, StrEq(data1));
+
+  CreateBuffer(5);
+  data1 = "1234";
+  block_idx = 0;
+  write_result = Write(data1, &block_idx);
+  ASSERT_EQ(1u, block_idx);
+  EXPECT_THAT(write_result, StrEq(data1));
+}
+
+TEST_F(BalsaBufferTest, TwoWritesSmallerThanBlocksizeThenAnotherWrite) {
+  CreateBuffer(10);
+  std::string data1 = "12345";
+  size_t block_idx = 0;
+  absl::string_view write_result = Write(data1, &block_idx);
+  ASSERT_EQ(1u, block_idx);
+  EXPECT_THAT(write_result, StrEq(data1));
+
+  std::string data2 = "data2";
+  block_idx = 0;
+  write_result = Write(data2, &block_idx);
+  ASSERT_EQ(1u, block_idx);
+  EXPECT_THAT(write_result, StrEq(data2));
+
+  std::string data3 = "data3";
+  block_idx = 0;
+  write_result = Write(data3, &block_idx);
+  ASSERT_EQ(2u, block_idx);
+  EXPECT_THAT(write_result, StrEq(data3));
+
+  CreateBuffer(10);
+  buffer_->NoMoreWriteToContiguousBuffer();
+  data1 = "12345";
+  block_idx = 0;
+  write_result = Write(data1, &block_idx);
+  ASSERT_EQ(0u, block_idx);
+  EXPECT_THAT(write_result, StrEq(data1));
+
+  data2 = "data2";
+  block_idx = 0;
+  write_result = Write(data2, &block_idx);
+  ASSERT_EQ(0u, block_idx);
+  EXPECT_THAT(write_result, StrEq(data2));
+
+  data3 = "data3";
+  block_idx = 0;
+  write_result = Write(data3, &block_idx);
+  ASSERT_EQ(1u, block_idx);
+  EXPECT_THAT(write_result, StrEq(data3));
+}
+
+TEST_F(BalsaBufferTest, WriteLargerThanBlocksize) {
+  CreateBuffer(5);
+  std::string data1 = "123456789";
+  size_t block_idx = 0;
+  absl::string_view write_result = Write(data1, &block_idx);
+  ASSERT_EQ(1u, block_idx);
+  EXPECT_THAT(write_result, StrEq(data1));
+
+  CreateBuffer(5);
+  buffer_->NoMoreWriteToContiguousBuffer();
+  data1 = "123456789";
+  block_idx = 0;
+  write_result = Write(data1, &block_idx);
+  ASSERT_EQ(1u, block_idx);
+  EXPECT_THAT(write_result, StrEq(data1));
+}
+
+TEST_F(BalsaBufferTest, ContiguousThenTwoSmallerThanBlocksize) {
+  CreateBuffer(5);
+  std::string data1 = "1234567890";
+  buffer_->WriteToContiguousBuffer(data1);
+  size_t block_idx = 0;
+  std::string data2 = "1234";
+  absl::string_view write_result = Write(data2, &block_idx);
+  ASSERT_EQ(1u, block_idx);
+  std::string data3 = "1234";
+  write_result = Write(data3, &block_idx);
+  ASSERT_EQ(2u, block_idx);
+}
+
+TEST_F(BalsaBufferTest, AccessFirstBlockUninitialized) {
+  CreateBuffer(5);
+  EXPECT_EQ(0u, buffer_->GetReadableBytesOfFirstBlock());
+  EXPECT_DFATAL(buffer_->StartOfFirstBlock(), "First block not allocated yet!");
+  EXPECT_DFATAL(buffer_->EndOfFirstBlock(), "First block not allocated yet!");
+}
+
+TEST_F(BalsaBufferTest, AccessFirstBlockInitialized) {
+  CreateBuffer(5);
+  std::string data1 = "1234567890";
+  buffer_->WriteToContiguousBuffer(data1);
+  const char* start = buffer_->StartOfFirstBlock();
+  EXPECT_TRUE(start != nullptr);
+  const char* end = buffer_->EndOfFirstBlock();
+  EXPECT_TRUE(end != nullptr);
+  EXPECT_EQ(data1.length(), static_cast<size_t>(end - start));
+  EXPECT_EQ(data1.length(), buffer_->GetReadableBytesOfFirstBlock());
+}
+
+TEST(BalsaHeaders, CanAssignBeginToIterator) {
+  {
+    BalsaHeaders header;
+    BalsaHeaders::const_header_lines_iterator chli = header.lines().begin();
+    static_cast<void>(chli);
+  }
+  {
+    const BalsaHeaders header;
+    BalsaHeaders::const_header_lines_iterator chli = header.lines().begin();
+    static_cast<void>(chli);
+  }
+}
+
+TEST(BalsaHeaders, CanAssignEndToIterator) {
+  {
+    BalsaHeaders header;
+    BalsaHeaders::const_header_lines_iterator chli = header.lines().end();
+    static_cast<void>(chli);
+  }
+  {
+    const BalsaHeaders header;
+    BalsaHeaders::const_header_lines_iterator chli = header.lines().end();
+    static_cast<void>(chli);
+  }
+}
+
+TEST(BalsaHeaders, ReplaceOrAppendHeaderTestAppending) {
+  BalsaHeaders header;
+  std::string key_1 = "key_1";
+  std::string value_1 = "value_1";
+  header.ReplaceOrAppendHeader(key_1, value_1);
+
+  BalsaHeaders::const_header_lines_iterator chli = header.lines().begin();
+  ASSERT_EQ(absl::string_view("key_1"), chli->first);
+  ASSERT_EQ(absl::string_view("value_1"), chli->second);
+  ++chli;
+  ASSERT_NE(header.lines().begin(), chli);
+  ASSERT_EQ(header.lines().end(), chli);
+}
+
+TEST(BalsaHeaders, ReplaceOrAppendHeaderTestReplacing) {
+  BalsaHeaders header;
+  std::string key_1 = "key_1";
+  std::string value_1 = "value_1";
+  std::string key_2 = "key_2";
+  header.ReplaceOrAppendHeader(key_1, value_1);
+  header.ReplaceOrAppendHeader(key_2, value_1);
+  std::string value_2 = "value_2_string";
+  header.ReplaceOrAppendHeader(key_1, value_2);
+
+  BalsaHeaders::const_header_lines_iterator chli = header.lines().begin();
+  ASSERT_EQ(key_1, chli->first);
+  ASSERT_EQ(value_2, chli->second);
+  ++chli;
+  ASSERT_EQ(key_2, chli->first);
+  ASSERT_EQ(value_1, chli->second);
+  ++chli;
+  ASSERT_NE(header.lines().begin(), chli);
+  ASSERT_EQ(header.lines().end(), chli);
+}
+
+TEST(BalsaHeaders, ReplaceOrAppendHeaderTestReplacingMultiple) {
+  BalsaHeaders header;
+  std::string key_1 = "key_1";
+  std::string key_2 = "key_2";
+  std::string value_1 = "val_1";
+  std::string value_2 = "val_2";
+  std::string value_3 =
+      "value_3_is_longer_than_value_1_and_value_2_and_their_keys";
+  // Set up header keys 1, 1, 2.  We will replace the value of key 1 with a long
+  // enough string that it should be moved to the end.  This regression tests
+  // that replacement works if we move the header to the end.
+  header.AppendHeader(key_1, value_1);
+  header.AppendHeader(key_1, value_2);
+  header.AppendHeader(key_2, value_1);
+  header.ReplaceOrAppendHeader(key_1, value_3);
+
+  BalsaHeaders::const_header_lines_iterator chli = header.lines().begin();
+  ASSERT_EQ(key_1, chli->first);
+  ASSERT_EQ(value_3, chli->second);
+  ++chli;
+  ASSERT_EQ(key_2, chli->first);
+  ASSERT_EQ(value_1, chli->second);
+  ++chli;
+  ASSERT_NE(header.lines().begin(), chli);
+  ASSERT_EQ(header.lines().end(), chli);
+
+  // Now test that replacement works with a shorter value, so that if we ever do
+  // in-place replacement it's tested.
+  header.ReplaceOrAppendHeader(key_1, value_1);
+  chli = header.lines().begin();
+  ASSERT_EQ(key_1, chli->first);
+  ASSERT_EQ(value_1, chli->second);
+  ++chli;
+  ASSERT_EQ(key_2, chli->first);
+  ASSERT_EQ(value_1, chli->second);
+  ++chli;
+  ASSERT_NE(header.lines().begin(), chli);
+  ASSERT_EQ(header.lines().end(), chli);
+}
+
+TEST(BalsaHeaders, AppendHeaderAndIteratorTest1) {
+  BalsaHeaders header;
+  ASSERT_EQ(header.lines().begin(), header.lines().end());
+  {
+    std::string key_1 = "key_1";
+    std::string value_1 = "value_1";
+    header.AppendHeader(key_1, value_1);
+    key_1 = "garbage";
+    value_1 = "garbage";
+  }
+
+  ASSERT_NE(header.lines().begin(), header.lines().end());
+  BalsaHeaders::const_header_lines_iterator chli = header.lines().begin();
+  ASSERT_EQ(header.lines().begin(), chli);
+  ASSERT_NE(header.lines().end(), chli);
+  ASSERT_EQ(absl::string_view("key_1"), chli->first);
+  ASSERT_EQ(absl::string_view("value_1"), chli->second);
+
+  ++chli;
+  ASSERT_NE(header.lines().begin(), chli);
+  ASSERT_EQ(header.lines().end(), chli);
+}
+
+TEST(BalsaHeaders, AppendHeaderAndIteratorTest2) {
+  BalsaHeaders header;
+  ASSERT_EQ(header.lines().begin(), header.lines().end());
+  {
+    std::string key_1 = "key_1";
+    std::string value_1 = "value_1";
+    header.AppendHeader(key_1, value_1);
+    key_1 = "garbage";
+    value_1 = "garbage";
+  }
+  {
+    std::string key_2 = "key_2";
+    std::string value_2 = "value_2";
+    header.AppendHeader(key_2, value_2);
+    key_2 = "garbage";
+    value_2 = "garbage";
+  }
+
+  ASSERT_NE(header.lines().begin(), header.lines().end());
+  BalsaHeaders::const_header_lines_iterator chli = header.lines().begin();
+  ASSERT_EQ(header.lines().begin(), chli);
+  ASSERT_NE(header.lines().end(), chli);
+  ASSERT_EQ(absl::string_view("key_1"), chli->first);
+  ASSERT_EQ(absl::string_view("value_1"), chli->second);
+
+  ++chli;
+  ASSERT_NE(header.lines().begin(), chli);
+  ASSERT_NE(header.lines().end(), chli);
+  ASSERT_EQ(absl::string_view("key_2"), chli->first);
+  ASSERT_EQ(absl::string_view("value_2"), chli->second);
+
+  ++chli;
+  ASSERT_NE(header.lines().begin(), chli);
+  ASSERT_EQ(header.lines().end(), chli);
+}
+
+TEST(BalsaHeaders, AppendHeaderAndIteratorTest3) {
+  BalsaHeaders header;
+  ASSERT_EQ(header.lines().begin(), header.lines().end());
+  {
+    std::string key_1 = "key_1";
+    std::string value_1 = "value_1";
+    header.AppendHeader(key_1, value_1);
+    key_1 = "garbage";
+    value_1 = "garbage";
+  }
+  {
+    std::string key_2 = "key_2";
+    std::string value_2 = "value_2";
+    header.AppendHeader(key_2, value_2);
+    key_2 = "garbage";
+    value_2 = "garbage";
+  }
+  {
+    std::string key_3 = "key_3";
+    std::string value_3 = "value_3";
+    header.AppendHeader(key_3, value_3);
+    key_3 = "garbage";
+    value_3 = "garbage";
+  }
+
+  ASSERT_NE(header.lines().begin(), header.lines().end());
+  BalsaHeaders::const_header_lines_iterator chli = header.lines().begin();
+  ASSERT_EQ(header.lines().begin(), chli);
+  ASSERT_NE(header.lines().end(), chli);
+  ASSERT_EQ(absl::string_view("key_1"), chli->first);
+  ASSERT_EQ(absl::string_view("value_1"), chli->second);
+
+  ++chli;
+  ASSERT_NE(header.lines().begin(), chli);
+  ASSERT_NE(header.lines().end(), chli);
+  ASSERT_EQ(absl::string_view("key_2"), chli->first);
+  ASSERT_EQ(absl::string_view("value_2"), chli->second);
+
+  ++chli;
+  ASSERT_NE(header.lines().begin(), chli);
+  ASSERT_NE(header.lines().end(), chli);
+  ASSERT_EQ(absl::string_view("key_3"), chli->first);
+  ASSERT_EQ(absl::string_view("value_3"), chli->second);
+
+  ++chli;
+  ASSERT_NE(header.lines().begin(), chli);
+  ASSERT_EQ(header.lines().end(), chli);
+}
+
+TEST(BalsaHeaders, AppendHeaderAndTestEraseWithIterator) {
+  BalsaHeaders header;
+  ASSERT_EQ(header.lines().begin(), header.lines().end());
+  {
+    std::string key_1 = "key_1";
+    std::string value_1 = "value_1";
+    header.AppendHeader(key_1, value_1);
+    key_1 = "garbage";
+    value_1 = "garbage";
+  }
+  {
+    std::string key_2 = "key_2";
+    std::string value_2 = "value_2";
+    header.AppendHeader(key_2, value_2);
+    key_2 = "garbage";
+    value_2 = "garbage";
+  }
+  {
+    std::string key_3 = "key_3";
+    std::string value_3 = "value_3";
+    header.AppendHeader(key_3, value_3);
+    key_3 = "garbage";
+    value_3 = "garbage";
+  }
+  BalsaHeaders::const_header_lines_iterator chli = header.lines().begin();
+  ++chli;  // should now point to key_2.
+  ASSERT_EQ(absl::string_view("key_2"), chli->first);
+  header.erase(chli);
+  chli = header.lines().begin();
+
+  ASSERT_NE(header.lines().begin(), header.lines().end());
+  ASSERT_EQ(header.lines().begin(), chli);
+  ASSERT_NE(header.lines().end(), chli);
+  ASSERT_EQ(absl::string_view("key_1"), chli->first);
+  ASSERT_EQ(absl::string_view("value_1"), chli->second);
+
+  ++chli;
+  ASSERT_NE(header.lines().begin(), chli);
+  ASSERT_NE(header.lines().end(), chli);
+  ASSERT_EQ(absl::string_view("key_3"), chli->first);
+  ASSERT_EQ(absl::string_view("value_3"), chli->second);
+
+  ++chli;
+  ASSERT_NE(header.lines().begin(), chli);
+  ASSERT_EQ(header.lines().end(), chli);
+}
+
+TEST(BalsaHeaders, TestSetFirstlineInAdditionalBuffer) {
+  BalsaHeaders headers;
+  headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0");
+  ASSERT_THAT(headers.first_line(), StrEq("GET / HTTP/1.0"));
+}
+
+TEST(BalsaHeaders, TestSetFirstlineInOriginalBufferAndIsShorterThanOriginal) {
+  BalsaHeaders headers = CreateHTTPHeaders(true,
+                                           "GET /foobar HTTP/1.0\r\n"
+                                           "\r\n");
+  ASSERT_THAT(headers.first_line(), StrEq("GET /foobar HTTP/1.0"));
+  // Note that this SetRequestFirstlineFromStringPieces should replace the
+  // original one in the -non- 'additional' buffer.
+  headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0");
+  ASSERT_THAT(headers.first_line(), StrEq("GET / HTTP/1.0"));
+}
+
+TEST(BalsaHeaders, TestSetFirstlineInOriginalBufferAndIsLongerThanOriginal) {
+  // Similar to above, but this time the new firstline is larger than
+  // the original, yet it should still fit into the original -non-
+  // 'additional' buffer as the first header-line has been erased.
+  BalsaHeaders headers = CreateHTTPHeaders(true,
+                                           "GET / HTTP/1.0\r\n"
+                                           "some_key: some_value\r\n"
+                                           "another_key: another_value\r\n"
+                                           "\r\n");
+  ASSERT_THAT(headers.first_line(), StrEq("GET / HTTP/1.0"));
+  headers.erase(headers.lines().begin());
+  // Note that this SetRequestFirstlineFromStringPieces should replace the
+  // original one in the -non- 'additional' buffer.
+  headers.SetRequestFirstlineFromStringPieces("GET", "/foobar", "HTTP/1.0");
+  ASSERT_THAT(headers.first_line(), StrEq("GET /foobar HTTP/1.0"));
+}
+
+TEST(BalsaHeaders, TestSetFirstlineInAdditionalDataAndIsShorterThanOriginal) {
+  BalsaHeaders headers;
+  headers.SetRequestFirstlineFromStringPieces("GET", "/foobar", "HTTP/1.0");
+  ASSERT_THAT(headers.first_line(), StrEq("GET /foobar HTTP/1.0"));
+  headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0");
+  ASSERT_THAT(headers.first_line(), StrEq("GET / HTTP/1.0"));
+}
+
+TEST(BalsaHeaders, TestSetFirstlineInAdditionalDataAndIsLongerThanOriginal) {
+  BalsaHeaders headers;
+  headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0");
+  ASSERT_THAT(headers.first_line(), StrEq("GET / HTTP/1.0"));
+  headers.SetRequestFirstlineFromStringPieces("GET", "/foobar", "HTTP/1.0");
+  ASSERT_THAT(headers.first_line(), StrEq("GET /foobar HTTP/1.0"));
+}
+
+TEST(BalsaHeaders, TestDeletingSubstring) {
+  BalsaHeaders headers;
+  headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0");
+  headers.AppendHeader("key1", "value1");
+  headers.AppendHeader("key2", "value2");
+  headers.AppendHeader("key", "value");
+  headers.AppendHeader("unrelated", "value");
+
+  // RemoveAllOfHeader should not delete key1 or key2 given a substring.
+  headers.RemoveAllOfHeader("key");
+  EXPECT_TRUE(headers.HasHeader("key1"));
+  EXPECT_TRUE(headers.HasHeader("key2"));
+  EXPECT_TRUE(headers.HasHeader("unrelated"));
+  EXPECT_FALSE(headers.HasHeader("key"));
+  EXPECT_TRUE(headers.HasHeadersWithPrefix("key"));
+  EXPECT_TRUE(headers.HasHeadersWithPrefix("KeY"));
+  EXPECT_TRUE(headers.HasHeadersWithPrefix("UNREL"));
+  EXPECT_FALSE(headers.HasHeadersWithPrefix("key3"));
+
+  EXPECT_FALSE(headers.GetHeader("key1").empty());
+  EXPECT_FALSE(headers.GetHeader("KEY1").empty());
+  EXPECT_FALSE(headers.GetHeader("key2").empty());
+  EXPECT_FALSE(headers.GetHeader("unrelated").empty());
+  EXPECT_TRUE(headers.GetHeader("key").empty());
+
+  // Add key back in.
+  headers.AppendHeader("key", "");
+  EXPECT_TRUE(headers.HasHeader("key"));
+  EXPECT_TRUE(headers.HasHeadersWithPrefix("key"));
+  EXPECT_TRUE(headers.GetHeader("key").empty());
+
+  // RemoveAllHeadersWithPrefix should delete everything starting with key.
+  headers.RemoveAllHeadersWithPrefix("key");
+  EXPECT_FALSE(headers.HasHeader("key1"));
+  EXPECT_FALSE(headers.HasHeader("key2"));
+  EXPECT_TRUE(headers.HasHeader("unrelated"));
+  EXPECT_FALSE(headers.HasHeader("key"));
+  EXPECT_FALSE(headers.HasHeadersWithPrefix("key"));
+  EXPECT_FALSE(headers.HasHeadersWithPrefix("key1"));
+  EXPECT_FALSE(headers.HasHeadersWithPrefix("key2"));
+  EXPECT_FALSE(headers.HasHeadersWithPrefix("kEy"));
+  EXPECT_TRUE(headers.HasHeadersWithPrefix("unrelated"));
+
+  EXPECT_TRUE(headers.GetHeader("key1").empty());
+  EXPECT_TRUE(headers.GetHeader("key2").empty());
+  EXPECT_FALSE(headers.GetHeader("unrelated").empty());
+  EXPECT_TRUE(headers.GetHeader("key").empty());
+}
+
+TEST(BalsaHeaders, TestRemovingValues) {
+  // Remove entire line from headers, twice. Ensures working line-skipping.
+  // Skip consideration of a line whose key is larger than our search key.
+  // Skip consideration of a line whose key is smaller than our search key.
+  // Skip consideration of a line that is already marked for skipping.
+  // Skip consideration of a line whose value is too small.
+  // Skip consideration of a line whose key is correct in length but doesn't
+  // match.
+  {
+    BalsaHeaders headers;
+    headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0");
+    headers.AppendHeader("hi", "hello");
+    headers.AppendHeader("key1", "val1");
+    headers.AppendHeader("key1", "value2");
+    headers.AppendHeader("key1", "value3");
+    headers.AppendHeader("key2", "value4");
+    headers.AppendHeader("unrelated", "value");
+
+    EXPECT_EQ(0u, headers.RemoveValue("key1", ""));
+    EXPECT_EQ(1u, headers.RemoveValue("key1", "value2"));
+
+    std::string key1_vals = headers.GetAllOfHeaderAsString("key1");
+    EXPECT_THAT(key1_vals, StrEq("val1,value3"));
+
+    EXPECT_TRUE(headers.HeaderHasValue("key1", "val1"));
+    EXPECT_TRUE(headers.HeaderHasValue("key1", "value3"));
+    EXPECT_EQ("value4", headers.GetHeader("key2"));
+    EXPECT_EQ("hello", headers.GetHeader("hi"));
+    EXPECT_EQ("value", headers.GetHeader("unrelated"));
+    EXPECT_FALSE(headers.HeaderHasValue("key1", "value2"));
+
+    EXPECT_EQ(1u, headers.RemoveValue("key1", "value3"));
+
+    key1_vals = headers.GetAllOfHeaderAsString("key1");
+    EXPECT_THAT(key1_vals, StrEq("val1"));
+
+    EXPECT_TRUE(headers.HeaderHasValue("key1", "val1"));
+    EXPECT_EQ("value4", headers.GetHeader("key2"));
+    EXPECT_EQ("hello", headers.GetHeader("hi"));
+    EXPECT_EQ("value", headers.GetHeader("unrelated"));
+    EXPECT_FALSE(headers.HeaderHasValue("key1", "value3"));
+    EXPECT_FALSE(headers.HeaderHasValue("key1", "value2"));
+  }
+
+  // Remove/keep values with surrounding spaces.
+  // Remove values from in between others in multi-value line.
+  // Remove entire multi-value line.
+  // Keep value in between removed values in multi-value line.
+  // Keep trailing value that is too small to be matched after removing a match.
+  // Keep value containing matched value (partial but not complete match).
+  // Keep an empty header.
+  {
+    BalsaHeaders headers;
+    headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0");
+    headers.AppendHeader("key1", "value1");
+    headers.AppendHeader("key1", "value2, value3,value2");
+    headers.AppendHeader("key1", "value4 ,value2,value5,val6");
+    headers.AppendHeader("key1", "value2,  value2   , value2");
+    headers.AppendHeader("key1", "  value2  ,   value2   ");
+    headers.AppendHeader("key1", " value2 a");
+    headers.AppendHeader("key1", "");
+    headers.AppendHeader("key1", ",  ,,");
+    headers.AppendHeader("unrelated", "value");
+
+    EXPECT_EQ(8u, headers.RemoveValue("key1", "value2"));
+
+    std::string key1_vals = headers.GetAllOfHeaderAsString("key1");
+    EXPECT_THAT(key1_vals,
+                StrEq("value1,value3,value4 ,value5,val6,value2 a,,,  ,,"));
+
+    EXPECT_EQ("value", headers.GetHeader("unrelated"));
+    EXPECT_TRUE(headers.HeaderHasValue("key1", "value1"));
+    EXPECT_TRUE(headers.HeaderHasValue("key1", "value3"));
+    EXPECT_TRUE(headers.HeaderHasValue("key1", "value4"));
+    EXPECT_TRUE(headers.HeaderHasValue("key1", "value5"));
+    EXPECT_TRUE(headers.HeaderHasValue("key1", "val6"));
+    EXPECT_FALSE(headers.HeaderHasValue("key1", "value2"));
+  }
+
+  {
+    const absl::string_view key("key");
+    const absl::string_view value1("foo\0bar", 7);
+    const absl::string_view value2("value2");
+    const std::string value = absl::StrCat(value1, ",", value2);
+
+    {
+      BalsaHeaders headers;
+      headers.AppendHeader(key, value);
+
+      EXPECT_TRUE(headers.HeaderHasValue(key, value1));
+      EXPECT_TRUE(headers.HeaderHasValue(key, value2));
+      EXPECT_EQ(value, headers.GetAllOfHeaderAsString(key));
+
+        EXPECT_EQ(1u, headers.RemoveValue(key, value2));
+
+        EXPECT_TRUE(headers.HeaderHasValue(key, value1));
+        EXPECT_FALSE(headers.HeaderHasValue(key, value2));
+        EXPECT_EQ(value1, headers.GetAllOfHeaderAsString(key));
+    }
+
+    {
+      BalsaHeaders headers;
+      headers.AppendHeader(key, value1);
+      headers.AppendHeader(key, value2);
+
+      EXPECT_TRUE(headers.HeaderHasValue(key, value1));
+      EXPECT_TRUE(headers.HeaderHasValue(key, value2));
+      EXPECT_EQ(value, headers.GetAllOfHeaderAsString(key));
+
+      EXPECT_EQ(1u, headers.RemoveValue(key, value2));
+
+      EXPECT_TRUE(headers.HeaderHasValue(key, value1));
+      EXPECT_FALSE(headers.HeaderHasValue(key, value2));
+      EXPECT_EQ(value1, headers.GetAllOfHeaderAsString(key));
+    }
+  }
+}
+
+TEST(BalsaHeaders, ZeroAppendToHeaderWithCommaAndSpace) {
+  // Create an initial header with zero 'X-Forwarded-For' headers.
+  BalsaHeaders headers = CreateHTTPHeaders(true,
+                                           "GET / HTTP/1.0\r\n"
+                                           "\r\n");
+
+  // Use AppendToHeaderWithCommaAndSpace to add 4 new 'X-Forwarded-For' headers.
+  // Appending these headers should preserve the order in which they are added.
+  // i.e. 1.1.1.1, 2.2.2.2, 3.3.3.3, 4.4.4.4
+  headers.AppendToHeaderWithCommaAndSpace("X-Forwarded-For", "1.1.1.1");
+  headers.AppendToHeaderWithCommaAndSpace("X-Forwarded-For", "2.2.2.2");
+  headers.AppendToHeaderWithCommaAndSpace("X-Forwarded-For", "3.3.3.3");
+  headers.AppendToHeaderWithCommaAndSpace("X-Forwarded-For", "4.4.4.4");
+
+  // Fetch the 'X-Forwarded-For' headers and compare them to the expected order.
+  EXPECT_THAT(headers.GetAllOfHeader("X-Forwarded-For"),
+              ElementsAre("1.1.1.1, 2.2.2.2, 3.3.3.3, 4.4.4.4"));
+}
+
+TEST(BalsaHeaders, SingleAppendToHeaderWithCommaAndSpace) {
+  // Create an initial header with one 'X-Forwarded-For' header.
+  BalsaHeaders headers = CreateHTTPHeaders(true,
+                                           "GET / HTTP/1.0\r\n"
+                                           "X-Forwarded-For: 1.1.1.1\r\n"
+                                           "\r\n");
+
+  // Use AppendToHeaderWithCommaAndSpace to add 4 new 'X-Forwarded-For' headers.
+  // Appending these headers should preserve the order in which they are added.
+  // i.e. 1.1.1.1, 2.2.2.2, 3.3.3.3, 4.4.4.4, 5.5.5.5
+  headers.AppendToHeaderWithCommaAndSpace("X-Forwarded-For", "2.2.2.2");
+  headers.AppendToHeaderWithCommaAndSpace("X-Forwarded-For", "3.3.3.3");
+  headers.AppendToHeaderWithCommaAndSpace("X-Forwarded-For", "4.4.4.4");
+  headers.AppendToHeaderWithCommaAndSpace("X-Forwarded-For", "5.5.5.5");
+
+  // Fetch the 'X-Forwarded-For' headers and compare them to the expected order.
+  EXPECT_THAT(headers.GetAllOfHeader("X-Forwarded-For"),
+              ElementsAre("1.1.1.1, 2.2.2.2, 3.3.3.3, 4.4.4.4, 5.5.5.5"));
+}
+
+TEST(BalsaHeaders, MultipleAppendToHeaderWithCommaAndSpace) {
+  // Create an initial header with two 'X-Forwarded-For' headers.
+  BalsaHeaders headers = CreateHTTPHeaders(true,
+                                           "GET / HTTP/1.0\r\n"
+                                           "X-Forwarded-For: 1.1.1.1\r\n"
+                                           "X-Forwarded-For: 2.2.2.2\r\n"
+                                           "\r\n");
+
+  // Use AppendToHeaderWithCommaAndSpace to add 4 new 'X-Forwarded-For' headers.
+  // Appending these headers should preserve the order in which they are added.
+  // i.e. 1.1.1.1, 2.2.2.2, 3.3.3.3, 4.4.4.4, 5.5.5.5, 6.6.6.6
+  headers.AppendToHeaderWithCommaAndSpace("X-Forwarded-For", "3.3.3.3");
+  headers.AppendToHeaderWithCommaAndSpace("X-Forwarded-For", "4.4.4.4");
+  headers.AppendToHeaderWithCommaAndSpace("X-Forwarded-For", "5.5.5.5");
+  headers.AppendToHeaderWithCommaAndSpace("X-Forwarded-For", "6.6.6.6");
+
+  // Fetch the 'X-Forwarded-For' headers and compare them to the expected order.
+  EXPECT_THAT(
+      headers.GetAllOfHeader("X-Forwarded-For"),
+      ElementsAre("1.1.1.1", "2.2.2.2, 3.3.3.3, 4.4.4.4, 5.5.5.5, 6.6.6.6"));
+}
+
+TEST(BalsaHeaders, HeaderHasValues) {
+  BalsaHeaders headers;
+  headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0");
+  // Make sure we find values at the beginning, middle, and end, and we handle
+  // multiple .find() calls correctly.
+  headers.AppendHeader("key", "val1,val2val2,val2,val3");
+  // Make sure we don't mess up comma/boundary checks for beginning, middle and
+  // end.
+  headers.AppendHeader("key", "val4val5val6");
+  headers.AppendHeader("key", "val11 val12");
+  headers.AppendHeader("key", "v val13");
+  // Make sure we catch the line header
+  headers.AppendHeader("key", "val7");
+  // Make sure there's no out-of-bounds indexing on an empty line.
+  headers.AppendHeader("key", "");
+  // Make sure it works when there's spaces before or after a comma.
+  headers.AppendHeader("key", "val8 , val9 , val10");
+  // Make sure it works when val is surrounded by spaces.
+  headers.AppendHeader("key", " val14 ");
+  // Make sure other keys aren't used.
+  headers.AppendHeader("key2", "val15");
+  // Mixed case.
+  headers.AppendHeader("key", "Val16");
+  headers.AppendHeader("key", "foo, Val17, bar");
+
+  // All case-sensitive.
+  EXPECT_TRUE(headers.HeaderHasValue("key", "val1"));
+  EXPECT_TRUE(headers.HeaderHasValue("key", "val2"));
+  EXPECT_TRUE(headers.HeaderHasValue("key", "val3"));
+  EXPECT_TRUE(headers.HeaderHasValue("key", "val7"));
+  EXPECT_TRUE(headers.HeaderHasValue("key", "val8"));
+  EXPECT_TRUE(headers.HeaderHasValue("key", "val9"));
+  EXPECT_TRUE(headers.HeaderHasValue("key", "val10"));
+  EXPECT_TRUE(headers.HeaderHasValue("key", "val14"));
+  EXPECT_FALSE(headers.HeaderHasValue("key", "val4"));
+  EXPECT_FALSE(headers.HeaderHasValue("key", "val5"));
+  EXPECT_FALSE(headers.HeaderHasValue("key", "val6"));
+  EXPECT_FALSE(headers.HeaderHasValue("key", "val11"));
+  EXPECT_FALSE(headers.HeaderHasValue("key", "val12"));
+  EXPECT_FALSE(headers.HeaderHasValue("key", "val13"));
+  EXPECT_FALSE(headers.HeaderHasValue("key", "val15"));
+  EXPECT_FALSE(headers.HeaderHasValue("key", "val16"));
+  EXPECT_FALSE(headers.HeaderHasValue("key", "val17"));
+
+  // All case-insensitive, only change is for val16 and val17.
+  EXPECT_TRUE(headers.HeaderHasValueIgnoreCase("key", "val1"));
+  EXPECT_TRUE(headers.HeaderHasValueIgnoreCase("key", "val2"));
+  EXPECT_TRUE(headers.HeaderHasValueIgnoreCase("key", "val3"));
+  EXPECT_TRUE(headers.HeaderHasValueIgnoreCase("key", "val7"));
+  EXPECT_TRUE(headers.HeaderHasValueIgnoreCase("key", "val8"));
+  EXPECT_TRUE(headers.HeaderHasValueIgnoreCase("key", "val9"));
+  EXPECT_TRUE(headers.HeaderHasValueIgnoreCase("key", "val10"));
+  EXPECT_TRUE(headers.HeaderHasValueIgnoreCase("key", "val14"));
+  EXPECT_FALSE(headers.HeaderHasValueIgnoreCase("key", "val4"));
+  EXPECT_FALSE(headers.HeaderHasValueIgnoreCase("key", "val5"));
+  EXPECT_FALSE(headers.HeaderHasValueIgnoreCase("key", "val6"));
+  EXPECT_FALSE(headers.HeaderHasValueIgnoreCase("key", "val11"));
+  EXPECT_FALSE(headers.HeaderHasValueIgnoreCase("key", "val12"));
+  EXPECT_FALSE(headers.HeaderHasValueIgnoreCase("key", "val13"));
+  EXPECT_FALSE(headers.HeaderHasValueIgnoreCase("key", "val15"));
+  EXPECT_TRUE(headers.HeaderHasValueIgnoreCase("key", "val16"));
+  EXPECT_TRUE(headers.HeaderHasValueIgnoreCase("key", "val17"));
+}
+
+// Because we're dealing with one giant buffer, make sure we don't go beyond
+// the bounds of the key when doing compares!
+TEST(BalsaHeaders, TestNotDeletingBeyondString) {
+  BalsaHeaders headers;
+  headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0");
+  headers.AppendHeader("key1", "value1");
+
+  headers.RemoveAllHeadersWithPrefix("key1: value1");
+  EXPECT_NE(headers.lines().begin(), headers.lines().end());
+}
+
+TEST(BalsaHeaders, TestIteratingOverErasedHeaders) {
+  BalsaHeaders headers;
+  headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0");
+  headers.AppendHeader("key1", "value1");
+  headers.AppendHeader("key2", "value2");
+  headers.AppendHeader("key3", "value3");
+  headers.AppendHeader("key4", "value4");
+  headers.AppendHeader("key5", "value5");
+  headers.AppendHeader("key6", "value6");
+
+  headers.RemoveAllOfHeader("key6");
+  headers.RemoveAllOfHeader("key5");
+  headers.RemoveAllOfHeader("key4");
+
+  BalsaHeaders::const_header_lines_iterator chli = headers.lines().begin();
+  EXPECT_NE(headers.lines().end(), chli);
+  EXPECT_EQ(headers.lines().begin(), chli);
+  EXPECT_THAT(chli->first, StrEq("key1"));
+  EXPECT_THAT(chli->second, StrEq("value1"));
+
+  ++chli;
+  EXPECT_NE(headers.lines().end(), chli);
+  EXPECT_NE(headers.lines().begin(), chli);
+  EXPECT_THAT(chli->first, StrEq("key2"));
+  EXPECT_THAT(chli->second, StrEq("value2"));
+
+  ++chli;
+  EXPECT_NE(headers.lines().end(), chli);
+  EXPECT_NE(headers.lines().begin(), chli);
+  EXPECT_THAT(chli->first, StrEq("key3"));
+  EXPECT_THAT(chli->second, StrEq("value3"));
+
+  ++chli;
+  EXPECT_EQ(headers.lines().end(), chli);
+  EXPECT_NE(headers.lines().begin(), chli);
+
+  headers.RemoveAllOfHeader("key1");
+  headers.RemoveAllOfHeader("key2");
+  chli = headers.lines().begin();
+  EXPECT_THAT(chli->first, StrEq("key3"));
+  EXPECT_THAT(chli->second, StrEq("value3"));
+  EXPECT_NE(headers.lines().end(), chli);
+  EXPECT_EQ(headers.lines().begin(), chli);
+
+  ++chli;
+  EXPECT_EQ(headers.lines().end(), chli);
+  EXPECT_NE(headers.lines().begin(), chli);
+}
+
+TEST(BalsaHeaders, CanCompareIterators) {
+  BalsaHeaders header;
+  ASSERT_EQ(header.lines().begin(), header.lines().end());
+  {
+    std::string key_1 = "key_1";
+    std::string value_1 = "value_1";
+    header.AppendHeader(key_1, value_1);
+    key_1 = "garbage";
+    value_1 = "garbage";
+  }
+  {
+    std::string key_2 = "key_2";
+    std::string value_2 = "value_2";
+    header.AppendHeader(key_2, value_2);
+    key_2 = "garbage";
+    value_2 = "garbage";
+  }
+  BalsaHeaders::const_header_lines_iterator chli = header.lines().begin();
+  BalsaHeaders::const_header_lines_iterator chlj = header.lines().begin();
+  EXPECT_EQ(chli, chlj);
+  ++chlj;
+  EXPECT_NE(chli, chlj);
+  EXPECT_LT(chli, chlj);
+  EXPECT_LE(chli, chlj);
+  EXPECT_LE(chli, chli);
+  EXPECT_GT(chlj, chli);
+  EXPECT_GE(chlj, chli);
+  EXPECT_GE(chlj, chlj);
+}
+
+TEST(BalsaHeaders, AppendHeaderAndTestThatYouCanEraseEverything) {
+  BalsaHeaders header;
+  ASSERT_EQ(header.lines().begin(), header.lines().end());
+  {
+    std::string key_1 = "key_1";
+    std::string value_1 = "value_1";
+    header.AppendHeader(key_1, value_1);
+    key_1 = "garbage";
+    value_1 = "garbage";
+  }
+  {
+    std::string key_2 = "key_2";
+    std::string value_2 = "value_2";
+    header.AppendHeader(key_2, value_2);
+    key_2 = "garbage";
+    value_2 = "garbage";
+  }
+  {
+    std::string key_3 = "key_3";
+    std::string value_3 = "value_3";
+    header.AppendHeader(key_3, value_3);
+    key_3 = "garbage";
+    value_3 = "garbage";
+  }
+  EXPECT_NE(header.lines().begin(), header.lines().end());
+  BalsaHeaders::const_header_lines_iterator chli = header.lines().begin();
+  while (chli != header.lines().end()) {
+    header.erase(chli);
+    chli = header.lines().begin();
+  }
+  ASSERT_EQ(header.lines().begin(), header.lines().end());
+}
+
+TEST(BalsaHeaders, GetHeaderPositionWorksAsExpectedWithNoHeaderLines) {
+  BalsaHeaders header;
+  BalsaHeaders::const_header_lines_iterator i = header.GetHeaderPosition("foo");
+  EXPECT_EQ(i, header.lines().end());
+}
+
+TEST(BalsaHeaders, GetHeaderPositionWorksAsExpectedWithBalsaFrameProcessInput) {
+  BalsaHeaders headers = CreateHTTPHeaders(
+      true,
+      "GET / HTTP/1.0\r\n"
+      "key1: value_1\r\n"
+      "key1: value_foo\r\n"  // this one cannot be fetched via GetHeader
+      "key2: value_2\r\n"
+      "key3: value_3\r\n"
+      "a: value_a\r\n"
+      "b: value_b\r\n"
+      "\r\n");
+
+  BalsaHeaders::const_header_lines_iterator header_position_b =
+      headers.GetHeaderPosition("b");
+  ASSERT_NE(header_position_b, headers.lines().end());
+  absl::string_view header_key_b_value = header_position_b->second;
+  ASSERT_FALSE(header_key_b_value.empty());
+  EXPECT_EQ(std::string("value_b"), header_key_b_value);
+
+  BalsaHeaders::const_header_lines_iterator header_position_1 =
+      headers.GetHeaderPosition("key1");
+  ASSERT_NE(header_position_1, headers.lines().end());
+  absl::string_view header_key_1_value = header_position_1->second;
+  ASSERT_FALSE(header_key_1_value.empty());
+  EXPECT_EQ(std::string("value_1"), header_key_1_value);
+
+  BalsaHeaders::const_header_lines_iterator header_position_3 =
+      headers.GetHeaderPosition("key3");
+  ASSERT_NE(header_position_3, headers.lines().end());
+  absl::string_view header_key_3_value = header_position_3->second;
+  ASSERT_FALSE(header_key_3_value.empty());
+  EXPECT_EQ(std::string("value_3"), header_key_3_value);
+
+  BalsaHeaders::const_header_lines_iterator header_position_2 =
+      headers.GetHeaderPosition("key2");
+  ASSERT_NE(header_position_2, headers.lines().end());
+  absl::string_view header_key_2_value = header_position_2->second;
+  ASSERT_FALSE(header_key_2_value.empty());
+  EXPECT_EQ(std::string("value_2"), header_key_2_value);
+
+  BalsaHeaders::const_header_lines_iterator header_position_a =
+      headers.GetHeaderPosition("a");
+  ASSERT_NE(header_position_a, headers.lines().end());
+  absl::string_view header_key_a_value = header_position_a->second;
+  ASSERT_FALSE(header_key_a_value.empty());
+  EXPECT_EQ(std::string("value_a"), header_key_a_value);
+}
+
+TEST(BalsaHeaders, GetHeaderWorksAsExpectedWithNoHeaderLines) {
+  BalsaHeaders header;
+  absl::string_view value = header.GetHeader("foo");
+  EXPECT_TRUE(value.empty());
+  value = header.GetHeader("");
+  EXPECT_TRUE(value.empty());
+}
+
+TEST(BalsaHeaders, HasHeaderWorksAsExpectedWithNoHeaderLines) {
+  BalsaHeaders header;
+  EXPECT_FALSE(header.HasHeader("foo"));
+  EXPECT_FALSE(header.HasHeader(""));
+  EXPECT_FALSE(header.HasHeadersWithPrefix("foo"));
+  EXPECT_FALSE(header.HasHeadersWithPrefix(""));
+}
+
+TEST(BalsaHeaders, HasHeaderWorksAsExpectedWithBalsaFrameProcessInput) {
+  BalsaHeaders headers = CreateHTTPHeaders(true,
+                                           "GET / HTTP/1.0\r\n"
+                                           "key1: value_1\r\n"
+                                           "key1: value_foo\r\n"
+                                           "key2:\r\n"
+                                           "\r\n");
+
+  EXPECT_FALSE(headers.HasHeader("foo"));
+  EXPECT_TRUE(headers.HasHeader("key1"));
+  EXPECT_TRUE(headers.HasHeader("key2"));
+  EXPECT_FALSE(headers.HasHeadersWithPrefix("foo"));
+  EXPECT_TRUE(headers.HasHeadersWithPrefix("key"));
+  EXPECT_TRUE(headers.HasHeadersWithPrefix("KEY"));
+}
+
+TEST(BalsaHeaders, GetHeaderWorksAsExpectedWithBalsaFrameProcessInput) {
+  BalsaHeaders headers = CreateHTTPHeaders(
+      true,
+      "GET / HTTP/1.0\r\n"
+      "key1: value_1\r\n"
+      "key1: value_foo\r\n"  // this one cannot be fetched via GetHeader
+      "key2: value_2\r\n"
+      "key3: value_3\r\n"
+      "key4:\r\n"
+      "a: value_a\r\n"
+      "b: value_b\r\n"
+      "\r\n");
+
+  absl::string_view header_key_b_value = headers.GetHeader("b");
+  ASSERT_FALSE(header_key_b_value.empty());
+  EXPECT_EQ(std::string("value_b"), header_key_b_value);
+
+  absl::string_view header_key_1_value = headers.GetHeader("key1");
+  ASSERT_FALSE(header_key_1_value.empty());
+  EXPECT_EQ(std::string("value_1"), header_key_1_value);
+
+  absl::string_view header_key_3_value = headers.GetHeader("key3");
+  ASSERT_FALSE(header_key_3_value.empty());
+  EXPECT_EQ(std::string("value_3"), header_key_3_value);
+
+  absl::string_view header_key_2_value = headers.GetHeader("key2");
+  ASSERT_FALSE(header_key_2_value.empty());
+  EXPECT_EQ(std::string("value_2"), header_key_2_value);
+
+  absl::string_view header_key_a_value = headers.GetHeader("a");
+  ASSERT_FALSE(header_key_a_value.empty());
+  EXPECT_EQ(std::string("value_a"), header_key_a_value);
+
+  EXPECT_TRUE(headers.GetHeader("key4").empty());
+}
+
+TEST(BalsaHeaders, GetHeaderWorksAsExpectedWithAppendHeader) {
+  BalsaHeaders header;
+
+  header.AppendHeader("key1", "value_1");
+  // note that this (following) one cannot be found using GetHeader.
+  header.AppendHeader("key1", "value_2");
+  header.AppendHeader("key2", "value_2");
+  header.AppendHeader("key3", "value_3");
+  header.AppendHeader("a", "value_a");
+  header.AppendHeader("b", "value_b");
+
+  absl::string_view header_key_b_value = header.GetHeader("b");
+  absl::string_view header_key_1_value = header.GetHeader("key1");
+  absl::string_view header_key_3_value = header.GetHeader("key3");
+  absl::string_view header_key_2_value = header.GetHeader("key2");
+  absl::string_view header_key_a_value = header.GetHeader("a");
+
+  ASSERT_FALSE(header_key_1_value.empty());
+  ASSERT_FALSE(header_key_2_value.empty());
+  ASSERT_FALSE(header_key_3_value.empty());
+  ASSERT_FALSE(header_key_a_value.empty());
+  ASSERT_FALSE(header_key_b_value.empty());
+
+  EXPECT_TRUE(header.HasHeader("key1"));
+  EXPECT_TRUE(header.HasHeader("key2"));
+  EXPECT_TRUE(header.HasHeader("key3"));
+  EXPECT_TRUE(header.HasHeader("a"));
+  EXPECT_TRUE(header.HasHeader("b"));
+
+  EXPECT_TRUE(header.HasHeadersWithPrefix("key1"));
+  EXPECT_TRUE(header.HasHeadersWithPrefix("key2"));
+  EXPECT_TRUE(header.HasHeadersWithPrefix("key3"));
+  EXPECT_TRUE(header.HasHeadersWithPrefix("a"));
+  EXPECT_TRUE(header.HasHeadersWithPrefix("b"));
+
+  EXPECT_EQ(std::string("value_1"), header_key_1_value);
+  EXPECT_EQ(std::string("value_2"), header_key_2_value);
+  EXPECT_EQ(std::string("value_3"), header_key_3_value);
+  EXPECT_EQ(std::string("value_a"), header_key_a_value);
+  EXPECT_EQ(std::string("value_b"), header_key_b_value);
+}
+
+TEST(BalsaHeaders, HasHeaderWorksAsExpectedWithAppendHeader) {
+  BalsaHeaders header;
+
+  ASSERT_FALSE(header.HasHeader("key1"));
+  EXPECT_FALSE(header.HasHeadersWithPrefix("K"));
+  EXPECT_FALSE(header.HasHeadersWithPrefix("ke"));
+  EXPECT_FALSE(header.HasHeadersWithPrefix("key"));
+  EXPECT_FALSE(header.HasHeadersWithPrefix("key1"));
+  EXPECT_FALSE(header.HasHeadersWithPrefix("key2"));
+  header.AppendHeader("key1", "value_1");
+  EXPECT_TRUE(header.HasHeader("key1"));
+  EXPECT_TRUE(header.HasHeadersWithPrefix("K"));
+  EXPECT_TRUE(header.HasHeadersWithPrefix("ke"));
+  EXPECT_TRUE(header.HasHeadersWithPrefix("key"));
+  EXPECT_TRUE(header.HasHeadersWithPrefix("key1"));
+  EXPECT_FALSE(header.HasHeadersWithPrefix("key2"));
+
+  header.AppendHeader("key1", "value_2");
+  EXPECT_TRUE(header.HasHeader("key1"));
+  EXPECT_FALSE(header.HasHeader("key2"));
+  EXPECT_TRUE(header.HasHeadersWithPrefix("k"));
+  EXPECT_TRUE(header.HasHeadersWithPrefix("ke"));
+  EXPECT_TRUE(header.HasHeadersWithPrefix("key"));
+  EXPECT_TRUE(header.HasHeadersWithPrefix("key1"));
+  EXPECT_FALSE(header.HasHeadersWithPrefix("key2"));
+}
+
+TEST(BalsaHeaders, GetHeaderWorksAsExpectedWithHeadersErased) {
+  BalsaHeaders header;
+  header.AppendHeader("key1", "value_1");
+  header.AppendHeader("key1", "value_2");
+  header.AppendHeader("key2", "value_2");
+  header.AppendHeader("key3", "value_3");
+  header.AppendHeader("a", "value_a");
+  header.AppendHeader("b", "value_b");
+
+  header.erase(header.GetHeaderPosition("key2"));
+
+  absl::string_view header_key_b_value = header.GetHeader("b");
+  absl::string_view header_key_1_value = header.GetHeader("key1");
+  absl::string_view header_key_3_value = header.GetHeader("key3");
+  absl::string_view header_key_2_value = header.GetHeader("key2");
+  absl::string_view header_key_a_value = header.GetHeader("a");
+
+  ASSERT_FALSE(header_key_1_value.empty());
+  ASSERT_TRUE(header_key_2_value.empty());
+  ASSERT_FALSE(header_key_3_value.empty());
+  ASSERT_FALSE(header_key_a_value.empty());
+  ASSERT_FALSE(header_key_b_value.empty());
+
+  EXPECT_EQ(std::string("value_1"), header_key_1_value);
+  EXPECT_EQ(std::string("value_3"), header_key_3_value);
+  EXPECT_EQ(std::string("value_a"), header_key_a_value);
+  EXPECT_EQ(std::string("value_b"), header_key_b_value);
+
+  // Erasing one makes the next one visible:
+  header.erase(header.GetHeaderPosition("key1"));
+  header_key_1_value = header.GetHeader("key1");
+  ASSERT_FALSE(header_key_1_value.empty());
+  EXPECT_EQ(std::string("value_2"), header_key_1_value);
+
+  // Erase both:
+  header.erase(header.GetHeaderPosition("key1"));
+  ASSERT_TRUE(header.GetHeader("key1").empty());
+}
+
+TEST(BalsaHeaders, HasHeaderWorksAsExpectedWithHeadersErased) {
+  BalsaHeaders header;
+  header.AppendHeader("key1", "value_1");
+  header.AppendHeader("key2", "value_2a");
+  header.AppendHeader("key2", "value_2b");
+
+  ASSERT_TRUE(header.HasHeader("key1"));
+  ASSERT_TRUE(header.HasHeadersWithPrefix("key1"));
+  ASSERT_TRUE(header.HasHeadersWithPrefix("key2"));
+  ASSERT_TRUE(header.HasHeadersWithPrefix("kEY"));
+  header.erase(header.GetHeaderPosition("key1"));
+  EXPECT_FALSE(header.HasHeader("key1"));
+  EXPECT_FALSE(header.HasHeadersWithPrefix("key1"));
+  EXPECT_TRUE(header.HasHeadersWithPrefix("key2"));
+  EXPECT_TRUE(header.HasHeadersWithPrefix("kEY"));
+
+  ASSERT_TRUE(header.HasHeader("key2"));
+  header.erase(header.GetHeaderPosition("key2"));
+  ASSERT_TRUE(header.HasHeader("key2"));
+  EXPECT_FALSE(header.HasHeadersWithPrefix("key1"));
+  EXPECT_TRUE(header.HasHeadersWithPrefix("key2"));
+  EXPECT_TRUE(header.HasHeadersWithPrefix("kEY"));
+  header.erase(header.GetHeaderPosition("key2"));
+  EXPECT_FALSE(header.HasHeader("key2"));
+  EXPECT_FALSE(header.HasHeadersWithPrefix("key1"));
+  EXPECT_FALSE(header.HasHeadersWithPrefix("key2"));
+  EXPECT_FALSE(header.HasHeadersWithPrefix("kEY"));
+}
+
+TEST(BalsaHeaders, HasNonEmptyHeaderWorksAsExpectedWithNoHeaderLines) {
+  BalsaHeaders header;
+  EXPECT_FALSE(header.HasNonEmptyHeader("foo"));
+  EXPECT_FALSE(header.HasNonEmptyHeader(""));
+}
+
+TEST(BalsaHeaders, HasNonEmptyHeaderWorksAsExpectedWithAppendHeader) {
+  BalsaHeaders header;
+
+  EXPECT_FALSE(header.HasNonEmptyHeader("key1"));
+  header.AppendHeader("key1", "");
+  EXPECT_FALSE(header.HasNonEmptyHeader("key1"));
+
+  header.AppendHeader("key1", "value_2");
+  EXPECT_TRUE(header.HasNonEmptyHeader("key1"));
+  EXPECT_FALSE(header.HasNonEmptyHeader("key2"));
+}
+
+TEST(BalsaHeaders, HasNonEmptyHeaderWorksAsExpectedWithHeadersErased) {
+  BalsaHeaders header;
+  header.AppendHeader("key1", "value_1");
+  header.AppendHeader("key2", "value_2a");
+  header.AppendHeader("key2", "");
+
+  EXPECT_TRUE(header.HasNonEmptyHeader("key1"));
+  header.erase(header.GetHeaderPosition("key1"));
+  EXPECT_FALSE(header.HasNonEmptyHeader("key1"));
+
+  EXPECT_TRUE(header.HasNonEmptyHeader("key2"));
+  header.erase(header.GetHeaderPosition("key2"));
+  EXPECT_FALSE(header.HasNonEmptyHeader("key2"));
+  header.erase(header.GetHeaderPosition("key2"));
+  EXPECT_FALSE(header.HasNonEmptyHeader("key2"));
+}
+
+TEST(BalsaHeaders, HasNonEmptyHeaderWorksAsExpectedWithBalsaFrameProcessInput) {
+  BalsaHeaders headers = CreateHTTPHeaders(true,
+                                           "GET / HTTP/1.0\r\n"
+                                           "key1: value_1\r\n"
+                                           "key2:\r\n"
+                                           "key3:\r\n"
+                                           "key3: value_3\r\n"
+                                           "key4:\r\n"
+                                           "key4:\r\n"
+                                           "key5: value_5\r\n"
+                                           "key5:\r\n"
+                                           "\r\n");
+
+  EXPECT_FALSE(headers.HasNonEmptyHeader("foo"));
+  EXPECT_TRUE(headers.HasNonEmptyHeader("key1"));
+  EXPECT_FALSE(headers.HasNonEmptyHeader("key2"));
+  EXPECT_TRUE(headers.HasNonEmptyHeader("key3"));
+  EXPECT_FALSE(headers.HasNonEmptyHeader("key4"));
+  EXPECT_TRUE(headers.HasNonEmptyHeader("key5"));
+
+  headers.erase(headers.GetHeaderPosition("key5"));
+  EXPECT_FALSE(headers.HasNonEmptyHeader("key5"));
+}
+
+TEST(BalsaHeaders, GetAllOfHeader) {
+  BalsaHeaders header;
+  header.AppendHeader("key", "value_1");
+  header.AppendHeader("Key", "value_2,value_3");
+  header.AppendHeader("key", "");
+  header.AppendHeader("KEY", "value_4");
+
+  std::vector<absl::string_view> result;
+  header.GetAllOfHeader("key", &result);
+  ASSERT_EQ(4u, result.size());
+  EXPECT_EQ("value_1", result[0]);
+  EXPECT_EQ("value_2,value_3", result[1]);
+  EXPECT_EQ("", result[2]);
+  EXPECT_EQ("value_4", result[3]);
+
+  EXPECT_EQ(header.GetAllOfHeader("key"), result);
+}
+
+TEST(BalsaHeaders, GetAllOfHeaderDoesWhatItSays) {
+  BalsaHeaders header;
+  // Multiple values for a given header.
+  // Some values appear multiple times
+  header.AppendHeader("key", "value_1");
+  header.AppendHeader("key", "value_2");
+  header.AppendHeader("key", "");
+  header.AppendHeader("key", "value_1");
+
+  ASSERT_NE(header.lines().begin(), header.lines().end());
+  std::vector<absl::string_view> out;
+
+  header.GetAllOfHeader("key", &out);
+  ASSERT_EQ(4u, out.size());
+  EXPECT_EQ("value_1", out[0]);
+  EXPECT_EQ("value_2", out[1]);
+  EXPECT_EQ("", out[2]);
+  EXPECT_EQ("value_1", out[3]);
+
+  EXPECT_EQ(header.GetAllOfHeader("key"), out);
+}
+
+TEST(BalsaHeaders, GetAllOfHeaderWithPrefix) {
+  BalsaHeaders header;
+  header.AppendHeader("foo-Foo", "value_1");
+  header.AppendHeader("Foo-bar", "value_2,value_3");
+  header.AppendHeader("foo-Foo", "");
+  header.AppendHeader("bar", "value_not");
+  header.AppendHeader("fOO-fOO", "value_4");
+
+  std::vector<std::pair<absl::string_view, absl::string_view>> result;
+  header.GetAllOfHeaderWithPrefix("abc", &result);
+  ASSERT_EQ(0u, result.size());
+
+  header.GetAllOfHeaderWithPrefix("foo", &result);
+  ASSERT_EQ(4u, result.size());
+  EXPECT_EQ("foo-Foo", result[0].first);
+  EXPECT_EQ("value_1", result[0].second);
+  EXPECT_EQ("Foo-bar", result[1].first);
+  EXPECT_EQ("value_2,value_3", result[1].second);
+  EXPECT_EQ("", result[2].second);
+  EXPECT_EQ("value_4", result[3].second);
+
+  std::vector<std::pair<absl::string_view, absl::string_view>> result2;
+  header.GetAllOfHeaderWithPrefix("FoO", &result2);
+  ASSERT_EQ(4u, result2.size());
+}
+
+TEST(BalsaHeaders, GetAllHeadersWithLimit) {
+  BalsaHeaders header;
+  header.AppendHeader("foo-Foo", "value_1");
+  header.AppendHeader("Foo-bar", "value_2,value_3");
+  header.AppendHeader("foo-Foo", "");
+  header.AppendHeader("bar", "value_4");
+  header.AppendHeader("fOO-fOO", "value_5");
+
+  std::vector<std::pair<absl::string_view, absl::string_view>> result;
+  header.GetAllHeadersWithLimit(&result, 4);
+  ASSERT_EQ(4u, result.size());
+  EXPECT_EQ("foo-Foo", result[0].first);
+  EXPECT_EQ("value_1", result[0].second);
+  EXPECT_EQ("Foo-bar", result[1].first);
+  EXPECT_EQ("value_2,value_3", result[1].second);
+  EXPECT_EQ("", result[2].second);
+  EXPECT_EQ("value_4", result[3].second);
+
+  std::vector<std::pair<absl::string_view, absl::string_view>> result2;
+  header.GetAllHeadersWithLimit(&result2, -1);
+  ASSERT_EQ(5u, result2.size());
+}
+
+TEST(BalsaHeaders, RangeFor) {
+  BalsaHeaders header;
+  // Multiple values for a given header.
+  // Some values appear multiple times
+  header.AppendHeader("key1", "value_1a");
+  header.AppendHeader("key1", "value_1b");
+  header.AppendHeader("key2", "");
+  header.AppendHeader("key3", "value_3");
+
+  std::vector<std::pair<absl::string_view, absl::string_view>> out;
+  for (const auto& line : header.lines()) {
+    out.push_back(line);
+  }
+  const std::vector<std::pair<absl::string_view, absl::string_view>> expected =
+      {{"key1", "value_1a"},
+       {"key1", "value_1b"},
+       {"key2", ""},
+       {"key3", "value_3"}};
+  EXPECT_EQ(expected, out);
+}
+
+TEST(BalsaHeaders, GetAllOfHeaderWithNonExistentKey) {
+  BalsaHeaders header;
+  header.AppendHeader("key", "value_1");
+  header.AppendHeader("key", "value_2");
+  std::vector<absl::string_view> out;
+
+  header.GetAllOfHeader("key_non_existent", &out);
+  ASSERT_EQ(0u, out.size());
+
+  EXPECT_EQ(header.GetAllOfHeader("key_non_existent"), out);
+}
+
+TEST(BalsaHeaders, GetAllOfHeaderEmptyValVariation1) {
+  BalsaHeaders header;
+  header.AppendHeader("key", "");
+  header.AppendHeader("key", "");
+  header.AppendHeader("key", "v1");
+  std::vector<absl::string_view> out;
+  header.GetAllOfHeader("key", &out);
+  ASSERT_EQ(3u, out.size());
+  EXPECT_EQ("", out[0]);
+  EXPECT_EQ("", out[1]);
+  EXPECT_EQ("v1", out[2]);
+
+  EXPECT_EQ(header.GetAllOfHeader("key"), out);
+}
+
+TEST(BalsaHeaders, GetAllOfHeaderEmptyValVariation2) {
+  BalsaHeaders header;
+  header.AppendHeader("key", "");
+  header.AppendHeader("key", "v1");
+  header.AppendHeader("key", "");
+  std::vector<absl::string_view> out;
+  header.GetAllOfHeader("key", &out);
+  ASSERT_EQ(3u, out.size());
+  EXPECT_EQ("", out[0]);
+  EXPECT_EQ("v1", out[1]);
+  EXPECT_EQ("", out[2]);
+
+  EXPECT_EQ(header.GetAllOfHeader("key"), out);
+}
+
+TEST(BalsaHeaders, GetAllOfHeaderEmptyValVariation3) {
+  BalsaHeaders header;
+  header.AppendHeader("key", "");
+  header.AppendHeader("key", "v1");
+  std::vector<absl::string_view> out;
+  header.GetAllOfHeader("key", &out);
+  ASSERT_EQ(2u, out.size());
+  EXPECT_EQ("", out[0]);
+  EXPECT_EQ("v1", out[1]);
+
+  EXPECT_EQ(header.GetAllOfHeader("key"), out);
+}
+
+TEST(BalsaHeaders, GetAllOfHeaderEmptyValVariation4) {
+  BalsaHeaders header;
+  header.AppendHeader("key", "v1");
+  header.AppendHeader("key", "");
+  std::vector<absl::string_view> out;
+  header.GetAllOfHeader("key", &out);
+  ASSERT_EQ(2u, out.size());
+  EXPECT_EQ("v1", out[0]);
+  EXPECT_EQ("", out[1]);
+
+  EXPECT_EQ(header.GetAllOfHeader("key"), out);
+}
+
+TEST(BalsaHeaders, GetAllOfHeaderWithAppendHeaders) {
+  BalsaHeaders header;
+  header.AppendHeader("key", "value_1");
+  header.AppendHeader("key", "value_2");
+  std::vector<absl::string_view> out;
+
+  header.GetAllOfHeader("key_new", &out);
+  ASSERT_EQ(0u, out.size());
+  EXPECT_EQ(header.GetAllOfHeader("key_new"), out);
+
+  // Add key_new to the header
+  header.AppendHeader("key_new", "value_3");
+  header.GetAllOfHeader("key_new", &out);
+  ASSERT_EQ(1u, out.size());
+  EXPECT_EQ("value_3", out[0]);
+  EXPECT_EQ(header.GetAllOfHeader("key_new"), out);
+
+  // Get the keys that are not modified
+  header.GetAllOfHeader("key", &out);
+  ASSERT_EQ(3u, out.size());
+  EXPECT_EQ("value_1", out[1]);
+  EXPECT_EQ("value_2", out[2]);
+  EXPECT_THAT(header.GetAllOfHeader("key"), ElementsAre("value_1", "value_2"));
+}
+
+TEST(BalsaHeaders, GetAllOfHeaderWithRemoveHeaders) {
+  BalsaHeaders header;
+  header.AppendHeader("key", "value_1");
+  header.AppendHeader("key", "value_2");
+  header.AppendHeader("a", "va");
+
+  header.RemoveAllOfHeader("key");
+  std::vector<absl::string_view> out;
+  header.GetAllOfHeader("key", &out);
+  ASSERT_EQ(0u, out.size());
+  EXPECT_EQ(header.GetAllOfHeader("key"), out);
+
+  header.GetAllOfHeader("a", &out);
+  ASSERT_EQ(1u, out.size());
+  EXPECT_EQ(header.GetAllOfHeader("a"), out);
+
+  out.clear();
+  header.RemoveAllOfHeader("a");
+  header.GetAllOfHeader("a", &out);
+  ASSERT_EQ(0u, out.size());
+  EXPECT_EQ(header.GetAllOfHeader("a"), out);
+}
+
+TEST(BalsaHeaders, GetAllOfHeaderWithRemoveNonExistentHeaders) {
+  BalsaHeaders headers;
+  headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0");
+  headers.AppendHeader("Accept-Encoding", "deflate,compress");
+  EXPECT_EQ(0u, headers.RemoveValue("Accept-Encoding", "gzip(gfe)"));
+  std::string accept_encoding_vals =
+      headers.GetAllOfHeaderAsString("Accept-Encoding");
+  EXPECT_EQ("deflate,compress", accept_encoding_vals);
+}
+
+TEST(BalsaHeaders, GetAllOfHeaderWithEraseHeaders) {
+  BalsaHeaders header;
+  header.AppendHeader("key", "value_1");
+  header.AppendHeader("key", "value_2");
+  header.AppendHeader("a", "va");
+
+  std::vector<absl::string_view> out;
+
+  header.erase(header.GetHeaderPosition("key"));
+  header.GetAllOfHeader("key", &out);
+  ASSERT_EQ(1u, out.size());
+  EXPECT_EQ("value_2", out[0]);
+  EXPECT_EQ(header.GetAllOfHeader("key"), out);
+
+  out.clear();
+  header.erase(header.GetHeaderPosition("key"));
+  header.GetAllOfHeader("key", &out);
+  ASSERT_EQ(0u, out.size());
+  EXPECT_EQ(header.GetAllOfHeader("key"), out);
+
+  out.clear();
+  header.GetAllOfHeader("a", &out);
+  ASSERT_EQ(1u, out.size());
+  EXPECT_EQ(header.GetAllOfHeader("a"), out);
+
+  out.clear();
+  header.erase(header.GetHeaderPosition("a"));
+  header.GetAllOfHeader("a", &out);
+  ASSERT_EQ(0u, out.size());
+  EXPECT_EQ(header.GetAllOfHeader("key"), out);
+}
+
+TEST(BalsaHeaders, GetAllOfHeaderWithNoHeaderLines) {
+  BalsaHeaders header;
+  std::vector<absl::string_view> out;
+  header.GetAllOfHeader("key", &out);
+  EXPECT_EQ(0u, out.size());
+  EXPECT_EQ(header.GetAllOfHeader("key"), out);
+}
+
+TEST(BalsaHeaders, GetAllOfHeaderDoesWhatItSaysForVariousKeys) {
+  BalsaHeaders header;
+  header.AppendHeader("key1", "value_11");
+  header.AppendHeader("key2", "value_21");
+  header.AppendHeader("key1", "value_12");
+  header.AppendHeader("key2", "value_22");
+
+  std::vector<absl::string_view> out;
+
+  header.GetAllOfHeader("key1", &out);
+  EXPECT_EQ("value_11", out[0]);
+  EXPECT_EQ("value_12", out[1]);
+  EXPECT_EQ(header.GetAllOfHeader("key1"), out);
+
+  header.GetAllOfHeader("key2", &out);
+  EXPECT_EQ("value_21", out[2]);
+  EXPECT_EQ("value_22", out[3]);
+  EXPECT_THAT(header.GetAllOfHeader("key2"),
+              ElementsAre("value_21", "value_22"));
+}
+
+TEST(BalsaHeaders, GetAllOfHeaderWithBalsaFrameProcessInput) {
+  BalsaHeaders header = CreateHTTPHeaders(true,
+                                          "GET / HTTP/1.0\r\n"
+                                          "key1: value_1\r\n"
+                                          "key1: value_foo\r\n"
+                                          "key2: value_2\r\n"
+                                          "a: value_a\r\n"
+                                          "key2: \r\n"
+                                          "b: value_b\r\n"
+                                          "\r\n");
+
+  std::vector<absl::string_view> out;
+  int index = 0;
+  header.GetAllOfHeader("key1", &out);
+  EXPECT_EQ("value_1", out[index++]);
+  EXPECT_EQ("value_foo", out[index++]);
+  EXPECT_EQ(header.GetAllOfHeader("key1"), out);
+
+  header.GetAllOfHeader("key2", &out);
+  EXPECT_EQ("value_2", out[index++]);
+  EXPECT_EQ("", out[index++]);
+  EXPECT_THAT(header.GetAllOfHeader("key2"), ElementsAre("value_2", ""));
+
+  header.GetAllOfHeader("a", &out);
+  EXPECT_EQ("value_a", out[index++]);
+  EXPECT_THAT(header.GetAllOfHeader("a"), ElementsAre("value_a"));
+
+  header.GetAllOfHeader("b", &out);
+  EXPECT_EQ("value_b", out[index++]);
+  EXPECT_THAT(header.GetAllOfHeader("b"), ElementsAre("value_b"));
+}
+
+TEST(BalsaHeaders, GetAllOfHeaderIncludeRemovedDoesWhatItSays) {
+  BalsaHeaders header;
+  header.AppendHeader("key1", "value_11");
+  header.AppendHeader("key2", "value_21");
+  header.AppendHeader("key1", "value_12");
+  header.AppendHeader("key2", "value_22");
+  header.AppendHeader("key1", "");
+
+  std::vector<absl::string_view> out;
+  header.GetAllOfHeaderIncludeRemoved("key1", &out);
+  ASSERT_EQ(3u, out.size());
+  EXPECT_EQ("value_11", out[0]);
+  EXPECT_EQ("value_12", out[1]);
+  EXPECT_EQ("", out[2]);
+  header.GetAllOfHeaderIncludeRemoved("key2", &out);
+  ASSERT_EQ(5u, out.size());
+  EXPECT_EQ("value_21", out[3]);
+  EXPECT_EQ("value_22", out[4]);
+
+  header.erase(header.GetHeaderPosition("key1"));
+  out.clear();
+  header.GetAllOfHeaderIncludeRemoved("key1", &out);
+  ASSERT_EQ(3u, out.size());
+  EXPECT_EQ("value_12", out[0]);
+  EXPECT_EQ("", out[1]);
+  EXPECT_EQ("value_11", out[2]);
+  header.GetAllOfHeaderIncludeRemoved("key2", &out);
+  ASSERT_EQ(5u, out.size());
+  EXPECT_EQ("value_21", out[3]);
+  EXPECT_EQ("value_22", out[4]);
+
+  header.RemoveAllOfHeader("key1");
+  out.clear();
+  header.GetAllOfHeaderIncludeRemoved("key1", &out);
+  ASSERT_EQ(3u, out.size());
+  EXPECT_EQ("value_11", out[0]);
+  EXPECT_EQ("value_12", out[1]);
+  EXPECT_EQ("", out[2]);
+  header.GetAllOfHeaderIncludeRemoved("key2", &out);
+  ASSERT_EQ(5u, out.size());
+  EXPECT_EQ("value_21", out[3]);
+  EXPECT_EQ("value_22", out[4]);
+
+  header.Clear();
+  out.clear();
+  header.GetAllOfHeaderIncludeRemoved("key1", &out);
+  ASSERT_EQ(0u, out.size());
+  header.GetAllOfHeaderIncludeRemoved("key2", &out);
+  ASSERT_EQ(0u, out.size());
+}
+
+TEST(BalsaHeaders, GetAllOfHeaderIncludeRemovedWithNonExistentKey) {
+  BalsaHeaders header;
+  header.AppendHeader("key", "value_1");
+  header.AppendHeader("key", "value_2");
+  std::vector<absl::string_view> out;
+  header.GetAllOfHeaderIncludeRemoved("key_non_existent", &out);
+  ASSERT_EQ(0u, out.size());
+}
+
+TEST(BalsaHeaders, GetIteratorForKeyDoesWhatItSays) {
+  BalsaHeaders header;
+  // Multiple values for a given header.
+  // Some values appear multiple times
+  header.AppendHeader("key", "value_1");
+  header.AppendHeader("Key", "value_2");
+  header.AppendHeader("key", "");
+  header.AppendHeader("KEY", "value_1");
+
+  BalsaHeaders::const_header_lines_key_iterator key_it =
+      header.GetIteratorForKey("key");
+  EXPECT_NE(header.lines().end(), key_it);
+  EXPECT_NE(header.header_lines_key_end(), key_it);
+  EXPECT_EQ("key", key_it->first);
+  EXPECT_EQ("value_1", key_it->second);
+  ++key_it;
+  EXPECT_NE(header.lines().end(), key_it);
+  EXPECT_NE(header.header_lines_key_end(), key_it);
+  EXPECT_EQ("Key", key_it->first);
+  EXPECT_EQ("value_2", key_it->second);
+  ++key_it;
+  EXPECT_NE(header.lines().end(), key_it);
+  EXPECT_NE(header.header_lines_key_end(), key_it);
+  EXPECT_EQ("key", key_it->first);
+  EXPECT_EQ("", key_it->second);
+  ++key_it;
+  EXPECT_NE(header.lines().end(), key_it);
+  EXPECT_NE(header.header_lines_key_end(), key_it);
+  EXPECT_EQ("KEY", key_it->first);
+  EXPECT_EQ("value_1", key_it->second);
+  ++key_it;
+  EXPECT_EQ(header.lines().end(), key_it);
+  EXPECT_EQ(header.header_lines_key_end(), key_it);
+}
+
+TEST(BalsaHeaders, GetIteratorForKeyWithNonExistentKey) {
+  BalsaHeaders header;
+  header.AppendHeader("key", "value_1");
+  header.AppendHeader("key", "value_2");
+
+  BalsaHeaders::const_header_lines_key_iterator key_it =
+      header.GetIteratorForKey("key_non_existent");
+  EXPECT_EQ(header.lines().end(), key_it);
+  EXPECT_EQ(header.header_lines_key_end(), key_it);
+  const auto lines = header.lines("key_non_existent");
+  EXPECT_EQ(lines.begin(), header.lines().end());
+  EXPECT_EQ(lines.end(), header.header_lines_key_end());
+}
+
+TEST(BalsaHeaders, GetIteratorForKeyWithAppendHeaders) {
+  BalsaHeaders header;
+  header.AppendHeader("key", "value_1");
+  header.AppendHeader("key", "value_2");
+
+  BalsaHeaders::const_header_lines_key_iterator key_it =
+      header.GetIteratorForKey("key_new");
+  EXPECT_EQ(header.lines().end(), key_it);
+  EXPECT_EQ(header.header_lines_key_end(), key_it);
+
+  // Add key_new to the header
+  header.AppendHeader("key_new", "value_3");
+  key_it = header.GetIteratorForKey("key_new");
+  const auto lines1 = header.lines("key_new");
+  EXPECT_EQ(lines1.begin(), key_it);
+  EXPECT_EQ(lines1.end(), header.header_lines_key_end());
+
+  EXPECT_NE(header.lines().end(), key_it);
+  EXPECT_NE(header.header_lines_key_end(), key_it);
+  EXPECT_EQ("key_new", key_it->first);
+  EXPECT_EQ("value_3", key_it->second);
+  ++key_it;
+  EXPECT_EQ(header.lines().end(), key_it);
+  EXPECT_EQ(header.header_lines_key_end(), key_it);
+
+  // Get the keys that are not modified
+  key_it = header.GetIteratorForKey("key");
+  const auto lines2 = header.lines("key");
+  EXPECT_EQ(lines2.begin(), key_it);
+  EXPECT_EQ(lines2.end(), header.header_lines_key_end());
+  EXPECT_NE(header.lines().end(), key_it);
+  EXPECT_NE(header.header_lines_key_end(), key_it);
+  EXPECT_EQ("key", key_it->first);
+  EXPECT_EQ("value_1", key_it->second);
+  ++key_it;
+  EXPECT_NE(header.lines().end(), key_it);
+  EXPECT_NE(header.header_lines_key_end(), key_it);
+  EXPECT_EQ("key", key_it->first);
+  EXPECT_EQ("value_2", key_it->second);
+  ++key_it;
+  EXPECT_EQ(header.lines().end(), key_it);
+  EXPECT_EQ(header.header_lines_key_end(), key_it);
+}
+
+TEST(BalsaHeaders, GetIteratorForKeyWithRemoveHeaders) {
+  BalsaHeaders header;
+  header.AppendHeader("key", "value_1");
+  header.AppendHeader("key", "value_2");
+  header.AppendHeader("a", "va");
+
+  header.RemoveAllOfHeader("a");
+  BalsaHeaders::const_header_lines_key_iterator key_it =
+      header.GetIteratorForKey("key");
+  EXPECT_NE(header.lines().end(), key_it);
+  const auto lines1 = header.lines("key");
+  EXPECT_EQ(lines1.begin(), key_it);
+  EXPECT_EQ(lines1.end(), header.header_lines_key_end());
+  EXPECT_EQ("value_1", key_it->second);
+  ++key_it;
+  EXPECT_NE(header.lines().end(), key_it);
+  EXPECT_NE(header.header_lines_key_end(), key_it);
+  EXPECT_EQ("key", key_it->first);
+  EXPECT_EQ("value_2", key_it->second);
+  ++key_it;
+  EXPECT_EQ(header.lines().end(), key_it);
+  EXPECT_EQ(header.header_lines_key_end(), key_it);
+
+  // Check that a typical loop works properly.
+  for (BalsaHeaders::const_header_lines_key_iterator it =
+           header.GetIteratorForKey("key");
+       it != header.lines().end(); ++it) {
+    EXPECT_EQ("key", it->first);
+  }
+}
+
+TEST(BalsaHeaders, GetIteratorForKeyWithEraseHeaders) {
+  BalsaHeaders header;
+  header.AppendHeader("key", "value_1");
+  header.AppendHeader("key", "value_2");
+  header.AppendHeader("a", "va");
+  header.erase(header.GetHeaderPosition("key"));
+
+  BalsaHeaders::const_header_lines_key_iterator key_it =
+      header.GetIteratorForKey("key");
+  EXPECT_NE(header.lines().end(), key_it);
+  const auto lines1 = header.lines("key");
+  EXPECT_EQ(lines1.begin(), key_it);
+  EXPECT_EQ(lines1.end(), header.header_lines_key_end());
+  EXPECT_NE(header.header_lines_key_end(), key_it);
+  EXPECT_EQ("key", key_it->first);
+  EXPECT_EQ("value_2", key_it->second);
+  ++key_it;
+  EXPECT_EQ(header.lines().end(), key_it);
+  EXPECT_EQ(header.header_lines_key_end(), key_it);
+
+  header.erase(header.GetHeaderPosition("key"));
+  key_it = header.GetIteratorForKey("key");
+  const auto lines2 = header.lines("key");
+  EXPECT_EQ(lines2.begin(), key_it);
+  EXPECT_EQ(lines2.end(), header.header_lines_key_end());
+  EXPECT_EQ(header.lines().end(), key_it);
+  EXPECT_EQ(header.header_lines_key_end(), key_it);
+
+  key_it = header.GetIteratorForKey("a");
+  const auto lines3 = header.lines("a");
+  EXPECT_EQ(lines3.begin(), key_it);
+  EXPECT_EQ(lines3.end(), header.header_lines_key_end());
+  EXPECT_NE(header.lines().end(), key_it);
+  EXPECT_NE(header.header_lines_key_end(), key_it);
+  EXPECT_EQ("a", key_it->first);
+  EXPECT_EQ("va", key_it->second);
+  ++key_it;
+  EXPECT_EQ(header.lines().end(), key_it);
+  EXPECT_EQ(header.header_lines_key_end(), key_it);
+
+  header.erase(header.GetHeaderPosition("a"));
+  key_it = header.GetIteratorForKey("a");
+  const auto lines4 = header.lines("a");
+  EXPECT_EQ(lines4.begin(), key_it);
+  EXPECT_EQ(lines4.end(), header.header_lines_key_end());
+  EXPECT_EQ(header.lines().end(), key_it);
+  EXPECT_EQ(header.header_lines_key_end(), key_it);
+}
+
+TEST(BalsaHeaders, GetIteratorForKeyWithNoHeaderLines) {
+  BalsaHeaders header;
+  BalsaHeaders::const_header_lines_key_iterator key_it =
+      header.GetIteratorForKey("key");
+  const auto lines = header.lines("key");
+  EXPECT_EQ(lines.begin(), key_it);
+  EXPECT_EQ(lines.end(), header.header_lines_key_end());
+  EXPECT_EQ(header.lines().end(), key_it);
+  EXPECT_EQ(header.header_lines_key_end(), key_it);
+}
+
+TEST(BalsaHeaders, GetIteratorForKeyWithBalsaFrameProcessInput) {
+  BalsaHeaders header = CreateHTTPHeaders(true,
+                                          "GET / HTTP/1.0\r\n"
+                                          "key1: value_1\r\n"
+                                          "Key1: value_foo\r\n"
+                                          "key2: value_2\r\n"
+                                          "a: value_a\r\n"
+                                          "key2: \r\n"
+                                          "b: value_b\r\n"
+                                          "\r\n");
+
+  BalsaHeaders::const_header_lines_key_iterator key_it =
+      header.GetIteratorForKey("Key1");
+  const auto lines1 = header.lines("Key1");
+  EXPECT_EQ(lines1.begin(), key_it);
+  EXPECT_EQ(lines1.end(), header.header_lines_key_end());
+  EXPECT_NE(header.lines().end(), key_it);
+  EXPECT_NE(header.header_lines_key_end(), key_it);
+  EXPECT_EQ("key1", key_it->first);
+  EXPECT_EQ("value_1", key_it->second);
+  ++key_it;
+  EXPECT_NE(header.lines().end(), key_it);
+  EXPECT_NE(header.header_lines_key_end(), key_it);
+  EXPECT_EQ("Key1", key_it->first);
+  EXPECT_EQ("value_foo", key_it->second);
+  ++key_it;
+  EXPECT_EQ(header.lines().end(), key_it);
+  EXPECT_EQ(header.header_lines_key_end(), key_it);
+
+  key_it = header.GetIteratorForKey("key2");
+  EXPECT_NE(header.lines().end(), key_it);
+  const auto lines2 = header.lines("key2");
+  EXPECT_EQ(lines2.begin(), key_it);
+  EXPECT_EQ(lines2.end(), header.header_lines_key_end());
+  EXPECT_NE(header.header_lines_key_end(), key_it);
+  EXPECT_EQ("key2", key_it->first);
+  EXPECT_EQ("value_2", key_it->second);
+  ++key_it;
+  EXPECT_NE(header.lines().end(), key_it);
+  EXPECT_NE(header.header_lines_key_end(), key_it);
+  EXPECT_EQ("key2", key_it->first);
+  EXPECT_EQ("", key_it->second);
+  ++key_it;
+  EXPECT_EQ(header.lines().end(), key_it);
+  EXPECT_EQ(header.header_lines_key_end(), key_it);
+
+  key_it = header.GetIteratorForKey("a");
+  EXPECT_NE(header.lines().end(), key_it);
+  const auto lines3 = header.lines("a");
+  EXPECT_EQ(lines3.begin(), key_it);
+  EXPECT_EQ(lines3.end(), header.header_lines_key_end());
+  EXPECT_NE(header.header_lines_key_end(), key_it);
+  EXPECT_EQ("a", key_it->first);
+  EXPECT_EQ("value_a", key_it->second);
+  ++key_it;
+  EXPECT_EQ(header.lines().end(), key_it);
+  EXPECT_EQ(header.header_lines_key_end(), key_it);
+
+  key_it = header.GetIteratorForKey("b");
+  EXPECT_NE(header.lines().end(), key_it);
+  const auto lines4 = header.lines("b");
+  EXPECT_EQ(lines4.begin(), key_it);
+  EXPECT_EQ(lines4.end(), header.header_lines_key_end());
+  EXPECT_NE(header.header_lines_key_end(), key_it);
+  EXPECT_EQ("b", key_it->first);
+  EXPECT_EQ("value_b", key_it->second);
+  ++key_it;
+  EXPECT_EQ(header.lines().end(), key_it);
+  EXPECT_EQ(header.header_lines_key_end(), key_it);
+}
+
+TEST(BalsaHeaders, GetAllOfHeaderAsStringDoesWhatItSays) {
+  BalsaHeaders header;
+  // Multiple values for a given header.
+  // Some values appear multiple times
+  header.AppendHeader("key", "value_1");
+  header.AppendHeader("Key", "value_2");
+  header.AppendHeader("key", "");
+  header.AppendHeader("KEY", "value_1");
+
+  std::string result = header.GetAllOfHeaderAsString("key");
+  EXPECT_EQ("value_1,value_2,,value_1", result);
+}
+
+TEST(BalsaHeaders, RemoveAllOfHeaderDoesWhatItSays) {
+  BalsaHeaders header;
+  header.AppendHeader("key", "value_1");
+  header.AppendHeader("key", "value_2");
+  ASSERT_NE(header.lines().begin(), header.lines().end());
+  header.RemoveAllOfHeader("key");
+  ASSERT_EQ(header.lines().begin(), header.lines().end());
+}
+
+TEST(BalsaHeaders,
+     RemoveAllOfHeaderDoesWhatItSaysEvenWhenThingsHaveBeenErased) {
+  BalsaHeaders header;
+  header.AppendHeader("key1", "value_1");
+  header.AppendHeader("key1", "value_2");
+  header.AppendHeader("key2", "value_3");
+  header.AppendHeader("key1", "value_4");
+  header.AppendHeader("key2", "value_5");
+  header.AppendHeader("key1", "value_6");
+  ASSERT_NE(header.lines().begin(), header.lines().end());
+
+  BalsaHeaders::const_header_lines_iterator chli = header.lines().begin();
+  ++chli;
+  ++chli;
+  ++chli;
+  header.erase(chli);
+
+  chli = header.lines().begin();
+  ++chli;
+  header.erase(chli);
+
+  header.RemoveAllOfHeader("key1");
+  for (const auto& line : header.lines()) {
+    EXPECT_NE(std::string("key1"), line.first);
+  }
+}
+
+TEST(BalsaHeaders, RemoveAllOfHeaderDoesNothingWhenNoKeyOfThatNameExists) {
+  BalsaHeaders header;
+  header.AppendHeader("key", "value_1");
+  header.AppendHeader("key", "value_2");
+  ASSERT_NE(header.lines().begin(), header.lines().end());
+  header.RemoveAllOfHeader("foo");
+  int num_found = 0;
+  for (const auto& line : header.lines()) {
+    ++num_found;
+    EXPECT_EQ(absl::string_view("key"), line.first);
+  }
+  EXPECT_EQ(2, num_found);
+  EXPECT_NE(header.lines().begin(), header.lines().end());
+}
+
+TEST(BalsaHeaders, WriteHeaderEndingToBuffer) {
+  BalsaHeaders header;
+  SimpleBuffer simple_buffer;
+  header.WriteHeaderEndingToBuffer(&simple_buffer);
+  EXPECT_THAT(simple_buffer.GetReadableRegion(), StrEq("\r\n"));
+}
+
+TEST(BalsaHeaders, WriteToBufferDoesntCrashWithUninitializedHeader) {
+  BalsaHeaders header;
+  SimpleBuffer simple_buffer;
+  header.WriteHeaderAndEndingToBuffer(&simple_buffer);
+}
+
+TEST(BalsaHeaders, WriteToBufferWorksWithBalsaHeadersParsedByFramer) {
+  std::string input =
+      "GET / HTTP/1.0\r\n"
+      "key_with_value: value\r\n"
+      "key_with_continuation_value: \r\n"
+      " with continuation\r\n"
+      "key_with_two_continuation_value: \r\n"
+      " continuation 1\r\n"
+      " continuation 2\r\n"
+      "a: foo    \r\n"
+      "b-s:\n"
+      " bar\t\n"
+      "foo: \r\n"
+      "bazzzzzzzleriffic!: snaps\n"
+      "\n";
+  std::string expected =
+      "GET / HTTP/1.0\r\n"
+      "key_with_value: value\r\n"
+      "key_with_continuation_value: with continuation\r\n"
+      "key_with_two_continuation_value: continuation 1\r\n"
+      " continuation 2\r\n"
+      "a: foo\r\n"
+      "b-s: bar\r\n"
+      "foo: \r\n"
+      "bazzzzzzzleriffic!: snaps\r\n"
+      "\r\n";
+
+  BalsaHeaders headers = CreateHTTPHeaders(true, input);
+  SimpleBuffer simple_buffer;
+  size_t expected_write_buffer_size = headers.GetSizeForWriteBuffer();
+  headers.WriteHeaderAndEndingToBuffer(&simple_buffer);
+  EXPECT_THAT(simple_buffer.GetReadableRegion(), StrEq(expected));
+  EXPECT_EQ(expected_write_buffer_size,
+            static_cast<size_t>(simple_buffer.ReadableBytes()));
+}
+
+TEST(BalsaHeaders,
+     WriteToBufferWorksWithBalsaHeadersParsedByFramerTabContinuations) {
+  std::string input =
+      "GET / HTTP/1.0\r\n"
+      "key_with_value: value\r\n"
+      "key_with_continuation_value: \r\n"
+      "\twith continuation\r\n"
+      "key_with_two_continuation_value: \r\n"
+      "\tcontinuation 1\r\n"
+      "\tcontinuation 2\r\n"
+      "a: foo    \r\n"
+      "b-s:\n"
+      "\tbar\t\n"
+      "foo: \r\n"
+      "bazzzzzzzleriffic!: snaps\n"
+      "\n";
+  std::string expected =
+      "GET / HTTP/1.0\r\n"
+      "key_with_value: value\r\n"
+      "key_with_continuation_value: with continuation\r\n"
+      "key_with_two_continuation_value: continuation 1\r\n"
+      "\tcontinuation 2\r\n"
+      "a: foo\r\n"
+      "b-s: bar\r\n"
+      "foo: \r\n"
+      "bazzzzzzzleriffic!: snaps\r\n"
+      "\r\n";
+
+  BalsaHeaders headers = CreateHTTPHeaders(true, input);
+  SimpleBuffer simple_buffer;
+  size_t expected_write_buffer_size = headers.GetSizeForWriteBuffer();
+  headers.WriteHeaderAndEndingToBuffer(&simple_buffer);
+  EXPECT_THAT(simple_buffer.GetReadableRegion(), StrEq(expected));
+  EXPECT_EQ(expected_write_buffer_size,
+            static_cast<size_t>(simple_buffer.ReadableBytes()));
+}
+
+TEST(BalsaHeaders, WriteToBufferWorksWhenFirstlineSetThroughHeaders) {
+  BalsaHeaders headers;
+  headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0");
+  std::string expected =
+      "GET / HTTP/1.0\r\n"
+      "\r\n";
+  SimpleBuffer simple_buffer;
+  size_t expected_write_buffer_size = headers.GetSizeForWriteBuffer();
+  headers.WriteHeaderAndEndingToBuffer(&simple_buffer);
+  EXPECT_THAT(simple_buffer.GetReadableRegion(), StrEq(expected));
+  EXPECT_EQ(expected_write_buffer_size,
+            static_cast<size_t>(simple_buffer.ReadableBytes()));
+}
+
+TEST(BalsaHeaders, WriteToBufferWorksWhenSetThroughHeaders) {
+  BalsaHeaders headers;
+  headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0");
+  headers.AppendHeader("key1", "value1");
+  headers.AppendHeader("key 2", "value\n 2");
+  headers.AppendHeader("key\n 3", "value3");
+  std::string expected =
+      "GET / HTTP/1.0\r\n"
+      "key1: value1\r\n"
+      "key 2: value\n"
+      " 2\r\n"
+      "key\n"
+      " 3: value3\r\n"
+      "\r\n";
+  SimpleBuffer simple_buffer;
+  size_t expected_write_buffer_size = headers.GetSizeForWriteBuffer();
+  headers.WriteHeaderAndEndingToBuffer(&simple_buffer);
+  EXPECT_THAT(simple_buffer.GetReadableRegion(), StrEq(expected));
+  EXPECT_EQ(expected_write_buffer_size,
+            static_cast<size_t>(simple_buffer.ReadableBytes()));
+}
+
+TEST(BalsaHeaders, WriteToBufferWorkWhensOnlyLinesSetThroughHeaders) {
+  BalsaHeaders headers;
+  headers.AppendHeader("key1", "value1");
+  headers.AppendHeader("key 2", "value\n 2");
+  headers.AppendHeader("key\n 3", "value3");
+  std::string expected =
+      "\r\n"
+      "key1: value1\r\n"
+      "key 2: value\n"
+      " 2\r\n"
+      "key\n"
+      " 3: value3\r\n"
+      "\r\n";
+  SimpleBuffer simple_buffer;
+  size_t expected_write_buffer_size = headers.GetSizeForWriteBuffer();
+  headers.WriteHeaderAndEndingToBuffer(&simple_buffer);
+  EXPECT_THAT(simple_buffer.GetReadableRegion(), StrEq(expected));
+  EXPECT_EQ(expected_write_buffer_size,
+            static_cast<size_t>(simple_buffer.ReadableBytes()));
+}
+
+TEST(BalsaHeaders, WriteToBufferWorksWhenSetThroughHeadersWithElementsErased) {
+  BalsaHeaders headers;
+  headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0");
+  headers.AppendHeader("key1", "value1");
+  headers.AppendHeader("key 2", "value\n 2");
+  headers.AppendHeader("key\n 3", "value3");
+  headers.RemoveAllOfHeader("key1");
+  headers.RemoveAllOfHeader("key\n 3");
+  std::string expected =
+      "GET / HTTP/1.0\r\n"
+      "key 2: value\n"
+      " 2\r\n"
+      "\r\n";
+  SimpleBuffer simple_buffer;
+  size_t expected_write_buffer_size = headers.GetSizeForWriteBuffer();
+  headers.WriteHeaderAndEndingToBuffer(&simple_buffer);
+  EXPECT_THAT(simple_buffer.GetReadableRegion(), StrEq(expected));
+  EXPECT_EQ(expected_write_buffer_size,
+            static_cast<size_t>(simple_buffer.ReadableBytes()));
+}
+
+TEST(BalsaHeaders, WriteToBufferWithManuallyAppendedHeaderLine) {
+  BalsaHeaders headers;
+  headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0");
+  headers.AppendHeader("key1", "value1");
+  headers.AppendHeader("key 2", "value\n 2");
+  std::string expected =
+      "GET / HTTP/1.0\r\n"
+      "key1: value1\r\n"
+      "key 2: value\n"
+      " 2\r\n"
+      "key 3: value 3\r\n"
+      "\r\n";
+
+  SimpleBuffer simple_buffer;
+  size_t expected_write_buffer_size = headers.GetSizeForWriteBuffer();
+  headers.WriteToBuffer(&simple_buffer);
+  headers.WriteHeaderLineToBuffer(&simple_buffer, "key 3", "value 3",
+                                  BalsaHeaders::CaseOption::kNoModification);
+  headers.WriteHeaderEndingToBuffer(&simple_buffer);
+  EXPECT_THAT(simple_buffer.GetReadableRegion(), StrEq(expected));
+  EXPECT_EQ(expected_write_buffer_size + 16,
+            static_cast<size_t>(simple_buffer.ReadableBytes()));
+}
+
+TEST(BalsaHeaders, DumpToStringEmptyHeaders) {
+  BalsaHeaders headers;
+  std::string headers_str;
+  headers.DumpToString(&headers_str);
+  EXPECT_EQ("\n <empty header>\n", headers_str);
+}
+
+TEST(BalsaHeaders, DumpToStringParsedHeaders) {
+  std::string input =
+      "GET / HTTP/1.0\r\n"
+      "Header1: value\r\n"
+      "Header2: value\r\n"
+      "\r\n";
+  std::string output =
+      "\n"
+      " GET / HTTP/1.0\n"
+      " Header1: value\n"
+      " Header2: value\n";
+
+  BalsaHeaders headers = CreateHTTPHeaders(true, input);
+  std::string headers_str;
+  headers.DumpToString(&headers_str);
+  EXPECT_EQ(output, headers_str);
+  EXPECT_TRUE(headers.FramerIsDoneWriting());
+}
+
+TEST(BalsaHeaders, DumpToStringPartialHeaders) {
+  BalsaHeaders headers;
+  BalsaFrame balsa_frame;
+  balsa_frame.set_is_request(true);
+  balsa_frame.set_balsa_headers(&headers);
+  std::string input =
+      "GET / HTTP/1.0\r\n"
+      "Header1: value\r\n"
+      "Header2: value\r\n";
+  std::string output = absl::StrFormat("\n <incomplete header len: %d>\n ",
+                                       static_cast<int>(input.size()));
+  output += input;
+  output += '\n';
+
+  ASSERT_EQ(input.size(), balsa_frame.ProcessInput(input.data(), input.size()));
+  ASSERT_FALSE(balsa_frame.MessageFullyRead());
+  std::string headers_str;
+  headers.DumpToString(&headers_str);
+  EXPECT_EQ(output, headers_str);
+  EXPECT_FALSE(headers.FramerIsDoneWriting());
+}
+
+TEST(BalsaHeaders, DumpToStringParsingNonHeadersData) {
+  BalsaHeaders headers;
+  BalsaFrame balsa_frame;
+  balsa_frame.set_is_request(true);
+  balsa_frame.set_balsa_headers(&headers);
+  std::string input =
+      "This is not a header. "
+      "Just some random data to simulate mismatch.";
+  std::string output = absl::StrFormat("\n <incomplete header len: %d>\n ",
+                                       static_cast<int>(input.size()));
+  output += input;
+  output += '\n';
+
+  ASSERT_EQ(input.size(), balsa_frame.ProcessInput(input.data(), input.size()));
+  ASSERT_FALSE(balsa_frame.MessageFullyRead());
+  std::string headers_str;
+  headers.DumpToString(&headers_str);
+  EXPECT_EQ(output, headers_str);
+}
+
+TEST(BalsaHeaders, Clear) {
+  BalsaHeaders headers;
+  headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0");
+  headers.AppendHeader("key1", "value1");
+  headers.AppendHeader("key 2", "value\n 2");
+  headers.AppendHeader("key\n 3", "value3");
+  headers.RemoveAllOfHeader("key1");
+  headers.RemoveAllOfHeader("key\n 3");
+  headers.Clear();
+  EXPECT_TRUE(headers.first_line().empty());
+  EXPECT_EQ(headers.lines().begin(), headers.lines().end());
+  EXPECT_TRUE(headers.IsEmpty());
+}
+
+TEST(BalsaHeaders,
+     TestSetFromStringPiecesWithInitialFirstlineInHeaderStreamAndNewToo) {
+  BalsaHeaders headers = CreateHTTPHeaders(false,
+                                           "HTTP/1.1 200 reason phrase\r\n"
+                                           "content-length: 0\r\n"
+                                           "\r\n");
+  EXPECT_THAT(headers.response_version(), StrEq("HTTP/1.1"));
+  EXPECT_THAT(headers.response_code(), StrEq("200"));
+  EXPECT_THAT(headers.response_reason_phrase(), StrEq("reason phrase"));
+
+  headers.SetResponseFirstline("HTTP/1.0", 404, "a reason");
+  EXPECT_THAT(headers.response_version(), StrEq("HTTP/1.0"));
+  EXPECT_THAT(headers.response_code(), StrEq("404"));
+  EXPECT_THAT(headers.parsed_response_code(), Eq(404));
+  EXPECT_THAT(headers.response_reason_phrase(), StrEq("a reason"));
+  EXPECT_THAT(headers.first_line(), StrEq("HTTP/1.0 404 a reason"));
+}
+
+TEST(BalsaHeaders,
+     TestSetFromStringPiecesWithInitialFirstlineInHeaderStreamButNotNew) {
+  BalsaHeaders headers = CreateHTTPHeaders(false,
+                                           "HTTP/1.1 200 reason phrase\r\n"
+                                           "content-length: 0\r\n"
+                                           "\r\n");
+  EXPECT_THAT(headers.response_version(), StrEq("HTTP/1.1"));
+  EXPECT_THAT(headers.response_code(), StrEq("200"));
+  EXPECT_THAT(headers.response_reason_phrase(), StrEq("reason phrase"));
+
+  headers.SetResponseFirstline("HTTP/1.000", 404000,
+                               "supercalifragilisticexpealidocious");
+  EXPECT_THAT(headers.response_version(), StrEq("HTTP/1.000"));
+  EXPECT_THAT(headers.response_code(), StrEq("404000"));
+  EXPECT_THAT(headers.parsed_response_code(), Eq(404000));
+  EXPECT_THAT(headers.response_reason_phrase(),
+              StrEq("supercalifragilisticexpealidocious"));
+  EXPECT_THAT(headers.first_line(),
+              StrEq("HTTP/1.000 404000 supercalifragilisticexpealidocious"));
+}
+
+TEST(BalsaHeaders,
+     TestSetFromStringPiecesWithFirstFirstlineInHeaderStreamButNotNew2) {
+  SCOPED_TRACE(
+      "This test tests the codepath where the new firstline is"
+      " too large to fit within the space used by the original"
+      " firstline, but large enuogh to space in the free space"
+      " available in both firstline plus the space made available"
+      " with deleted header lines (specifically, the first one");
+  BalsaHeaders headers = CreateHTTPHeaders(
+      false,
+      "HTTP/1.1 200 reason phrase\r\n"
+      "a: 0987123409871234078130948710938471093827401983740198327401982374\r\n"
+      "content-length: 0\r\n"
+      "\r\n");
+  EXPECT_THAT(headers.response_version(), StrEq("HTTP/1.1"));
+  EXPECT_THAT(headers.response_code(), StrEq("200"));
+  EXPECT_THAT(headers.response_reason_phrase(), StrEq("reason phrase"));
+
+  headers.erase(headers.lines().begin());
+  headers.SetResponseFirstline("HTTP/1.000", 404000,
+                               "supercalifragilisticexpealidocious");
+  EXPECT_THAT(headers.response_version(), StrEq("HTTP/1.000"));
+  EXPECT_THAT(headers.response_code(), StrEq("404000"));
+  EXPECT_THAT(headers.parsed_response_code(), Eq(404000));
+  EXPECT_THAT(headers.response_reason_phrase(),
+              StrEq("supercalifragilisticexpealidocious"));
+  EXPECT_THAT(headers.first_line(),
+              StrEq("HTTP/1.000 404000 supercalifragilisticexpealidocious"));
+}
+
+TEST(BalsaHeaders, TestSetFirstlineFromStringPiecesWithNoInitialFirstline) {
+  BalsaHeaders headers;
+  headers.SetResponseFirstline("HTTP/1.1", 200, "don't need a reason");
+  EXPECT_THAT(headers.response_version(), StrEq("HTTP/1.1"));
+  EXPECT_THAT(headers.response_code(), StrEq("200"));
+  EXPECT_THAT(headers.parsed_response_code(), Eq(200));
+  EXPECT_THAT(headers.response_reason_phrase(), StrEq("don't need a reason"));
+  EXPECT_THAT(headers.first_line(), StrEq("HTTP/1.1 200 don't need a reason"));
+}
+
+TEST(BalsaHeaders, TestSettingFirstlineElementsWithOtherElementsMissing) {
+  {
+    BalsaHeaders headers;
+    headers.SetRequestMethod("GET");
+    headers.SetRequestUri("/");
+    EXPECT_THAT(headers.first_line(), StrEq("GET / "));
+  }
+  {
+    BalsaHeaders headers;
+    headers.SetRequestMethod("GET");
+    headers.SetRequestVersion("HTTP/1.1");
+    EXPECT_THAT(headers.first_line(), StrEq("GET  HTTP/1.1"));
+  }
+  {
+    BalsaHeaders headers;
+    headers.SetRequestUri("/");
+    headers.SetRequestVersion("HTTP/1.1");
+    EXPECT_THAT(headers.first_line(), StrEq(" / HTTP/1.1"));
+  }
+}
+
+TEST(BalsaHeaders, TestSettingMissingFirstlineElementsAfterBalsaHeadersParsed) {
+  {
+    BalsaHeaders headers = CreateHTTPHeaders(true, "GET /foo\r\n");
+    ASSERT_THAT(headers.first_line(), StrEq("GET /foo"));
+
+    headers.SetRequestVersion("HTTP/1.1");
+    EXPECT_THAT(headers.first_line(), StrEq("GET /foo HTTP/1.1"));
+  }
+  {
+    BalsaHeaders headers = CreateHTTPHeaders(true, "GET\r\n");
+    ASSERT_THAT(headers.first_line(), StrEq("GET"));
+
+    headers.SetRequestUri("/foo");
+    EXPECT_THAT(headers.first_line(), StrEq("GET /foo "));
+  }
+}
+
+// Here we exersize the codepaths involved in setting a new firstine when the
+// previously set firstline is stored in the 'additional_data_stream_'
+// variable, and the new firstline is larger than the previously set firstline.
+TEST(BalsaHeaders,
+     SetFirstlineFromStringPiecesFirstInAdditionalDataAndNewLarger) {
+  BalsaHeaders headers;
+  // This one will end up being put into the additional data stream
+  headers.SetResponseFirstline("HTTP/1.1", 200, "don't need a reason");
+  EXPECT_THAT(headers.response_version(), StrEq("HTTP/1.1"));
+  EXPECT_THAT(headers.response_code(), StrEq("200"));
+  EXPECT_THAT(headers.parsed_response_code(), Eq(200));
+  EXPECT_THAT(headers.response_reason_phrase(), StrEq("don't need a reason"));
+  EXPECT_THAT(headers.first_line(), StrEq("HTTP/1.1 200 don't need a reason"));
+
+  // Now, we set it again, this time we're extending what exists
+  // here.
+  headers.SetResponseFirstline("HTTP/1.10", 2000, "REALLY don't need a reason");
+  EXPECT_THAT(headers.response_version(), StrEq("HTTP/1.10"));
+  EXPECT_THAT(headers.response_code(), StrEq("2000"));
+  EXPECT_THAT(headers.parsed_response_code(), Eq(2000));
+  EXPECT_THAT(headers.response_reason_phrase(),
+              StrEq("REALLY don't need a reason"));
+  EXPECT_THAT(headers.first_line(),
+              StrEq("HTTP/1.10 2000 REALLY don't need a reason"));
+}
+
+// Here we exersize the codepaths involved in setting a new firstine when the
+// previously set firstline is stored in the 'additional_data_stream_'
+// variable, and the new firstline is smaller than the previously set firstline.
+TEST(BalsaHeaders,
+     TestSetFirstlineFromStringPiecesWithPreviousInAdditionalDataNewSmaller) {
+  BalsaHeaders headers;
+  // This one will end up being put into the additional data stream
+  //
+  headers.SetResponseFirstline("HTTP/1.10", 2000, "REALLY don't need a reason");
+  EXPECT_THAT(headers.response_version(), StrEq("HTTP/1.10"));
+  EXPECT_THAT(headers.response_code(), StrEq("2000"));
+  EXPECT_THAT(headers.parsed_response_code(), Eq(2000));
+  EXPECT_THAT(headers.response_reason_phrase(),
+              StrEq("REALLY don't need a reason"));
+  EXPECT_THAT(headers.first_line(),
+              StrEq("HTTP/1.10 2000 REALLY don't need a reason"));
+
+  // Now, we set it again, this time we're extending what exists
+  // here.
+  headers.SetResponseFirstline("HTTP/1.0", 200, "a reason");
+  EXPECT_THAT(headers.response_version(), StrEq("HTTP/1.0"));
+  EXPECT_THAT(headers.response_code(), StrEq("200"));
+  EXPECT_THAT(headers.parsed_response_code(), Eq(200));
+  EXPECT_THAT(headers.response_reason_phrase(), StrEq("a reason"));
+  EXPECT_THAT(headers.first_line(), StrEq("HTTP/1.0 200 a reason"));
+}
+
+TEST(BalsaHeaders, CopyFrom) {
+  // TODO(fenix): test -all- member variables.
+  // (remaining: transfer_encoding_is_chunked_
+  //             content_length_
+  //             content_length_status_
+  //             parsed_response_code_
+  //             connection_close_token_found_
+  //             connection_keep_alive_token_found_
+  //             content_encoding_gzip_token_found_)
+  BalsaHeaders headers1, headers2;
+  absl::string_view method("GET");
+  absl::string_view uri("/foo");
+  absl::string_view version("HTTP/1.0");
+  headers1.SetRequestFirstlineFromStringPieces(method, uri, version);
+  headers1.AppendHeader("key1", "value1");
+  headers1.AppendHeader("key 2", "value\n 2");
+  headers1.AppendHeader("key\n 3", "value3");
+
+  // "GET /foo HTTP/1.0"     // 17
+  // "key1: value1\r\n"      // 14
+  // "key 2: value\n 2\r\n"  // 17
+  // "key\n 3: value3\r\n"   // 16
+
+  headers2.CopyFrom(headers1);
+
+  EXPECT_THAT(headers1.first_line(), StrEq("GET /foo HTTP/1.0"));
+  BalsaHeaders::const_header_lines_iterator chli = headers1.lines().begin();
+  EXPECT_THAT(chli->first, StrEq("key1"));
+  EXPECT_THAT(chli->second, StrEq("value1"));
+  ++chli;
+  EXPECT_THAT(chli->first, StrEq("key 2"));
+  EXPECT_THAT(chli->second, StrEq("value\n 2"));
+  ++chli;
+  EXPECT_THAT(chli->first, StrEq("key\n 3"));
+  EXPECT_THAT(chli->second, StrEq("value3"));
+  ++chli;
+  EXPECT_EQ(headers1.lines().end(), chli);
+
+  EXPECT_THAT(headers1.request_method(),
+              StrEq((std::string(headers2.request_method()))));
+  EXPECT_THAT(headers1.request_uri(),
+              StrEq((std::string(headers2.request_uri()))));
+  EXPECT_THAT(headers1.request_version(),
+              StrEq((std::string(headers2.request_version()))));
+
+  EXPECT_THAT(headers2.first_line(), StrEq("GET /foo HTTP/1.0"));
+  chli = headers2.lines().begin();
+  EXPECT_THAT(chli->first, StrEq("key1"));
+  EXPECT_THAT(chli->second, StrEq("value1"));
+  ++chli;
+  EXPECT_THAT(chli->first, StrEq("key 2"));
+  EXPECT_THAT(chli->second, StrEq("value\n 2"));
+  ++chli;
+  EXPECT_THAT(chli->first, StrEq("key\n 3"));
+  EXPECT_THAT(chli->second, StrEq("value3"));
+  ++chli;
+  EXPECT_EQ(headers2.lines().end(), chli);
+
+  version = absl::string_view("HTTP/1.1");
+  int code = 200;
+  absl::string_view reason_phrase("reason phrase asdf");
+
+  headers1.RemoveAllOfHeader("key1");
+  headers1.AppendHeader("key4", "value4");
+
+  headers1.SetResponseFirstline(version, code, reason_phrase);
+
+  headers2.CopyFrom(headers1);
+
+  // "GET /foo HTTP/1.0"     // 17
+  // "XXXXXXXXXXXXXX"        // 14
+  // "key 2: value\n 2\r\n"  // 17
+  // "key\n 3: value3\r\n"   // 16
+  // "key4: value4\r\n"      // 14
+  //
+  //       ->
+  //
+  // "HTTP/1.1 200 reason phrase asdf"  // 31 = (17 + 14)
+  // "key 2: value\n 2\r\n"             // 17
+  // "key\n 3: value3\r\n"              // 16
+  // "key4: value4\r\n"                 // 14
+
+  EXPECT_THAT(headers1.request_method(),
+              StrEq((std::string(headers2.request_method()))));
+  EXPECT_THAT(headers1.request_uri(),
+              StrEq((std::string(headers2.request_uri()))));
+  EXPECT_THAT(headers1.request_version(),
+              StrEq((std::string(headers2.request_version()))));
+
+  EXPECT_THAT(headers2.first_line(), StrEq("HTTP/1.1 200 reason phrase asdf"));
+  chli = headers2.lines().begin();
+  EXPECT_THAT(chli->first, StrEq("key 2"));
+  EXPECT_THAT(chli->second, StrEq("value\n 2"));
+  ++chli;
+  EXPECT_THAT(chli->first, StrEq("key\n 3"));
+  EXPECT_THAT(chli->second, StrEq("value3"));
+  ++chli;
+  EXPECT_THAT(chli->first, StrEq("key4"));
+  EXPECT_THAT(chli->second, StrEq("value4"));
+  ++chli;
+  EXPECT_EQ(headers2.lines().end(), chli);
+}
+
+// Test BalsaHeaders move constructor and move assignment operator.
+TEST(BalsaHeaders, Move) {
+  BalsaHeaders headers1, headers3;
+  absl::string_view method("GET");
+  absl::string_view uri("/foo");
+  absl::string_view version("HTTP/1.0");
+  headers1.SetRequestFirstlineFromStringPieces(method, uri, version);
+  headers1.AppendHeader("key1", "value1");
+  headers1.AppendHeader("key 2", "value\n 2");
+  headers1.AppendHeader("key\n 3", "value3");
+
+  // "GET /foo HTTP/1.0"     // 17
+  // "key1: value1\r\n"      // 14
+  // "key 2: value\n 2\r\n"  // 17
+  // "key\n 3: value3\r\n"   // 16
+
+  BalsaHeaders headers2 = std::move(headers1);
+
+  EXPECT_EQ("GET /foo HTTP/1.0", headers2.first_line());
+  BalsaHeaders::const_header_lines_iterator chli = headers2.lines().begin();
+  EXPECT_EQ("key1", chli->first);
+  EXPECT_EQ("value1", chli->second);
+  ++chli;
+  EXPECT_EQ("key 2", chli->first);
+  EXPECT_EQ("value\n 2", chli->second);
+  ++chli;
+  EXPECT_EQ("key\n 3", chli->first);
+  EXPECT_EQ("value3", chli->second);
+  ++chli;
+  EXPECT_EQ(headers2.lines().end(), chli);
+
+  EXPECT_EQ("GET", headers2.request_method());
+  EXPECT_EQ("/foo", headers2.request_uri());
+  EXPECT_EQ("HTTP/1.0", headers2.request_version());
+
+  headers3 = std::move(headers2);
+  version = absl::string_view("HTTP/1.1");
+  int code = 200;
+  absl::string_view reason_phrase("reason phrase asdf");
+
+  headers3.RemoveAllOfHeader("key1");
+  headers3.AppendHeader("key4", "value4");
+
+  headers3.SetResponseFirstline(version, code, reason_phrase);
+
+  BalsaHeaders headers4 = std::move(headers3);
+
+  // "GET /foo HTTP/1.0"     // 17
+  // "XXXXXXXXXXXXXX"        // 14
+  // "key 2: value\n 2\r\n"  // 17
+  // "key\n 3: value3\r\n"   // 16
+  // "key4: value4\r\n"      // 14
+  //
+  //       ->
+  //
+  // "HTTP/1.1 200 reason phrase asdf"  // 31 = (17 + 14)
+  // "key 2: value\n 2\r\n"             // 17
+  // "key\n 3: value3\r\n"              // 16
+  // "key4: value4\r\n"                 // 14
+
+  EXPECT_EQ("200", headers4.response_code());
+  EXPECT_EQ("reason phrase asdf", headers4.response_reason_phrase());
+  EXPECT_EQ("HTTP/1.1", headers4.response_version());
+
+  EXPECT_EQ("HTTP/1.1 200 reason phrase asdf", headers4.first_line());
+  chli = headers4.lines().begin();
+  EXPECT_EQ("key 2", chli->first);
+  EXPECT_EQ("value\n 2", chli->second);
+  ++chli;
+  EXPECT_EQ("key\n 3", chli->first);
+  EXPECT_EQ("value3", chli->second);
+  ++chli;
+  EXPECT_EQ("key4", chli->first);
+  EXPECT_EQ("value4", chli->second);
+  ++chli;
+  EXPECT_EQ(headers4.lines().end(), chli);
+}
+
+TEST(BalsaHeaders, IteratorWorksWithOStreamAsExpected) {
+  {
+    std::stringstream actual;
+    BalsaHeaders::const_header_lines_iterator chli;
+    actual << chli;
+    // Note that the output depends on the flavor of standard library in use.
+    EXPECT_THAT(actual.str(), AnyOf(StrEq("[0, 0]"),        // libstdc++
+                                    StrEq("[(nil), 0]")));  // libc++
+  }
+  {
+    BalsaHeaders headers;
+    std::stringstream actual;
+    BalsaHeaders::const_header_lines_iterator chli = headers.lines().begin();
+    actual << chli;
+    std::stringstream expected;
+    expected << "[" << &headers << ", 0]";
+    EXPECT_THAT(expected.str(), StrEq(actual.str()));
+  }
+}
+
+TEST(BalsaHeaders, TestSetResponseReasonPhraseWithNoInitialFirstline) {
+  BalsaHeaders balsa_headers;
+  balsa_headers.SetResponseReasonPhrase("don't need a reason");
+  EXPECT_THAT(balsa_headers.first_line(), StrEq("  don't need a reason"));
+  EXPECT_TRUE(balsa_headers.response_version().empty());
+  EXPECT_TRUE(balsa_headers.response_code().empty());
+  EXPECT_THAT(balsa_headers.response_reason_phrase(),
+              StrEq("don't need a reason"));
+}
+
+// Testing each of 9 combinations separately was taking up way too much of this
+// file (not to mention the inordinate amount of stupid code duplication), thus
+// this test tests all 9 combinations of smaller, equal, and larger in one
+// place.
+TEST(BalsaHeaders, TestSetResponseReasonPhrase) {
+  const char* response_reason_phrases[] = {
+      "qwerty asdfgh",
+      "qwerty",
+      "qwerty asdfghjkl",
+  };
+  size_t arraysize_squared = (ABSL_ARRAYSIZE(response_reason_phrases) *
+                              ABSL_ARRAYSIZE(response_reason_phrases));
+  // We go through the 9 different permutations of (response_reason_phrases
+  // choose 2) in the loop below. For each permutation, we mutate the firstline
+  // twice-- once from the original, and once from the previous.
+  for (size_t iteration = 0; iteration < arraysize_squared; ++iteration) {
+    SCOPED_TRACE("Original firstline: \"HTTP/1.0 200 reason phrase\"");
+    BalsaHeaders headers = CreateHTTPHeaders(true,
+                                             "HTTP/1.0 200 reason phrase\r\n"
+                                             "content-length: 0\r\n"
+                                             "\r\n");
+    ASSERT_THAT(headers.first_line(), StrEq("HTTP/1.0 200 reason phrase"));
+
+    {
+      int first = iteration / ABSL_ARRAYSIZE(response_reason_phrases);
+      const char* response_reason_phrase_first = response_reason_phrases[first];
+      std::string expected_new_firstline =
+          absl::StrFormat("HTTP/1.0 200 %s", response_reason_phrase_first);
+      SCOPED_TRACE(absl::StrFormat("Then set response_reason_phrase(\"%s\")",
+                                   response_reason_phrase_first));
+
+      headers.SetResponseReasonPhrase(response_reason_phrase_first);
+      EXPECT_THAT(headers.first_line(),
+                  StrEq(absl::StrFormat("HTTP/1.0 200 %s",
+                                        response_reason_phrase_first)));
+      EXPECT_THAT(headers.response_version(), StrEq("HTTP/1.0"));
+      EXPECT_THAT(headers.response_code(), StrEq("200"));
+      EXPECT_THAT(headers.response_reason_phrase(),
+                  StrEq(response_reason_phrase_first));
+    }
+
+    // Note that each iteration of the outer loop causes the headers to be left
+    // in a different state. Nothing wrong with that, but we should use each of
+    // these states, and try each of our scenarios again. This inner loop does
+    // that.
+    {
+      int second = iteration % ABSL_ARRAYSIZE(response_reason_phrases);
+      const char* response_reason_phrase_second =
+          response_reason_phrases[second];
+      std::string expected_new_firstline =
+          absl::StrFormat("HTTP/1.0 200 %s", response_reason_phrase_second);
+      SCOPED_TRACE(absl::StrFormat("Then set response_reason_phrase(\"%s\")",
+                                   response_reason_phrase_second));
+
+      headers.SetResponseReasonPhrase(response_reason_phrase_second);
+      EXPECT_THAT(headers.first_line(),
+                  StrEq(absl::StrFormat("HTTP/1.0 200 %s",
+                                        response_reason_phrase_second)));
+      EXPECT_THAT(headers.response_version(), StrEq("HTTP/1.0"));
+      EXPECT_THAT(headers.response_code(), StrEq("200"));
+      EXPECT_THAT(headers.response_reason_phrase(),
+                  StrEq(response_reason_phrase_second));
+    }
+  }
+}
+
+TEST(BalsaHeaders, TestSetResponseVersionWithNoInitialFirstline) {
+  BalsaHeaders balsa_headers;
+  balsa_headers.SetResponseVersion("HTTP/1.1");
+  EXPECT_THAT(balsa_headers.first_line(), StrEq("HTTP/1.1  "));
+  EXPECT_THAT(balsa_headers.response_version(), StrEq("HTTP/1.1"));
+  EXPECT_TRUE(balsa_headers.response_code().empty());
+  EXPECT_TRUE(balsa_headers.response_reason_phrase().empty());
+}
+
+// Testing each of 9 combinations separately was taking up way too much of this
+// file (not to mention the inordinate amount of stupid code duplication), thus
+// this test tests all 9 combinations of smaller, equal, and larger in one
+// place.
+TEST(BalsaHeaders, TestSetResponseVersion) {
+  const char* response_versions[] = {
+      "ABCD/123",
+      "ABCD",
+      "ABCD/123456",
+  };
+  size_t arraysize_squared =
+      (ABSL_ARRAYSIZE(response_versions) * ABSL_ARRAYSIZE(response_versions));
+  // We go through the 9 different permutations of (response_versions choose 2)
+  // in the loop below. For each permutation, we mutate the firstline twice--
+  // once from the original, and once from the previous.
+  for (size_t iteration = 0; iteration < arraysize_squared; ++iteration) {
+    SCOPED_TRACE("Original firstline: \"HTTP/1.0 200 reason phrase\"");
+    BalsaHeaders headers = CreateHTTPHeaders(false,
+                                             "HTTP/1.0 200 reason phrase\r\n"
+                                             "content-length: 0\r\n"
+                                             "\r\n");
+    ASSERT_THAT(headers.first_line(), StrEq("HTTP/1.0 200 reason phrase"));
+
+    // This structure guarantees that we'll visit all of the possible
+    // variations of setting.
+
+    {
+      int first = iteration / ABSL_ARRAYSIZE(response_versions);
+      const char* response_version_first = response_versions[first];
+      std::string expected_new_firstline =
+          absl::StrFormat("%s 200 reason phrase", response_version_first);
+      SCOPED_TRACE(absl::StrFormat("Then set response_version(\"%s\")",
+                                   response_version_first));
+
+      headers.SetResponseVersion(response_version_first);
+      EXPECT_THAT(headers.first_line(), StrEq(expected_new_firstline));
+      EXPECT_THAT(headers.response_version(), StrEq(response_version_first));
+      EXPECT_THAT(headers.response_code(), StrEq("200"));
+      EXPECT_THAT(headers.response_reason_phrase(), StrEq("reason phrase"));
+    }
+    {
+      int second = iteration % ABSL_ARRAYSIZE(response_versions);
+      const char* response_version_second = response_versions[second];
+      std::string expected_new_firstline =
+          absl::StrFormat("%s 200 reason phrase", response_version_second);
+      SCOPED_TRACE(absl::StrFormat("Then set response_version(\"%s\")",
+                                   response_version_second));
+
+      headers.SetResponseVersion(response_version_second);
+      EXPECT_THAT(headers.first_line(), StrEq(expected_new_firstline));
+      EXPECT_THAT(headers.response_version(), StrEq(response_version_second));
+      EXPECT_THAT(headers.response_code(), StrEq("200"));
+      EXPECT_THAT(headers.response_reason_phrase(), StrEq("reason phrase"));
+    }
+  }
+}
+
+TEST(BalsaHeaders, TestSetResponseReasonAndVersionWithNoInitialFirstline) {
+  BalsaHeaders headers;
+  headers.SetResponseVersion("HTTP/1.1");
+  headers.SetResponseReasonPhrase("don't need a reason");
+  EXPECT_THAT(headers.first_line(), StrEq("HTTP/1.1  don't need a reason"));
+  EXPECT_THAT(headers.response_version(), StrEq("HTTP/1.1"));
+  EXPECT_TRUE(headers.response_code().empty());
+  EXPECT_THAT(headers.response_reason_phrase(), StrEq("don't need a reason"));
+}
+
+TEST(BalsaHeaders, TestSetResponseCodeWithNoInitialFirstline) {
+  BalsaHeaders balsa_headers;
+  balsa_headers.SetParsedResponseCodeAndUpdateFirstline(2002);
+  EXPECT_THAT(balsa_headers.first_line(), StrEq(" 2002 "));
+  EXPECT_TRUE(balsa_headers.response_version().empty());
+  EXPECT_THAT(balsa_headers.response_code(), StrEq("2002"));
+  EXPECT_TRUE(balsa_headers.response_reason_phrase().empty());
+  EXPECT_THAT(balsa_headers.parsed_response_code(), Eq(2002));
+}
+
+TEST(BalsaHeaders, TestSetParsedResponseCode) {
+  BalsaHeaders balsa_headers;
+  balsa_headers.set_parsed_response_code(std::numeric_limits<int>::max());
+  EXPECT_THAT(balsa_headers.parsed_response_code(),
+              Eq(std::numeric_limits<int>::max()));
+}
+
+TEST(BalsaHeaders, TestSetResponseCode) {
+  const char* response_codes[] = {
+      "200"
+      "23",
+      "200200",
+  };
+  size_t arraysize_squared =
+      (ABSL_ARRAYSIZE(response_codes) * ABSL_ARRAYSIZE(response_codes));
+  // We go through the 9 different permutations of (response_codes choose 2)
+  // in the loop below. For each permutation, we mutate the firstline twice--
+  // once from the original, and once from the previous.
+  for (size_t iteration = 0; iteration < arraysize_squared; ++iteration) {
+    SCOPED_TRACE("Original firstline: \"HTTP/1.0 200 reason phrase\"");
+    BalsaHeaders headers = CreateHTTPHeaders(false,
+                                             "HTTP/1.0 200 reason phrase\r\n"
+                                             "content-length: 0\r\n"
+                                             "\r\n");
+    ASSERT_THAT(headers.first_line(), StrEq("HTTP/1.0 200 reason phrase"));
+
+    // This structure guarantees that we'll visit all of the possible
+    // variations of setting.
+
+    {
+      int first = iteration / ABSL_ARRAYSIZE(response_codes);
+      const char* response_code_first = response_codes[first];
+      std::string expected_new_firstline =
+          absl::StrFormat("HTTP/1.0 %s reason phrase", response_code_first);
+      SCOPED_TRACE(absl::StrFormat("Then set response_code(\"%s\")",
+                                   response_code_first));
+
+      headers.SetResponseCode(response_code_first);
+
+      EXPECT_THAT(headers.first_line(), StrEq(expected_new_firstline));
+      EXPECT_THAT(headers.response_version(), StrEq("HTTP/1.0"));
+      EXPECT_THAT(headers.response_code(), StrEq(response_code_first));
+      EXPECT_THAT(headers.response_reason_phrase(), StrEq("reason phrase"));
+    }
+    {
+      int second = iteration % ABSL_ARRAYSIZE(response_codes);
+      const char* response_code_second = response_codes[second];
+      std::string expected_new_secondline =
+          absl::StrFormat("HTTP/1.0 %s reason phrase", response_code_second);
+      SCOPED_TRACE(absl::StrFormat("Then set response_code(\"%s\")",
+                                   response_code_second));
+
+      headers.SetResponseCode(response_code_second);
+
+      EXPECT_THAT(headers.first_line(), StrEq(expected_new_secondline));
+      EXPECT_THAT(headers.response_version(), StrEq("HTTP/1.0"));
+      EXPECT_THAT(headers.response_code(), StrEq(response_code_second));
+      EXPECT_THAT(headers.response_reason_phrase(), StrEq("reason phrase"));
+    }
+  }
+}
+
+TEST(BalsaHeaders, TestAppendToHeader) {
+  // Test the basic case of appending to a header.
+  BalsaHeaders headers;
+  headers.AppendHeader("foo", "foo_value");
+  headers.AppendHeader("bar", "bar_value");
+  headers.AppendToHeader("foo", "foo_value2");
+
+  EXPECT_THAT(headers.GetHeader("foo"), StrEq("foo_value,foo_value2"));
+  EXPECT_THAT(headers.GetHeader("bar"), StrEq("bar_value"));
+}
+
+TEST(BalsaHeaders, TestInitialAppend) {
+  // Test that AppendToHeader works properly when the header did not already
+  // exist.
+  BalsaHeaders headers;
+  headers.AppendToHeader("foo", "foo_value");
+  EXPECT_THAT(headers.GetHeader("foo"), StrEq("foo_value"));
+  headers.AppendToHeader("foo", "foo_value2");
+  EXPECT_THAT(headers.GetHeader("foo"), StrEq("foo_value,foo_value2"));
+}
+
+TEST(BalsaHeaders, TestAppendAndRemove) {
+  // Test that AppendToHeader works properly with removing.
+  BalsaHeaders headers;
+  headers.AppendToHeader("foo", "foo_value");
+  EXPECT_THAT(headers.GetHeader("foo"), StrEq("foo_value"));
+  headers.AppendToHeader("foo", "foo_value2");
+  EXPECT_THAT(headers.GetHeader("foo"), StrEq("foo_value,foo_value2"));
+  headers.RemoveAllOfHeader("foo");
+  headers.AppendToHeader("foo", "foo_value3");
+  EXPECT_THAT(headers.GetHeader("foo"), StrEq("foo_value3"));
+  headers.AppendToHeader("foo", "foo_value4");
+  EXPECT_THAT(headers.GetHeader("foo"), StrEq("foo_value3,foo_value4"));
+}
+
+TEST(BalsaHeaders, TestAppendToHeaderWithCommaAndSpace) {
+  // Test the basic case of appending to a header with comma and space.
+  BalsaHeaders headers;
+  headers.AppendHeader("foo", "foo_value");
+  headers.AppendHeader("bar", "bar_value");
+  headers.AppendToHeaderWithCommaAndSpace("foo", "foo_value2");
+
+  EXPECT_THAT(headers.GetHeader("foo"), StrEq("foo_value, foo_value2"));
+  EXPECT_THAT(headers.GetHeader("bar"), StrEq("bar_value"));
+}
+
+TEST(BalsaHeaders, TestInitialAppendWithCommaAndSpace) {
+  // Test that AppendToHeadeWithCommaAndSpace works properly when the
+  // header did not already exist.
+  BalsaHeaders headers;
+  headers.AppendToHeaderWithCommaAndSpace("foo", "foo_value");
+  EXPECT_THAT(headers.GetHeader("foo"), StrEq("foo_value"));
+  headers.AppendToHeaderWithCommaAndSpace("foo", "foo_value2");
+  EXPECT_THAT(headers.GetHeader("foo"), StrEq("foo_value, foo_value2"));
+}
+
+TEST(BalsaHeaders, TestAppendWithCommaAndSpaceAndRemove) {
+  // Test that AppendToHeadeWithCommaAndSpace works properly with removing.
+  BalsaHeaders headers;
+  headers.AppendToHeaderWithCommaAndSpace("foo", "foo_value");
+  EXPECT_THAT(headers.GetHeader("foo"), StrEq("foo_value"));
+  headers.AppendToHeaderWithCommaAndSpace("foo", "foo_value2");
+  EXPECT_THAT(headers.GetHeader("foo"), StrEq("foo_value, foo_value2"));
+  headers.RemoveAllOfHeader("foo");
+  headers.AppendToHeaderWithCommaAndSpace("foo", "foo_value3");
+  EXPECT_THAT(headers.GetHeader("foo"), StrEq("foo_value3"));
+  headers.AppendToHeaderWithCommaAndSpace("foo", "foo_value4");
+  EXPECT_THAT(headers.GetHeader("foo"), StrEq("foo_value3, foo_value4"));
+}
+
+TEST(BalsaHeaders, SetContentLength) {
+  // Test that SetContentLength correctly sets the content-length header and
+  // sets the content length status.
+  BalsaHeaders headers;
+  headers.SetContentLength(10);
+  EXPECT_THAT(headers.GetHeader("Content-length"), StrEq("10"));
+  EXPECT_EQ(BalsaHeadersEnums::VALID_CONTENT_LENGTH,
+            headers.content_length_status());
+  EXPECT_TRUE(headers.content_length_valid());
+
+  // Test overwriting the content-length.
+  headers.SetContentLength(0);
+  EXPECT_THAT(headers.GetHeader("Content-length"), StrEq("0"));
+  EXPECT_EQ(BalsaHeadersEnums::VALID_CONTENT_LENGTH,
+            headers.content_length_status());
+  EXPECT_TRUE(headers.content_length_valid());
+
+  // Make sure there is only one header line after the overwrite.
+  BalsaHeaders::const_header_lines_iterator iter =
+      headers.GetHeaderPosition("Content-length");
+  EXPECT_EQ(headers.lines().begin(), iter);
+  EXPECT_EQ(headers.lines().end(), ++iter);
+
+  // Test setting the same content-length again, this should be no-op.
+  headers.SetContentLength(0);
+  EXPECT_THAT(headers.GetHeader("Content-length"), StrEq("0"));
+  EXPECT_EQ(BalsaHeadersEnums::VALID_CONTENT_LENGTH,
+            headers.content_length_status());
+  EXPECT_TRUE(headers.content_length_valid());
+
+  // Make sure the number of header lines didn't change.
+  iter = headers.GetHeaderPosition("Content-length");
+  EXPECT_EQ(headers.lines().begin(), iter);
+  EXPECT_EQ(headers.lines().end(), ++iter);
+}
+
+TEST(BalsaHeaders, ToggleChunkedEncoding) {
+  // Test that SetTransferEncodingToChunkedAndClearContentLength correctly adds
+  // chunk-encoding header and sets the transfer_encoding_is_chunked_
+  // flag.
+  BalsaHeaders headers;
+  headers.SetTransferEncodingToChunkedAndClearContentLength();
+  EXPECT_EQ("chunked", headers.GetAllOfHeaderAsString("Transfer-Encoding"));
+  EXPECT_TRUE(headers.HasHeadersWithPrefix("Transfer-Encoding"));
+  EXPECT_TRUE(headers.HasHeadersWithPrefix("transfer-encoding"));
+  EXPECT_TRUE(headers.HasHeadersWithPrefix("transfer"));
+  EXPECT_TRUE(headers.transfer_encoding_is_chunked());
+
+  // Set it to the same value, nothing should change.
+  headers.SetTransferEncodingToChunkedAndClearContentLength();
+  EXPECT_EQ("chunked", headers.GetAllOfHeaderAsString("Transfer-Encoding"));
+  EXPECT_TRUE(headers.HasHeadersWithPrefix("Transfer-Encoding"));
+  EXPECT_TRUE(headers.HasHeadersWithPrefix("transfer-encoding"));
+  EXPECT_TRUE(headers.HasHeadersWithPrefix("transfer"));
+  EXPECT_TRUE(headers.transfer_encoding_is_chunked());
+  BalsaHeaders::const_header_lines_iterator iter =
+      headers.GetHeaderPosition("Transfer-Encoding");
+  EXPECT_EQ(headers.lines().begin(), iter);
+  EXPECT_EQ(headers.lines().end(), ++iter);
+
+  // Removes the chunked encoding, and there should be no transfer-encoding
+  // headers left.
+  headers.SetNoTransferEncoding();
+  EXPECT_FALSE(headers.HasHeader("Transfer-Encoding"));
+  EXPECT_FALSE(headers.HasHeadersWithPrefix("Transfer-Encoding"));
+  EXPECT_FALSE(headers.HasHeadersWithPrefix("transfer-encoding"));
+  EXPECT_FALSE(headers.HasHeadersWithPrefix("transfer"));
+  EXPECT_FALSE(headers.transfer_encoding_is_chunked());
+  EXPECT_EQ(headers.lines().end(), headers.lines().begin());
+
+  // Clear chunked again, this should be a no-op and the header should not
+  // change.
+  headers.SetNoTransferEncoding();
+  EXPECT_FALSE(headers.HasHeader("Transfer-Encoding"));
+  EXPECT_FALSE(headers.HasHeadersWithPrefix("Transfer-Encoding"));
+  EXPECT_FALSE(headers.HasHeadersWithPrefix("transfer-encoding"));
+  EXPECT_FALSE(headers.HasHeadersWithPrefix("transfer"));
+  EXPECT_FALSE(headers.transfer_encoding_is_chunked());
+  EXPECT_EQ(headers.lines().end(), headers.lines().begin());
+}
+
+TEST(BalsaHeaders, SetNoTransferEncodingByRemoveHeader) {
+  // Tests that calling Remove() methods to clear the Transfer-Encoding
+  // header correctly resets transfer_encoding_is_chunked_ internal state.
+  BalsaHeaders headers;
+  headers.SetTransferEncodingToChunkedAndClearContentLength();
+  headers.RemoveAllOfHeader("Transfer-Encoding");
+  EXPECT_FALSE(headers.transfer_encoding_is_chunked());
+
+  headers.SetTransferEncodingToChunkedAndClearContentLength();
+  std::vector<absl::string_view> headers_to_remove;
+  headers_to_remove.emplace_back("Transfer-Encoding");
+  headers.RemoveAllOfHeaderInList(headers_to_remove);
+  EXPECT_FALSE(headers.transfer_encoding_is_chunked());
+
+  headers.SetTransferEncodingToChunkedAndClearContentLength();
+  headers.RemoveAllHeadersWithPrefix("Transfer");
+  EXPECT_FALSE(headers.transfer_encoding_is_chunked());
+}
+
+TEST(BalsaHeaders, ClearContentLength) {
+  // Test that ClearContentLength() removes the content-length header and
+  // resets content_length_status().
+  BalsaHeaders headers;
+  headers.SetContentLength(10);
+  headers.ClearContentLength();
+  EXPECT_FALSE(headers.HasHeader("Content-length"));
+  EXPECT_EQ(BalsaHeadersEnums::NO_CONTENT_LENGTH,
+            headers.content_length_status());
+  EXPECT_FALSE(headers.content_length_valid());
+
+  // Clear it again; nothing should change.
+  headers.ClearContentLength();
+  EXPECT_FALSE(headers.HasHeader("Content-length"));
+  EXPECT_EQ(BalsaHeadersEnums::NO_CONTENT_LENGTH,
+            headers.content_length_status());
+  EXPECT_FALSE(headers.content_length_valid());
+
+  // Set chunked encoding and test that ClearContentLength() has no effect.
+  headers.SetTransferEncodingToChunkedAndClearContentLength();
+  headers.ClearContentLength();
+  EXPECT_EQ("chunked", headers.GetAllOfHeaderAsString("Transfer-Encoding"));
+  EXPECT_TRUE(headers.transfer_encoding_is_chunked());
+  BalsaHeaders::const_header_lines_iterator iter =
+      headers.GetHeaderPosition("Transfer-Encoding");
+  EXPECT_EQ(headers.lines().begin(), iter);
+  EXPECT_EQ(headers.lines().end(), ++iter);
+
+  // Remove chunked encoding, and verify that the state is the same as after
+  // ClearContentLength().
+  headers.SetNoTransferEncoding();
+  EXPECT_EQ(BalsaHeadersEnums::NO_CONTENT_LENGTH,
+            headers.content_length_status());
+  EXPECT_FALSE(headers.content_length_valid());
+}
+
+TEST(BalsaHeaders, ClearContentLengthByRemoveHeader) {
+  // Test that calling Remove() methods to clear the content-length header
+  // correctly resets internal content length fields.
+  BalsaHeaders headers;
+  headers.SetContentLength(10);
+  headers.RemoveAllOfHeader("Content-Length");
+  EXPECT_EQ(BalsaHeadersEnums::NO_CONTENT_LENGTH,
+            headers.content_length_status());
+  EXPECT_EQ(0u, headers.content_length());
+  EXPECT_FALSE(headers.content_length_valid());
+
+  headers.SetContentLength(11);
+  std::vector<absl::string_view> headers_to_remove;
+  headers_to_remove.emplace_back("Content-Length");
+  headers.RemoveAllOfHeaderInList(headers_to_remove);
+  EXPECT_EQ(BalsaHeadersEnums::NO_CONTENT_LENGTH,
+            headers.content_length_status());
+  EXPECT_EQ(0u, headers.content_length());
+  EXPECT_FALSE(headers.content_length_valid());
+
+  headers.SetContentLength(12);
+  headers.RemoveAllHeadersWithPrefix("Content");
+  EXPECT_EQ(BalsaHeadersEnums::NO_CONTENT_LENGTH,
+            headers.content_length_status());
+  EXPECT_EQ(0u, headers.content_length());
+  EXPECT_FALSE(headers.content_length_valid());
+}
+
+// Chunk-encoding an identity-coded BalsaHeaders removes the identity-coding.
+TEST(BalsaHeaders, IdentityCodingToChunked) {
+  std::string message =
+      "HTTP/1.1 200 OK\r\n"
+      "Transfer-Encoding: identity\r\n\r\n";
+  BalsaHeaders headers;
+  BalsaFrame balsa_frame;
+  balsa_frame.set_is_request(false);
+  balsa_frame.set_balsa_headers(&headers);
+  EXPECT_EQ(message.size(),
+            balsa_frame.ProcessInput(message.data(), message.size()));
+
+  EXPECT_TRUE(headers.is_framed_by_connection_close());
+  EXPECT_FALSE(headers.transfer_encoding_is_chunked());
+  EXPECT_THAT(headers.GetAllOfHeader("Transfer-Encoding"),
+              ElementsAre("identity"));
+
+  headers.SetTransferEncodingToChunkedAndClearContentLength();
+
+  EXPECT_FALSE(headers.is_framed_by_connection_close());
+  EXPECT_TRUE(headers.transfer_encoding_is_chunked());
+  EXPECT_THAT(headers.GetAllOfHeader("Transfer-Encoding"),
+              ElementsAre("chunked"));
+}
+
+TEST(BalsaHeaders, SwitchContentLengthToChunk) {
+  // Test that a header originally with content length header is correctly
+  // switched to using chunk encoding.
+  BalsaHeaders headers;
+  headers.SetContentLength(10);
+  EXPECT_THAT(headers.GetHeader("Content-length"), StrEq("10"));
+  EXPECT_EQ(BalsaHeadersEnums::VALID_CONTENT_LENGTH,
+            headers.content_length_status());
+  EXPECT_TRUE(headers.content_length_valid());
+
+  headers.SetTransferEncodingToChunkedAndClearContentLength();
+  EXPECT_EQ("chunked", headers.GetAllOfHeaderAsString("Transfer-Encoding"));
+  EXPECT_TRUE(headers.transfer_encoding_is_chunked());
+  EXPECT_FALSE(headers.HasHeader("Content-length"));
+  EXPECT_EQ(BalsaHeadersEnums::NO_CONTENT_LENGTH,
+            headers.content_length_status());
+  EXPECT_FALSE(headers.content_length_valid());
+}
+
+TEST(BalsaHeaders, SwitchChunkedToContentLength) {
+  // Test that a header originally with chunk encoding is correctly
+  // switched to using content length.
+  BalsaHeaders headers;
+  headers.SetTransferEncodingToChunkedAndClearContentLength();
+  EXPECT_EQ("chunked", headers.GetAllOfHeaderAsString("Transfer-Encoding"));
+  EXPECT_TRUE(headers.transfer_encoding_is_chunked());
+  EXPECT_FALSE(headers.HasHeader("Content-length"));
+  EXPECT_EQ(BalsaHeadersEnums::NO_CONTENT_LENGTH,
+            headers.content_length_status());
+  EXPECT_FALSE(headers.content_length_valid());
+
+  headers.SetContentLength(10);
+  EXPECT_THAT(headers.GetHeader("Content-length"), StrEq("10"));
+  EXPECT_EQ(BalsaHeadersEnums::VALID_CONTENT_LENGTH,
+            headers.content_length_status());
+  EXPECT_TRUE(headers.content_length_valid());
+  EXPECT_FALSE(headers.HasHeader("Transfer-Encoding"));
+  EXPECT_FALSE(headers.transfer_encoding_is_chunked());
+}
+
+TEST(BalsaHeaders, OneHundredResponseMessagesNoFramedByClose) {
+  BalsaHeaders headers;
+  headers.SetResponseFirstline("HTTP/1.1", 100, "Continue");
+  EXPECT_FALSE(headers.is_framed_by_connection_close());
+}
+
+TEST(BalsaHeaders, TwoOhFourResponseMessagesNoFramedByClose) {
+  BalsaHeaders headers;
+  headers.SetResponseFirstline("HTTP/1.1", 204, "Continue");
+  EXPECT_FALSE(headers.is_framed_by_connection_close());
+}
+
+TEST(BalsaHeaders, ThreeOhFourResponseMessagesNoFramedByClose) {
+  BalsaHeaders headers;
+  headers.SetResponseFirstline("HTTP/1.1", 304, "Continue");
+  EXPECT_FALSE(headers.is_framed_by_connection_close());
+}
+
+TEST(BalsaHeaders, InvalidCharInHeaderValue) {
+  std::string message =
+      "GET http://www.256.com/foo HTTP/1.1\r\n"
+      "Host: \x01\x01www.265.com\r\n"
+      "\r\n";
+  BalsaHeaders headers = CreateHTTPHeaders(true, message);
+  EXPECT_EQ("www.265.com", headers.GetHeader("Host"));
+  SimpleBuffer buffer;
+  headers.WriteHeaderAndEndingToBuffer(&buffer);
+  message.replace(message.find_first_of(0x1), 2, "");
+  EXPECT_EQ(message, buffer.GetReadableRegion());
+}
+
+TEST(BalsaHeaders, CarriageReturnAtStartOfLine) {
+  std::string message =
+      "GET /foo HTTP/1.1\r\n"
+      "Host: www.265.com\r\n"
+      "Foo: bar\r\n"
+      "\rX-User-Ip: 1.2.3.4\r\n"
+      "\r\n";
+  BalsaHeaders headers;
+  BalsaFrame balsa_frame;
+  balsa_frame.set_is_request(true);
+  balsa_frame.set_balsa_headers(&headers);
+  EXPECT_EQ(message.size(),
+            balsa_frame.ProcessInput(message.data(), message.size()));
+  EXPECT_EQ(BalsaFrameEnums::INVALID_HEADER_FORMAT, balsa_frame.ErrorCode());
+  EXPECT_TRUE(balsa_frame.Error());
+}
+
+TEST(BalsaHeaders, CheckEmpty) {
+  BalsaHeaders headers;
+  EXPECT_TRUE(headers.IsEmpty());
+}
+
+TEST(BalsaHeaders, CheckNonEmpty) {
+  BalsaHeaders headers;
+  BalsaHeadersTestPeer::WriteFromFramer(&headers, "a b c", 5);
+  EXPECT_FALSE(headers.IsEmpty());
+}
+
+TEST(BalsaHeaders, ForEachHeader) {
+  BalsaHeaders headers;
+  headers.AppendHeader(":host", "SomeHost");
+  headers.AppendHeader("key", "val1,val2val2,val2,val3");
+  headers.AppendHeader("key", "val4val5val6");
+  headers.AppendHeader("key", "val11 val12");
+  headers.AppendHeader("key", "v val13");
+  headers.AppendHeader("key", "val7");
+  headers.AppendHeader("key", "");
+  headers.AppendHeader("key", "val8 , val9 ,, val10");
+  headers.AppendHeader("key", " val14 ");
+  headers.AppendHeader("key2", "val15");
+  headers.AppendHeader("key", "Val16");
+  headers.AppendHeader("key", "foo, Val17, bar");
+  headers.AppendHeader("date", "2 Jan 1970");
+  headers.AppendHeader("AcceptEncoding", "MyFavoriteEncoding");
+
+  {
+    std::string result;
+    EXPECT_TRUE(headers.ForEachHeader(
+        [&result](const absl::string_view key, absl::string_view value) {
+          result.append("<")
+              .append(key.data(), key.size())
+              .append("> = <")
+              .append(value.data(), value.size())
+              .append(">\n");
+          return true;
+        }));
+
+    EXPECT_EQ(result,
+              "<:host> = <SomeHost>\n"
+              "<key> = <val1,val2val2,val2,val3>\n"
+              "<key> = <val4val5val6>\n"
+              "<key> = <val11 val12>\n"
+              "<key> = <v val13>\n"
+              "<key> = <val7>\n"
+              "<key> = <>\n"
+              "<key> = <val8 , val9 ,, val10>\n"
+              "<key> = < val14 >\n"
+              "<key2> = <val15>\n"
+              "<key> = <Val16>\n"
+              "<key> = <foo, Val17, bar>\n"
+              "<date> = <2 Jan 1970>\n"
+              "<AcceptEncoding> = <MyFavoriteEncoding>\n");
+  }
+
+  {
+    std::string result;
+    EXPECT_FALSE(headers.ForEachHeader(
+        [&result](const absl::string_view key, absl::string_view value) {
+          result.append("<")
+              .append(key.data(), key.size())
+              .append("> = <")
+              .append(value.data(), value.size())
+              .append(">\n");
+          return !value.empty();
+        }));
+
+    EXPECT_EQ(result,
+              "<:host> = <SomeHost>\n"
+              "<key> = <val1,val2val2,val2,val3>\n"
+              "<key> = <val4val5val6>\n"
+              "<key> = <val11 val12>\n"
+              "<key> = <v val13>\n"
+              "<key> = <val7>\n"
+              "<key> = <>\n");
+  }
+}
+
+TEST(BalsaHeaders, WriteToBufferWithLowerCasedHeaderKey) {
+  BalsaHeaders headers;
+  headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0");
+  headers.AppendHeader("Key1", "value1");
+  headers.AppendHeader("Key2", "value2");
+  std::string expected_lower_case =
+      "GET / HTTP/1.0\r\n"
+      "key1: value1\r\n"
+      "key2: value2\r\n";
+  std::string expected_lower_case_with_end =
+      "GET / HTTP/1.0\r\n"
+      "key1: value1\r\n"
+      "key2: value2\r\n\r\n";
+  std::string expected_upper_case =
+      "GET / HTTP/1.0\r\n"
+      "Key1: value1\r\n"
+      "Key2: value2\r\n";
+  std::string expected_upper_case_with_end =
+      "GET / HTTP/1.0\r\n"
+      "Key1: value1\r\n"
+      "Key2: value2\r\n\r\n";
+
+  SimpleBuffer simple_buffer;
+  headers.WriteToBuffer(&simple_buffer, BalsaHeaders::CaseOption::kLowercase,
+                        BalsaHeaders::CoalesceOption::kNoCoalesce);
+  EXPECT_THAT(simple_buffer.GetReadableRegion(), StrEq(expected_lower_case));
+
+  simple_buffer.Clear();
+  headers.WriteToBuffer(&simple_buffer);
+  EXPECT_THAT(simple_buffer.GetReadableRegion(), StrEq(expected_upper_case));
+
+  simple_buffer.Clear();
+  headers.WriteHeaderAndEndingToBuffer(&simple_buffer);
+  EXPECT_THAT(simple_buffer.GetReadableRegion(),
+              StrEq(expected_upper_case_with_end));
+
+  simple_buffer.Clear();
+  headers.WriteHeaderAndEndingToBuffer(
+      &simple_buffer, BalsaHeaders::CaseOption::kLowercase,
+      BalsaHeaders::CoalesceOption::kNoCoalesce);
+  EXPECT_THAT(simple_buffer.GetReadableRegion(),
+              StrEq(expected_lower_case_with_end));
+}
+
+TEST(BalsaHeaders, WriteToBufferWithProperCasedHeaderKey) {
+  BalsaHeaders headers;
+  headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0");
+  headers.AppendHeader("Te", "value1");
+  headers.AppendHeader("my-Test-header", "value2");
+  std::string expected_proper_case =
+      "GET / HTTP/1.0\r\n"
+      "TE: value1\r\n"
+      "My-Test-Header: value2\r\n";
+  std::string expected_proper_case_with_end =
+      "GET / HTTP/1.0\r\n"
+      "TE: value1\r\n"
+      "My-Test-Header: value2\r\n\r\n";
+  std::string expected_unmodified =
+      "GET / HTTP/1.0\r\n"
+      "Te: value1\r\n"
+      "my-Test-header: value2\r\n";
+  std::string expected_unmodified_with_end =
+      "GET / HTTP/1.0\r\n"
+      "Te: value1\r\n"
+      "my-Test-header: value2\r\n\r\n";
+
+  SimpleBuffer simple_buffer;
+  headers.WriteToBuffer(&simple_buffer, BalsaHeaders::CaseOption::kPropercase,
+                        BalsaHeaders::CoalesceOption::kNoCoalesce);
+  EXPECT_EQ(simple_buffer.GetReadableRegion(), expected_proper_case);
+
+  simple_buffer.Clear();
+  headers.WriteToBuffer(&simple_buffer,
+                        BalsaHeaders::CaseOption::kNoModification,
+                        BalsaHeaders::CoalesceOption::kNoCoalesce);
+  EXPECT_EQ(simple_buffer.GetReadableRegion(), expected_unmodified);
+
+  simple_buffer.Clear();
+  headers.WriteHeaderAndEndingToBuffer(
+      &simple_buffer, BalsaHeaders::CaseOption::kNoModification,
+      BalsaHeaders::CoalesceOption::kNoCoalesce);
+  EXPECT_EQ(simple_buffer.GetReadableRegion(), expected_unmodified_with_end);
+
+  simple_buffer.Clear();
+  headers.WriteHeaderAndEndingToBuffer(
+      &simple_buffer, BalsaHeaders::CaseOption::kPropercase,
+      BalsaHeaders::CoalesceOption::kNoCoalesce);
+  EXPECT_EQ(simple_buffer.GetReadableRegion(), expected_proper_case_with_end);
+}
+
+TEST(BalsaHeadersTest, ToPropercaseTest) {
+  EXPECT_EQ(BalsaHeaders::ToPropercase(""), "");
+  EXPECT_EQ(BalsaHeaders::ToPropercase("Foo"), "Foo");
+  EXPECT_EQ(BalsaHeaders::ToPropercase("foO"), "Foo");
+  EXPECT_EQ(BalsaHeaders::ToPropercase("my-test-header"), "My-Test-Header");
+  EXPECT_EQ(BalsaHeaders::ToPropercase("my--test-header"), "My--Test-Header");
+}
+
+TEST(BalsaHeaders, WriteToBufferCoalescingMultivaluedHeaders) {
+  BalsaHeaders::MultivaluedHeadersSet multivalued_headers;
+  multivalued_headers.insert("KeY1");
+  multivalued_headers.insert("another_KEY");
+
+  BalsaHeaders headers;
+  headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0");
+  headers.AppendHeader("Key1", "value1");
+  headers.AppendHeader("Key2", "value2");
+  headers.AppendHeader("Key1", "value11");
+  headers.AppendHeader("Key2", "value21");
+  headers.AppendHeader("Key1", "multiples, values, already");
+  std::string expected_non_coalesced =
+      "GET / HTTP/1.0\r\n"
+      "Key1: value1\r\n"
+      "Key2: value2\r\n"
+      "Key1: value11\r\n"
+      "Key2: value21\r\n"
+      "Key1: multiples, values, already\r\n";
+  std::string expected_coalesced =
+      "Key1: value1,value11,multiples, values, already\r\n"
+      "Key2: value2\r\n"
+      "Key2: value21\r\n";
+
+  SimpleBuffer simple_buffer;
+  headers.WriteToBuffer(&simple_buffer);
+  EXPECT_EQ(simple_buffer.GetReadableRegion(), expected_non_coalesced);
+
+  simple_buffer.Clear();
+  headers.WriteToBufferCoalescingMultivaluedHeaders(
+      &simple_buffer, multivalued_headers,
+      BalsaHeaders::CaseOption::kNoModification);
+  EXPECT_EQ(simple_buffer.GetReadableRegion(), expected_coalesced);
+}
+
+TEST(BalsaHeaders, WriteToBufferCoalescingMultivaluedHeadersMultiLine) {
+  BalsaHeaders::MultivaluedHeadersSet multivalued_headers;
+  multivalued_headers.insert("Key 2");
+  multivalued_headers.insert("key\n 3");
+
+  BalsaHeaders headers;
+  headers.AppendHeader("key1", "value1");
+  headers.AppendHeader("key 2", "value\n 2");
+  headers.AppendHeader("key\n 3", "value3");
+  headers.AppendHeader("key 2", "value 21");
+  headers.AppendHeader("key 3", "value 33");
+  std::string expected_non_coalesced =
+      "\r\n"
+      "key1: value1\r\n"
+      "key 2: value\n"
+      " 2\r\n"
+      "key\n"
+      " 3: value3\r\n"
+      "key 2: value 21\r\n"
+      "key 3: value 33\r\n";
+
+  SimpleBuffer simple_buffer;
+  headers.WriteToBuffer(&simple_buffer);
+  EXPECT_EQ(simple_buffer.GetReadableRegion(), expected_non_coalesced);
+
+  std::string expected_coalesced =
+      "key1: value1\r\n"
+      "key 2: value\n"
+      " 2,value 21\r\n"
+      "key\n"
+      " 3: value3\r\n"
+      "key 3: value 33\r\n";
+
+  simple_buffer.Clear();
+  headers.WriteToBufferCoalescingMultivaluedHeaders(
+      &simple_buffer, multivalued_headers,
+      BalsaHeaders::CaseOption::kNoModification);
+  EXPECT_EQ(simple_buffer.GetReadableRegion(), expected_coalesced);
+}
+
+TEST(BalsaHeaders, WriteToBufferCoalescingEnvoyHeaders) {
+  BalsaHeaders headers;
+  headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0");
+  headers.AppendHeader("User-Agent", "UserAgent1");
+  headers.AppendHeader("Key2", "value2");
+  headers.AppendHeader("USER-AGENT", "UA2");
+  headers.AppendHeader("Set-Cookie", "Cookie1=aaa");
+  headers.AppendHeader("user-agent", "agent3");
+  headers.AppendHeader("Set-Cookie", "Cookie2=bbb");
+  std::string expected_non_coalesced =
+      "GET / HTTP/1.0\r\n"
+      "User-Agent: UserAgent1\r\n"
+      "Key2: value2\r\n"
+      "USER-AGENT: UA2\r\n"
+      "Set-Cookie: Cookie1=aaa\r\n"
+      "user-agent: agent3\r\n"
+      "Set-Cookie: Cookie2=bbb\r\n"
+      "\r\n";
+  std::string expected_coalesced =
+      "GET / HTTP/1.0\r\n"
+      "User-Agent: UserAgent1,UA2,agent3\r\n"
+      "Key2: value2\r\n"
+      "Set-Cookie: Cookie1=aaa\r\n"
+      "Set-Cookie: Cookie2=bbb\r\n"
+      "\r\n";
+
+  SimpleBuffer simple_buffer;
+  headers.WriteHeaderAndEndingToBuffer(&simple_buffer);
+  EXPECT_EQ(simple_buffer.GetReadableRegion(), expected_non_coalesced);
+
+  simple_buffer.Clear();
+  headers.WriteHeaderAndEndingToBuffer(
+      &simple_buffer, BalsaHeaders::CaseOption::kNoModification,
+      BalsaHeaders::CoalesceOption::kCoalesce);
+  EXPECT_EQ(simple_buffer.GetReadableRegion(), expected_coalesced);
+}
+
+TEST(BalsaHeadersTest, GfeHeaderPolicyGfeBug) {
+  BalsaHeaders headers;
+  headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0");
+  headers.AppendHeader("foo", "bar");
+  EXPECT_QUICHE_BUG(headers.AppendHeader("X-Goog-Wombat", "tree"),
+                    "Header X-Goog-Wombat violates go/gfe-header-policy");
+}
+
+TEST(BalsaHeadersTest, GfeHeaderPolicyExemptionNoGfeBug) {
+  BalsaHeaders headers;
+  headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0");
+  // Exempt header should not trigger a QUICHE_BUG.
+  headers.AppendHeader("X-Goog-Authenticated-User-ID", "id");
+  headers.AppendHeader("x-goog-authenticated-user-id", "id");
+}
+
+TEST(BalsaHeadersTest, RemoveLastTokenFromOneLineHeader) {
+  BalsaHeaders headers =
+      CreateHTTPHeaders(true,
+                        "GET /foo HTTP/1.1\r\n"
+                        "Content-Length: 0\r\n"
+                        "Content-Encoding: gzip, 3des, tar, prc\r\n\r\n");
+
+  BalsaHeaders::const_header_lines_key_iterator it =
+      headers.GetIteratorForKey("Content-Encoding");
+  ASSERT_EQ("gzip, 3des, tar, prc", it->second);
+  EXPECT_EQ(headers.header_lines_key_end(), ++it);
+
+  headers.RemoveLastTokenFromHeaderValue("Content-Encoding");
+  it = headers.GetIteratorForKey("Content-Encoding");
+  ASSERT_EQ("gzip, 3des, tar", it->second);
+  EXPECT_EQ(headers.header_lines_key_end(), ++it);
+
+  headers.RemoveLastTokenFromHeaderValue("Content-Encoding");
+  it = headers.GetIteratorForKey("Content-Encoding");
+  ASSERT_EQ("gzip, 3des", it->second);
+  EXPECT_EQ(headers.header_lines_key_end(), ++it);
+
+  headers.RemoveLastTokenFromHeaderValue("Content-Encoding");
+  it = headers.GetIteratorForKey("Content-Encoding");
+  ASSERT_EQ("gzip", it->second);
+  EXPECT_EQ(headers.header_lines_key_end(), ++it);
+
+  headers.RemoveLastTokenFromHeaderValue("Content-Encoding");
+
+  EXPECT_FALSE(headers.HasHeader("Content-Encoding"));
+}
+
+TEST(BalsaHeadersTest, RemoveLastTokenFromMultiLineHeader) {
+  BalsaHeaders headers =
+      CreateHTTPHeaders(true,
+                        "GET /foo HTTP/1.1\r\n"
+                        "Content-Length: 0\r\n"
+                        "Content-Encoding: gzip, 3des\r\n"
+                        "Content-Encoding: tar, prc\r\n\r\n");
+
+  BalsaHeaders::const_header_lines_key_iterator it =
+      headers.GetIteratorForKey("Content-Encoding");
+  ASSERT_EQ("gzip, 3des", it->second);
+  ASSERT_EQ("tar, prc", (++it)->second);
+  ASSERT_EQ(headers.header_lines_key_end(), ++it);
+
+  // First, we should start removing tokens from the second line.
+  headers.RemoveLastTokenFromHeaderValue("Content-Encoding");
+  it = headers.GetIteratorForKey("Content-Encoding");
+  ASSERT_EQ("gzip, 3des", it->second);
+  ASSERT_EQ("tar", (++it)->second);
+  ASSERT_EQ(headers.header_lines_key_end(), ++it);
+
+  // Second line should be entirely removed after all its tokens are gone.
+  headers.RemoveLastTokenFromHeaderValue("Content-Encoding");
+  it = headers.GetIteratorForKey("Content-Encoding");
+  ASSERT_EQ("gzip, 3des", it->second);
+  ASSERT_EQ(headers.header_lines_key_end(), ++it);
+
+  // Now we should be removing the tokens from the first line.
+  headers.RemoveLastTokenFromHeaderValue("Content-Encoding");
+  it = headers.GetIteratorForKey("Content-Encoding");
+  ASSERT_EQ("gzip", it->second);
+  ASSERT_EQ(headers.header_lines_key_end(), ++it);
+
+  headers.RemoveLastTokenFromHeaderValue("Content-Encoding");
+  EXPECT_FALSE(headers.HasHeader("Content-Encoding"));
+}
+
+TEST(BalsaHeadersTest, ResponseCanHaveBody) {
+  // 1xx, 204 no content and 304 not modified responses can't have bodies.
+  EXPECT_FALSE(BalsaHeaders::ResponseCanHaveBody(100));
+  EXPECT_FALSE(BalsaHeaders::ResponseCanHaveBody(101));
+  EXPECT_FALSE(BalsaHeaders::ResponseCanHaveBody(102));
+  EXPECT_FALSE(BalsaHeaders::ResponseCanHaveBody(204));
+  EXPECT_FALSE(BalsaHeaders::ResponseCanHaveBody(304));
+
+  // Other responses can have body.
+  EXPECT_TRUE(BalsaHeaders::ResponseCanHaveBody(200));
+  EXPECT_TRUE(BalsaHeaders::ResponseCanHaveBody(302));
+  EXPECT_TRUE(BalsaHeaders::ResponseCanHaveBody(404));
+  EXPECT_TRUE(BalsaHeaders::ResponseCanHaveBody(502));
+}
+
+}  // namespace
+
+}  // namespace test
+
+}  // namespace quiche