Dorian 0d073fa89e Add comprehensive installation and setup documentation
- Add GETTING_STARTED.md with quick start guide and development modes
- Add INSTALL.sh automated installation script
- Add INSTALLATION_CHECKLIST.md, INSTALLATION_SUCCESS.md, and INSTALLATION_SUMMARY.md
- Add QUICK_REFERENCE.md for common commands
- Add SETUP_GUIDE.md with detailed setup instructions
- Update README.md with improved project overview
- Add did-wallet app dependencies and node_modules
2026-01-27 17:18:21 +00:00

309 lines
8.3 KiB
JavaScript

// TODO: shift some of the bytes logic to bytes-utils so we can use Buffer
// where possible
import { Token, Type } from './token.js'
import { decodeErrPrefix } from './common.js'
import { encodeUint } from './0uint.js'
/**
* @typedef {import('./bl.js').Bl} Bl
* @typedef {import('../interface').DecodeOptions} DecodeOptions
* @typedef {import('../interface').EncodeOptions} EncodeOptions
*/
const MINOR_FALSE = 20
const MINOR_TRUE = 21
const MINOR_NULL = 22
const MINOR_UNDEFINED = 23
/**
* @param {Uint8Array} _data
* @param {number} _pos
* @param {number} _minor
* @param {DecodeOptions} options
* @returns {Token}
*/
export function decodeUndefined (_data, _pos, _minor, options) {
if (options.allowUndefined === false) {
throw new Error(`${decodeErrPrefix} undefined values are not supported`)
} else if (options.coerceUndefinedToNull === true) {
return new Token(Type.null, null, 1)
}
return new Token(Type.undefined, undefined, 1)
}
/**
* @param {Uint8Array} _data
* @param {number} _pos
* @param {number} _minor
* @param {DecodeOptions} options
* @returns {Token}
*/
export function decodeBreak (_data, _pos, _minor, options) {
if (options.allowIndefinite === false) {
throw new Error(`${decodeErrPrefix} indefinite length items not allowed`)
}
return new Token(Type.break, undefined, 1)
}
/**
* @param {number} value
* @param {number} bytes
* @param {DecodeOptions} options
* @returns {Token}
*/
function createToken (value, bytes, options) {
if (options) {
if (options.allowNaN === false && Number.isNaN(value)) {
throw new Error(`${decodeErrPrefix} NaN values are not supported`)
}
if (options.allowInfinity === false && (value === Infinity || value === -Infinity)) {
throw new Error(`${decodeErrPrefix} Infinity values are not supported`)
}
}
return new Token(Type.float, value, bytes)
}
/**
* @param {Uint8Array} data
* @param {number} pos
* @param {number} _minor
* @param {DecodeOptions} options
* @returns {Token}
*/
export function decodeFloat16 (data, pos, _minor, options) {
return createToken(readFloat16(data, pos + 1), 3, options)
}
/**
* @param {Uint8Array} data
* @param {number} pos
* @param {number} _minor
* @param {DecodeOptions} options
* @returns {Token}
*/
export function decodeFloat32 (data, pos, _minor, options) {
return createToken(readFloat32(data, pos + 1), 5, options)
}
/**
* @param {Uint8Array} data
* @param {number} pos
* @param {number} _minor
* @param {DecodeOptions} options
* @returns {Token}
*/
export function decodeFloat64 (data, pos, _minor, options) {
return createToken(readFloat64(data, pos + 1), 9, options)
}
/**
* @param {Bl} buf
* @param {Token} token
* @param {EncodeOptions} options
*/
export function encodeFloat (buf, token, options) {
const float = token.value
if (float === false) {
buf.push([Type.float.majorEncoded | MINOR_FALSE])
} else if (float === true) {
buf.push([Type.float.majorEncoded | MINOR_TRUE])
} else if (float === null) {
buf.push([Type.float.majorEncoded | MINOR_NULL])
} else if (float === undefined) {
buf.push([Type.float.majorEncoded | MINOR_UNDEFINED])
} else {
let decoded
let success = false
if (!options || options.float64 !== true) {
encodeFloat16(float)
decoded = readFloat16(ui8a, 1)
if (float === decoded || Number.isNaN(float)) {
ui8a[0] = 0xf9
buf.push(ui8a.slice(0, 3))
success = true
} else {
encodeFloat32(float)
decoded = readFloat32(ui8a, 1)
if (float === decoded) {
ui8a[0] = 0xfa
buf.push(ui8a.slice(0, 5))
success = true
}
}
}
if (!success) {
encodeFloat64(float)
decoded = readFloat64(ui8a, 1)
ui8a[0] = 0xfb
buf.push(ui8a.slice(0, 9))
}
}
}
/**
* @param {Token} token
* @param {EncodeOptions} options
* @returns {number}
*/
encodeFloat.encodedSize = function encodedSize (token, options) {
const float = token.value
if (float === false || float === true || float === null || float === undefined) {
return 1
}
if (!options || options.float64 !== true) {
encodeFloat16(float)
let decoded = readFloat16(ui8a, 1)
if (float === decoded || Number.isNaN(float)) {
return 3
}
encodeFloat32(float)
decoded = readFloat32(ui8a, 1)
if (float === decoded) {
return 5
}
}
return 9
}
const buffer = new ArrayBuffer(9)
const dataView = new DataView(buffer, 1)
const ui8a = new Uint8Array(buffer, 0)
/**
* @param {number} inp
*/
function encodeFloat16 (inp) {
if (inp === Infinity) {
dataView.setUint16(0, 0x7c00, false)
} else if (inp === -Infinity) {
dataView.setUint16(0, 0xfc00, false)
} else if (Number.isNaN(inp)) {
dataView.setUint16(0, 0x7e00, false)
} else {
dataView.setFloat32(0, inp)
const valu32 = dataView.getUint32(0)
const exponent = (valu32 & 0x7f800000) >> 23
const mantissa = valu32 & 0x7fffff
/* c8 ignore next 6 */
if (exponent === 0xff) {
// too big, Infinity, but this should be hard (impossible?) to trigger
dataView.setUint16(0, 0x7c00, false)
} else if (exponent === 0x00) {
// 0.0, -0.0 and subnormals, shouldn't be possible to get here because 0.0 should be counted as an int
dataView.setUint16(0, ((inp & 0x80000000) >> 16) | (mantissa >> 13), false)
} else { // standard numbers
// chunks of logic here borrowed from https://github.com/PJK/libcbor/blob/c78f437182533e3efa8d963ff4b945bb635c2284/src/cbor/encoding.c#L127
const logicalExponent = exponent - 127
// Now we know that 2^exponent <= 0 logically
/* c8 ignore next 6 */
if (logicalExponent < -24) {
/* No unambiguous representation exists, this float is not a half float
and is too small to be represented using a half, round off to zero.
Consistent with the reference implementation. */
// should be difficult (impossible?) to get here in JS
dataView.setUint16(0, 0)
} else if (logicalExponent < -14) {
/* Offset the remaining decimal places by shifting the significand, the
value is lost. This is an implementation decision that works around the
absence of standard half-float in the language. */
dataView.setUint16(0, ((valu32 & 0x80000000) >> 16) | /* sign bit */ (1 << (24 + logicalExponent)), false)
} else {
dataView.setUint16(0, ((valu32 & 0x80000000) >> 16) | ((logicalExponent + 15) << 10) | (mantissa >> 13), false)
}
}
}
}
/**
* @param {Uint8Array} ui8a
* @param {number} pos
* @returns {number}
*/
function readFloat16 (ui8a, pos) {
if (ui8a.length - pos < 2) {
throw new Error(`${decodeErrPrefix} not enough data for float16`)
}
const half = (ui8a[pos] << 8) + ui8a[pos + 1]
if (half === 0x7c00) {
return Infinity
}
if (half === 0xfc00) {
return -Infinity
}
if (half === 0x7e00) {
return NaN
}
const exp = (half >> 10) & 0x1f
const mant = half & 0x3ff
let val
if (exp === 0) {
val = mant * (2 ** -24)
} else if (exp !== 31) {
val = (mant + 1024) * (2 ** (exp - 25))
/* c8 ignore next 4 */
} else {
// may not be possible to get here
val = mant === 0 ? Infinity : NaN
}
return (half & 0x8000) ? -val : val
}
/**
* @param {number} inp
*/
function encodeFloat32 (inp) {
dataView.setFloat32(0, inp, false)
}
/**
* @param {Uint8Array} ui8a
* @param {number} pos
* @returns {number}
*/
function readFloat32 (ui8a, pos) {
if (ui8a.length - pos < 4) {
throw new Error(`${decodeErrPrefix} not enough data for float32`)
}
const offset = (ui8a.byteOffset || 0) + pos
return new DataView(ui8a.buffer, offset, 4).getFloat32(0, false)
}
/**
* @param {number} inp
*/
function encodeFloat64 (inp) {
dataView.setFloat64(0, inp, false)
}
/**
* @param {Uint8Array} ui8a
* @param {number} pos
* @returns {number}
*/
function readFloat64 (ui8a, pos) {
if (ui8a.length - pos < 8) {
throw new Error(`${decodeErrPrefix} not enough data for float64`)
}
const offset = (ui8a.byteOffset || 0) + pos
return new DataView(ui8a.buffer, offset, 8).getFloat64(0, false)
}
/**
* @param {Token} _tok1
* @param {Token} _tok2
* @returns {number}
*/
encodeFloat.compareTokens = encodeUint.compareTokens
/*
encodeFloat.compareTokens = function compareTokens (_tok1, _tok2) {
return _tok1
throw new Error(`${encodeErrPrefix} cannot use floats as map keys`)
}
*/