/* * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * */ #include #include #include #include #include #include #include #include using namespace testing; namespace quic { namespace test { using UnderlyingFrameType = std::underlying_type::type; class DecodeTest : public Test {}; ShortHeader makeHeader() { PacketNum packetNum = 100; return ShortHeader( ProtectionType::KeyPhaseZero, getTestConnectionId(), packetNum); } // NormalizedAckBlocks are in order needed. struct NormalizedAckBlock { QuicInteger gap; // Gap to previous AckBlock QuicInteger blockLen; NormalizedAckBlock(QuicInteger gapIn, QuicInteger blockLenIn) : gap(gapIn), blockLen(blockLenIn) {} }; template std::unique_ptr createAckFrame( folly::Optional largestAcked, folly::Optional ackDelay = folly::none, folly::Optional numAdditionalBlocks = folly::none, folly::Optional firstAckBlockLength = folly::none, std::vector ackBlocks = {}, bool useRealValuesForLargestAcked = false, bool useRealValuesForAckDelay = false) { std::unique_ptr ackFrame = folly::IOBuf::create(0); BufAppender wcursor(ackFrame.get(), 10); auto appenderOp = [&](auto val) { wcursor.writeBE(val); }; if (largestAcked) { if (useRealValuesForLargestAcked) { wcursor.writeBE(largestAcked->getValue()); } else { largestAcked->encode(appenderOp); } } if (ackDelay) { if (useRealValuesForAckDelay) { wcursor.writeBE(ackDelay->getValue()); } else { ackDelay->encode(appenderOp); } } if (numAdditionalBlocks) { numAdditionalBlocks->encode(appenderOp); } if (firstAckBlockLength) { firstAckBlockLength->encode(appenderOp); } for (size_t i = 0; i < ackBlocks.size(); ++i) { ackBlocks[i].gap.encode(appenderOp); ackBlocks[i].blockLen.encode(appenderOp); } return ackFrame; } template std::unique_ptr createStreamFrame( folly::Optional streamId, folly::Optional offset = folly::none, folly::Optional dataLength = folly::none, Buf data = nullptr, bool useRealValuesForStreamId = false) { std::unique_ptr streamFrame = folly::IOBuf::create(0); BufAppender wcursor(streamFrame.get(), 10); auto appenderOp = [&](auto val) { wcursor.writeBE(val); }; if (streamId) { if (useRealValuesForStreamId) { wcursor.writeBE(streamId->getValue()); } else { streamId->encode(appenderOp); } } if (offset) { offset->encode(appenderOp); } if (dataLength) { dataLength->encode(appenderOp); } if (data) { wcursor.insert(std::move(data)); } return streamFrame; } std::unique_ptr createCryptoFrame( folly::Optional offset = folly::none, folly::Optional dataLength = folly::none, Buf data = nullptr) { std::unique_ptr cryptoFrame = folly::IOBuf::create(0); BufAppender wcursor(cryptoFrame.get(), 10); auto appenderOp = [&](auto val) { wcursor.writeBE(val); }; if (offset) { offset->encode(appenderOp); } if (dataLength) { dataLength->encode(appenderOp); } if (data) { wcursor.insert(std::move(data)); } return cryptoFrame; } TEST_F(DecodeTest, VersionNegotiationPacketDecodeTest) { ConnectionId srcCid = getTestConnectionId(0), destCid = getTestConnectionId(1); std::vector versions{{static_cast(1234), static_cast(4321), static_cast(2341), static_cast(3412), static_cast(4123)}}; auto packet = VersionNegotiationPacketBuilder(srcCid, destCid, versions).buildPacket(); auto codec = std::make_unique(QuicNodeType::Server); AckStates ackStates; auto packetQueue = bufToQueue(std::move(packet.second)); auto versionPacket = codec->tryParsingVersionNegotiation(packetQueue); ASSERT_TRUE(versionPacket.has_value()); EXPECT_EQ(versionPacket->destinationConnectionId, destCid); EXPECT_EQ(versionPacket->sourceConnectionId, srcCid); EXPECT_EQ(versionPacket->versions.size(), versions.size()); EXPECT_EQ(versionPacket->versions, versions); } TEST_F(DecodeTest, DifferentCIDLength) { ConnectionId sourceConnectionId = getTestConnectionId(); ConnectionId destinationConnectionId({1, 2, 3, 4, 5, 6}); std::vector versions{{static_cast(1234), static_cast(4321), static_cast(2341), static_cast(3412), static_cast(4123)}}; auto packet = VersionNegotiationPacketBuilder( sourceConnectionId, destinationConnectionId, versions) .buildPacket(); auto codec = std::make_unique(QuicNodeType::Server); AckStates ackStates; auto packetQueue = bufToQueue(std::move(packet.second)); auto versionPacket = codec->tryParsingVersionNegotiation(packetQueue); ASSERT_TRUE(versionPacket.has_value()); EXPECT_EQ(versionPacket->sourceConnectionId, sourceConnectionId); EXPECT_EQ(versionPacket->destinationConnectionId, destinationConnectionId); EXPECT_EQ(versionPacket->versions.size(), versions.size()); EXPECT_EQ(versionPacket->versions, versions); } TEST_F(DecodeTest, VersionNegotiationPacketBadPacketTest) { ConnectionId connId = getTestConnectionId(); QuicVersionType version = static_cast(QuicVersion::MVFST); auto buf = folly::IOBuf::create(10); folly::io::Appender appender(buf.get(), 10); appender.writeBE(kHeaderFormMask); appender.push(connId.data(), connId.size()); appender.writeBE( static_cast(QuicVersion::VERSION_NEGOTIATION)); appender.push((uint8_t*)&version, sizeof(QuicVersion) - 1); auto codec = std::make_unique(QuicNodeType::Server); AckStates ackStates; auto packetQueue = bufToQueue(std::move(buf)); auto packet = codec->parsePacket(packetQueue, ackStates); EXPECT_EQ(packet.regularPacket(), nullptr); buf = folly::IOBuf::create(0); packetQueue = bufToQueue(std::move(buf)); packet = codec->parsePacket(packetQueue, ackStates); // Packet with empty versions EXPECT_EQ(packet.regularPacket(), nullptr); } TEST_F(DecodeTest, ValidAckFrame) { QuicInteger largestAcked(1000); QuicInteger ackDelay(100); QuicInteger numAdditionalBlocks(1); QuicInteger firstAckBlockLength(10); std::vector ackBlocks; ackBlocks.emplace_back(QuicInteger(10), QuicInteger(10)); auto result = createAckFrame( largestAcked, ackDelay, numAdditionalBlocks, firstAckBlockLength, ackBlocks); folly::io::Cursor cursor(result.get()); auto ackFrame = decodeAckFrame( cursor, makeHeader(), CodecParameters(kDefaultAckDelayExponent, QuicVersion::MVFST)); EXPECT_EQ(ackFrame.ackBlocks.size(), 2); EXPECT_EQ(ackFrame.largestAcked, 1000); // Since 100 is the encoded value, we use the decoded value. EXPECT_EQ(ackFrame.ackDelay.count(), 100 << kDefaultAckDelayExponent); } TEST_F(DecodeTest, AckFrameLargestAckExceedsRange) { // An integer larger than the representable range of quic integer. QuicInteger largestAcked(std::numeric_limits::max()); QuicInteger ackDelay(10); QuicInteger numAdditionalBlocks(0); QuicInteger firstAckBlockLength(10); auto result = createAckFrame( largestAcked, ackDelay, numAdditionalBlocks, firstAckBlockLength, {}, true); folly::io::Cursor cursor(result.get()); auto ackFrame = decodeAckFrame( cursor, makeHeader(), CodecParameters(kDefaultAckDelayExponent, QuicVersion::MVFST)); // it will interpret this as a 8 byte range with the max value. EXPECT_EQ(ackFrame.largestAcked, 4611686018427387903); } TEST_F(DecodeTest, AckFrameLargestAckInvalid) { // An integer larger than the representable range of quic integer. QuicInteger largestAcked(std::numeric_limits::max()); QuicInteger ackDelay(10); QuicInteger numAdditionalBlocks(0); QuicInteger firstAckBlockLength(10); auto result = createAckFrame( largestAcked, ackDelay, numAdditionalBlocks, firstAckBlockLength, {}, true); folly::io::Cursor cursor(result.get()); EXPECT_THROW( decodeAckFrame( cursor, makeHeader(), CodecParameters(kDefaultAckDelayExponent, QuicVersion::MVFST)), QuicTransportException); } TEST_F(DecodeTest, AckFrameDelayEncodingInvalid) { QuicInteger largestAcked(1000); // Maximal representable value by quic integer. QuicInteger ackDelay(4611686018427387903); QuicInteger numAdditionalBlocks(0); QuicInteger firstAckBlockLength(10); auto result = createAckFrame( largestAcked, ackDelay, numAdditionalBlocks, firstAckBlockLength, {}, false, true); folly::io::Cursor cursor(result.get()); EXPECT_THROW( decodeAckFrame( cursor, makeHeader(), CodecParameters(kDefaultAckDelayExponent, QuicVersion::MVFST)), QuicTransportException); } TEST_F(DecodeTest, AckFrameDelayExceedsRange) { QuicInteger largestAcked(1000); // Maximal representable value by quic integer. QuicInteger ackDelay(4611686018427387903); QuicInteger numAdditionalBlocks(0); QuicInteger firstAckBlockLength(10); auto result = createAckFrame( largestAcked, ackDelay, numAdditionalBlocks, firstAckBlockLength); folly::io::Cursor cursor(result.get()); EXPECT_THROW( decodeAckFrame( cursor, makeHeader(), CodecParameters(kDefaultAckDelayExponent, QuicVersion::MVFST)), QuicTransportException); } TEST_F(DecodeTest, AckFrameAdditionalBlocksUnderflow) { QuicInteger largestAcked(1000); QuicInteger ackDelay(100); QuicInteger numAdditionalBlocks(2); QuicInteger firstAckBlockLength(10); std::vector ackBlocks; ackBlocks.emplace_back(QuicInteger(10), QuicInteger(10)); auto result = createAckFrame( largestAcked, ackDelay, numAdditionalBlocks, firstAckBlockLength, ackBlocks); folly::io::Cursor cursor(result.get()); EXPECT_THROW( decodeAckFrame( cursor, makeHeader(), CodecParameters(kDefaultAckDelayExponent, QuicVersion::MVFST)), QuicTransportException); } TEST_F(DecodeTest, AckFrameAdditionalBlocksOverflow) { QuicInteger largestAcked(1000); QuicInteger ackDelay(100); QuicInteger numAdditionalBlocks(2); QuicInteger firstAckBlockLength(10); std::vector ackBlocks; ackBlocks.emplace_back(QuicInteger(10), QuicInteger(10)); ackBlocks.emplace_back(QuicInteger(10), QuicInteger(10)); ackBlocks.emplace_back(QuicInteger(10), QuicInteger(10)); auto result = createAckFrame( largestAcked, ackDelay, numAdditionalBlocks, firstAckBlockLength, ackBlocks); folly::io::Cursor cursor(result.get()); decodeAckFrame( cursor, makeHeader(), CodecParameters(kDefaultAckDelayExponent, QuicVersion::MVFST)); } TEST_F(DecodeTest, AckFrameMissingFields) { QuicInteger largestAcked(1000); QuicInteger ackDelay(100); QuicInteger numAdditionalBlocks(2); QuicInteger firstAckBlockLength(10); std::vector ackBlocks; ackBlocks.emplace_back(QuicInteger(10), QuicInteger(10)); ackBlocks.emplace_back(QuicInteger(10), QuicInteger(10)); auto result1 = createAckFrame( largestAcked, folly::none, numAdditionalBlocks, firstAckBlockLength, ackBlocks); folly::io::Cursor cursor1(result1.get()); EXPECT_THROW( decodeAckFrame( cursor1, makeHeader(), CodecParameters(kDefaultAckDelayExponent, QuicVersion::MVFST)), QuicTransportException); auto result2 = createAckFrame( largestAcked, ackDelay, folly::none, firstAckBlockLength, ackBlocks); folly::io::Cursor cursor2(result2.get()); EXPECT_THROW( decodeAckFrame( cursor2, makeHeader(), CodecParameters(kDefaultAckDelayExponent, QuicVersion::MVFST)), QuicTransportException); auto result3 = createAckFrame( largestAcked, ackDelay, folly::none, firstAckBlockLength, ackBlocks); folly::io::Cursor cursor3(result3.get()); EXPECT_THROW( decodeAckFrame( cursor3, makeHeader(), CodecParameters(kDefaultAckDelayExponent, QuicVersion::MVFST)), QuicTransportException); auto result4 = createAckFrame( largestAcked, ackDelay, numAdditionalBlocks, folly::none, ackBlocks); folly::io::Cursor cursor4(result4.get()); EXPECT_THROW( decodeAckFrame( cursor4, makeHeader(), CodecParameters(kDefaultAckDelayExponent, QuicVersion::MVFST)), QuicTransportException); auto result5 = createAckFrame( largestAcked, ackDelay, numAdditionalBlocks, firstAckBlockLength, {}); folly::io::Cursor cursor5(result5.get()); EXPECT_THROW( decodeAckFrame( cursor5, makeHeader(), CodecParameters(kDefaultAckDelayExponent, QuicVersion::MVFST)), QuicTransportException); } TEST_F(DecodeTest, AckFrameFirstBlockLengthInvalid) { QuicInteger largestAcked(1000); QuicInteger ackDelay(100); QuicInteger numAdditionalBlocks(0); QuicInteger firstAckBlockLength(2000); auto result = createAckFrame( largestAcked, ackDelay, numAdditionalBlocks, firstAckBlockLength); folly::io::Cursor cursor(result.get()); EXPECT_THROW( decodeAckFrame( cursor, makeHeader(), CodecParameters(kDefaultAckDelayExponent, QuicVersion::MVFST)), QuicTransportException); } TEST_F(DecodeTest, AckFrameBlockLengthInvalid) { QuicInteger largestAcked(1000); QuicInteger ackDelay(100); QuicInteger numAdditionalBlocks(2); QuicInteger firstAckBlockLength(10); std::vector ackBlocks; ackBlocks.emplace_back(QuicInteger(10), QuicInteger(10)); ackBlocks.emplace_back(QuicInteger(10), QuicInteger(1000)); auto result = createAckFrame( largestAcked, ackDelay, numAdditionalBlocks, firstAckBlockLength, ackBlocks); folly::io::Cursor cursor(result.get()); EXPECT_THROW( decodeAckFrame( cursor, makeHeader(), CodecParameters(kDefaultAckDelayExponent, QuicVersion::MVFST)), QuicTransportException); } TEST_F(DecodeTest, AckFrameBlockGapInvalid) { QuicInteger largestAcked(1000); QuicInteger ackDelay(100); QuicInteger numAdditionalBlocks(2); QuicInteger firstAckBlockLength(10); std::vector ackBlocks; ackBlocks.emplace_back(QuicInteger(10), QuicInteger(10)); ackBlocks.emplace_back(QuicInteger(1000), QuicInteger(0)); auto result = createAckFrame( largestAcked, ackDelay, numAdditionalBlocks, firstAckBlockLength, ackBlocks); folly::io::Cursor cursor(result.get()); EXPECT_THROW( decodeAckFrame( cursor, makeHeader(), CodecParameters(kDefaultAckDelayExponent, QuicVersion::MVFST)), QuicTransportException); } TEST_F(DecodeTest, AckFrameBlockLengthZero) { QuicInteger largestAcked(1000); QuicInteger ackDelay(100); QuicInteger numAdditionalBlocks(3); QuicInteger firstAckBlockLength(10); std::vector ackBlocks; ackBlocks.emplace_back(QuicInteger(10), QuicInteger(10)); ackBlocks.emplace_back(QuicInteger(10), QuicInteger(0)); ackBlocks.emplace_back(QuicInteger(0), QuicInteger(10)); auto result = createAckFrame( largestAcked, ackDelay, numAdditionalBlocks, firstAckBlockLength, ackBlocks); folly::io::Cursor cursor(result.get()); auto readAckFrame = decodeAckFrame( cursor, makeHeader(), CodecParameters(kDefaultAckDelayExponent, QuicVersion::MVFST)); EXPECT_EQ(readAckFrame.ackBlocks[0].endPacket, 1000); EXPECT_EQ(readAckFrame.ackBlocks[0].startPacket, 990); EXPECT_EQ(readAckFrame.ackBlocks[1].endPacket, 978); EXPECT_EQ(readAckFrame.ackBlocks[1].startPacket, 968); EXPECT_EQ(readAckFrame.ackBlocks[2].endPacket, 956); EXPECT_EQ(readAckFrame.ackBlocks[2].startPacket, 956); EXPECT_EQ(readAckFrame.ackBlocks[3].endPacket, 954); EXPECT_EQ(readAckFrame.ackBlocks[3].startPacket, 944); } TEST_F(DecodeTest, StreamDecodeSuccess) { QuicInteger streamId(10); QuicInteger offset(10); QuicInteger length(1); auto streamType = StreamTypeField::Builder().setFin().setOffset().setLength().build(); auto streamFrame = createStreamFrame( streamId, offset, length, folly::IOBuf::copyBuffer("a")); BufQueue queue; queue.append(streamFrame->clone()); auto decodedFrame = decodeStreamFrame(queue, streamType); EXPECT_EQ(decodedFrame.offset, 10); EXPECT_EQ(decodedFrame.data->computeChainDataLength(), 1); EXPECT_EQ(decodedFrame.streamId, 10); EXPECT_TRUE(decodedFrame.fin); } TEST_F(DecodeTest, StreamLengthStreamIdInvalid) { QuicInteger streamId(std::numeric_limits::max()); auto streamType = StreamTypeField::Builder().setFin().setOffset().setLength().build(); auto streamFrame = createStreamFrame( streamId, folly::none, folly::none, nullptr, true); BufQueue queue; queue.append(streamFrame->clone()); EXPECT_THROW(decodeStreamFrame(queue, streamType), QuicTransportException); } TEST_F(DecodeTest, StreamOffsetNotPresent) { QuicInteger streamId(10); QuicInteger length(1); auto streamType = StreamTypeField::Builder().setFin().setOffset().setLength().build(); auto streamFrame = createStreamFrame( streamId, folly::none, length, folly::IOBuf::copyBuffer("a")); BufQueue queue; queue.append(streamFrame->clone()); EXPECT_THROW(decodeStreamFrame(queue, streamType), QuicTransportException); } TEST_F(DecodeTest, StreamIncorrectDataLength) { QuicInteger streamId(10); QuicInteger offset(10); QuicInteger length(10); auto streamType = StreamTypeField::Builder().setFin().setOffset().setLength().build(); auto streamFrame = createStreamFrame( streamId, offset, length, folly::IOBuf::copyBuffer("a")); BufQueue queue; queue.append(streamFrame->clone()); EXPECT_THROW(decodeStreamFrame(queue, streamType), QuicTransportException); } TEST_F(DecodeTest, CryptoDecodeSuccess) { QuicInteger offset(10); QuicInteger length(1); auto cryptoFrame = createCryptoFrame(offset, length, folly::IOBuf::copyBuffer("a")); folly::io::Cursor cursor(cryptoFrame.get()); auto decodedFrame = decodeCryptoFrame(cursor); EXPECT_EQ(decodedFrame.offset, 10); EXPECT_EQ(decodedFrame.data->computeChainDataLength(), 1); } TEST_F(DecodeTest, CryptoOffsetNotPresent) { QuicInteger length(1); auto cryptoFrame = createCryptoFrame(folly::none, length, folly::IOBuf::copyBuffer("a")); folly::io::Cursor cursor(cryptoFrame.get()); EXPECT_THROW(decodeCryptoFrame(cursor), QuicTransportException); } TEST_F(DecodeTest, CryptoLengthNotPresent) { QuicInteger offset(0); auto cryptoFrame = createCryptoFrame(offset, folly::none, nullptr); folly::io::Cursor cursor(cryptoFrame.get()); EXPECT_THROW(decodeCryptoFrame(cursor), QuicTransportException); } TEST_F(DecodeTest, CryptoIncorrectDataLength) { QuicInteger offset(10); QuicInteger length(10); auto cryptoFrame = createCryptoFrame(offset, length, folly::IOBuf::copyBuffer("a")); folly::io::Cursor cursor(cryptoFrame.get()); EXPECT_THROW(decodeCryptoFrame(cursor), QuicTransportException); } TEST_F(DecodeTest, PaddingFrameTest) { auto buf = folly::IOBuf::create(sizeof(UnderlyingFrameType)); buf->append(1); memset(buf->writableData(), 0, 1); folly::io::Cursor cursor(buf.get()); decodePaddingFrame(cursor); } TEST_F(DecodeTest, PaddingFrameNoBytesTest) { auto buf = folly::IOBuf::create(sizeof(UnderlyingFrameType)); folly::io::Cursor cursor(buf.get()); decodePaddingFrame(cursor); } TEST_F(DecodeTest, DecodeMultiplePaddingInterleavedTest) { auto buf = folly::IOBuf::create(20); buf->append(10); memset(buf->writableData(), 0, 10); buf->append(1); // something which is not padding memset(buf->writableData() + 10, 5, 1); folly::io::Cursor cursor(buf.get()); decodePaddingFrame(cursor); // If we encountered an interleaved frame, leave the whole thing // as is EXPECT_EQ(cursor.totalLength(), 11); } TEST_F(DecodeTest, DecodeMultiplePaddingTest) { auto buf = folly::IOBuf::create(20); buf->append(10); memset(buf->writableData(), 0, 10); folly::io::Cursor cursor(buf.get()); decodePaddingFrame(cursor); EXPECT_EQ(cursor.totalLength(), 0); } std::unique_ptr createNewTokenFrame( folly::Optional tokenLength = folly::none, Buf token = nullptr) { std::unique_ptr newTokenFrame = folly::IOBuf::create(0); BufAppender wcursor(newTokenFrame.get(), 10); auto appenderOp = [&](auto val) { wcursor.writeBE(val); }; if (tokenLength) { tokenLength->encode(appenderOp); } if (token) { wcursor.insert(std::move(token)); } return newTokenFrame; } TEST_F(DecodeTest, NewTokenDecodeSuccess) { QuicInteger length(1); auto newTokenFrame = createNewTokenFrame(length, folly::IOBuf::copyBuffer("a")); folly::io::Cursor cursor(newTokenFrame.get()); auto decodedFrame = decodeNewTokenFrame(cursor); EXPECT_EQ(decodedFrame.token->computeChainDataLength(), 1); } TEST_F(DecodeTest, NewTokenLengthNotPresent) { auto newTokenFrame = createNewTokenFrame(folly::none, folly::IOBuf::copyBuffer("a")); folly::io::Cursor cursor(newTokenFrame.get()); EXPECT_THROW(decodeNewTokenFrame(cursor), QuicTransportException); } TEST_F(DecodeTest, NewTokenIncorrectDataLength) { QuicInteger length(10); auto newTokenFrame = createNewTokenFrame(length, folly::IOBuf::copyBuffer("a")); folly::io::Cursor cursor(newTokenFrame.get()); EXPECT_THROW(decodeNewTokenFrame(cursor), QuicTransportException); } std::unique_ptr createMinOrExpiredStreamDataFrame( QuicInteger streamId, folly::Optional maximumData = folly::none, folly::Optional minimumStreamOffset = folly::none) { std::unique_ptr bufQueue = folly::IOBuf::create(0); BufAppender wcursor(bufQueue.get(), 10); auto appenderOp = [&](auto val) { wcursor.writeBE(val); }; streamId.encode(appenderOp); if (maximumData) { maximumData->encode(appenderOp); } if (minimumStreamOffset) { minimumStreamOffset->encode(appenderOp); } return bufQueue; } TEST_F(DecodeTest, DecodeMinStreamDataFrame) { QuicInteger streamId(10); QuicInteger maximumData(1000); QuicInteger minimumStreamOffset(100); auto noOffset = createMinOrExpiredStreamDataFrame(streamId, maximumData); folly::io::Cursor cursor0(noOffset.get()); EXPECT_THROW(decodeMinStreamDataFrame(cursor0), QuicTransportException); auto minStreamDataFrame = createMinOrExpiredStreamDataFrame( streamId, maximumData, minimumStreamOffset); folly::io::Cursor cursor(minStreamDataFrame.get()); auto result = decodeMinStreamDataFrame(cursor); EXPECT_EQ(result.streamId, 10); EXPECT_EQ(result.maximumData, 1000); EXPECT_EQ(result.minimumStreamOffset, 100); } TEST_F(DecodeTest, DecodeExpiredStreamDataFrame) { QuicInteger streamId(10); QuicInteger offset(100); auto noOffset = createMinOrExpiredStreamDataFrame(streamId); folly::io::Cursor cursor0(noOffset.get()); EXPECT_THROW(decodeExpiredStreamDataFrame(cursor0), QuicTransportException); auto expiredStreamDataFrame = createMinOrExpiredStreamDataFrame(streamId, folly::none, offset); folly::io::Cursor cursor(expiredStreamDataFrame.get()); auto result = decodeExpiredStreamDataFrame(cursor); EXPECT_EQ(result.streamId, 10); EXPECT_EQ(result.minimumStreamOffset, 100); } TEST_F(DecodeTest, ParsePlaintextRetryToken) { ConnectionId odcid = getTestConnectionId(); folly::IPAddress clientIp("109.115.3.49"); uint16_t clientPort = 42069; RetryToken retryToken(odcid, clientIp, clientPort); Buf plaintextRetryToken = retryToken.getPlaintextToken(); folly::io::Cursor cursor(plaintextRetryToken.get()); auto parseResult = parsePlaintextRetryToken(cursor); EXPECT_TRUE(parseResult.hasValue()); EXPECT_EQ(parseResult->originalDstConnId, odcid); EXPECT_EQ(parseResult->clientIp, clientIp); EXPECT_EQ(parseResult->clientPort, clientPort); } TEST_F(DecodeTest, ParsePlaintextRetryTokenMalformed) { Buf plaintextRetryToken = folly::IOBuf::copyBuffer("This is some garbage"); folly::io::Cursor cursor(plaintextRetryToken.get()); auto parseResult = parsePlaintextRetryToken(cursor); EXPECT_TRUE(parseResult.hasError()); EXPECT_EQ(parseResult.error(), TransportErrorCode::INVALID_TOKEN); } } // namespace test } // namespace quic