golang.org/x/net@v0.25.1-0.20240516223405-c87a5b62e243/quic/pipe.go (about) 1 // Copyright 2023 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 //go:build go1.21 6 7 package quic 8 9 import ( 10 "sync" 11 ) 12 13 // A pipe is a byte buffer used in implementing streams. 14 // 15 // A pipe contains a window of stream data. 16 // Random access reads and writes are supported within the window. 17 // Writing past the end of the window extends it. 18 // Data may be discarded from the start of the pipe, advancing the window. 19 type pipe struct { 20 start int64 // stream position of first stored byte 21 end int64 // stream position just past the last stored byte 22 head *pipebuf // if non-nil, then head.off + len(head.b) > start 23 tail *pipebuf // if non-nil, then tail.off + len(tail.b) == end 24 } 25 26 type pipebuf struct { 27 off int64 // stream position of b[0] 28 b []byte 29 next *pipebuf 30 } 31 32 func (pb *pipebuf) end() int64 { 33 return pb.off + int64(len(pb.b)) 34 } 35 36 var pipebufPool = sync.Pool{ 37 New: func() any { 38 return &pipebuf{ 39 b: make([]byte, 4096), 40 } 41 }, 42 } 43 44 func newPipebuf() *pipebuf { 45 return pipebufPool.Get().(*pipebuf) 46 } 47 48 func (b *pipebuf) recycle() { 49 b.off = 0 50 b.next = nil 51 pipebufPool.Put(b) 52 } 53 54 // writeAt writes len(b) bytes to the pipe at offset off. 55 // 56 // Writes to offsets before p.start are discarded. 57 // Writes to offsets after p.end extend the pipe window. 58 func (p *pipe) writeAt(b []byte, off int64) { 59 end := off + int64(len(b)) 60 if end > p.end { 61 p.end = end 62 } else if end <= p.start { 63 return 64 } 65 66 if off < p.start { 67 // Discard the portion of b which falls before p.start. 68 trim := p.start - off 69 b = b[trim:] 70 off = p.start 71 } 72 73 if p.head == nil { 74 p.head = newPipebuf() 75 p.head.off = p.start 76 p.tail = p.head 77 } 78 pb := p.head 79 if off >= p.tail.off { 80 // Common case: Writing past the end of the pipe. 81 pb = p.tail 82 } 83 for { 84 pboff := off - pb.off 85 if pboff < int64(len(pb.b)) { 86 n := copy(pb.b[pboff:], b) 87 if n == len(b) { 88 return 89 } 90 off += int64(n) 91 b = b[n:] 92 } 93 if pb.next == nil { 94 pb.next = newPipebuf() 95 pb.next.off = pb.off + int64(len(pb.b)) 96 p.tail = pb.next 97 } 98 pb = pb.next 99 } 100 } 101 102 // copy copies len(b) bytes into b starting from off. 103 // The pipe must contain [off, off+len(b)). 104 func (p *pipe) copy(off int64, b []byte) { 105 dst := b[:0] 106 p.read(off, len(b), func(c []byte) error { 107 dst = append(dst, c...) 108 return nil 109 }) 110 } 111 112 // read calls f with the data in [off, off+n) 113 // The data may be provided sequentially across multiple calls to f. 114 // Note that read (unlike an io.Reader) does not consume the read data. 115 func (p *pipe) read(off int64, n int, f func([]byte) error) error { 116 if off < p.start { 117 panic("invalid read range") 118 } 119 for pb := p.head; pb != nil && n > 0; pb = pb.next { 120 if off >= pb.end() { 121 continue 122 } 123 b := pb.b[off-pb.off:] 124 if len(b) > n { 125 b = b[:n] 126 } 127 off += int64(len(b)) 128 n -= len(b) 129 if err := f(b); err != nil { 130 return err 131 } 132 } 133 if n > 0 { 134 panic("invalid read range") 135 } 136 return nil 137 } 138 139 // peek returns a reference to up to n bytes of internal data buffer, starting at p.start. 140 // The returned slice is valid until the next call to discardBefore. 141 // The length of the returned slice will be in the range [0,n]. 142 func (p *pipe) peek(n int64) []byte { 143 pb := p.head 144 if pb == nil { 145 return nil 146 } 147 b := pb.b[p.start-pb.off:] 148 return b[:min(int64(len(b)), n)] 149 } 150 151 // availableBuffer returns the available contiguous, allocated buffer space 152 // following the pipe window. 153 // 154 // This is used by the stream write fast path, which makes multiple writes into the pipe buffer 155 // without a lock, and then adjusts p.end at a later time with a lock held. 156 func (p *pipe) availableBuffer() []byte { 157 if p.tail == nil { 158 return nil 159 } 160 return p.tail.b[p.end-p.tail.off:] 161 } 162 163 // discardBefore discards all data prior to off. 164 func (p *pipe) discardBefore(off int64) { 165 for p.head != nil && p.head.end() < off { 166 head := p.head 167 p.head = p.head.next 168 head.recycle() 169 } 170 if p.head == nil { 171 p.tail = nil 172 } 173 p.start = off 174 p.end = max(p.end, off) 175 }