OverviewFilesCommitsIssuesDocsReleasesPeers

autobase-doctor / node_modules / index-encoder / index.js

484 lines · javascript ref:
const b4a = require('b4a')

const EMPTY = b4a.alloc(0)
const MAX = b4a.from([0xff])
const BUFFER = {}

BUFFER.preencode = function (state, buf) {
  if (buf === null) buf = EMPTY

  let i = 0
  let extra = 3

  while ((i = b4a.indexOf(buf, 0x00, i)) > -1) {
    i++
    extra++
  }

  state.end += buf.byteLength + extra
}

BUFFER.encode = function (state, buf) {
  if (buf === null) buf = EMPTY

  state.buffer[state.start++] = 0x00

  let prev = 0
  let i = 0

  while ((i = b4a.indexOf(buf, 0x00, i)) > -1) {
    const slice = buf.subarray(prev, ++i)

    state.buffer.set(slice, state.start)
    state.start += slice.byteLength
    state.buffer[state.start++] = 0x02
    prev = i
  }

  const slice = buf.subarray(prev)

  state.buffer.set(slice, state.start)
  state.start += slice.byteLength

  state.buffer[state.start++] = 0x00
  state.buffer[state.start++] = 0x01
}

BUFFER.decode = function (state) {
  if (state.start >= state.end) throw new Error('Out of bounds')
  if (state.buffer[state.start++] !== 0x00) {
    throw new Error('Invalid start of string')
  }

  let escaped = null

  let prev = state.start
  let i = state.start

  while ((i = b4a.indexOf(state.buffer, 0x00, i)) > -1) {
    const next = ++i < state.end ? state.buffer[i] : 0x00

    i++

    if (next === 0x01) {
      break
    }

    if (next === 0x02) {
      if (escaped === null) escaped = []
      escaped.push(state.buffer.subarray(prev, i - 1))
      prev = i
      continue
    }

    throw new Error('Unknown value in terminator')
  }

  if (i === -1) {
    throw new Error('No terminator found')
  }

  state.start = i
  const last = state.buffer.subarray(prev, i - 2)
  if (escaped === null) return last

  escaped.push(last)
  return b4a.concat(escaped)
}

// TODO: can be optimised a lot

const STRING = {}

STRING.preencode = function (state, str) {
  BUFFER.preencode(state, b4a.from(str || ''))
}

STRING.encode = function (state, str) {
  BUFFER.encode(state, b4a.from(str || ''))
}

STRING.decode = function (state, str) {
  return b4a.toString(BUFFER.decode(state))
}

const UINT = {}

UINT.preencode = function (state, n) {
  state.end += n <= 0xfb ? 1 : n <= 0xffff ? 3 : n <= 0xffffffff ? 5 : n === Infinity ? 1 : 9
}

UINT.encode = function (state, n) {
  if (n === Infinity) {
    state.buffer[state.start++] = 0xff
    return
  }

  if (n <= 0xfb) {
    state.buffer[state.start++] = n
    return
  }

  if (n <= 0xffff) {
    state.buffer[state.start++] = 0xfc
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n
    return
  }

  if (n <= 0xffffffff) {
    state.buffer[state.start++] = 0xfd
    encodeUint32(state, n)
    return
  }

  if (Number.isSafeInteger(n)) {
    state.buffer[state.start++] = 0xfe

    const r = Math.floor(n / 0x100000000)
    encodeUint32(state, r)
    encodeUint32(state, n)
    return
  }

  throw new Error('Invalid number ' + n)
}

UINT.decode = function (state) {
  if (state.start >= state.end) throw new Error('Out of bounds')

  const a = state.buffer[state.start++]

  if (a <= 0xfb) return a

  if (a === 0xfc) {
    if (state.end - state.start < 2) throw new Error('Out of bounds')
    return state.buffer[state.start++] * 0x100 + state.buffer[state.start++]
  }

  if (a === 0xfd) {
    return decodeUint32(state)
  }

  if (a === 0xfe) {
    return decodeUint32(state) * 0x100000000 + decodeUint32(state)
  }

  return Infinity
}

const INT = {}

INT.preencode = function (state, n) {
  // One asymmetric condition
  if (n >= -0xff && n < 0) {
    state.end += 2
    return
  }
  n = Math.abs(n)
  state.end += n <= 0xf6 ? 1 : n <= 0xffff ? 3 : n <= 0xffffffff ? 5 : n === Infinity ? 1 : 9
}

INT.encode = function (state, n) {
  if (n === Infinity) {
    state.buffer[state.start++] = 0xff
    return
  }

  if (n === -Infinity) {
    state.buffer[state.start++] = 0x00
    return
  }

  // Negative 64 bit
  if (n < -0xffffffff) {
    if (!Number.isSafeInteger(n)) throw new Error('Invalid number ' + n)
    state.buffer[state.start++] = 0x01

    const r = Math.floor(-n / 0x100000000)
    encodeUint32(state, -r + 0xffffffff)
    encodeUint32(state, -(-n % 0x100000000) + 0xffffffff)
    return
  }

  // Negative 32 bit
  if (n < -0xffff) {
    state.buffer[state.start++] = 0x02

    n += 0xffffffff
    encodeUint32(state, n)
    return
  }

  // Negative 16bit
  if (n < -0xff) {
    state.buffer[state.start++] = 0x03

    n += 0xffff
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n
    return
  }

  // Negative 8bit
  if (n < 0) {
    state.buffer[state.start++] = 0x04

    n += 0xff
    state.buffer[state.start++] = n
    return
  }

  // Remainder of 8bit space for positive int
  if (n <= 0xf6) {
    state.buffer[state.start++] = n + 0x05
    return
  }

  // Positive 16bit
  if (n <= 0xffff) {
    state.buffer[state.start++] = 0xfc
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n
    return
  }

  // Positive 32bit
  if (n <= 0xffffffff) {
    state.buffer[state.start++] = 0xfd
    encodeUint32(state, n)
    return
  }

  // Positive 64bit
  if (Number.isSafeInteger(n)) {
    state.buffer[state.start++] = 0xfe

    const r = Math.floor(n / 0x100000000)
    encodeUint32(state, r)
    encodeUint32(state, n)
    return
  }

  throw new Error('Invalid number ' + n)
}

INT.decode = function (state) {
  if (state.start >= state.end) throw new Error('Out of bounds')

  const a = state.buffer[state.start++]

  if (a === 0x00) return -Infinity

  if (a === 0x01) {
    // Split (2^64 - 1) into its 32 bit components
    // r * 0x100000000 + n - (2^64 - 1)
    // r * 0x100000000 + n - (0xffffffff * 0x100000000) - 0xffffffff
    // (r - 0xffffffff) * 0x100000000 + n - 0xffffffff
    return (decodeUint32(state) - 0xffffffff) * 0x100000000 + decodeUint32(state) - 0xffffffff
  }

  if (a === 0x02) {
    return decodeUint32(state) - 0xffffffff
  }

  if (a === 0x03) {
    if (state.end - state.start < 2) throw new Error('Out of bounds')
    return state.buffer[state.start++] * 0x100 + state.buffer[state.start++] - 0xffff
  }

  if (a === 0x04) return state.buffer[state.start++] - 0xff

  if (a <= 0xfb) return a - 0x05

  if (a === 0xfc) {
    if (state.end - state.start < 2) throw new Error('Out of bounds')
    return state.buffer[state.start++] * 0x100 + state.buffer[state.start++]
  }

  if (a === 0xfd) {
    return decodeUint32(state)
  }

  if (a === 0xfe) {
    return decodeUint32(state) * 0x100000000 + decodeUint32(state)
  }

  return Infinity
}

const DATE = {}

DATE.preencode = (state, d) => INT.preencode(state, d.getTime())
DATE.encode = (state, d) => INT.encode(state, d.getTime())
DATE.decode = (state) => new Date(INT.decode(state))

const BOOL = {}

BOOL.preencode = (state, b) => UINT.preencode(state, b ? 1 : 0)
BOOL.encode = (state, b) => UINT.encode(state, b ? 1 : 0)
BOOL.decode = (state) => !!UINT.decode(state)

module.exports = class IndexEncoder {
  constructor(encodings, { prefix = -1 } = {}) {
    this.encodings = encodings
    this.prefix = prefix
  }

  static BUFFER = BUFFER
  static STRING = STRING
  static UINT = UINT
  static INT = INT
  static DATE = DATE
  static BOOL = BOOL

  static lookup(c) {
    switch (c) {
      case 'uint':
        return UINT
      case 'uint8':
        return UINT
      case 'uint16':
        return UINT
      case 'uint24':
        return UINT
      case 'uint32':
        return UINT
      case 'uint40':
        return UINT
      case 'uint48':
        return UINT
      case 'uint56':
        return UINT
      case 'uint64':
        return UINT
      case 'int':
        return INT
      case 'int8':
        return INT
      case 'int16':
        return INT
      case 'int24':
        return INT
      case 'int32':
        return INT
      case 'int40':
        return INT
      case 'int48':
        return INT
      case 'int56':
        return INT
      case 'int64':
        return INT
      case 'string':
        return STRING
      case 'utf8':
        return STRING
      case 'ascii':
        return STRING
      case 'hex':
        return STRING
      case 'base64':
        return STRING
      case 'fixed32':
        return BUFFER
      case 'fixed64':
        return BUFFER
      case 'buffer':
        return BUFFER
      case 'date':
        return DATE
      case 'bool':
        return BOOL
    }

    throw new Error('Unknown type')
  }

  encode(keys) {
    return this._encode(keys, false)
  }

  _encode(keys, terminate) {
    if (b4a.isBuffer(keys)) return keys

    const state = { start: 0, end: 0, buffer: null }

    if (this.prefix !== -1) UINT.preencode(state, this.prefix)
    for (let i = 0; i < keys.length; i++) {
      this.encodings[i].preencode(state, keys[i])
    }

    if (terminate && keys.length < this.encodings.length) {
      state.end++
    }

    state.buffer = b4a.allocUnsafe(state.end)

    if (this.prefix !== -1) UINT.encode(state, this.prefix)
    for (let i = 0; i < keys.length; i++) {
      this.encodings[i].encode(state, keys[i])
    }

    if (terminate && keys.length < this.encodings.length) {
      state.buffer[state.start++] = MAX[0]
    }

    return state.buffer
  }

  decode(buffer) {
    const state = { start: 0, end: buffer.byteLength, buffer }
    const result = []

    if (this.prefix !== -1) UINT.decode(state)
    for (const enc of this.encodings) {
      const key = state.start < state.end ? enc.decode(state) : enc === UINT ? 0 : null
      result.push(key)
    }

    return result
  }

  encodeRange({ gt, gte, lt, lte }) {
    const range = {
      gt: gt && this._encode(gt, true),
      gte: gte && this._encode(gte, false),
      lt: lt && this._encode(lt, false),
      lte: lte && this._encode(lte, true)
    }

    if (this.prefix !== -1) {
      if (!gt && !gte) range.gte = encodeUint(this.prefix)
      if (!lt && !lte) range.lt = encodeUint(this.prefix + 1)
    }

    return range
  }
}

function encodeUint(n) {
  const state = { start: 0, end: 0, buffer: null }
  UINT.preencode(state, n)
  state.buffer = b4a.allocUnsafe(state.end)
  UINT.encode(state, n)
  return state.buffer
}

function encodeUint32(state, n) {
  state.buffer[state.start++] = n >>> 24
  state.buffer[state.start++] = n >>> 16
  state.buffer[state.start++] = n >>> 8
  state.buffer[state.start++] = n
}

function decodeUint32(state) {
  if (state.end - state.start < 4) throw new Error('Out of bounds')
  return (
    state.buffer[state.start++] * 0x1000000 +
    state.buffer[state.start++] * 0x10000 +
    state.buffer[state.start++] * 0x100 +
    state.buffer[state.start++]
  )
}