/** * @license * Copyright 2022-2026 Matter.js Authors * SPDX-License-Identifier: Apache-2.0 */ import { Bytes, capitalize, Merge, UnexpectedDataError } from "@matter/general"; import { Schema } from "./Schema.js"; const enum BitRangeType { Flag, Number, Enum, } type BitRange = { type: TType; offset: number; length: number; _type?: T; }; const BitRange = ( type: TType, offset: number, length: number, ): BitRange => ({ type, offset, length }); /** Defines the bit position of a boolean flag. */ export type BitFlag = BitRange; export const BitFlag = (offset: number): BitFlag => BitRange(BitRangeType.Flag, offset, 1); /** Defines the bit position and bit length of a numeric value. */ export type BitField = BitRange; export const BitField = (offset: number, length: number): BitField => BitRange(BitRangeType.Number, offset, length); /** Defines the bit position and bit length of an enum flag. */ export type BitFieldEnum = BitRange; export const BitFieldEnum = (offset: number, length: number): BitFieldEnum => BitRange(BitRangeType.Enum, offset, length); export type BitSchema = { [key: string]: BitFlag | BitField | BitFieldEnum }; export type TypeFromBitSchema = { [K in keyof T]: T[K] extends BitFieldEnum ? E : T[K] extends BitField ? number : boolean; }; export type TypeFromPartialBitSchema = { [K in keyof T]?: T[K] extends BitFieldEnum ? E : T[K] extends BitField ? number : boolean; }; export type TypeFromBitmapSchema> = S extends Schema ? T : never; type MaskFromBitSchema = { [K in keyof T]: number }; type MaskOffsetFromBitSchema = { [K in keyof T]: { mask: number; byteOffset: number; bitOffset: number }; }; export class BitmapSchemaInternal extends Schema, number> { private readonly masks: MaskFromBitSchema; constructor(private readonly bitSchemas: T) { super(); const masks = >{}; for (const name in this.bitSchemas) { const { offset, length } = this.bitSchemas[name]; masks[name] = ((1 << length) - 1) << offset; } this.masks = masks; // TODO: validate that bitSchemas is coherent } /** * Allow to use a fully defined Bitmap schema as input, but also allow one where only the entries of bits set are * provided, rest is unset. */ override encode(value: TypeFromPartialBitSchema) { return super.encode(value as TypeFromBitSchema); } override encodeInternal(value: TypeFromPartialBitSchema) { let result = 0; for (const name in this.bitSchemas) { const { type, offset } = this.bitSchemas[name]; switch (type) { case BitRangeType.Flag: if (value[name]) result |= this.masks[name]; break; case BitRangeType.Enum: case BitRangeType.Number: result |= (value[name] as number) << offset; } } return result; } override decodeInternal(bitmap: number) { const result = {} as any; for (const name in this.bitSchemas) { const { type, offset } = this.bitSchemas[name]; const mask = this.masks[name]; if (type === BitRangeType.Flag) { result[name] = (bitmap & mask) !== 0; } else { result[name] = (bitmap & mask) >> offset; } } return result as TypeFromBitSchema; } } export class ByteArrayBitmapSchemaInternal extends Schema> { private readonly byteArrayLength: number; private readonly maskOffset: MaskOffsetFromBitSchema; constructor(private readonly bitSchemas: T) { super(); let maxBitLength = 0; const maskOffset = >{}; for (const name in this.bitSchemas) { const { type, offset, length } = this.bitSchemas[name]; const bitOffset = offset % 8; const byteOffset = (offset - bitOffset) / 8; let mask: number; if (type === BitRangeType.Flag) { mask = 1 << bitOffset; } else { // Do not shift number mask, they might go beyond 2^32 mask = (1 << length) - 1; } maskOffset[name] = { bitOffset, byteOffset, mask }; maxBitLength = Math.max(maxBitLength, offset + length); } this.byteArrayLength = Math.ceil(maxBitLength / 8); this.maskOffset = maskOffset; } override encodeInternal(value: TypeFromBitSchema) { const result = new Uint8Array(this.byteArrayLength); for (const name in this.bitSchemas) { const { type } = this.bitSchemas[name]; let { bitOffset, byteOffset } = this.maskOffset[name]; const { mask } = this.maskOffset[name]; switch (type) { case BitRangeType.Flag: if (value[name]) result[byteOffset] |= mask; break; case BitRangeType.Enum: case BitRangeType.Number: { let numValue = (value[name] as number) & mask; while (numValue !== 0) { result[byteOffset++] |= (numValue << bitOffset) & 0xff; const bitWritten = 8 - bitOffset; bitOffset = 0; numValue = numValue >> bitWritten; } } } } return result; } override decodeInternal(data: Bytes) { const bitmap = Bytes.of(data); if (bitmap.length !== this.byteArrayLength) throw new UnexpectedDataError(`Unexpected length: ${bitmap.length}. Expected ${this.byteArrayLength}`); const result = {} as any; for (const name in this.bitSchemas) { const { type } = this.bitSchemas[name]; let { mask, bitOffset, byteOffset } = this.maskOffset[name]; if (type === BitRangeType.Flag) { result[name] = (bitmap[byteOffset] & mask) !== 0; } else { let value = 0; let valueBitOffset = 0; while (mask !== 0) { value |= ((bitmap[byteOffset++] >> bitOffset) & mask) << valueBitOffset; const bitRead = 8 - bitOffset; bitOffset = 0; valueBitOffset += bitRead; mask = mask >> bitRead; } result[name] = value; } } return result as TypeFromBitSchema; } } /** Create a partial bitmap from a flag sequence */ export type FlagsToBitmap = { [name in Uncapitalize]: true; }; /** Create a type with specified bit flags set */ export type BitFlags>[]> = Merge< { [key in keyof T]: false }, FlagsToBitmap >; /** Create a bitmap schema with a named subset of flags set */ export function BitFlags>[]>( bitSchemas: T, ...flags: [...F] ) { return Object.fromEntries( Object.keys(bitSchemas).map(name => [ name, !(flags.indexOf(capitalize(name as Extract)) === -1), ]), ) as BitFlags; } /** Create a full bitmap schema from a partial bitmap schema */ export function BitsFromPartial>(schema: S, bits: P) { const result = {} as { [key: string]: boolean | number }; for (const k in schema) { const value = bits[k]; if (value !== undefined) { result[k] = value; } else { result[k] = 0; } } return result as TypeFromBitSchema; } /** Declares a bitmap schema by indicating the bit position and their names. */ export const BitmapSchema = (bitSchemas: T) => new BitmapSchemaInternal(bitSchemas); /** Declares a bitmap schema backed by a ByteArray by indicating the bit position and their names. */ export const ByteArrayBitmapSchema = (bitSchemas: T) => new ByteArrayBitmapSchemaInternal(bitSchemas);