go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/data/chunkstream/buffer.go (about) 1 // Copyright 2015 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package chunkstream 16 17 import ( 18 "errors" 19 ) 20 21 // Buffer is a collection of ordered Chunks that can cheaply read and shifted 22 // as if it were a continuous byte stream. 23 // 24 // A Buffer is not goroutine-safe. 25 // 26 // The primary means of interacting with a Buffer is to construct a View and 27 // then use it to access the Buffer's contents. Views can be used concurrently, 28 // and View operations are goroutine-safe. 29 type Buffer struct { 30 // First is a pointer to the first Chunk node in the buffer. 31 first *chunkNode 32 // Last is a pointer to the last Chunk node in the buffer. 33 last *chunkNode 34 35 // size is the total number of bytes in the Buffer. 36 size int64 37 38 // fidx is the current byte offset in first. 39 fidx int 40 } 41 42 // Append adds additional Chunks to the buffer. 43 // 44 // After completion, the Chunk is now owned by the Buffer and should not be used 45 // anymore externally. 46 func (b *Buffer) Append(c ...Chunk) { 47 for _, chunk := range c { 48 b.appendChunk(chunk) 49 } 50 } 51 52 func (b *Buffer) appendChunk(c Chunk) { 53 // Ignore/discard zero-length data. 54 if len(c.Bytes()) == 0 { 55 c.Release() 56 return 57 } 58 59 cn := newChunkNode(c) 60 cn.next = nil 61 if b.last == nil { 62 // First node. 63 b.first = cn 64 } else { 65 b.last.next = cn 66 } 67 b.last = cn 68 b.size += int64(cn.length()) 69 } 70 71 // Bytes constructs a byte slice containing the contents of the Buffer. 72 // 73 // This is a potentially expensive operation, and should generally be used only 74 // for debugging and tests, as it defeats most of the purpose of this package. 75 func (b *Buffer) Bytes() []byte { 76 if b.Len() == 0 { 77 return nil 78 } 79 80 m := make([]byte, 0, b.Len()) 81 idx := b.fidx 82 for cur := b.first; cur != nil; cur = cur.next { 83 m = append(m, cur.Bytes()[idx:]...) 84 idx = 0 85 } 86 return m 87 } 88 89 // Len returns the total amount of data in the buffer. 90 func (b *Buffer) Len() int64 { 91 return b.size 92 } 93 94 // FirstChunk returns the first Chunk in the Buffer, or nil if the Buffer has 95 // no Chunks. 96 func (b *Buffer) FirstChunk() Chunk { 97 if b.first == nil { 98 return nil 99 } 100 return b.first.Chunk 101 } 102 103 // View returns a View instance bound to this Buffer and spanning all data 104 // currently in the Buffer. 105 // 106 // The View is no longer valid after Consume is called on the Buffer. 107 func (b *Buffer) View() *View { 108 return b.ViewLimit(b.size) 109 } 110 111 // ViewLimit constructs a View instance, but artificially constrains it to 112 // read at most the specified number of bytes. 113 // 114 // This is useful when reading a subset of the data into a Buffer, as ReadFrom 115 // does not allow a size to be specified. 116 func (b *Buffer) ViewLimit(limit int64) *View { 117 if limit > b.size { 118 limit = b.size 119 } 120 121 return &View{ 122 cur: b.first, 123 cidx: b.fidx, 124 size: limit, 125 126 b: b, 127 } 128 } 129 130 // Consume removes the specified number of bytes from the beginning of the 131 // Buffer. If Consume skips past all of the data in a Chunk is no longer needed, 132 // it is Release()d. 133 func (b *Buffer) Consume(c int64) { 134 if c == 0 { 135 return 136 } 137 138 if c > b.size { 139 panic(errors.New("consuming more data than available")) 140 } 141 b.size -= c 142 143 for c > 0 { 144 // Do we consume the entire chunk? 145 if int64(b.first.length()-b.fidx) > c { 146 // No. Advance our chunk index and terminate. 147 b.fidx += int(c) 148 break 149 } 150 151 n := b.first 152 c -= int64(n.length() - b.fidx) 153 b.first = n.next 154 b.fidx = 0 155 if b.first == nil { 156 b.last = nil 157 } 158 159 // Release our node. We must not reference it after this. 160 n.release() 161 } 162 }