Generate and parse extended Google QUIC Error Code in IETF Conn. Close frames.
Add code to prepend "<QuicErrorCode>:" to the error string in IETF QUIC
Connection Close Frames and to detect/parse the string back out and set QuicConnectionCloseFrame::extracted_error_code.
Google QUIC Error Codes are richer than standardized IETF QUIC Error Codes. This CL includes the Google QUIC Error Code in the error details string, allowing
the richer information to be retained when IETF QUIC is used. This is important since it lets us continue to use the current varz's for operational diagnosis/etc.
gfe-relnote: N/A IETF QUIC/v99 code only.
PiperOrigin-RevId: 261676212
Change-Id: Icf9287eb59224bd8bd5e6620827c1ddba67872b3
diff --git a/quic/core/quic_framer.cc b/quic/core/quic_framer.cc
index 91d4a0a..426f0de 100644
--- a/quic/core/quic_framer.cc
+++ b/quic/core/quic_framer.cc
@@ -456,6 +456,17 @@
: IETF_QUIC_SHORT_HEADER_PACKET;
}
+std::string GenerateErrorString(std::string initial_error_string,
+ QuicErrorCode quic_error_code) {
+ if (quic_error_code == QUIC_IETF_GQUIC_ERROR_MISSING) {
+ // QUIC_IETF_GQUIC_ERROR_MISSING is special -- it means not to encode
+ // the error value in the string.
+ return initial_error_string;
+ }
+ return QuicStrCat(std::to_string(static_cast<unsigned>(quic_error_code)), ":",
+ initial_error_string);
+}
+
} // namespace
QuicFramer::QuicFramer(const ParsedQuicVersionVector& supported_versions,
@@ -577,18 +588,19 @@
kQuicErrorDetailsLengthSize +
TruncatedErrorStringSize(frame.error_details);
}
- // TODO(fkastenholz): For complete support of IETF QUIC CONNECTION_CLOSE,
- // check if the frame is a Transport close and if the frame's
- // extracted_error_code is not QUIC_IETF_GQUIC_ERROR_MISSING. If so,
- // extend the error string to include " QuicErrorCode: #"
- const size_t truncated_error_string_size =
- TruncatedErrorStringSize(frame.error_details);
+
+ // Prepend the extra error information to the string and get the result's
+ // length.
+ const size_t truncated_error_string_size = TruncatedErrorStringSize(
+ GenerateErrorString(frame.error_details, frame.extracted_error_code));
+
uint64_t close_code = 0;
if (frame.close_type == IETF_QUIC_TRANSPORT_CONNECTION_CLOSE) {
close_code = static_cast<uint64_t>(frame.transport_error_code);
} else if (frame.close_type == IETF_QUIC_APPLICATION_CONNECTION_CLOSE) {
close_code = static_cast<uint64_t>(frame.application_error_code);
}
+
const size_t frame_size =
truncated_error_string_size +
QuicDataWriter::GetVarInt62Len(truncated_error_string_size) +
@@ -596,7 +608,8 @@
if (frame.close_type == IETF_QUIC_APPLICATION_CONNECTION_CLOSE) {
return frame_size;
}
- // frame includes the transport_close_frame_type, so include its length.
+ // The Transport close frame has the transport_close_frame_type, so include
+ // its length.
return frame_size +
QuicDataWriter::GetVarInt62Len(frame.transport_close_frame_type);
}
@@ -5755,12 +5768,13 @@
}
}
- // TODO(fkastenholz): For full IETF CONNECTION CLOSE support,
- // if this is a Transport CONNECTION_CLOSE and the extended
- // error is not QUIC_IETF_GQUIC_ERROR_MISSING then append the extended
- // "QuicErrorCode: #" string to the phrase.
+ // There may be additional error information available in the extracted error
+ // code. Encode the error information in the reason phrase and serialize the
+ // result.
+ std::string final_error_string =
+ GenerateErrorString(frame.error_details, frame.extracted_error_code);
if (!writer->WriteStringPieceVarInt62(
- TruncateErrorString(frame.error_details))) {
+ TruncateErrorString(final_error_string))) {
set_detailed_error("Can not write connection close phrase");
return false;
}
@@ -5773,11 +5787,14 @@
QuicConnectionCloseFrame* frame) {
frame->close_type = type;
uint64_t error_code;
+
if (!reader->ReadVarInt62(&error_code)) {
set_detailed_error("Unable to read connection close error code.");
return false;
}
+ // TODO(fkastenholz): When error codes uniformly go to uint64, remove the
+ // range check.
if (frame->close_type == IETF_QUIC_TRANSPORT_CONNECTION_CLOSE) {
if (error_code > 0xffff) {
frame->transport_error_code =
@@ -5796,6 +5813,7 @@
frame->application_error_code = static_cast<uint16_t>(error_code);
}
}
+
if (type == IETF_QUIC_TRANSPORT_CONNECTION_CLOSE) {
// The frame-type of the frame causing the error is present only
// if it's a CONNECTION_CLOSE/Transport.
@@ -5810,16 +5828,18 @@
set_detailed_error("Unable to read connection close error details.");
return false;
}
+
QuicStringPiece phrase;
if (!reader->ReadStringPiece(&phrase, static_cast<size_t>(phrase_length))) {
set_detailed_error("Unable to read connection close error details.");
return false;
}
- // TODO(fkastenholz): when full support is done, add code here
- // to extract the extended error code from the reason phrase
- // and set it into frame->extracted_error_code.
frame->error_details = std::string(phrase);
+ // The frame may have an extracted error code in it. Look for it and
+ // extract it. If it's not present, MaybeExtract will return
+ // QUIC_IETF_GQUIC_ERROR_MISSING.
+ frame->extracted_error_code = MaybeExtractQuicErrorCode(phrase);
return true;
}
@@ -6773,5 +6793,21 @@
return true;
}
+// Look for and parse the error code from the "<quic_error_code>:" text that
+// may be present at the start of the CONNECTION_CLOSE error details string.
+// This text, inserted by the peer if it's using Google's QUIC implementation,
+// contains additional error information that narrows down the exact error. If
+// the string is not found, or is not properly formed, it returns
+// ErrorCode::QUIC_IETF_GQUIC_ERROR_MISSING
+QuicErrorCode MaybeExtractQuicErrorCode(QuicStringPiece error_details) {
+ std::vector<QuicStringPiece> ed = QuicTextUtils::Split(error_details, ':');
+ uint64_t extracted_error_code;
+ if (ed.size() < 2 || !QuicTextUtils::IsAllDigits(ed[0]) ||
+ !QuicTextUtils::StringToUint64(ed[0], &extracted_error_code)) {
+ return QUIC_IETF_GQUIC_ERROR_MISSING;
+ }
+ return static_cast<QuicErrorCode>(extracted_error_code);
+}
+
#undef ENDPOINT // undef for jumbo builds
} // namespace quic
diff --git a/quic/core/quic_framer.h b/quic/core/quic_framer.h
index 94632c6..a5891f0 100644
--- a/quic/core/quic_framer.h
+++ b/quic/core/quic_framer.h
@@ -1091,6 +1091,14 @@
uint32_t local_ack_delay_exponent_;
};
+// Look for and parse the error code from the "<quic_error_code>:" text that
+// may be present at the start of the CONNECTION_CLOSE error details string.
+// This text, inserted by the peer if it's using Google's QUIC implementation,
+// contains additional error information that narrows down the exact error. If
+// the string is not found, or is not properly formed, it returns
+// ErrorCode::QUIC_IETF_GQUIC_ERROR_MISSING
+QuicErrorCode MaybeExtractQuicErrorCode(QuicStringPiece error_details);
+
} // namespace quic
#endif // QUICHE_QUIC_CORE_QUIC_FRAMER_H_
diff --git a/quic/core/quic_framer_test.cc b/quic/core/quic_framer_test.cc
index 32432fb..a853829 100644
--- a/quic/core/quic_framer_test.cc
+++ b/quic/core/quic_framer_test.cc
@@ -4735,7 +4735,7 @@
// packet number
{"",
{0x12, 0x34, 0x56, 0x78}},
- // frame type (IETF_CONNECTION_CLOSE frame)
+ // frame type (IETF Transport CONNECTION_CLOSE frame)
{"",
{0x1c}},
// error code
@@ -4780,6 +4780,171 @@
if (VersionHasIetfQuicFrames(framer_.transport_version())) {
EXPECT_EQ(0x1234u,
visitor_.connection_close_frame_.transport_close_frame_type);
+ EXPECT_EQ(QUIC_IETF_GQUIC_ERROR_MISSING,
+ visitor_.connection_close_frame_.extracted_error_code);
+ }
+
+ ASSERT_EQ(0u, visitor_.ack_frames_.size());
+
+ CheckFramingBoundaries(fragments, QUIC_INVALID_CONNECTION_CLOSE_DATA);
+}
+
+// As above, but checks that for Google-QUIC, if there happens
+// to be an ErrorCode string at the start of the details, it is
+// NOT extracted/parsed/folded/spindled/and/mutilated.
+TEST_P(QuicFramerTest, ConnectionCloseFrameWithExtractedInfoIgnoreGCuic) {
+ SetDecrypterLevel(ENCRYPTION_FORWARD_SECURE);
+
+ // clang-format off
+ PacketFragments packet = {
+ // public flags (8 byte connection_id)
+ {"",
+ {0x28}},
+ // connection_id
+ {"",
+ {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+ // packet number
+ {"",
+ {0x12, 0x34, 0x56, 0x78}},
+ // frame type (connection close frame)
+ {"",
+ {0x02}},
+ // error code
+ {"Unable to read connection close error code.",
+ {0x00, 0x00, 0x00, 0x11}},
+ {"Unable to read connection close error details.",
+ {
+ // error details length
+ 0x0, 0x13,
+ // error details
+ '1', '7', '7', '6',
+ '7', ':', 'b', 'e',
+ 'c', 'a', 'u', 's',
+ 'e', ' ', 'I', ' ',
+ 'c', 'a', 'n'}
+ }
+ };
+
+ PacketFragments packet44 = {
+ // type (short header, 4 byte packet number)
+ {"",
+ {0x32}},
+ // connection_id
+ {"",
+ {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+ // packet number
+ {"",
+ {0x12, 0x34, 0x56, 0x78}},
+ // frame type (IETF Transport CONNECTION_CLOSE frame)
+ {"",
+ {0x02}},
+ // error code
+ {"Unable to read connection close error code.",
+ {0x00, 0x00, 0x00, 0x11}},
+ {"Unable to read connection close error details.",
+ {
+ // error details length
+ 0x00, 0x13,
+ // error details
+ '1', '7', '7', '6',
+ '7', ':', 'b', 'e',
+ 'c', 'a', 'u', 's',
+ 'e', ' ', 'I', ' ',
+ 'c', 'a', 'n'}
+ }
+ };
+
+ PacketFragments packet46 = {
+ // type (short header, 4 byte packet number)
+ {"",
+ {0x43}},
+ // connection_id
+ {"",
+ {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+ // packet number
+ {"",
+ {0x12, 0x34, 0x56, 0x78}},
+ // frame type (connection close frame)
+ {"",
+ {0x02}},
+ // error code
+ {"Unable to read connection close error code.",
+ {0x00, 0x00, 0x00, 0x11}},
+ {"Unable to read connection close error details.",
+ {
+ // error details length
+ 0x0, 0x13,
+ // error details
+ '1', '7', '7', '6',
+ '7', ':', 'b', 'e',
+ 'c', 'a', 'u', 's',
+ 'e', ' ', 'I', ' ',
+ 'c', 'a', 'n'}
+ }
+ };
+
+ PacketFragments packet99 = {
+ // type (short header, 4 byte packet number)
+ {"",
+ {0x43}},
+ // connection_id
+ {"",
+ {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+ // packet number
+ {"",
+ {0x12, 0x34, 0x56, 0x78}},
+ // frame type (IETF Transport CONNECTION_CLOSE frame)
+ {"",
+ {0x1c}},
+ // error code
+ {"Unable to read connection close error code.",
+ {kVarInt62OneByte + 0x11}},
+ {"Unable to read connection close frame type.",
+ {kVarInt62TwoBytes + 0x12, 0x34 }},
+ {"Unable to read connection close error details.",
+ {
+ // error details length
+ kVarInt62OneByte + 0x13,
+ // error details
+ '1', '7', '7', '6',
+ '7', ':', 'b', 'e',
+ 'c', 'a', 'u', 's',
+ 'e', ' ', 'I', ' ',
+ 'c', 'a', 'n'}
+ }
+ };
+ // clang-format on
+
+ PacketFragments& fragments =
+ VersionHasIetfQuicFrames(framer_.transport_version())
+ ? packet99
+ : (framer_.transport_version() > QUIC_VERSION_44
+ ? packet46
+ : (framer_.transport_version() > QUIC_VERSION_43 ? packet44
+ : packet));
+ std::unique_ptr<QuicEncryptedPacket> encrypted(
+ AssemblePacketFromFragments(fragments));
+ EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+
+ EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+ ASSERT_TRUE(visitor_.header_.get());
+ EXPECT_TRUE(CheckDecryption(
+ *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+ PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+ EXPECT_EQ(0u, visitor_.stream_frames_.size());
+ EXPECT_EQ(0x11u, static_cast<unsigned>(
+ visitor_.connection_close_frame_.quic_error_code));
+ // For this test, all versions have the QuicErrorCode tag
+ EXPECT_EQ("17767:because I can",
+ visitor_.connection_close_frame_.error_details);
+ if (VersionHasIetfQuicFrames(framer_.transport_version())) {
+ EXPECT_EQ(0x1234u,
+ visitor_.connection_close_frame_.transport_close_frame_type);
+ EXPECT_EQ(17767u, visitor_.connection_close_frame_.extracted_error_code);
+ } else {
+ // QUIC_IETF_GQUIC_ERROR_MISSING is 122
+ EXPECT_EQ(122u, visitor_.connection_close_frame_.extracted_error_code);
}
ASSERT_EQ(0u, visitor_.ack_frames_.size());
@@ -4848,6 +5013,69 @@
CheckFramingBoundaries(packet99, QUIC_INVALID_CONNECTION_CLOSE_DATA);
}
+// Check that we can extract an error code from an application close.
+TEST_P(QuicFramerTest, ApplicationCloseFrameExtract) {
+ if (!VersionHasIetfQuicFrames(framer_.transport_version())) {
+ // This frame does not exist in versions other than 99.
+ return;
+ }
+ SetDecrypterLevel(ENCRYPTION_FORWARD_SECURE);
+
+ // clang-format off
+ PacketFragments packet99 = {
+ // type (short header, 4 byte packet number)
+ {"",
+ {0x43}},
+ // connection_id
+ {"",
+ {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+ // packet number
+ {"",
+ {0x12, 0x34, 0x56, 0x78}},
+ // frame type (IETF_CONNECTION_CLOSE/Application frame)
+ {"",
+ {0x1d}},
+ // error code
+ {"Unable to read connection close error code.",
+ {kVarInt62OneByte + 0x11}},
+ {"Unable to read connection close error details.",
+ {
+ // error details length
+ kVarInt62OneByte + 0x13,
+ // error details
+ '1', '7', '7', '6',
+ '7', ':', 'b', 'e',
+ 'c', 'a', 'u', 's',
+ 'e', ' ', 'I', ' ',
+ 'c', 'a', 'n'}
+ }
+ };
+ // clang-format on
+
+ std::unique_ptr<QuicEncryptedPacket> encrypted(
+ AssemblePacketFromFragments(packet99));
+ EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+
+ EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+ ASSERT_TRUE(visitor_.header_.get());
+ EXPECT_TRUE(CheckDecryption(
+ *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+ PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+ EXPECT_EQ(0u, visitor_.stream_frames_.size());
+
+ EXPECT_EQ(IETF_QUIC_APPLICATION_CONNECTION_CLOSE,
+ visitor_.connection_close_frame_.close_type);
+ EXPECT_EQ(17767u, visitor_.connection_close_frame_.extracted_error_code);
+ EXPECT_EQ(0x11u, visitor_.connection_close_frame_.quic_error_code);
+ EXPECT_EQ("17767:because I can",
+ visitor_.connection_close_frame_.error_details);
+
+ ASSERT_EQ(0u, visitor_.ack_frames_.size());
+
+ CheckFramingBoundaries(packet99, QUIC_INVALID_CONNECTION_CLOSE_DATA);
+}
+
TEST_P(QuicFramerTest, GoAwayFrame) {
if (VersionHasIetfQuicFrames(framer_.transport_version())) {
// This frame is not supported in version 99.
@@ -8115,6 +8343,142 @@
data->length(), AsChars(p), p_size);
}
+TEST_P(QuicFramerTest, BuildCloseFramePacketExtendedInfo) {
+ QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
+ QuicPacketHeader header;
+ header.destination_connection_id = FramerTestConnectionId();
+ header.reset_flag = false;
+ header.version_flag = false;
+ header.packet_number = kPacketNumber;
+
+ QuicConnectionCloseFrame close_frame;
+ if (VersionHasIetfQuicFrames(framer_.transport_version())) {
+ close_frame.transport_error_code =
+ static_cast<QuicIetfTransportErrorCodes>(0x11);
+ close_frame.transport_close_frame_type = 0x05;
+ close_frame.close_type = IETF_QUIC_TRANSPORT_CONNECTION_CLOSE;
+ } else {
+ close_frame.quic_error_code = static_cast<QuicErrorCode>(0x05060708);
+ }
+ // Set this so that it is "there" for both Google QUIC and IETF QUIC
+ // framing. It better not show up for Google QUIC!
+ close_frame.extracted_error_code = static_cast<QuicErrorCode>(0x4567);
+
+ // For IETF QUIC this will be prefaced with "17767:"
+ // (17767 == 0x4567).
+ close_frame.error_details = "because I can";
+
+ QuicFrames frames = {QuicFrame(&close_frame)};
+
+ // clang-format off
+ unsigned char packet[] = {
+ // public flags (8 byte connection_id)
+ 0x2C,
+ // connection_id
+ 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+ // packet number
+ 0x12, 0x34, 0x56, 0x78,
+
+ // frame type (connection close frame)
+ 0x02,
+ // error code
+ 0x05, 0x06, 0x07, 0x08,
+ // error details length
+ 0x00, 0x0d,
+ // error details
+ 'b', 'e', 'c', 'a',
+ 'u', 's', 'e', ' ',
+ 'I', ' ', 'c', 'a',
+ 'n',
+ };
+
+ unsigned char packet44[] = {
+ // type (short header, 4 byte packet number)
+ 0x32,
+ // connection_id
+ 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+ // packet number
+ 0x12, 0x34, 0x56, 0x78,
+
+ // frame type (connection close frame)
+ 0x02,
+ // error code
+ 0x05, 0x06, 0x07, 0x08,
+ // error details length
+ 0x00, 0x0d,
+ // error details
+ 'b', 'e', 'c', 'a',
+ 'u', 's', 'e', ' ',
+ 'I', ' ', 'c', 'a',
+ 'n',
+ };
+
+ unsigned char packet46[] = {
+ // type (short header, 4 byte packet number)
+ 0x43,
+ // connection_id
+ 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+ // packet number
+ 0x12, 0x34, 0x56, 0x78,
+
+ // frame type (connection close frame)
+ 0x02,
+ // error code
+ 0x05, 0x06, 0x07, 0x08,
+ // error details length
+ 0x00, 0x0d,
+ // error details
+ 'b', 'e', 'c', 'a',
+ 'u', 's', 'e', ' ',
+ 'I', ' ', 'c', 'a',
+ 'n',
+ };
+
+ unsigned char packet99[] = {
+ // type (short header, 4 byte packet number)
+ 0x43,
+ // connection_id
+ 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+ // packet number
+ 0x12, 0x34, 0x56, 0x78,
+
+ // frame type (IETF_CONNECTION_CLOSE frame)
+ 0x1c,
+ // error code
+ kVarInt62OneByte + 0x11,
+ // Frame type within the CONNECTION_CLOSE frame
+ kVarInt62OneByte + 0x05,
+ // error details length
+ kVarInt62OneByte + 0x13,
+ // error details
+ '1', '7', '7', '6',
+ '7', ':', 'b', 'e',
+ 'c', 'a', 'u', 's',
+ 'e', ' ', 'I', ' ',
+ 'c', 'a', 'n'
+ };
+ // clang-format on
+
+ unsigned char* p = packet;
+ size_t p_size = QUIC_ARRAYSIZE(packet);
+ if (VersionHasIetfQuicFrames(framer_.transport_version())) {
+ p = packet99;
+ p_size = QUIC_ARRAYSIZE(packet99);
+ } else if (framer_.transport_version() > QUIC_VERSION_44) {
+ p = packet46;
+ p_size = QUIC_ARRAYSIZE(packet46);
+ } else if (framer_.transport_version() > QUIC_VERSION_43) {
+ p = packet44;
+ p_size = QUIC_ARRAYSIZE(packet44);
+ }
+
+ std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+ ASSERT_TRUE(data != nullptr);
+
+ test::CompareCharArraysWithHexError("constructed packet", data->data(),
+ data->length(), AsChars(p), p_size);
+}
+
TEST_P(QuicFramerTest, BuildTruncatedCloseFramePacket) {
QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
QuicPacketHeader header;
@@ -14429,6 +14793,40 @@
EXPECT_EQ("", detailed_error);
}
+TEST_P(QuicFramerTest, TestExtendedErrorCodeParser) {
+ if (VersionHasIetfQuicFrames(framer_.transport_version())) {
+ return;
+ }
+ EXPECT_EQ(QUIC_IETF_GQUIC_ERROR_MISSING,
+ MaybeExtractQuicErrorCode("this has no error code info in it"));
+ EXPECT_EQ(
+ QUIC_IETF_GQUIC_ERROR_MISSING,
+ MaybeExtractQuicErrorCode("1234this does not have the colon in it"));
+ EXPECT_EQ(QUIC_IETF_GQUIC_ERROR_MISSING,
+ MaybeExtractQuicErrorCode(
+ "1a234:this has a colon, but a malformed error number"));
+ EXPECT_EQ(1234u, MaybeExtractQuicErrorCode("1234:this is good"));
+ EXPECT_EQ(QUIC_IETF_GQUIC_ERROR_MISSING,
+ MaybeExtractQuicErrorCode(
+ "1234 :this is not good, space between last digit and colon"));
+ EXPECT_EQ(
+ QUIC_IETF_GQUIC_ERROR_MISSING,
+ MaybeExtractQuicErrorCode("123456789")); // Not good, all numbers, no :
+ EXPECT_EQ(1234u, MaybeExtractQuicErrorCode("1234:")); // corner case.
+ EXPECT_EQ(1234u,
+ MaybeExtractQuicErrorCode("1234:5678")); // another corner case.
+ EXPECT_EQ(QUIC_IETF_GQUIC_ERROR_MISSING,
+ MaybeExtractQuicErrorCode("12345 6789:")); // Not good
+ EXPECT_EQ(QUIC_IETF_GQUIC_ERROR_MISSING,
+ MaybeExtractQuicErrorCode(":no numbers, is not good"));
+ EXPECT_EQ(QUIC_IETF_GQUIC_ERROR_MISSING,
+ MaybeExtractQuicErrorCode("qwer:also no numbers, is not good"));
+ EXPECT_EQ(QUIC_IETF_GQUIC_ERROR_MISSING,
+ MaybeExtractQuicErrorCode(
+ " 1234:this is not good, space before first digit"));
+ EXPECT_EQ(1234u, MaybeExtractQuicErrorCode("1234:")); // this is good
+}
+
} // namespace
} // namespace test
} // namespace quic
diff --git a/quic/platform/api/quic_text_utils.h b/quic/platform/api/quic_text_utils.h
index 8db44ce..1fa3c72 100644
--- a/quic/platform/api/quic_text_utils.h
+++ b/quic/platform/api/quic_text_utils.h
@@ -109,6 +109,11 @@
return QuicTextUtilsImpl::ContainsUpperCase(data);
}
+ // Returns true if |data| contains only decimal digits.
+ static bool IsAllDigits(QuicStringPiece data) {
+ return QuicTextUtilsImpl::IsAllDigits(data);
+ }
+
// Splits |data| into a vector of pieces delimited by |delim|.
static std::vector<QuicStringPiece> Split(QuicStringPiece data, char delim) {
return QuicTextUtilsImpl::Split(data, delim);