diff --git a/packages/evolution/src/CBOR.ts b/packages/evolution/src/CBOR.ts index d06c5c1f..ac21be81 100644 --- a/packages/evolution/src/CBOR.ts +++ b/packages/evolution/src/CBOR.ts @@ -1582,372 +1582,3 @@ const decodeSimpleOrFloatAt = (data: Uint8Array, offset: number): DecodeAtResult } throw new CBORError({ message: `Unsupported simple/float encoding: ${additionalInfo}` }) } - -const decodeUintSync = (data: Uint8Array): CBOR => { - const firstByte = data[0] - const additionalInfo = firstByte & 0x1f - if (additionalInfo < 24) { - return BigInt(additionalInfo) - } else if (additionalInfo === 24) { - if (data.length < 2) throw new CBORError({ message: "Insufficient data for 1-byte unsigned integer" }) - return BigInt(data[1]) - } else if (additionalInfo === 25) { - if (data.length < 3) throw new CBORError({ message: "Insufficient data for 2-byte unsigned integer" }) - return BigInt(data[1]) * 256n + BigInt(data[2]) - } else if (additionalInfo === 26) { - if (data.length < 5) throw new CBORError({ message: "Insufficient data for 4-byte unsigned integer" }) - return BigInt(data[1]) * 16777216n + BigInt(data[2]) * 65536n + BigInt(data[3]) * 256n + BigInt(data[4]) - } else if (additionalInfo === 27) { - if (data.length < 9) throw new CBORError({ message: "Insufficient data for 8-byte unsigned integer" }) - let result = 0n - for (let i = 1; i <= 8; i++) result = result * 256n + BigInt(data[i]) - return result - } else { - throw new CBORError({ message: `Unsupported additional info for unsigned integer: ${additionalInfo}` }) - } -} - -const decodeNintSync = (data: Uint8Array): CBOR => { - const firstByte = data[0] - const additionalInfo = firstByte & 0x1f - if (additionalInfo < 24) { - return -1n - BigInt(additionalInfo) - } else if (additionalInfo === 24) { - if (data.length < 2) throw new CBORError({ message: "Insufficient data for 1-byte negative integer" }) - return -1n - BigInt(data[1]) - } else if (additionalInfo === 25) { - if (data.length < 3) throw new CBORError({ message: "Insufficient data for 2-byte negative integer" }) - return -1n - (BigInt(data[1]) * 256n + BigInt(data[2])) - } else if (additionalInfo === 26) { - if (data.length < 5) throw new CBORError({ message: "Insufficient data for 4-byte negative integer" }) - return -1n - (BigInt(data[1]) * 16777216n + BigInt(data[2]) * 65536n + BigInt(data[3]) * 256n + BigInt(data[4])) - } else if (additionalInfo === 27) { - if (data.length < 9) throw new CBORError({ message: "Insufficient data for 8-byte negative integer" }) - let result = 0n - for (let i = 1; i <= 8; i++) result = result * 256n + BigInt(data[i]) - return -1n - result - } else { - throw new CBORError({ message: `Unsupported additional info for negative integer: ${additionalInfo}` }) - } -} - -const decodeBytesWithLengthSync = (data: Uint8Array): { item: CBOR; bytesConsumed: number } => { - const firstByte = data[0] - const additionalInfo = firstByte & 0x1f - if (additionalInfo === CBOR_ADDITIONAL_INFO.INDEFINITE) { - let offset = 1 - const chunks: Array = [] - let foundBreak = false - while (offset < data.length) { - if (data[offset] === 0xff) { - offset++ - foundBreak = true - break - } - const chunkFirstByte = data[offset] - const chunkMajorType = (chunkFirstByte >> 5) & 0x07 - if (chunkMajorType !== CBOR_MAJOR_TYPE.BYTE_STRING) { - throw new CBORError({ message: `Invalid chunk in indefinite byte string: major type ${chunkMajorType}` }) - } - const { bytesRead, length: chunkLength } = decodeLengthSync(data, offset) - offset += bytesRead - if (data.length < offset + chunkLength) - throw new CBORError({ message: "Insufficient data for byte string chunk" }) - const chunk = data.slice(offset, offset + chunkLength) - chunks.push(chunk) - offset += chunkLength - } - if (!foundBreak) throw new CBORError({ message: "Missing break in indefinite-length byte string" }) - const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0) - const bytes = new Uint8Array(totalLength) - let pos = 0 - for (const chunk of chunks) { - bytes.set(chunk, pos) - pos += chunk.length - } - return { item: bytes, bytesConsumed: offset } - } else { - const { bytesRead, length } = decodeLengthSync(data, 0) - if (data.length < bytesRead + length) throw new CBORError({ message: "Insufficient data for byte string" }) - const bytes = data.slice(bytesRead, bytesRead + length) - return { item: bytes, bytesConsumed: bytesRead + length } - } -} - -const decodeTextWithLengthSync = (data: Uint8Array): { item: CBOR; bytesConsumed: number } => { - const firstByte = data[0] - const additionalInfo = firstByte & 0x1f - if (additionalInfo === CBOR_ADDITIONAL_INFO.INDEFINITE) { - let offset = 1 - const parts: Array = [] - let foundBreak = false - while (offset < data.length) { - if (data[offset] === 0xff) { - offset++ - foundBreak = true - break - } - const chunkFirstByte = data[offset] - const chunkMajorType = (chunkFirstByte >> 5) & 0x07 - if (chunkMajorType !== CBOR_MAJOR_TYPE.TEXT_STRING) { - throw new CBORError({ message: `Invalid chunk in indefinite text string: major type ${chunkMajorType}` }) - } - const { bytesRead, length: chunkLength } = decodeLengthSync(data, offset) - offset += bytesRead - if (data.length < offset + chunkLength) - throw new CBORError({ message: "Insufficient data for text string chunk" }) - const chunkBytes = data.slice(offset, offset + chunkLength) - const chunkText = TEXT_DECODER.decode(chunkBytes) - parts.push(chunkText) - offset += chunkLength - } - if (!foundBreak) throw new CBORError({ message: "Missing break in indefinite-length text string" }) - return { item: parts.join(""), bytesConsumed: offset } - } else { - const { bytesRead, length } = decodeLengthSync(data, 0) - if (data.length < bytesRead + length) throw new CBORError({ message: "Insufficient data for text string" }) - const textBytes = data.slice(bytesRead, bytesRead + length) - const text = TEXT_DECODER.decode(textBytes) - return { item: text, bytesConsumed: bytesRead + length } - } -} - -// Decode an item and return both the item and bytes consumed (sync) -const decodeItemWithLengthSync = (data: Uint8Array, options: CodecOptions): { item: CBOR; bytesConsumed: number } => { - const firstByte = data[0] - const majorType = (firstByte >> 5) & 0x07 - let item: CBOR - let bytesConsumed: number - switch (majorType) { - case CBOR_MAJOR_TYPE.UNSIGNED_INTEGER: { - item = decodeUintSync(data) - const additionalInfo = firstByte & 0x1f - bytesConsumed = - additionalInfo < 24 ? 1 : additionalInfo === 24 ? 2 : additionalInfo === 25 ? 3 : additionalInfo === 26 ? 5 : 9 - break - } - case CBOR_MAJOR_TYPE.NEGATIVE_INTEGER: { - item = decodeNintSync(data) - const additionalInfo = firstByte & 0x1f - bytesConsumed = - additionalInfo < 24 ? 1 : additionalInfo === 24 ? 2 : additionalInfo === 25 ? 3 : additionalInfo === 26 ? 5 : 9 - break - } - case CBOR_MAJOR_TYPE.BYTE_STRING: { - const { bytesConsumed: b, item: it } = decodeBytesWithLengthSync(data) - item = it - bytesConsumed = b - break - } - case CBOR_MAJOR_TYPE.TEXT_STRING: { - const { bytesConsumed: b, item: it } = decodeTextWithLengthSync(data) - item = it - bytesConsumed = b - break - } - case CBOR_MAJOR_TYPE.ARRAY: { - const { bytesConsumed: b, item: it } = decodeArrayWithLengthSync(data, options) - item = it - bytesConsumed = b - break - } - case CBOR_MAJOR_TYPE.MAP: { - const { bytesConsumed: b, item: it } = decodeMapWithLengthSync(data, options) - item = it - bytesConsumed = b - break - } - case CBOR_MAJOR_TYPE.TAG: { - const { bytesConsumed: b, item: it } = decodeTagWithLengthSync(data, options) - item = it - bytesConsumed = b - break - } - case CBOR_MAJOR_TYPE.SIMPLE_FLOAT: { - item = decodeSimpleOrFloatSync(data) - const additionalInfo = firstByte & 0x1f - bytesConsumed = - additionalInfo < 24 ? 1 : additionalInfo === 24 ? 2 : additionalInfo === 25 ? 3 : additionalInfo === 26 ? 5 : 9 - break - } - default: - throw new CBORError({ message: `Unsupported major type: ${majorType}` }) - } - return { item, bytesConsumed } -} - -const decodeArrayWithLengthSync = (data: Uint8Array, options: CodecOptions): { item: CBOR; bytesConsumed: number } => { - const firstByte = data[0] - const additionalInfo = firstByte & 0x1f - if (additionalInfo === CBOR_ADDITIONAL_INFO.INDEFINITE) { - const result: Array = [] - let offset = 1 - while (offset < data.length) { - if (data[offset] === 0xff) { - offset++ - break - } - const { bytesConsumed, item } = decodeItemWithLengthSync(data.slice(offset), options) - result.push(item) - offset += bytesConsumed - } - return { item: result, bytesConsumed: offset } - } else { - const { bytesRead, length } = decodeLengthSync(data, 0) - const result: Array = [] - let offset = bytesRead - for (let i = 0; i < length; i++) { - const { bytesConsumed, item } = decodeItemWithLengthSync(data.slice(offset), options) - result.push(item) - offset += bytesConsumed - } - return { item: result, bytesConsumed: offset } - } -} - -const decodeMapWithLengthSync = (data: Uint8Array, options: CodecOptions): { item: CBOR; bytesConsumed: number } => { - const firstByte = data[0] - const additionalInfo = firstByte & 0x1f - if (additionalInfo === CBOR_ADDITIONAL_INFO.INDEFINITE) { - const result = - options.mode === "custom" && options.mapsAsObjects ? ({} as Record) : new Map() - let offset = 1 - while (offset < data.length) { - if (data[offset] === 0xff) { - offset++ - break - } - const { bytesConsumed: keyBytes, item: key } = decodeItemWithLengthSync(data.slice(offset), options) - offset += keyBytes - const { bytesConsumed: valueBytes, item: value } = decodeItemWithLengthSync(data.slice(offset), options) - offset += valueBytes - if (result instanceof Map) { - result.set(key, value) - } else { - result[String(key as any)] = value - } - } - return { item: result, bytesConsumed: offset } - } else { - const { bytesRead, length } = decodeLengthSync(data, 0) - const result = - options.mode === "custom" && options.mapsAsObjects ? ({} as Record) : new Map() - let offset = bytesRead - for (let i = 0; i < length; i++) { - const { bytesConsumed: keyBytes, item: key } = decodeItemWithLengthSync(data.slice(offset), options) - offset += keyBytes - const { bytesConsumed: valueBytes, item: value } = decodeItemWithLengthSync(data.slice(offset), options) - offset += valueBytes - if (result instanceof Map) { - result.set(key, value) - } else { - result[String(key as any)] = value - } - } - return { item: result, bytesConsumed: offset } - } -} - -const decodeTagWithLengthSync = (data: Uint8Array, options: CodecOptions): { item: CBOR; bytesConsumed: number } => { - const firstByte = data[0] - const additionalInfo = firstByte & 0x1f - let tag: number - let dataOffset: number - if (additionalInfo < 24) { - tag = additionalInfo - dataOffset = 1 - } else if (additionalInfo === 24) { - if (data.length < 2) throw new CBORError({ message: "Insufficient data for 1-byte tag" }) - tag = data[1] - dataOffset = 2 - } else if (additionalInfo === 25) { - if (data.length < 3) throw new CBORError({ message: "Insufficient data for 2-byte tag" }) - tag = data[1] * 256 + data[2] - dataOffset = 3 - } else { - throw new CBORError({ message: `Unsupported additional info for tag: ${additionalInfo}` }) - } - const { bytesConsumed, item: innerValue } = decodeItemWithLengthSync(data.slice(dataOffset), options) - if (tag === 2 || tag === 3) { - if (!(innerValue instanceof Uint8Array)) throw new CBORError({ message: `Invalid value for bigint tag ${tag}` }) - const bigintValue = (() => { - let result = 0n - for (let i = 0; i < innerValue.length; i++) result = (result << 8n) + BigInt(innerValue[i]) - return tag === 2 ? result : -1n - result - })() - return { item: bigintValue, bytesConsumed: dataOffset + bytesConsumed } - } - return { item: { _tag: "Tag", tag, value: innerValue }, bytesConsumed: dataOffset + bytesConsumed } -} - -const decodeSimpleOrFloatSync = (data: Uint8Array): CBOR => { - const firstByte = data[0] - const additionalInfo = firstByte & 0x1f - if (additionalInfo < 20) { - // Return unassigned simple values as numbers - return additionalInfo - } else if (additionalInfo === CBOR_SIMPLE.FALSE) { - return false - } else if (additionalInfo === CBOR_SIMPLE.TRUE) { - return true - } else if (additionalInfo === CBOR_SIMPLE.NULL) { - return null - } else if (additionalInfo === CBOR_SIMPLE.UNDEFINED) { - return undefined - } else if (additionalInfo === CBOR_ADDITIONAL_INFO.DIRECT) { - if (data.length < 2) throw new CBORError({ message: "Insufficient data for simple value (one byte)" }) - const simpleValue = data[1] - return simpleValue - } else if (additionalInfo === CBOR_ADDITIONAL_INFO.UINT16) { - if (data.length < 3) throw new CBORError({ message: "Insufficient data for half-precision float" }) - const value = (data[1] << 8) | data[2] - const float = decodeFloat16(value) - return float - } else if (additionalInfo === CBOR_ADDITIONAL_INFO.UINT32) { - if (data.length < 5) throw new CBORError({ message: "Insufficient data for single-precision float" }) - const buffer = data.slice(1, 5) - const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength) - return view.getFloat32(0, false) - } else if (additionalInfo === CBOR_ADDITIONAL_INFO.UINT64) { - if (data.length < 9) throw new CBORError({ message: "Insufficient data for double-precision float" }) - const buffer = data.slice(1, 9) - const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength) - return view.getFloat64(0, false) - } - throw new CBORError({ message: `Unsupported additional info for simple/float: ${additionalInfo}` }) -} - -const decodeLengthSync = (data: Uint8Array, offset: number): { length: number; bytesRead: number } => { - const firstByte = data[offset] - const majorType = (firstByte >> 5) & 0x07 - const additionalInfo = firstByte & 0x1f - let length = 0 - let bytesRead = 0 - if ( - majorType !== CBOR_MAJOR_TYPE.BYTE_STRING && - majorType !== CBOR_MAJOR_TYPE.TEXT_STRING && - majorType !== CBOR_MAJOR_TYPE.ARRAY && - majorType !== CBOR_MAJOR_TYPE.MAP - ) { - throw new CBORError({ message: `Invalid major type for length decoding: ${majorType}` }) - } - if (additionalInfo < 24) { - length = additionalInfo - bytesRead = 1 - } else if (additionalInfo === 24) { - if (data.length < offset + 2) throw new CBORError({ message: "Insufficient data for 1-byte length" }) - length = data[offset + 1] - bytesRead = 2 - } else if (additionalInfo === 25) { - if (data.length < offset + 3) throw new CBORError({ message: "Insufficient data for 2-byte length" }) - length = data[offset + 1] * 256 + data[offset + 2] - bytesRead = 3 - } else if (additionalInfo === 26) { - if (data.length < offset + 5) throw new CBORError({ message: "Insufficient data for 4-byte length" }) - length = (data[offset + 1] << 24) | (data[offset + 2] << 16) | (data[offset + 3] << 8) | data[offset + 4] - bytesRead = 5 - } else { - throw new CBORError({ message: `Unsupported additional info for length: ${additionalInfo}` }) - } - return { length, bytesRead } -}