in quic/codec/QuicWriteCodec.cpp [31:144]
folly::Optional<uint64_t> writeStreamFrameHeader(
PacketBuilderInterface& builder,
StreamId id,
uint64_t offset,
uint64_t writeBufferLen,
uint64_t flowControlLen,
bool fin,
folly::Optional<bool> skipLenHint) {
if (builder.remainingSpaceInPkt() == 0) {
return folly::none;
}
if (writeBufferLen == 0 && !fin) {
throw QuicInternalException(
"No data or fin supplied when writing stream.",
LocalErrorCode::INTERNAL_ERROR);
}
StreamTypeField::Builder streamTypeBuilder;
QuicInteger idInt(id);
// First account for the things that are non-optional: frame type and stream
// id.
uint64_t headerSize = sizeof(uint8_t) + idInt.getSize();
if (builder.remainingSpaceInPkt() < headerSize) {
VLOG(4) << "No space in packet for stream header. stream=" << id
<< " remaining=" << builder.remainingSpaceInPkt();
return folly::none;
}
QuicInteger offsetInt(offset);
if (offset != 0) {
streamTypeBuilder.setOffset();
headerSize += offsetInt.getSize();
}
// Next we have to deal with the data length. This is trickier. The length of
// data we are able to send depends on 3 things: how much we have in the
// buffer, how much flow control we have, and the remaining size in the
// packet. If the amount we want to send is >= the remaining packet size after
// the header so far we can omit the length field and consume the rest of the
// packet. If it is not then we need to use the minimal varint encoding
// possible to avoid sending not-full packets.
// Note: we don't bother with one potential optimization, which is writing
// a zero length fin-only stream frame and omitting the length field.
uint64_t dataLen = std::min(writeBufferLen, flowControlLen);
uint64_t dataLenLen = 0;
bool shouldSkipLengthField;
if (skipLenHint) {
shouldSkipLengthField = *skipLenHint;
} else {
// Check if we can fill the entire packet with the rest of this stream frame
shouldSkipLengthField =
dataLen > 0 && dataLen >= builder.remainingSpaceInPkt() - headerSize;
}
// At most we can write the minimal between data length and what the packet
// builder allows us to write.
dataLen = std::min(dataLen, builder.remainingSpaceInPkt() - headerSize);
if (!shouldSkipLengthField) {
if (dataLen <= kOneByteLimit - 1) {
dataLenLen = 1;
} else if (dataLen <= kTwoByteLimit - 2) {
dataLenLen = 2;
} else if (dataLen <= kFourByteLimit - 4) {
dataLenLen = 4;
} else if (dataLen <= kEightByteLimit - 8) {
dataLenLen = 8;
} else {
// This should never really happen as dataLen is bounded by the remaining
// space in the packet which should be << kEightByteLimit.
throw QuicInternalException(
"Stream frame length too large.", LocalErrorCode::INTERNAL_ERROR);
}
}
if (dataLenLen > 0) {
if (dataLen != 0 &&
headerSize + dataLenLen >= builder.remainingSpaceInPkt()) {
VLOG(4) << "No space in packet for stream header. stream=" << id
<< " remaining=" << builder.remainingSpaceInPkt();
return folly::none;
}
// We have to encode the actual data length in the header.
headerSize += dataLenLen;
if (builder.remainingSpaceInPkt() < dataLen + headerSize) {
dataLen = builder.remainingSpaceInPkt() - headerSize;
}
}
bool shouldSetFin = fin && dataLen == writeBufferLen;
if (dataLen == 0 && !shouldSetFin) {
// This would be an empty non-fin stream frame.
return folly::none;
}
if (builder.remainingSpaceInPkt() < headerSize) {
VLOG(4) << "No space in packet for stream header. stream=" << id
<< " remaining=" << builder.remainingSpaceInPkt();
return folly::none;
}
// Done with the accounting, set the bits and write the actual frame header.
if (dataLenLen > 0) {
streamTypeBuilder.setLength();
}
if (shouldSetFin) {
streamTypeBuilder.setFin();
}
auto streamType = streamTypeBuilder.build();
builder.writeBE(streamType.fieldValue());
builder.write(idInt);
if (offset != 0) {
builder.write(offsetInt);
}
if (dataLenLen > 0) {
builder.write(QuicInteger(dataLen));
}
builder.appendFrame(
WriteStreamFrame(id, offset, dataLen, streamType.hasFin()));
DCHECK(dataLen <= builder.remainingSpaceInPkt());
return folly::make_optional(dataLen);
}