| var hpack = require('../hpack'); |
| var utils = hpack.utils; |
| var huffman = hpack.huffman.decode; |
| var assert = utils.assert; |
| |
| var OffsetBuffer = require('obuf'); |
| |
| function Decoder() { |
| this.buffer = new OffsetBuffer(); |
| this.bitOffset = 0; |
| |
| // Used internally in decodeStr |
| this._huffmanNode = null; |
| } |
| module.exports = Decoder; |
| |
| Decoder.create = function create() { |
| return new Decoder(); |
| }; |
| |
| Decoder.prototype.isEmpty = function isEmpty() { |
| return this.buffer.isEmpty(); |
| }; |
| |
| Decoder.prototype.push = function push(chunk) { |
| this.buffer.push(chunk); |
| }; |
| |
| Decoder.prototype.decodeBit = function decodeBit() { |
| // Need at least one octet |
| assert(this.buffer.has(1), 'Buffer too small for an int'); |
| |
| var octet; |
| var offset = this.bitOffset; |
| |
| if (++this.bitOffset === 8) { |
| octet = this.buffer.readUInt8(); |
| this.bitOffset = 0; |
| } else { |
| octet = this.buffer.peekUInt8(); |
| } |
| return (octet >>> (7 - offset)) & 1; |
| }; |
| |
| // Just for testing |
| Decoder.prototype.skipBits = function skipBits(n) { |
| this.bitOffset += n; |
| this.buffer.skip(this.bitOffset >> 3); |
| this.bitOffset &= 0x7; |
| }; |
| |
| Decoder.prototype.decodeInt = function decodeInt() { |
| // Need at least one octet |
| assert(this.buffer.has(1), 'Buffer too small for an int'); |
| |
| var prefix = 8 - this.bitOffset; |
| |
| // We are going to end up octet-aligned |
| this.bitOffset = 0; |
| |
| var max = (1 << prefix) - 1; |
| var octet = this.buffer.readUInt8() & max; |
| |
| // Fast case - int fits into the prefix |
| if (octet !== max) |
| return octet; |
| |
| // TODO(indutny): what about > 32bit numbers? |
| var res = 0; |
| var isLast = false; |
| var len = 0; |
| do { |
| octet = this.buffer.readUInt8(); |
| isLast = (octet & 0x80) === 0; |
| |
| res <<= 7; |
| res |= octet & 0x7f; |
| len++; |
| } while (!isLast); |
| assert(isLast, 'Incomplete data for multi-octet integer'); |
| assert(len <= 4, 'Integer does not fit into 32 bits'); |
| |
| // Reverse bits |
| res = (res >>> 21) | |
| (((res >> 14) & 0x7f) << 7) | |
| (((res >> 7) & 0x7f) << 14) | |
| ((res & 0x7f) << 21); |
| res >>= (4 - len) * 7; |
| |
| // Append prefix max |
| res += max; |
| |
| return res; |
| }; |
| |
| Decoder.prototype.decodeHuffmanWord = function decodeHuffmanWord(input, |
| inputBits, |
| out) { |
| var root = huffman; |
| var node = this._huffmanNode; |
| var word = input; |
| var bits = inputBits; |
| |
| for (; bits > 0; word &= (1 << bits) - 1) { |
| // Nudge the word bit length to match it |
| for (var i = Math.max(0, bits - 8); i < bits; i++) { |
| var subnode = node[word >>> i]; |
| if (typeof subnode !== 'number') { |
| node = subnode; |
| bits = i; |
| break; |
| } |
| |
| if (subnode === 0) |
| continue; |
| |
| // Word bit length should match |
| if ((subnode >>> 9) !== bits - i) { |
| subnode = 0; |
| continue; |
| } |
| |
| var octet = subnode & 0x1ff; |
| assert(octet !== 256, 'EOS in encoding'); |
| out.push(octet); |
| node = root; |
| |
| bits = i; |
| break; |
| } |
| if (subnode === 0) |
| break; |
| } |
| this._huffmanNode = node; |
| |
| return bits; |
| }; |
| |
| Decoder.prototype.decodeStr = function decodeStr() { |
| var isHuffman = this.decodeBit(); |
| var len = this.decodeInt(); |
| assert(this.buffer.has(len), 'Not enough octets for string'); |
| |
| if (!isHuffman) |
| return this.buffer.take(len); |
| |
| this._huffmanNode = huffman; |
| |
| var out = []; |
| |
| var word = 0; |
| var bits = 0; |
| var lastKey = 0; |
| for (var i = 0; i < len; i++) { |
| word <<= 8; |
| word |= this.buffer.readUInt8(); |
| bits += 8; |
| |
| bits = this.decodeHuffmanWord(word, bits, out); |
| lastKey = word >> bits; |
| word &= (1 << bits) - 1; |
| } |
| assert(this._huffmanNode === huffman, '8-bit EOS'); |
| assert(word + 1 === (1 << bits), 'Final sequence is not EOS'); |
| |
| this._huffmanNode = null; |
| |
| return out; |
| }; |