/** * @license * Copyright 2022-2026 Matter.js Authors * SPDX-License-Identifier: Apache-2.0 */ import { Bytes, DataReader, DataWriter, Endian } from "@matter/general"; import type { Constraint, FieldElement, Quality } from "@matter/model"; import { Schema } from "../schema/Schema.js"; import { TlvCodec, TlvTag, TlvToPrimitive, TlvTypeLength } from "./TlvCodec.js"; export type TlvEncodingOptions = { /** * When true, the fabricIndex field will be excluded from the TLV encoding for list entries. * This flag must not be set together with the following flag. */ forWriteInteraction?: boolean; /** * When true, mandatory field validation is skipped when encoding TLV for list entries. * This flag must not be set together with the former flag. */ allowMissingFieldsForNonFabricFilteredRead?: boolean; }; export abstract class TlvSchema extends Schema implements TlvSchema { /** * Reverse-maps this TLV schema to model element fields. * * Used for old ClusterType() compatibility — converts TLV-carrying descriptors back to model elements. * Remove when ClusterType compat layer is dropped. * * @deprecated */ get element(): TlvSchema.Element | undefined { return undefined; } override decodeInternal(encoded: Bytes): T { return this.decodeTlvInternal(new TlvByteArrayReader(encoded)).value; } override encodeInternal(value: T): Bytes { const writer = new TlvByteArrayWriter(); this.encodeTlvInternal(writer, value); return writer.toByteArray(); } encodeTlv(value: T, options?: TlvEncodingOptions): TlvStream { const writer = new TlvArrayWriter(); this.encodeTlvInternal(writer, value, undefined, options); return writer.toTlvArray(); } decodeTlv(encoded: TlvStream): T { return this.decodeTlvInternal(new TlvArrayReader(encoded)).value; } decodeTlvInternal(reader: TlvReader): { value: T; tag?: TlvTag } { const { tag, typeLength } = reader.readTagType(); return { tag, value: this.decodeTlvInternalValue(reader, typeLength) }; } abstract decodeTlvInternalValue(reader: TlvReader, typeLength: TlvTypeLength): T; abstract encodeTlvInternal(writer: TlvWriter, value: T, tag?: TlvTag, options?: TlvEncodingOptions): void; injectField(value: T, _fieldId: number, _fieldValue: any, _injectChecker: (fieldValue: any) => boolean): T { return value; } removeField(value: T, _fieldId: number, _removeChecker: (fieldValue: any) => boolean): T { return value; } } export namespace TlvSchema { /** * Model element fields extracted from a TLV schema. * * Sufficient to construct a {@link FieldElement} or {@link ValueElement}. * * @deprecated Part of old ClusterType() compat layer. */ export interface Element { type?: string; constraint?: Constraint.Definition; quality?: Quality.Definition; children?: FieldElement[]; } } export type TlvStream = TlvElement[]; export type TlvElement = { tag?: TlvTag; typeLength: T; value?: TlvToPrimitive[T["type"]]; }; export class TlvArrayWriter implements TlvWriter { private readonly tlvArray = new Array>(); writeTag(typeLength: TlvTypeLength, tag?: TlvTag) { this.tlvArray.push({ tag, typeLength }); } writePrimitive(_typeLength: T, value: TlvToPrimitive[T["type"]]) { this.tlvArray[this.tlvArray.length - 1].value = value; } toTlvArray() { return this.tlvArray; } } export class TlvArrayReader implements TlvReader { private index = -1; constructor(private readonly tlvElements: TlvElement[]) {} readTagType() { this.index++; return this.tlvElements[this.index]; } readPrimitive(_typeLength: T): V { return this.tlvElements[this.index].value; } } /** Type defined by the TLV schema. */ export type TypeFromSchema> = S extends TlvSchema ? T : never; export interface TlvReader { readTagType(): { tag?: TlvTag; typeLength: TlvTypeLength }; readPrimitive(typeLength: T): V; } export interface TlvWriter { writeTag(typeLength: TlvTypeLength, tag?: TlvTag): void; writePrimitive(typeLength: T, value: TlvToPrimitive[T["type"]]): void; } export class TlvByteArrayWriter implements TlvWriter { private readonly writer = new DataWriter(Endian.Little); writeTag(typeLength: TlvTypeLength, tag?: TlvTag) { TlvCodec.writeTag(this.writer, typeLength, tag); } writePrimitive(typeLength: T, value: TlvToPrimitive[T["type"]]) { TlvCodec.writePrimitive(this.writer, typeLength, value); } toByteArray() { return this.writer.toByteArray(); } } export class TlvByteArrayReader implements TlvReader { private readonly reader: DataReader; constructor(byteArray: Bytes) { this.reader = new DataReader(byteArray, Endian.Little); } readTagType() { return TlvCodec.readTagType(this.reader); } readPrimitive(typeLength: T): V { return TlvCodec.readPrimitive(this.reader, typeLength); } }