1'use strict';
2
3const {
4  StringPrototypeSlice,
5  SymbolIterator,
6  TypedArrayPrototypeSet,
7  Uint8Array,
8} = primordials;
9
10const { Buffer } = require('buffer');
11const { inspect } = require('internal/util/inspect');
12
13module.exports = class BufferList {
14  constructor() {
15    this.head = null;
16    this.tail = null;
17    this.length = 0;
18  }
19
20  push(v) {
21    const entry = { data: v, next: null };
22    if (this.length > 0)
23      this.tail.next = entry;
24    else
25      this.head = entry;
26    this.tail = entry;
27    ++this.length;
28  }
29
30  unshift(v) {
31    const entry = { data: v, next: this.head };
32    if (this.length === 0)
33      this.tail = entry;
34    this.head = entry;
35    ++this.length;
36  }
37
38  shift() {
39    if (this.length === 0)
40      return;
41    const ret = this.head.data;
42    if (this.length === 1)
43      this.head = this.tail = null;
44    else
45      this.head = this.head.next;
46    --this.length;
47    return ret;
48  }
49
50  clear() {
51    this.head = this.tail = null;
52    this.length = 0;
53  }
54
55  join(s) {
56    if (this.length === 0)
57      return '';
58    let p = this.head;
59    let ret = '' + p.data;
60    while ((p = p.next) !== null)
61      ret += s + p.data;
62    return ret;
63  }
64
65  concat(n) {
66    if (this.length === 0)
67      return Buffer.alloc(0);
68    const ret = Buffer.allocUnsafe(n >>> 0);
69    let p = this.head;
70    let i = 0;
71    while (p) {
72      TypedArrayPrototypeSet(ret, p.data, i);
73      i += p.data.length;
74      p = p.next;
75    }
76    return ret;
77  }
78
79  // Consumes a specified amount of bytes or characters from the buffered data.
80  consume(n, hasStrings) {
81    const data = this.head.data;
82    if (n < data.length) {
83      // `slice` is the same for buffers and strings.
84      const slice = data.slice(0, n);
85      this.head.data = data.slice(n);
86      return slice;
87    }
88    if (n === data.length) {
89      // First chunk is a perfect match.
90      return this.shift();
91    }
92    // Result spans more than one buffer.
93    return hasStrings ? this._getString(n) : this._getBuffer(n);
94  }
95
96  first() {
97    return this.head.data;
98  }
99
100  *[SymbolIterator]() {
101    for (let p = this.head; p; p = p.next) {
102      yield p.data;
103    }
104  }
105
106  // Consumes a specified amount of characters from the buffered data.
107  _getString(n) {
108    let ret = '';
109    let p = this.head;
110    let c = 0;
111    do {
112      const str = p.data;
113      if (n > str.length) {
114        ret += str;
115        n -= str.length;
116      } else {
117        if (n === str.length) {
118          ret += str;
119          ++c;
120          if (p.next)
121            this.head = p.next;
122          else
123            this.head = this.tail = null;
124        } else {
125          ret += StringPrototypeSlice(str, 0, n);
126          this.head = p;
127          p.data = StringPrototypeSlice(str, n);
128        }
129        break;
130      }
131      ++c;
132    } while ((p = p.next) !== null);
133    this.length -= c;
134    return ret;
135  }
136
137  // Consumes a specified amount of bytes from the buffered data.
138  _getBuffer(n) {
139    const ret = Buffer.allocUnsafe(n);
140    const retLen = n;
141    let p = this.head;
142    let c = 0;
143    do {
144      const buf = p.data;
145      if (n > buf.length) {
146        TypedArrayPrototypeSet(ret, buf, retLen - n);
147        n -= buf.length;
148      } else {
149        if (n === buf.length) {
150          TypedArrayPrototypeSet(ret, buf, retLen - n);
151          ++c;
152          if (p.next)
153            this.head = p.next;
154          else
155            this.head = this.tail = null;
156        } else {
157          TypedArrayPrototypeSet(ret,
158                                 new Uint8Array(buf.buffer, buf.byteOffset, n),
159                                 retLen - n);
160          this.head = p;
161          p.data = buf.slice(n);
162        }
163        break;
164      }
165      ++c;
166    } while ((p = p.next) !== null);
167    this.length -= c;
168    return ret;
169  }
170
171  // Make sure the linked list only shows the minimal necessary information.
172  [inspect.custom](_, options) {
173    return inspect(this, {
174      ...options,
175      // Only inspect one level.
176      depth: 0,
177      // It should not recurse.
178      customInspect: false,
179    });
180  }
181};
182