github.com/iDigitalFlame/xmt@v0.5.4/data/chunk_base.go (about) 1 // Copyright (C) 2020 - 2023 iDigitalFlame 2 // 3 // This program is free software: you can redistribute it and/or modify 4 // it under the terms of the GNU General Public License as published by 5 // the Free Software Foundation, either version 3 of the License, or 6 // any later version. 7 // 8 // This program is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 // GNU General Public License for more details. 12 // 13 // You should have received a copy of the GNU General Public License 14 // along with this program. If not, see <https://www.gnu.org/licenses/>. 15 // 16 17 package data 18 19 import ( 20 "io" 21 "net" 22 "sync" 23 "time" 24 25 "github.com/iDigitalFlame/xmt/util/bugtrack" 26 "github.com/iDigitalFlame/xmt/util/xerr" 27 ) 28 29 const ( 30 max = int(^uint(0) >> 1) 31 bufSize = 2 << 13 32 ) 33 34 var bufs = sync.Pool{ 35 New: func() interface{} { 36 var b [bufSize]byte 37 return &b 38 }, 39 } 40 41 // Flush allows Chunk to support the io.Flusher interface. 42 func (Chunk) Flush() error { 43 return nil 44 } 45 46 // Close allows Chunk to support the io.Closer interface. 47 func (Chunk) Close() error { 48 return nil 49 } 50 51 // Space returns the amount of bytes available in this Chunk when a Limit is 52 // set. 53 // 54 // This function will return -1 if there is no limit set and returns 0 (zero) 55 // when a limit is set, but no byte space is available. 56 func (c *Chunk) Space() int { 57 if c.Limit <= 0 { 58 return -1 59 } 60 //if c.Empty() { 61 // return 0 62 //} 63 if r := c.Limit - c.Size(); r > 0 { 64 return r 65 } 66 return 0 67 } 68 69 // String returns a string representation of this Chunk's buffer. 70 func (c *Chunk) String() string { 71 if c.Empty() { 72 return "<nil>" 73 } 74 _ = c.buf[c.rpos] 75 return string(c.buf[c.rpos:]) 76 } 77 78 // Remaining returns the number of bytes left to be read in this Chunk. This is 79 // the length 'Size' minus the read cursor. 80 func (c *Chunk) Remaining() int { 81 if c.Empty() { 82 return 0 83 } 84 return c.Size() - c.rpos 85 } 86 87 // Payload returns a copy of the underlying UNREAD buffer contained in this 88 // Chunk. 89 // 90 // This may be empty depending on the read status of this chunk. To retrieve the 91 // full buffer, use the 'Seek' function to set the read cursor to zero. 92 func (c *Chunk) Payload() []byte { 93 if c.Empty() || c.rpos > c.Size() { 94 return nil 95 } 96 _ = c.buf[c.Size()-1] 97 return c.buf[c.rpos:c.Size()] 98 } 99 100 // Available returns if a limit will block the writing of n bytes. This function 101 // can be used to check if there is space to write before committing a write. 102 func (c *Chunk) Available(n int) bool { 103 return c.Limit <= 0 || c.Limit-c.Size() > n 104 } 105 106 // Read reads the next len(p) bytes from the Chunk or until the Chunk is 107 // drained. The return value n is the number of bytes read and any errors that 108 // may have occurred. 109 func (c *Chunk) Read(b []byte) (int, error) { 110 if c.Empty() && c.buf != nil { 111 if c.Reset(); len(b) == 0 { 112 return 0, nil 113 } 114 return 0, io.EOF 115 } 116 n := copy(b, c.buf[c.rpos:]) 117 c.rpos += n 118 return n, nil 119 } 120 121 // Write appends the contents of b to the buffer, growing the buffer as needed. 122 // 123 // If the buffer becomes too large, Write will return 'ErrTooLarge.' If there is 124 // a limit set, this function will return 'ErrLimit' if the Limit is being hit. 125 // 126 // If an 'ErrLimit' is returned, check the returned bytes as 'ErrLimit' is 127 // returned as a warning that not all bytes have been written before refusing 128 // writes. 129 func (c *Chunk) Write(b []byte) (int, error) { 130 m, err := c.quickSlice(len(b)) 131 if err != nil { 132 return 0, err 133 } 134 n := copy(c.buf[m:], b) 135 if n < len(b) && c.Limit > 0 && len(c.buf) >= c.Limit { 136 return n, ErrLimit 137 } 138 return n, nil 139 } 140 141 // MarshalStream writes the unread Chunk data into a binary data representation. 142 // This function will return an error if any part of the write fails. 143 func (c *Chunk) MarshalStream(w Writer) error { 144 if bugtrack.Enabled { 145 if _, ok := w.(*Chunk); ok { 146 bugtrack.Track("data.(*Chunk).MarshalStream(): MarshalStream was called from a Chunk to a Chunk!") 147 } 148 } 149 return w.WriteBytes(c.buf[c.rpos:]) 150 } 151 152 // WriteTo writes data to the supplied Writer until there's no more data to 153 // write or when an error occurs. 154 // 155 // The return value is the number of bytes written. Any error encountered 156 // during the write is also returned. 157 func (c *Chunk) WriteTo(w io.Writer) (int64, error) { 158 if c.Empty() { 159 return 0, nil 160 } 161 var ( 162 n int 163 err error 164 ) 165 for v, s, e := 0, c.rpos, c.rpos+bufSize; n < c.Size() && err == nil; { 166 if e > c.Size() { 167 e = c.Size() 168 } 169 if s == e { 170 break 171 } 172 v, err = w.Write(c.buf[s:e]) 173 if n += v; err != nil { 174 break 175 } 176 s = e 177 e += v 178 } 179 c.rpos += n 180 return int64(n), err 181 } 182 183 // Seek will attempt to seek to the provided read offset index and whence. This 184 // function will return the new offset if successful and will return an error 185 // if the offset and/or whence are invalid. 186 // 187 // NOTE: This only affects read operations. 188 func (c *Chunk) Seek(o int64, w int) (int64, error) { 189 switch w { 190 case io.SeekStart: 191 if o < 0 { 192 return 0, ErrInvalidIndex 193 } 194 case io.SeekCurrent: 195 o += int64(c.rpos) 196 case io.SeekEnd: 197 o += int64(c.Size()) 198 default: 199 return 0, xerr.Sub("invalid whence", 0x27) 200 } 201 if o < 0 || int(o) > c.Size() { 202 return 0, ErrInvalidIndex 203 } 204 c.rpos = int(o) 205 return o, nil 206 } 207 208 // ReadFrom reads data from the supplied Reader until EOF or error. 209 // 210 // The return value is the number of bytes read. 211 // Any error except 'io.EOF' encountered during the read is also returned. 212 func (c *Chunk) ReadFrom(r io.Reader) (int64, error) { 213 var ( 214 b = bufs.Get().(*[bufSize]byte) 215 t int64 216 n, w int 217 err, err2 error 218 ) 219 for { 220 if c.Limit > 0 { 221 x := c.Space() 222 if x <= 0 { 223 break 224 } 225 if x > bufSize { 226 x = bufSize 227 } 228 n, err = r.Read((*b)[:x]) 229 } else { 230 n, err = r.Read((*b)[:]) 231 } 232 if n > 0 { 233 w, err2 = c.Write((*b)[:n]) 234 if w < n { 235 t += int64(w) 236 } else { 237 t += int64(n) 238 } 239 if err2 != nil { 240 break 241 } 242 } 243 if bugtrack.Enabled { 244 bugtrack.Track("data.(*Chunk).ReadFrom(): n=%d, t=%d, len(b)=%d, err=%s, err2=%s", n, t, len(*b), err, err2) 245 } 246 if n == 0 || err != nil || err2 != nil || (c.Limit > 0 && n >= c.Limit) { 247 if err == io.EOF || err == ErrLimit { 248 err = nil 249 } 250 break 251 } 252 } 253 if bufs.Put(b); bugtrack.Enabled { 254 bugtrack.Track("data.(*Chunk).ReadFrom(): return t=%d, err=%s, err2=%s", t, err, err2) 255 } 256 return t, err 257 } 258 259 // ReadDeadline reads data from the supplied net.Conn until EOF or error. 260 // 261 // The return value is the number of bytes read. 262 // Any error except 'io.EOF' encountered during the read is also returned. 263 // 264 // If the specific duration is greater than zero, the read deadline will be 265 // applied. Timeout errors will NOT be returned and will instead break a read. 266 func (c *Chunk) ReadDeadline(r net.Conn, d time.Duration) (int64, error) { 267 var ( 268 b = bufs.Get().(*[bufSize]byte) 269 t int64 270 n, w int 271 err, err2 error 272 ) 273 if bugtrack.Enabled { 274 bugtrack.Track("data.(*Chunk).ReadDeadline(): start, d=%s", d.String()) 275 } 276 for { 277 if c.Limit > 0 { 278 x := c.Space() 279 if x <= 0 { 280 break 281 } 282 if x > bufSize { 283 x = bufSize 284 } 285 n, err = r.Read((*b)[:x]) 286 } else { 287 n, err = r.Read((*b)[:]) 288 } 289 if n > 0 { 290 w, err2 = c.Write((*b)[:n]) 291 if w < n { 292 t += int64(w) 293 } else { 294 t += int64(n) 295 } 296 if err2 != nil { 297 break 298 } 299 } 300 if bugtrack.Enabled { 301 bugtrack.Track("data.(*Chunk).ReadDeadline(): n=%d, t=%d, len(b)=%d, err=%s, err2=%s", n, t, len(*b), err, err2) 302 } 303 if n == 0 || err != nil || err2 != nil || (c.Limit > 0 && n >= c.Limit) { 304 if e, ok := err.(net.Error); ok && e.Timeout() { 305 err = nil 306 } else if err == io.EOF || err == ErrLimit { 307 err = nil 308 } 309 break 310 } 311 if d > 0 { 312 r.SetReadDeadline(time.Now().Add(d)) 313 } 314 } 315 if bufs.Put(b); bugtrack.Enabled { 316 bugtrack.Track("data.(*Chunk).ReadDeadline(): return t=%d, err=%s", t, err) 317 } 318 return t, err 319 }