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  }