Allow parsing PROX packets with length prefix

The PROX version is special in that it has custom data after the first 9 bytes of the connection IDs. That means that trying to parse it with length prefix as an unknown QUIC version means that a random payload byte can be interpreted as the source connection ID length. This CL makes parsing succeed in that case, ignoring the source connection ID. This allows a previously existing test to pass.

gfe-relnote: allow parsing PROX packets with length prefix, protected by gfe2_reloadable_flag_quic_parse_prox_source_connection_id

Startblock:
  after 2019-09-24 08:00 in US/Pacific
PiperOrigin-RevId: 271678159
Change-Id: Ib079697029d7378cd7297e2400cefe8be33fad43
diff --git a/quic/core/quic_framer.cc b/quic/core/quic_framer.cc
index 84dd00a..a6a663b 100644
--- a/quic/core/quic_framer.cc
+++ b/quic/core/quic_framer.cc
@@ -6366,6 +6366,8 @@
 
 namespace {
 
+const QuicVersionLabel kProxVersionLabel = 0x50524F58;  // "PROX"
+
 inline bool PacketHasLengthPrefixedConnectionIds(
     const QuicDataReader& reader,
     ParsedQuicVersion parsed_version,
@@ -6396,7 +6398,7 @@
 
   // Check for munged packets with version tag PROX.
   if ((connection_id_length_byte & 0x0f) == 0 &&
-      connection_id_length_byte >= 0x20 && version_label == 0x50524F58) {
+      connection_id_length_byte >= 0x20 && version_label == kProxVersionLabel) {
     return false;
   }
 
@@ -6406,6 +6408,7 @@
 inline bool ParseLongHeaderConnectionIds(
     QuicDataReader* reader,
     bool has_length_prefix,
+    QuicVersionLabel version_label,
     QuicConnectionId* destination_connection_id,
     QuicConnectionId* source_connection_id,
     std::string* detailed_error) {
@@ -6415,6 +6418,16 @@
       return false;
     }
     if (!reader->ReadLengthPrefixedConnectionId(source_connection_id)) {
+      if (GetQuicReloadableFlag(quic_parse_prox_source_connection_id) &&
+          version_label == kProxVersionLabel) {
+        QUIC_RELOADABLE_FLAG_COUNT(quic_parse_prox_source_connection_id);
+        // The "PROX" version does not follow the length-prefixed invariants,
+        // and can therefore attempt to read a payload byte and interpret it
+        // as the source connection ID length, which could fail to parse.
+        // In that scenario we keep the source connection ID empty but mark
+        // parsing as successful.
+        return true;
+      }
       *detailed_error = "Unable to read source connection ID.";
       return false;
     }
@@ -6525,7 +6538,7 @@
       *reader, *parsed_version, *version_label, *first_byte);
 
   // Parse connection IDs.
-  if (!ParseLongHeaderConnectionIds(reader, *has_length_prefix,
+  if (!ParseLongHeaderConnectionIds(reader, *has_length_prefix, *version_label,
                                     destination_connection_id,
                                     source_connection_id, detailed_error)) {
     return QUIC_INVALID_PACKET_HEADER;
diff --git a/quic/core/quic_framer_test.cc b/quic/core/quic_framer_test.cc
index d343c0d..9bc4076 100644
--- a/quic/core/quic_framer_test.cc
+++ b/quic/core/quic_framer_test.cc
@@ -1283,6 +1283,69 @@
   }
 }
 
+TEST_P(QuicFramerTest, ParsePublicHeaderProxBadSourceConnectionIdLength) {
+  if (!framer_.version().HasLengthPrefixedConnectionIds()) {
+    return;
+  }
+  SetQuicReloadableFlag(quic_parse_prox_source_connection_id, true);
+  // clang-format off
+  unsigned char packet[] = {
+    // public flags (long header with packet type HANDSHAKE and
+    // 4-byte packet number)
+    0xE3,
+    // version
+    'P', 'R', 'O', 'X',
+    // destination connection ID length
+    0x08,
+    // destination connection ID
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // source connection ID length (bogus)
+    0xEE,
+    // long header packet length
+    0x05,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+    // padding frame
+    0x00,
+  };
+  // clang-format on
+  unsigned char* p = packet;
+  size_t p_length = QUIC_ARRAYSIZE(packet);
+
+  uint8_t first_byte = 0x33;
+  PacketHeaderFormat format = GOOGLE_QUIC_PACKET;
+  bool version_present = false, has_length_prefix = false;
+  QuicVersionLabel version_label = 0;
+  ParsedQuicVersion parsed_version = UnsupportedQuicVersion();
+  QuicConnectionId destination_connection_id = EmptyQuicConnectionId(),
+                   source_connection_id = EmptyQuicConnectionId();
+  QuicLongHeaderType long_packet_type = INVALID_PACKET_TYPE;
+  QuicVariableLengthIntegerLength retry_token_length_length =
+      VARIABLE_LENGTH_INTEGER_LENGTH_4;
+  QuicStringPiece retry_token;
+  std::string detailed_error = "foobar";
+
+  QuicDataReader reader(AsChars(p), p_length);
+  const QuicErrorCode parse_error = QuicFramer::ParsePublicHeader(
+      &reader, kQuicDefaultConnectionIdLength,
+      /*ietf_format=*/true, &first_byte, &format, &version_present,
+      &has_length_prefix, &version_label, &parsed_version,
+      &destination_connection_id, &source_connection_id, &long_packet_type,
+      &retry_token_length_length, &retry_token, &detailed_error);
+  EXPECT_EQ(QUIC_NO_ERROR, parse_error);
+  EXPECT_EQ("", detailed_error);
+  EXPECT_EQ(p[0], first_byte);
+  EXPECT_TRUE(version_present);
+  EXPECT_TRUE(has_length_prefix);
+  EXPECT_EQ(0x50524F58u, version_label);  // "PROX"
+  EXPECT_EQ(UnsupportedQuicVersion(), parsed_version);
+  EXPECT_EQ(FramerTestConnectionId(), destination_connection_id);
+  EXPECT_EQ(EmptyQuicConnectionId(), source_connection_id);
+  EXPECT_EQ(VARIABLE_LENGTH_INTEGER_LENGTH_0, retry_token_length_length);
+  EXPECT_EQ(QuicStringPiece(), retry_token);
+  EXPECT_EQ(IETF_QUIC_LONG_HEADER_PACKET, format);
+}
+
 TEST_P(QuicFramerTest, ClientConnectionIdFromShortHeaderToClient) {
   if (!framer_.version().SupportsClientConnectionIds()) {
     return;