github.com/iDigitalFlame/xmt@v0.5.4/data/chunk.go (about) 1 //go:build !windows || !heap 2 // +build !windows !heap 3 4 // Copyright (C) 2020 - 2023 iDigitalFlame 5 // 6 // This program is free software: you can redistribute it and/or modify 7 // it under the terms of the GNU General Public License as published by 8 // the Free Software Foundation, either version 3 of the License, or 9 // any later version. 10 // 11 // This program is distributed in the hope that it will be useful, 12 // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 // GNU General Public License for more details. 15 // 16 // You should have received a copy of the GNU General Public License 17 // along with this program. If not, see <https://www.gnu.org/licenses/>. 18 // 19 20 package data 21 22 import "github.com/iDigitalFlame/xmt/util/bugtrack" 23 24 // Chunk is a low level data container. Chunks allow for simple read/write 25 // operations on static containers. 26 // 27 // Chunk fulfils the Reader, Seeker, Writer, Flusher and Closer interfaces. 28 // Seeking on Chunks is only supported in a read-only fashion. 29 // 30 // If the underlying device is running Windows and the "heap" build tag is used, 31 // Chunks will be created on the Process Heap not managed by Go. This prevents 32 // over-allocation and relieves pressure on the GC. By default, this is off. 33 type Chunk struct { 34 buf []byte 35 rpos int 36 37 Limit int 38 } 39 40 // Reset resets the Chunk buffer to be empty but retains the underlying storage 41 // for use by future writes. 42 func (c *Chunk) Reset() { 43 c.rpos, c.buf = 0, c.buf[:0] 44 } 45 46 // Clear is similar to Reset, but discards the buffer, which must be allocated 47 // again. If using the buffer the 'Reset' function is preferable. 48 func (c *Chunk) Clear() { 49 c.rpos, c.buf = 0, nil 50 } 51 52 // Size returns the internal size of the backing buffer, similar to len(b). 53 func (c *Chunk) Size() int { 54 return len(c.buf) 55 } 56 57 // Empty returns true if this Chunk's buffer is empty or has been drained by 58 // reads. 59 func (c *Chunk) Empty() bool { 60 return c == nil || len(c.buf) <= c.rpos 61 } 62 63 // NewChunk creates a new Chunk struct and will use the provided byte array as 64 // the underlying backing buffer. 65 func NewChunk(b []byte) *Chunk { 66 return &Chunk{buf: b} 67 } 68 69 // Grow grows the Chunk's buffer capacity, if necessary, to guarantee space for 70 // another n bytes. 71 func (c *Chunk) Grow(n int) error { 72 if n <= 0 { 73 return ErrInvalidIndex 74 } 75 m, err := c.grow(n) 76 if err != nil { 77 return err 78 } 79 c.buf = c.buf[:m] 80 return nil 81 } 82 83 // Truncate discards all but the first n unread bytes from the Chunk but 84 // continues to use the same allocated storage. 85 // 86 // This will return an error if n is negative or greater than the length of the 87 // buffer. 88 func (c *Chunk) Truncate(n int) error { 89 if n == 0 { 90 c.Reset() 91 return nil 92 } 93 if n < 0 || n > len(c.buf)-c.rpos { 94 return ErrInvalidIndex 95 } 96 c.buf = c.buf[:c.rpos+n] 97 return nil 98 } 99 func (c *Chunk) checkBounds(n int) bool { 100 return c.rpos+n > len(c.buf) 101 } 102 func (c *Chunk) grow(n int) (int, error) { 103 x := len(c.buf) - c.rpos 104 if x == 0 && c.rpos != 0 { 105 c.rpos, c.buf = 0, c.buf[:0] 106 } 107 if c.Limit > 0 { 108 if x >= c.Limit { 109 return 0, ErrLimit 110 } 111 if n > c.Limit { 112 n = c.Limit 113 } 114 } 115 if i, ok := c.reslice(n); ok { 116 return i, nil 117 } 118 if c.buf == nil && n <= 64 { 119 c.buf = make([]byte, n, 64) 120 return 0, nil 121 } 122 switch m := cap(c.buf); { 123 case n <= m/2-x: 124 // From the Golang source: 125 // We can slide things down instead of allocating a new 126 // slice. We only need m+n <= c to slide, but 127 // we instead let capacity get twice as large so we 128 // don't spend all our time copying. 129 copy(c.buf, c.buf[c.rpos:]) 130 case c.Limit > 0 && (m > c.Limit+n || x+n > c.Limit): 131 return 0, ErrLimit 132 case m > max-m-n: 133 return 0, ErrTooLarge 134 default: 135 b, err := trySlice(c.buf[c.rpos:], c.rpos+n) 136 if err != nil { 137 return 0, err 138 } 139 c.buf = nil // Reset and set. 140 c.buf = b 141 } 142 c.rpos, c.buf = 0, c.buf[:x+n] 143 return x, nil 144 } 145 func (c *Chunk) reslice(n int) (int, bool) { 146 if l := len(c.buf); n <= cap(c.buf)-l { 147 if c.Limit > 0 { 148 if l >= c.Limit { 149 return 0, false 150 } 151 if l+n >= c.Limit { 152 n = c.Limit - l 153 } 154 } 155 c.buf = c.buf[:l+n] 156 return l, true 157 } 158 return 0, false 159 } 160 func (c *Chunk) quickSlice(n int) (int, error) { 161 m, ok := c.reslice(n) 162 if ok { 163 return m, nil 164 } 165 return c.grow(n) 166 } 167 168 // UnmarshalStream reads the Chunk data from a binary data representation. This 169 // function will return an error if any part of the read fails. 170 func (c *Chunk) UnmarshalStream(r Reader) error { 171 if bugtrack.Enabled { 172 if _, ok := r.(*Chunk); ok { 173 bugtrack.Track("data.(*Chunk).UnmarshalStream(): UnmarshalStream was called from a Chunk to a Chunk!") 174 } 175 } 176 c.buf = nil 177 err := r.ReadBytes(&c.buf) 178 c.rpos = 0 179 return err 180 } 181 func trySlice(b []byte, n int) (x []byte, err error) { 182 if n > MaxSlice { 183 return nil, ErrTooLarge 184 } 185 defer func() { 186 if recover() != nil { 187 err = ErrTooLarge 188 } 189 }() 190 c := len(b) + n 191 if c < 2*cap(b) { 192 c = 2 * cap(b) 193 } 194 x = append([]byte(nil), make([]byte, c)...) 195 copy(x, b) 196 return x[:len(b)], nil 197 }