// Copyright 2023 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build go1.21 package quic import ( "encoding/binary" ) // A packetWriter constructs QUIC datagrams. // // A datagram consists of one or more packets. // A packet consists of a header followed by one or more frames. // // Packets are written in three steps: // - startProtectedLongHeaderPacket or start1RTT packet prepare the packet; // - append*Frame appends frames to the payload; and // - finishProtectedLongHeaderPacket or finish1RTT finalize the packet. // // The start functions are efficient, so we can start speculatively // writing a packet before we know whether we have any frames to // put in it. The finish functions will abandon the packet if the // payload contains no data. type packetWriter struct { dgramLim int // max datagram size pktLim int // max packet size pktOff int // offset of the start of the current packet payOff int // offset of the payload of the current packet b []byte sent *sentPacket } // reset prepares to write a datagram of at most lim bytes. func (w *packetWriter) reset(lim int) { if cap(w.b) < lim { w.b = make([]byte, 0, lim) } w.dgramLim = lim w.b = w.b[:0] } // datagram returns the current datagram. func (w *packetWriter) datagram() []byte { return w.b } // packet returns the size of the current packet. func (w *packetWriter) packetLen() int { return len(w.b[w.pktOff:]) + aeadOverhead } // payload returns the payload of the current packet. func (w *packetWriter) payload() []byte { return w.b[w.payOff:] } func (w *packetWriter) abandonPacket() { w.b = w.b[:w.payOff] w.sent.reset() } // startProtectedLongHeaderPacket starts writing an Initial, 0-RTT, or Handshake packet. func (w *packetWriter) startProtectedLongHeaderPacket(pnumMaxAcked packetNumber, p longPacket) { if w.sent == nil { w.sent = newSentPacket() } w.pktOff = len(w.b) hdrSize := 1 // packet type hdrSize += 4 // version hdrSize += 1 + len(p.dstConnID) hdrSize += 1 + len(p.srcConnID) switch p.ptype { case packetTypeInitial: hdrSize += sizeVarint(uint64(len(p.extra))) + len(p.extra) } hdrSize += 2 // length, hardcoded to a 2-byte varint pnumOff := len(w.b) + hdrSize hdrSize += packetNumberLength(p.num, pnumMaxAcked) payOff := len(w.b) + hdrSize // Check if we have enough space to hold the packet, including the header, // header protection sample (RFC 9001, section 5.4.2), and encryption overhead. if pnumOff+4+headerProtectionSampleSize+aeadOverhead >= w.dgramLim { // Set the limit on the packet size to be the current write buffer length, // ensuring that any writes to the payload fail. w.payOff = len(w.b) w.pktLim = len(w.b) return } w.payOff = payOff w.pktLim = w.dgramLim - aeadOverhead // We hardcode the payload length field to be 2 bytes, which limits the payload // (including the packet number) to 16383 bytes (the largest 2-byte QUIC varint). // // Most networks don't support datagrams over 1472 bytes, and even Ethernet // jumbo frames are generally only about 9000 bytes. if lim := pnumOff + 16383 - aeadOverhead; lim < w.pktLim { w.pktLim = lim } w.b = w.b[:payOff] } // finishProtectedLongHeaderPacket finishes writing an Initial, 0-RTT, or Handshake packet, // canceling the packet if it contains no payload. // It returns a sentPacket describing the packet, or nil if no packet was written. func (w *packetWriter) finishProtectedLongHeaderPacket(pnumMaxAcked packetNumber, k fixedKeys, p longPacket) *sentPacket { if len(w.b) == w.payOff { // The payload is empty, so just abandon the packet. w.b = w.b[:w.pktOff] return nil } pnumLen := packetNumberLength(p.num, pnumMaxAcked) plen := w.padPacketLength(pnumLen) hdr := w.b[:w.pktOff] var typeBits byte switch p.ptype { case packetTypeInitial: typeBits = longPacketTypeInitial case packetType0RTT: typeBits = longPacketType0RTT case packetTypeHandshake: typeBits = longPacketTypeHandshake case packetTypeRetry: typeBits = longPacketTypeRetry } hdr = append(hdr, headerFormLong|fixedBit|typeBits|byte(pnumLen-1)) hdr = binary.BigEndian.AppendUint32(hdr, p.version) hdr = appendUint8Bytes(hdr, p.dstConnID) hdr = appendUint8Bytes(hdr, p.srcConnID) switch p.ptype { case packetTypeInitial: hdr = appendVarintBytes(hdr, p.extra) // token } // Packet length, always encoded as a 2-byte varint. hdr = append(hdr, 0x40|byte(plen>>8), byte(plen)) pnumOff := len(hdr) hdr = appendPacketNumber(hdr, p.num, pnumMaxAcked) k.protect(hdr[w.pktOff:], w.b[len(hdr):], pnumOff-w.pktOff, p.num) return w.finish(p.num) } // start1RTTPacket starts writing a 1-RTT (short header) packet. func (w *packetWriter) start1RTTPacket(pnum, pnumMaxAcked packetNumber, dstConnID []byte) { if w.sent == nil { w.sent = newSentPacket() } w.pktOff = len(w.b) hdrSize := 1 // packet type hdrSize += len(dstConnID) // Ensure we have enough space to hold the packet, including the header, // header protection sample (RFC 9001, section 5.4.2), and encryption overhead. if len(w.b)+hdrSize+4+headerProtectionSampleSize+aeadOverhead >= w.dgramLim { w.payOff = len(w.b) w.pktLim = len(w.b) return } hdrSize += packetNumberLength(pnum, pnumMaxAcked) w.payOff = len(w.b) + hdrSize w.pktLim = w.dgramLim - aeadOverhead w.b = w.b[:w.payOff] } // finish1RTTPacket finishes writing a 1-RTT packet, // canceling the packet if it contains no payload. // It returns a sentPacket describing the packet, or nil if no packet was written. func (w *packetWriter) finish1RTTPacket(pnum, pnumMaxAcked packetNumber, dstConnID []byte, k *updatingKeyPair) *sentPacket { if len(w.b) == w.payOff { // The payload is empty, so just abandon the packet. w.b = w.b[:w.pktOff] return nil } // TODO: Spin pnumLen := packetNumberLength(pnum, pnumMaxAcked) hdr := w.b[:w.pktOff] hdr = append(hdr, 0x40|byte(pnumLen-1)) hdr = append(hdr, dstConnID...) pnumOff := len(hdr) hdr = appendPacketNumber(hdr, pnum, pnumMaxAcked) w.padPacketLength(pnumLen) k.protect(hdr[w.pktOff:], w.b[len(hdr):], pnumOff-w.pktOff, pnum) return w.finish(pnum) } // padPacketLength pads out the payload of the current packet to the minimum size, // and returns the combined length of the packet number and payload (used for the Length // field of long header packets). func (w *packetWriter) padPacketLength(pnumLen int) int { plen := len(w.b) - w.payOff + pnumLen + aeadOverhead // "To ensure that sufficient data is available for sampling, packets are // padded so that the combined lengths of the encoded packet number and // protected payload is at least 4 bytes longer than the sample required // for header protection." // https://www.rfc-editor.org/rfc/rfc9001.html#section-5.4.2 for plen < 4+headerProtectionSampleSize { w.b = append(w.b, 0) plen++ } return plen } // finish finishes the current packet after protection is applied. func (w *packetWriter) finish(pnum packetNumber) *sentPacket { w.b = w.b[:len(w.b)+aeadOverhead] w.sent.size = len(w.b) - w.pktOff w.sent.num = pnum sent := w.sent w.sent = nil return sent } // avail reports how many more bytes may be written to the current packet. func (w *packetWriter) avail() int { return w.pktLim - len(w.b) } // appendPaddingTo appends PADDING frames until the total datagram size // (including AEAD overhead of the current packet) is n. func (w *packetWriter) appendPaddingTo(n int) { n -= aeadOverhead lim := w.pktLim if n < lim { lim = n } if len(w.b) >= lim { return } for len(w.b) < lim { w.b = append(w.b, frameTypePadding) } // Packets are considered in flight when they contain a PADDING frame. // https://www.rfc-editor.org/rfc/rfc9002.html#section-2-3.6.1 w.sent.inFlight = true } func (w *packetWriter) appendPingFrame() (added bool) { if len(w.b) >= w.pktLim { return false } w.b = append(w.b, frameTypePing) // Mark this packet as ack-eliciting and in-flight, // but there's no need to record the presence of a PING frame in it. w.sent.ackEliciting = true w.sent.inFlight = true return true } // appendAckFrame appends an ACK frame to the payload. // It includes at least the most recent range in the rangeset // (the range with the largest packet numbers), // followed by as many additional ranges as fit within the packet. // // We always place ACK frames at the start of packets, // we limit the number of ack ranges retained, and // we set a minimum packet payload size. // As a result, appendAckFrame will rarely if ever drop ranges // in practice. // // In the event that ranges are dropped, the impact is limited // to the peer potentially failing to receive an acknowledgement // for an older packet during a period of high packet loss or // reordering. This may result in unnecessary retransmissions. func (w *packetWriter) appendAckFrame(seen rangeset[packetNumber], delay unscaledAckDelay) (added bool) { if len(seen) == 0 { return false } var ( largest = uint64(seen.max()) firstRange = uint64(seen[len(seen)-1].size() - 1) ) if w.avail() < 1+sizeVarint(largest)+sizeVarint(uint64(delay))+1+sizeVarint(firstRange) { return false } w.b = append(w.b, frameTypeAck) w.b = appendVarint(w.b, largest) w.b = appendVarint(w.b, uint64(delay)) // The range count is technically a varint, but we'll reserve a single byte for it // and never add more than 62 ranges (the maximum varint that fits in a byte). rangeCountOff := len(w.b) w.b = append(w.b, 0) w.b = appendVarint(w.b, firstRange) rangeCount := byte(0) for i := len(seen) - 2; i >= 0; i-- { gap := uint64(seen[i+1].start - seen[i].end - 1) size := uint64(seen[i].size() - 1) if w.avail() < sizeVarint(gap)+sizeVarint(size) || rangeCount > 62 { break } w.b = appendVarint(w.b, gap) w.b = appendVarint(w.b, size) rangeCount++ } w.b[rangeCountOff] = rangeCount w.sent.appendNonAckElicitingFrame(frameTypeAck) w.sent.appendInt(uint64(seen.max())) return true } func (w *packetWriter) appendNewTokenFrame(token []byte) (added bool) { if w.avail() < 1+sizeVarint(uint64(len(token)))+len(token) { return false } w.b = append(w.b, frameTypeNewToken) w.b = appendVarintBytes(w.b, token) return true } func (w *packetWriter) appendResetStreamFrame(id streamID, code uint64, finalSize int64) (added bool) { if w.avail() < 1+sizeVarint(uint64(id))+sizeVarint(code)+sizeVarint(uint64(finalSize)) { return false } w.b = append(w.b, frameTypeResetStream) w.b = appendVarint(w.b, uint64(id)) w.b = appendVarint(w.b, code) w.b = appendVarint(w.b, uint64(finalSize)) w.sent.appendAckElicitingFrame(frameTypeResetStream) w.sent.appendInt(uint64(id)) return true } func (w *packetWriter) appendStopSendingFrame(id streamID, code uint64) (added bool) { if w.avail() < 1+sizeVarint(uint64(id))+sizeVarint(code) { return false } w.b = append(w.b, frameTypeStopSending) w.b = appendVarint(w.b, uint64(id)) w.b = appendVarint(w.b, code) w.sent.appendAckElicitingFrame(frameTypeStopSending) w.sent.appendInt(uint64(id)) return true } // appendCryptoFrame appends a CRYPTO frame. // It returns a []byte into which the data should be written and whether a frame was added. // The returned []byte may be smaller than size if the packet cannot hold all the data. func (w *packetWriter) appendCryptoFrame(off int64, size int) (_ []byte, added bool) { max := w.avail() max -= 1 // frame type max -= sizeVarint(uint64(off)) // offset max -= sizeVarint(uint64(size)) // maximum length if max <= 0 { return nil, false } if max < size { size = max } w.b = append(w.b, frameTypeCrypto) w.b = appendVarint(w.b, uint64(off)) w.b = appendVarint(w.b, uint64(size)) start := len(w.b) w.b = w.b[:start+size] w.sent.appendAckElicitingFrame(frameTypeCrypto) w.sent.appendOffAndSize(off, size) return w.b[start:][:size], true } // appendStreamFrame appends a STREAM frame. // It returns a []byte into which the data should be written and whether a frame was added. // The returned []byte may be smaller than size if the packet cannot hold all the data. func (w *packetWriter) appendStreamFrame(id streamID, off int64, size int, fin bool) (_ []byte, added bool) { typ := uint8(frameTypeStreamBase | streamLenBit) max := w.avail() max -= 1 // frame type max -= sizeVarint(uint64(id)) if off != 0 { max -= sizeVarint(uint64(off)) typ |= streamOffBit } max -= sizeVarint(uint64(size)) // maximum length if max < 0 || (max == 0 && size > 0) { return nil, false } if max < size { size = max } else if fin { typ |= streamFinBit } w.b = append(w.b, typ) w.b = appendVarint(w.b, uint64(id)) if off != 0 { w.b = appendVarint(w.b, uint64(off)) } w.b = appendVarint(w.b, uint64(size)) start := len(w.b) w.b = w.b[:start+size] if fin { w.sent.appendAckElicitingFrame(frameTypeStreamBase | streamFinBit) } else { w.sent.appendAckElicitingFrame(frameTypeStreamBase) } w.sent.appendInt(uint64(id)) w.sent.appendOffAndSize(off, size) return w.b[start:][:size], true } func (w *packetWriter) appendMaxDataFrame(max int64) (added bool) { if w.avail() < 1+sizeVarint(uint64(max)) { return false } w.b = append(w.b, frameTypeMaxData) w.b = appendVarint(w.b, uint64(max)) w.sent.appendAckElicitingFrame(frameTypeMaxData) return true } func (w *packetWriter) appendMaxStreamDataFrame(id streamID, max int64) (added bool) { if w.avail() < 1+sizeVarint(uint64(id))+sizeVarint(uint64(max)) { return false } w.b = append(w.b, frameTypeMaxStreamData) w.b = appendVarint(w.b, uint64(id)) w.b = appendVarint(w.b, uint64(max)) w.sent.appendAckElicitingFrame(frameTypeMaxStreamData) w.sent.appendInt(uint64(id)) return true } func (w *packetWriter) appendMaxStreamsFrame(streamType streamType, max int64) (added bool) { if w.avail() < 1+sizeVarint(uint64(max)) { return false } var typ byte if streamType == bidiStream { typ = frameTypeMaxStreamsBidi } else { typ = frameTypeMaxStreamsUni } w.b = append(w.b, typ) w.b = appendVarint(w.b, uint64(max)) w.sent.appendAckElicitingFrame(typ) return true } func (w *packetWriter) appendDataBlockedFrame(max int64) (added bool) { if w.avail() < 1+sizeVarint(uint64(max)) { return false } w.b = append(w.b, frameTypeDataBlocked) w.b = appendVarint(w.b, uint64(max)) w.sent.appendAckElicitingFrame(frameTypeDataBlocked) return true } func (w *packetWriter) appendStreamDataBlockedFrame(id streamID, max int64) (added bool) { if w.avail() < 1+sizeVarint(uint64(id))+sizeVarint(uint64(max)) { return false } w.b = append(w.b, frameTypeStreamDataBlocked) w.b = appendVarint(w.b, uint64(id)) w.b = appendVarint(w.b, uint64(max)) w.sent.appendAckElicitingFrame(frameTypeStreamDataBlocked) w.sent.appendInt(uint64(id)) return true } func (w *packetWriter) appendStreamsBlockedFrame(typ streamType, max int64) (added bool) { if w.avail() < 1+sizeVarint(uint64(max)) { return false } var ftype byte if typ == bidiStream { ftype = frameTypeStreamsBlockedBidi } else { ftype = frameTypeStreamsBlockedUni } w.b = append(w.b, ftype) w.b = appendVarint(w.b, uint64(max)) w.sent.appendAckElicitingFrame(ftype) return true } func (w *packetWriter) appendNewConnectionIDFrame(seq, retirePriorTo int64, connID []byte, token [16]byte) (added bool) { if w.avail() < 1+sizeVarint(uint64(seq))+sizeVarint(uint64(retirePriorTo))+1+len(connID)+len(token) { return false } w.b = append(w.b, frameTypeNewConnectionID) w.b = appendVarint(w.b, uint64(seq)) w.b = appendVarint(w.b, uint64(retirePriorTo)) w.b = appendUint8Bytes(w.b, connID) w.b = append(w.b, token[:]...) w.sent.appendAckElicitingFrame(frameTypeNewConnectionID) w.sent.appendInt(uint64(seq)) return true } func (w *packetWriter) appendRetireConnectionIDFrame(seq int64) (added bool) { if w.avail() < 1+sizeVarint(uint64(seq)) { return false } w.b = append(w.b, frameTypeRetireConnectionID) w.b = appendVarint(w.b, uint64(seq)) w.sent.appendAckElicitingFrame(frameTypeRetireConnectionID) w.sent.appendInt(uint64(seq)) return true } func (w *packetWriter) appendPathChallengeFrame(data uint64) (added bool) { if w.avail() < 1+8 { return false } w.b = append(w.b, frameTypePathChallenge) w.b = binary.BigEndian.AppendUint64(w.b, data) w.sent.appendAckElicitingFrame(frameTypePathChallenge) return true } func (w *packetWriter) appendPathResponseFrame(data uint64) (added bool) { if w.avail() < 1+8 { return false } w.b = append(w.b, frameTypePathResponse) w.b = binary.BigEndian.AppendUint64(w.b, data) w.sent.appendAckElicitingFrame(frameTypePathResponse) return true } // appendConnectionCloseTransportFrame appends a CONNECTION_CLOSE frame // carrying a transport error code. func (w *packetWriter) appendConnectionCloseTransportFrame(code transportError, frameType uint64, reason string) (added bool) { if w.avail() < 1+sizeVarint(uint64(code))+sizeVarint(frameType)+sizeVarint(uint64(len(reason)))+len(reason) { return false } w.b = append(w.b, frameTypeConnectionCloseTransport) w.b = appendVarint(w.b, uint64(code)) w.b = appendVarint(w.b, frameType) w.b = appendVarintBytes(w.b, []byte(reason)) // We don't record CONNECTION_CLOSE frames in w.sent, since they are never acked or // detected as lost. return true } // appendConnectionCloseTransportFrame appends a CONNECTION_CLOSE frame // carrying an application protocol error code. func (w *packetWriter) appendConnectionCloseApplicationFrame(code uint64, reason string) (added bool) { if w.avail() < 1+sizeVarint(code)+sizeVarint(uint64(len(reason)))+len(reason) { return false } w.b = append(w.b, frameTypeConnectionCloseApplication) w.b = appendVarint(w.b, code) w.b = appendVarintBytes(w.b, []byte(reason)) // We don't record CONNECTION_CLOSE frames in w.sent, since they are never acked or // detected as lost. return true } func (w *packetWriter) appendHandshakeDoneFrame() (added bool) { if w.avail() < 1 { return false } w.b = append(w.b, frameTypeHandshakeDone) w.sent.appendAckElicitingFrame(frameTypeHandshakeDone) return true }