aboutsummaryrefslogtreecommitdiff
path: root/src/vendor/golang.org/x/net/quic/packet_writer.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/vendor/golang.org/x/net/quic/packet_writer.go')
-rw-r--r--src/vendor/golang.org/x/net/quic/packet_writer.go566
1 files changed, 566 insertions, 0 deletions
diff --git a/src/vendor/golang.org/x/net/quic/packet_writer.go b/src/vendor/golang.org/x/net/quic/packet_writer.go
new file mode 100644
index 0000000000..f446521d2b
--- /dev/null
+++ b/src/vendor/golang.org/x/net/quic/packet_writer.go
@@ -0,0 +1,566 @@
+// 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.
+
+package quic
+
+import (
+ "encoding/binary"
+
+ "golang.org/x/net/internal/quic/quicwire"
+)
+
+// 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
+}
+
+// packetLen 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 += quicwire.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 = quicwire.AppendUint8Bytes(hdr, p.dstConnID)
+ hdr = quicwire.AppendUint8Bytes(hdr, p.srcConnID)
+ switch p.ptype {
+ case packetTypeInitial:
+ hdr = quicwire.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.ptype, 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(packetType1RTT, 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(ptype packetType, pnum packetNumber) *sentPacket {
+ w.b = w.b[:len(w.b)+aeadOverhead]
+ w.sent.size = len(w.b) - w.pktOff
+ w.sent.ptype = ptype
+ 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)
+ w.sent.markAckEliciting() // no need to record the frame itself
+ 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, ecn ecnCounts) (added bool) {
+ if len(seen) == 0 {
+ return false
+ }
+ var (
+ largest = uint64(seen.max())
+ firstRange = uint64(seen[len(seen)-1].size() - 1)
+ )
+ var ecnLen int
+ ackType := byte(frameTypeAck)
+ if (ecn != ecnCounts{}) {
+ // "Even if an endpoint does not set an ECT field in packets it sends,
+ // the endpoint MUST provide feedback about ECN markings it receives, if
+ // these are accessible."
+ // https://www.rfc-editor.org/rfc/rfc9000.html#section-13.4.1-2
+ ecnLen = quicwire.SizeVarint(uint64(ecn.ce)) + quicwire.SizeVarint(uint64(ecn.t0)) + quicwire.SizeVarint(uint64(ecn.t1))
+ ackType = frameTypeAckECN
+ }
+ if w.avail() < 1+quicwire.SizeVarint(largest)+quicwire.SizeVarint(uint64(delay))+1+quicwire.SizeVarint(firstRange)+ecnLen {
+ return false
+ }
+ w.b = append(w.b, ackType)
+ w.b = quicwire.AppendVarint(w.b, largest)
+ w.b = quicwire.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 = quicwire.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() < quicwire.SizeVarint(gap)+quicwire.SizeVarint(size)+ecnLen || rangeCount > 62 {
+ break
+ }
+ w.b = quicwire.AppendVarint(w.b, gap)
+ w.b = quicwire.AppendVarint(w.b, size)
+ rangeCount++
+ }
+ w.b[rangeCountOff] = rangeCount
+ if ackType == frameTypeAckECN {
+ w.b = quicwire.AppendVarint(w.b, uint64(ecn.t0))
+ w.b = quicwire.AppendVarint(w.b, uint64(ecn.t1))
+ w.b = quicwire.AppendVarint(w.b, uint64(ecn.ce))
+ }
+ w.sent.appendNonAckElicitingFrame(ackType)
+ w.sent.appendInt(uint64(seen.max()))
+ return true
+}
+
+func (w *packetWriter) appendNewTokenFrame(token []byte) (added bool) {
+ if w.avail() < 1+quicwire.SizeVarint(uint64(len(token)))+len(token) {
+ return false
+ }
+ w.b = append(w.b, frameTypeNewToken)
+ w.b = quicwire.AppendVarintBytes(w.b, token)
+ return true
+}
+
+func (w *packetWriter) appendResetStreamFrame(id streamID, code uint64, finalSize int64) (added bool) {
+ if w.avail() < 1+quicwire.SizeVarint(uint64(id))+quicwire.SizeVarint(code)+quicwire.SizeVarint(uint64(finalSize)) {
+ return false
+ }
+ w.b = append(w.b, frameTypeResetStream)
+ w.b = quicwire.AppendVarint(w.b, uint64(id))
+ w.b = quicwire.AppendVarint(w.b, code)
+ w.b = quicwire.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+quicwire.SizeVarint(uint64(id))+quicwire.SizeVarint(code) {
+ return false
+ }
+ w.b = append(w.b, frameTypeStopSending)
+ w.b = quicwire.AppendVarint(w.b, uint64(id))
+ w.b = quicwire.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 -= quicwire.SizeVarint(uint64(off)) // offset
+ max -= quicwire.SizeVarint(uint64(size)) // maximum length
+ if max <= 0 {
+ return nil, false
+ }
+ if max < size {
+ size = max
+ }
+ w.b = append(w.b, frameTypeCrypto)
+ w.b = quicwire.AppendVarint(w.b, uint64(off))
+ w.b = quicwire.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 -= quicwire.SizeVarint(uint64(id))
+ if off != 0 {
+ max -= quicwire.SizeVarint(uint64(off))
+ typ |= streamOffBit
+ }
+ max -= quicwire.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 = quicwire.AppendVarint(w.b, uint64(id))
+ if off != 0 {
+ w.b = quicwire.AppendVarint(w.b, uint64(off))
+ }
+ w.b = quicwire.AppendVarint(w.b, uint64(size))
+ start := len(w.b)
+ w.b = w.b[:start+size]
+ w.sent.appendAckElicitingFrame(typ & (frameTypeStreamBase | streamFinBit))
+ 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+quicwire.SizeVarint(uint64(max)) {
+ return false
+ }
+ w.b = append(w.b, frameTypeMaxData)
+ w.b = quicwire.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+quicwire.SizeVarint(uint64(id))+quicwire.SizeVarint(uint64(max)) {
+ return false
+ }
+ w.b = append(w.b, frameTypeMaxStreamData)
+ w.b = quicwire.AppendVarint(w.b, uint64(id))
+ w.b = quicwire.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+quicwire.SizeVarint(uint64(max)) {
+ return false
+ }
+ var typ byte
+ if streamType == bidiStream {
+ typ = frameTypeMaxStreamsBidi
+ } else {
+ typ = frameTypeMaxStreamsUni
+ }
+ w.b = append(w.b, typ)
+ w.b = quicwire.AppendVarint(w.b, uint64(max))
+ w.sent.appendAckElicitingFrame(typ)
+ return true
+}
+
+func (w *packetWriter) appendDataBlockedFrame(max int64) (added bool) {
+ if w.avail() < 1+quicwire.SizeVarint(uint64(max)) {
+ return false
+ }
+ w.b = append(w.b, frameTypeDataBlocked)
+ w.b = quicwire.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+quicwire.SizeVarint(uint64(id))+quicwire.SizeVarint(uint64(max)) {
+ return false
+ }
+ w.b = append(w.b, frameTypeStreamDataBlocked)
+ w.b = quicwire.AppendVarint(w.b, uint64(id))
+ w.b = quicwire.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+quicwire.SizeVarint(uint64(max)) {
+ return false
+ }
+ var ftype byte
+ if typ == bidiStream {
+ ftype = frameTypeStreamsBlockedBidi
+ } else {
+ ftype = frameTypeStreamsBlockedUni
+ }
+ w.b = append(w.b, ftype)
+ w.b = quicwire.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+quicwire.SizeVarint(uint64(seq))+quicwire.SizeVarint(uint64(retirePriorTo))+1+len(connID)+len(token) {
+ return false
+ }
+ w.b = append(w.b, frameTypeNewConnectionID)
+ w.b = quicwire.AppendVarint(w.b, uint64(seq))
+ w.b = quicwire.AppendVarint(w.b, uint64(retirePriorTo))
+ w.b = quicwire.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+quicwire.SizeVarint(uint64(seq)) {
+ return false
+ }
+ w.b = append(w.b, frameTypeRetireConnectionID)
+ w.b = quicwire.AppendVarint(w.b, uint64(seq))
+ w.sent.appendAckElicitingFrame(frameTypeRetireConnectionID)
+ w.sent.appendInt(uint64(seq))
+ return true
+}
+
+func (w *packetWriter) appendPathChallengeFrame(data pathChallengeData) (added bool) {
+ if w.avail() < 1+8 {
+ return false
+ }
+ w.b = append(w.b, frameTypePathChallenge)
+ w.b = append(w.b, data[:]...)
+ w.sent.markAckEliciting() // no need to record the frame itself
+ return true
+}
+
+func (w *packetWriter) appendPathResponseFrame(data pathChallengeData) (added bool) {
+ if w.avail() < 1+8 {
+ return false
+ }
+ w.b = append(w.b, frameTypePathResponse)
+ w.b = append(w.b, data[:]...)
+ w.sent.markAckEliciting() // no need to record the frame itself
+ 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+quicwire.SizeVarint(uint64(code))+quicwire.SizeVarint(frameType)+quicwire.SizeVarint(uint64(len(reason)))+len(reason) {
+ return false
+ }
+ w.b = append(w.b, frameTypeConnectionCloseTransport)
+ w.b = quicwire.AppendVarint(w.b, uint64(code))
+ w.b = quicwire.AppendVarint(w.b, frameType)
+ w.b = quicwire.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
+}
+
+// appendConnectionCloseApplicationFrame 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+quicwire.SizeVarint(code)+quicwire.SizeVarint(uint64(len(reason)))+len(reason) {
+ return false
+ }
+ w.b = append(w.b, frameTypeConnectionCloseApplication)
+ w.b = quicwire.AppendVarint(w.b, code)
+ w.b = quicwire.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
+}