BufferList.js
1 'use strict' 2 3 const { Buffer } = require('buffer') 4 const symbol = Symbol.for('BufferList') 5 6 function BufferList (buf) { 7 if (!(this instanceof BufferList)) { 8 return new BufferList(buf) 9 } 10 11 BufferList._init.call(this, buf) 12 } 13 14 BufferList._init = function _init (buf) { 15 Object.defineProperty(this, symbol, { value: true }) 16 17 this._bufs = [] 18 this.length = 0 19 20 if (buf) { 21 this.append(buf) 22 } 23 } 24 25 BufferList.prototype._new = function _new (buf) { 26 return new BufferList(buf) 27 } 28 29 BufferList.prototype._offset = function _offset (offset) { 30 if (offset === 0) { 31 return [0, 0] 32 } 33 34 let tot = 0 35 36 for (let i = 0; i < this._bufs.length; i++) { 37 const _t = tot + this._bufs[i].length 38 if (offset < _t || i === this._bufs.length - 1) { 39 return [i, offset - tot] 40 } 41 tot = _t 42 } 43 } 44 45 BufferList.prototype._reverseOffset = function (blOffset) { 46 const bufferId = blOffset[0] 47 let offset = blOffset[1] 48 49 for (let i = 0; i < bufferId; i++) { 50 offset += this._bufs[i].length 51 } 52 53 return offset 54 } 55 56 BufferList.prototype.get = function get (index) { 57 if (index > this.length || index < 0) { 58 return undefined 59 } 60 61 const offset = this._offset(index) 62 63 return this._bufs[offset[0]][offset[1]] 64 } 65 66 BufferList.prototype.slice = function slice (start, end) { 67 if (typeof start === 'number' && start < 0) { 68 start += this.length 69 } 70 71 if (typeof end === 'number' && end < 0) { 72 end += this.length 73 } 74 75 return this.copy(null, 0, start, end) 76 } 77 78 BufferList.prototype.copy = function copy (dst, dstStart, srcStart, srcEnd) { 79 if (typeof srcStart !== 'number' || srcStart < 0) { 80 srcStart = 0 81 } 82 83 if (typeof srcEnd !== 'number' || srcEnd > this.length) { 84 srcEnd = this.length 85 } 86 87 if (srcStart >= this.length) { 88 return dst || Buffer.alloc(0) 89 } 90 91 if (srcEnd <= 0) { 92 return dst || Buffer.alloc(0) 93 } 94 95 const copy = !!dst 96 const off = this._offset(srcStart) 97 const len = srcEnd - srcStart 98 let bytes = len 99 let bufoff = (copy && dstStart) || 0 100 let start = off[1] 101 102 // copy/slice everything 103 if (srcStart === 0 && srcEnd === this.length) { 104 if (!copy) { 105 // slice, but full concat if multiple buffers 106 return this._bufs.length === 1 107 ? this._bufs[0] 108 : Buffer.concat(this._bufs, this.length) 109 } 110 111 // copy, need to copy individual buffers 112 for (let i = 0; i < this._bufs.length; i++) { 113 this._bufs[i].copy(dst, bufoff) 114 bufoff += this._bufs[i].length 115 } 116 117 return dst 118 } 119 120 // easy, cheap case where it's a subset of one of the buffers 121 if (bytes <= this._bufs[off[0]].length - start) { 122 return copy 123 ? this._bufs[off[0]].copy(dst, dstStart, start, start + bytes) 124 : this._bufs[off[0]].slice(start, start + bytes) 125 } 126 127 if (!copy) { 128 // a slice, we need something to copy in to 129 dst = Buffer.allocUnsafe(len) 130 } 131 132 for (let i = off[0]; i < this._bufs.length; i++) { 133 const l = this._bufs[i].length - start 134 135 if (bytes > l) { 136 this._bufs[i].copy(dst, bufoff, start) 137 bufoff += l 138 } else { 139 this._bufs[i].copy(dst, bufoff, start, start + bytes) 140 bufoff += l 141 break 142 } 143 144 bytes -= l 145 146 if (start) { 147 start = 0 148 } 149 } 150 151 // safeguard so that we don't return uninitialized memory 152 if (dst.length > bufoff) return dst.slice(0, bufoff) 153 154 return dst 155 } 156 157 BufferList.prototype.shallowSlice = function shallowSlice (start, end) { 158 start = start || 0 159 end = typeof end !== 'number' ? this.length : end 160 161 if (start < 0) { 162 start += this.length 163 } 164 165 if (end < 0) { 166 end += this.length 167 } 168 169 if (start === end) { 170 return this._new() 171 } 172 173 const startOffset = this._offset(start) 174 const endOffset = this._offset(end) 175 const buffers = this._bufs.slice(startOffset[0], endOffset[0] + 1) 176 177 if (endOffset[1] === 0) { 178 buffers.pop() 179 } else { 180 buffers[buffers.length - 1] = buffers[buffers.length - 1].slice(0, endOffset[1]) 181 } 182 183 if (startOffset[1] !== 0) { 184 buffers[0] = buffers[0].slice(startOffset[1]) 185 } 186 187 return this._new(buffers) 188 } 189 190 BufferList.prototype.toString = function toString (encoding, start, end) { 191 return this.slice(start, end).toString(encoding) 192 } 193 194 BufferList.prototype.consume = function consume (bytes) { 195 // first, normalize the argument, in accordance with how Buffer does it 196 bytes = Math.trunc(bytes) 197 // do nothing if not a positive number 198 if (Number.isNaN(bytes) || bytes <= 0) return this 199 200 while (this._bufs.length) { 201 if (bytes >= this._bufs[0].length) { 202 bytes -= this._bufs[0].length 203 this.length -= this._bufs[0].length 204 this._bufs.shift() 205 } else { 206 this._bufs[0] = this._bufs[0].slice(bytes) 207 this.length -= bytes 208 break 209 } 210 } 211 212 return this 213 } 214 215 BufferList.prototype.duplicate = function duplicate () { 216 const copy = this._new() 217 218 for (let i = 0; i < this._bufs.length; i++) { 219 copy.append(this._bufs[i]) 220 } 221 222 return copy 223 } 224 225 BufferList.prototype.append = function append (buf) { 226 if (buf == null) { 227 return this 228 } 229 230 if (buf.buffer) { 231 // append a view of the underlying ArrayBuffer 232 this._appendBuffer(Buffer.from(buf.buffer, buf.byteOffset, buf.byteLength)) 233 } else if (Array.isArray(buf)) { 234 for (let i = 0; i < buf.length; i++) { 235 this.append(buf[i]) 236 } 237 } else if (this._isBufferList(buf)) { 238 // unwrap argument into individual BufferLists 239 for (let i = 0; i < buf._bufs.length; i++) { 240 this.append(buf._bufs[i]) 241 } 242 } else { 243 // coerce number arguments to strings, since Buffer(number) does 244 // uninitialized memory allocation 245 if (typeof buf === 'number') { 246 buf = buf.toString() 247 } 248 249 this._appendBuffer(Buffer.from(buf)) 250 } 251 252 return this 253 } 254 255 BufferList.prototype._appendBuffer = function appendBuffer (buf) { 256 this._bufs.push(buf) 257 this.length += buf.length 258 } 259 260 BufferList.prototype.indexOf = function (search, offset, encoding) { 261 if (encoding === undefined && typeof offset === 'string') { 262 encoding = offset 263 offset = undefined 264 } 265 266 if (typeof search === 'function' || Array.isArray(search)) { 267 throw new TypeError('The "value" argument must be one of type string, Buffer, BufferList, or Uint8Array.') 268 } else if (typeof search === 'number') { 269 search = Buffer.from([search]) 270 } else if (typeof search === 'string') { 271 search = Buffer.from(search, encoding) 272 } else if (this._isBufferList(search)) { 273 search = search.slice() 274 } else if (Array.isArray(search.buffer)) { 275 search = Buffer.from(search.buffer, search.byteOffset, search.byteLength) 276 } else if (!Buffer.isBuffer(search)) { 277 search = Buffer.from(search) 278 } 279 280 offset = Number(offset || 0) 281 282 if (isNaN(offset)) { 283 offset = 0 284 } 285 286 if (offset < 0) { 287 offset = this.length + offset 288 } 289 290 if (offset < 0) { 291 offset = 0 292 } 293 294 if (search.length === 0) { 295 return offset > this.length ? this.length : offset 296 } 297 298 const blOffset = this._offset(offset) 299 let blIndex = blOffset[0] // index of which internal buffer we're working on 300 let buffOffset = blOffset[1] // offset of the internal buffer we're working on 301 302 // scan over each buffer 303 for (; blIndex < this._bufs.length; blIndex++) { 304 const buff = this._bufs[blIndex] 305 306 while (buffOffset < buff.length) { 307 const availableWindow = buff.length - buffOffset 308 309 if (availableWindow >= search.length) { 310 const nativeSearchResult = buff.indexOf(search, buffOffset) 311 312 if (nativeSearchResult !== -1) { 313 return this._reverseOffset([blIndex, nativeSearchResult]) 314 } 315 316 buffOffset = buff.length - search.length + 1 // end of native search window 317 } else { 318 const revOffset = this._reverseOffset([blIndex, buffOffset]) 319 320 if (this._match(revOffset, search)) { 321 return revOffset 322 } 323 324 buffOffset++ 325 } 326 } 327 328 buffOffset = 0 329 } 330 331 return -1 332 } 333 334 BufferList.prototype._match = function (offset, search) { 335 if (this.length - offset < search.length) { 336 return false 337 } 338 339 for (let searchOffset = 0; searchOffset < search.length; searchOffset++) { 340 if (this.get(offset + searchOffset) !== search[searchOffset]) { 341 return false 342 } 343 } 344 return true 345 } 346 347 ;(function () { 348 const methods = { 349 readDoubleBE: 8, 350 readDoubleLE: 8, 351 readFloatBE: 4, 352 readFloatLE: 4, 353 readInt32BE: 4, 354 readInt32LE: 4, 355 readUInt32BE: 4, 356 readUInt32LE: 4, 357 readInt16BE: 2, 358 readInt16LE: 2, 359 readUInt16BE: 2, 360 readUInt16LE: 2, 361 readInt8: 1, 362 readUInt8: 1, 363 readIntBE: null, 364 readIntLE: null, 365 readUIntBE: null, 366 readUIntLE: null 367 } 368 369 for (const m in methods) { 370 (function (m) { 371 if (methods[m] === null) { 372 BufferList.prototype[m] = function (offset, byteLength) { 373 return this.slice(offset, offset + byteLength)[m](0, byteLength) 374 } 375 } else { 376 BufferList.prototype[m] = function (offset = 0) { 377 return this.slice(offset, offset + methods[m])[m](0) 378 } 379 } 380 }(m)) 381 } 382 }()) 383 384 // Used internally by the class and also as an indicator of this object being 385 // a `BufferList`. It's not possible to use `instanceof BufferList` in a browser 386 // environment because there could be multiple different copies of the 387 // BufferList class and some `BufferList`s might be `BufferList`s. 388 BufferList.prototype._isBufferList = function _isBufferList (b) { 389 return b instanceof BufferList || BufferList.isBufferList(b) 390 } 391 392 BufferList.isBufferList = function isBufferList (b) { 393 return b != null && b[symbol] 394 } 395 396 module.exports = BufferList