diff --git a/docs/getting-started/migrating/v14.md b/docs/getting-started/migrating/v14.md index a07615b64fbe..385730642f0a 100644 --- a/docs/getting-started/migrating/v14.md +++ b/docs/getting-started/migrating/v14.md @@ -6,7 +6,24 @@ The `ZWaveHost` and `ZWaveApplicationHost` interfaces have been replaced by mult Furthermore, `Message` and `CommandClass` implementations are no longer bound to a specific host instance. Instead, their methods that need access to host functionality (like value DBs, home ID, device configuration, etc.) now receive a method-specific context object. Parsing of those instances no longer happens in the constructor, but in a separate `from` method. -All in all, this release contains a huge list of breaking changes, but most of those should not affect any application, unless very low-level APIs are frequently used. +In an attempt to make Z-Wave JS more portable, almost all usages of Node.js's `Buffer` class have been replaced with the native `Uint8Array`, or our new `Bytes` class that acts as a `Buffer` replacement. + +All in all, this release contains a huge list of breaking changes, but most of those are limited low-level APIs. + +## Replaced Node.js `Buffer` with `Uint8Array` or portable `Bytes` class + +Since the beginning of Z-Wave JS, we've been using Node.js's `Buffer` class to manipulate binary data. This works fine, but is not portable, and prevents us from exploring compatibility with other runtimes, or even doing things in the browser, e.g. flashing controllers or modifying NVM contents. +Following [Sindre Sorhus's example](https://sindresorhus.com/blog/goodbye-nodejs-buffer), the use of `Buffer`s was replaced with `Uint8Array`s where applicable. + +In input positions where Z-Wave JS previously accepted `Buffer`s, this change is backwards compatible, as `Buffer` is a subclass of `Uint8Array`. Applications can simply continue passing `Buffer` instances to Z-Wave JS. + +In output positions however, this is a breaking change. Where applications are affected by this, check if `Buffer` methods like `readUInt32BE` etc. are actually needed, or if changing the expected type to `Uint8Array` would be enough. This is usually the case when just passing the binary data around, or accessing its content by index. + +In some cases, Z-Wave JS now uses an almost drop-in replacement for Node.js's `Buffer`, the new `Bytes` class exported from `@zwave-js/shared`. This is a portable subclass of `Uint8Array` with some additions to make its API more (but not 100%) compatible with `Buffer`. It supports most of the `Buffer` functionality like `from()`, `concat()`, `toString()` (with limitations), `read/write[U]Int...LE/BE`. This should generally not leak into the public API, except for some rare edge cases. + +Both `Uint8Array` and `Bytes` can easily be converted to a `Buffer` instance if absolutely necessary by using `Buffer.from(...)`. + +To test whether something is a `Uint8Array`, use the `isUint8Array` function exported from `node:util/types` (not portable) or `@zwave-js/shared` (portable). ## Decoupled Serial API messages from host instances, split constructors and parsing diff --git a/eslint.config.mjs b/eslint.config.mjs index 5bd34809479e..815ecf17d8ab 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -155,6 +155,17 @@ export default tseslint.config( "unicorn/prefer-string-slice": "error", "unicorn/prefer-string-starts-ends-with": "error", "unicorn/prefer-string-replace-all": "error", + + // Prefer our own Buffer implementation (compatible with native Uint8array) + // See https://sindresorhus.com/blog/goodbye-nodejs-buffer for the reason behind this + "no-restricted-globals": [ + "error", + { + name: "Buffer", + message: + "Use Uint8Array or the Bytes implementation from @zwave-js/shared instead.", + }, + ], }, }, // Disable unnecessarily strict rules for test files diff --git a/packages/cc/src/cc/AlarmSensorCC.ts b/packages/cc/src/cc/AlarmSensorCC.ts index 54a8b63e0a1f..253e2c173bfd 100644 --- a/packages/cc/src/cc/AlarmSensorCC.ts +++ b/packages/cc/src/cc/AlarmSensorCC.ts @@ -17,6 +17,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName, isEnumMember, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { CCAPI, PhysicalCCAPI } from "../lib/API"; @@ -449,8 +450,8 @@ export class AlarmSensorCCGet extends AlarmSensorCC { public sensorType: AlarmSensorType; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.sensorType]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.sensorType]); return super.serialize(ctx); } diff --git a/packages/cc/src/cc/AssociationCC.ts b/packages/cc/src/cc/AssociationCC.ts index 276ba3732334..40e4fc177af8 100644 --- a/packages/cc/src/cc/AssociationCC.ts +++ b/packages/cc/src/cc/AssociationCC.ts @@ -20,6 +20,7 @@ import type { GetDeviceConfig, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { distinct } from "alcalzone-shared/arrays"; import { CCAPI, PhysicalCCAPI } from "../lib/API"; @@ -539,8 +540,8 @@ export class AssociationCCSet extends AssociationCC { public groupId: number; public nodeIds: number[]; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.groupId, ...this.nodeIds]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.groupId, ...this.nodeIds]); return super.serialize(ctx); } @@ -598,8 +599,8 @@ export class AssociationCCRemove extends AssociationCC { public groupId?: number; public nodeIds?: number[]; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ this.groupId || 0, ...(this.nodeIds || []), ]); @@ -692,8 +693,8 @@ export class AssociationCCReport extends AssociationCC { .reduce((prev, cur) => prev.concat(...cur), []); } - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ this.groupId, this.maxNodes, this.reportsToFollow, @@ -748,8 +749,8 @@ export class AssociationCCGet extends AssociationCC { public groupId: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.groupId]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.groupId]); return super.serialize(ctx); } @@ -792,8 +793,8 @@ export class AssociationCCSupportedGroupingsReport extends AssociationCC { @ccValue(AssociationCCValues.groupCount) public groupCount: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.groupCount]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.groupCount]); return super.serialize(ctx); } @@ -839,8 +840,8 @@ export class AssociationCCSpecificGroupReport extends AssociationCC { public group: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.group]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.group]); return super.serialize(ctx); } diff --git a/packages/cc/src/cc/AssociationGroupInfoCC.ts b/packages/cc/src/cc/AssociationGroupInfoCC.ts index 0f421077551b..57228b9aed8c 100644 --- a/packages/cc/src/cc/AssociationGroupInfoCC.ts +++ b/packages/cc/src/cc/AssociationGroupInfoCC.ts @@ -17,6 +17,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { cpp2js, getEnumMemberName, num2hex } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { CCAPI, PhysicalCCAPI } from "../lib/API"; @@ -520,10 +521,10 @@ export class AssociationGroupInfoCCNameReport extends AssociationGroupInfoCC { return true; } - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.concat([ - Buffer.from([this.groupId, this.name.length]), - Buffer.from(this.name, "utf8"), + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.concat([ + Bytes.from([this.groupId, this.name.length]), + Bytes.from(this.name, "utf8"), ]); return super.serialize(ctx); } @@ -569,8 +570,8 @@ export class AssociationGroupInfoCCNameGet extends AssociationGroupInfoCC { public groupId: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.groupId]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.groupId]); return super.serialize(ctx); } @@ -663,8 +664,8 @@ export class AssociationGroupInfoCCInfoReport extends AssociationGroupInfoCC { return true; } - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.alloc(1 + this.groups.length * 7, 0); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.alloc(1 + this.groups.length * 7, 0); this.payload[0] = (this.isListMode ? 0b1000_0000 : 0) | (this.hasDynamicInfo ? 0b0100_0000 : 0) @@ -673,7 +674,7 @@ export class AssociationGroupInfoCCInfoReport extends AssociationGroupInfoCC { for (let i = 0; i < this.groups.length; i++) { const offset = 1 + i * 7; this.payload[offset] = this.groups[i].groupId; - this.payload.writeUint16BE(this.groups[i].profile, offset + 2); + this.payload.writeUInt16BE(this.groups[i].profile, offset + 2); // The remaining bytes are zero } @@ -754,11 +755,11 @@ export class AssociationGroupInfoCCInfoGet extends AssociationGroupInfoCC { public listMode?: boolean; public groupId?: number; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { const isListMode = this.listMode === true; const optionByte = (this.refreshCache ? 0b1000_0000 : 0) | (isListMode ? 0b0100_0000 : 0); - this.payload = Buffer.from([ + this.payload = Bytes.from([ optionByte, isListMode ? 0 : this.groupId!, ]); @@ -836,10 +837,10 @@ export class AssociationGroupInfoCCCommandListReport ) public readonly commands: ReadonlyMap; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { // To make it easier to encode possible extended CCs, we first // allocate as much space as we may need, then trim it again - this.payload = Buffer.allocUnsafe(2 + this.commands.size * 3); + this.payload = new Bytes(2 + this.commands.size * 3); this.payload[0] = this.groupId; let offset = 2; for (const [ccId, commands] of this.commands) { @@ -912,8 +913,8 @@ export class AssociationGroupInfoCCCommandListGet public allowCache: boolean; public groupId: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ this.allowCache ? 0b1000_0000 : 0, this.groupId, ]); diff --git a/packages/cc/src/cc/BarrierOperatorCC.ts b/packages/cc/src/cc/BarrierOperatorCC.ts index 6e7e570d42b7..3978323afecf 100644 --- a/packages/cc/src/cc/BarrierOperatorCC.ts +++ b/packages/cc/src/cc/BarrierOperatorCC.ts @@ -20,6 +20,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName, isEnumMember, @@ -571,8 +572,8 @@ export class BarrierOperatorCCSet extends BarrierOperatorCC { public targetState: BarrierState.Open | BarrierState.Closed; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.targetState]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.targetState]); return super.serialize(ctx); } @@ -757,8 +758,8 @@ export class BarrierOperatorCCEventSignalingSet extends BarrierOperatorCC { public subsystemType: SubsystemType; public subsystemState: SubsystemState; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.subsystemType, this.subsystemState]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.subsystemType, this.subsystemState]); return super.serialize(ctx); } @@ -877,8 +878,8 @@ export class BarrierOperatorCCEventSignalingGet extends BarrierOperatorCC { public subsystemType: SubsystemType; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.subsystemType]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.subsystemType]); return super.serialize(ctx); } diff --git a/packages/cc/src/cc/BasicCC.ts b/packages/cc/src/cc/BasicCC.ts index f25235ed58ba..d94bdff5615d 100644 --- a/packages/cc/src/cc/BasicCC.ts +++ b/packages/cc/src/cc/BasicCC.ts @@ -27,6 +27,7 @@ import type { GetSupportedCCVersion, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { @@ -396,8 +397,8 @@ export class BasicCCSet extends BasicCC { public targetValue: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.targetValue]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.targetValue]); return super.serialize(ctx); } @@ -506,8 +507,8 @@ export class BasicCCReport extends BasicCC { return true; } - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ this.currentValue ?? 0xfe, this.targetValue ?? 0xfe, (this.duration ?? Duration.unknown()).serializeReport(), diff --git a/packages/cc/src/cc/BatteryCC.ts b/packages/cc/src/cc/BatteryCC.ts index ecae5e26e410..28065bb2f5fa 100644 --- a/packages/cc/src/cc/BatteryCC.ts +++ b/packages/cc/src/cc/BatteryCC.ts @@ -26,6 +26,7 @@ import type { GetSupportedCCVersion, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { type AllOrNone, getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { CCAPI, @@ -564,12 +565,12 @@ export class BatteryCCReport extends BatteryCC { @ccValue(BatteryCCValues.lowTemperatureStatus) public readonly lowTemperatureStatus: boolean | undefined; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.isLow ? 0xff : this.level]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.isLow ? 0xff : this.level]); if (this.chargingStatus != undefined) { - this.payload = Buffer.concat([ + this.payload = Bytes.concat([ this.payload, - Buffer.from([ + Bytes.from([ (this.chargingStatus << 6) + (this.rechargeable ? 0b0010_0000 : 0) + (this.backup ? 0b0001_0000 : 0) diff --git a/packages/cc/src/cc/BinarySensorCC.ts b/packages/cc/src/cc/BinarySensorCC.ts index e1f915135c81..3cd1f382089e 100644 --- a/packages/cc/src/cc/BinarySensorCC.ts +++ b/packages/cc/src/cc/BinarySensorCC.ts @@ -16,6 +16,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName, isEnumMember } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { @@ -399,8 +400,8 @@ export class BinarySensorCCReport extends BinarySensorCC { public type: BinarySensorType; public value: boolean; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.value ? 0xff : 0x00, this.type]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.value ? 0xff : 0x00, this.type]); return super.serialize(ctx); } @@ -459,8 +460,8 @@ export class BinarySensorCCGet extends BinarySensorCC { public sensorType: BinarySensorType | undefined; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.sensorType ?? BinarySensorType.Any]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.sensorType ?? BinarySensorType.Any]); return super.serialize(ctx); } @@ -516,7 +517,7 @@ export class BinarySensorCCSupportedReport extends BinarySensorCC { @ccValue(BinarySensorCCValues.supportedSensorTypes) public supportedSensorTypes: BinarySensorType[]; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { this.payload = encodeBitMask( this.supportedSensorTypes.filter((t) => t !== BinarySensorType.Any), undefined, diff --git a/packages/cc/src/cc/BinarySwitchCC.ts b/packages/cc/src/cc/BinarySwitchCC.ts index 64be97c29aa1..5af379e7e221 100644 --- a/packages/cc/src/cc/BinarySwitchCC.ts +++ b/packages/cc/src/cc/BinarySwitchCC.ts @@ -20,6 +20,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { CCAPI, @@ -339,8 +340,8 @@ export class BinarySwitchCCSet extends BinarySwitchCC { public targetValue: boolean; public duration: Duration | undefined; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ this.targetValue ? 0xff : 0x00, (this.duration ?? Duration.default()).serializeSet(), ]); @@ -425,14 +426,14 @@ export class BinarySwitchCCReport extends BinarySwitchCC { @ccValue(BinarySwitchCCValues.duration) public readonly duration: Duration | undefined; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ encodeMaybeBoolean(this.currentValue ?? UNKNOWN_STATE), ]); if (this.targetValue !== undefined) { - this.payload = Buffer.concat([ + this.payload = Bytes.concat([ this.payload, - Buffer.from([ + Bytes.from([ encodeMaybeBoolean(this.targetValue), (this.duration ?? Duration.default()).serializeReport(), ]), diff --git a/packages/cc/src/cc/CRC16CC.ts b/packages/cc/src/cc/CRC16CC.ts index d8368a895400..1d5017a081ec 100644 --- a/packages/cc/src/cc/CRC16CC.ts +++ b/packages/cc/src/cc/CRC16CC.ts @@ -22,9 +22,10 @@ import { implementedVersion, } from "../lib/CommandClassDecorators"; +import { Bytes } from "@zwave-js/shared/safe"; import { CRC16Command } from "../lib/_Types"; -const headerBuffer = Buffer.from([ +const headerBuffer = Bytes.from([ CommandClasses["CRC-16 Encapsulation"], CRC16Command.CommandEncapsulation, ]); @@ -144,10 +145,10 @@ export class CRC16CCCommandEncapsulation extends CRC16CC { public encapsulated: CommandClass; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { const commandBuffer = this.encapsulated.serialize(ctx); // Reserve 2 bytes for the CRC - this.payload = Buffer.concat([commandBuffer, Buffer.allocUnsafe(2)]); + this.payload = Bytes.concat([commandBuffer, new Bytes(2)]); // Compute and save the CRC16 in the payload // The CC header is included in the CRC computation diff --git a/packages/cc/src/cc/CentralSceneCC.ts b/packages/cc/src/cc/CentralSceneCC.ts index 8543402458fa..8ad235ff2574 100644 --- a/packages/cc/src/cc/CentralSceneCC.ts +++ b/packages/cc/src/cc/CentralSceneCC.ts @@ -20,6 +20,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { padStart } from "alcalzone-shared/strings"; @@ -590,8 +591,8 @@ export class CentralSceneCCConfigurationSet extends CentralSceneCC { public slowRefresh: boolean; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.slowRefresh ? 0b1000_0000 : 0]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.slowRefresh ? 0b1000_0000 : 0]); return super.serialize(ctx); } diff --git a/packages/cc/src/cc/ClimateControlScheduleCC.ts b/packages/cc/src/cc/ClimateControlScheduleCC.ts index 02a3bf8b2c87..0015e8ca18e1 100644 --- a/packages/cc/src/cc/ClimateControlScheduleCC.ts +++ b/packages/cc/src/cc/ClimateControlScheduleCC.ts @@ -15,6 +15,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { padStart } from "alcalzone-shared/strings"; @@ -247,7 +248,7 @@ export class ClimateControlScheduleCCSet extends ClimateControlScheduleCC { public switchPoints: Switchpoint[]; public weekday: Weekday; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { // Make sure we have exactly 9 entries const allSwitchPoints = this.switchPoints.slice(0, 9); // maximum 9 while (allSwitchPoints.length < 9) { @@ -257,8 +258,8 @@ export class ClimateControlScheduleCCSet extends ClimateControlScheduleCC { state: "Unused", }); } - this.payload = Buffer.concat([ - Buffer.from([this.weekday & 0b111]), + this.payload = Bytes.concat([ + Bytes.from([this.weekday & 0b111]), ...allSwitchPoints.map((sp) => encodeSwitchpoint(sp)), ]); return super.serialize(ctx); @@ -393,8 +394,8 @@ export class ClimateControlScheduleCCGet extends ClimateControlScheduleCC { public weekday: Weekday; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.weekday & 0b111]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.weekday & 0b111]); return super.serialize(ctx); } @@ -551,8 +552,8 @@ export class ClimateControlScheduleCCOverrideSet public overrideType: ScheduleOverrideType; public overrideState: SetbackState; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ this.overrideType & 0b11, encodeSetbackState(this.overrideState), ]); diff --git a/packages/cc/src/cc/ClockCC.ts b/packages/cc/src/cc/ClockCC.ts index ada36ace818d..fa4aa9d8b967 100644 --- a/packages/cc/src/cc/ClockCC.ts +++ b/packages/cc/src/cc/ClockCC.ts @@ -16,6 +16,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { padStart } from "alcalzone-shared/strings"; @@ -177,8 +178,8 @@ export class ClockCCSet extends ClockCC { public hour: number; public minute: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ ((this.weekday & 0b111) << 5) | (this.hour & 0b11111), this.minute, ]); diff --git a/packages/cc/src/cc/ColorSwitchCC.ts b/packages/cc/src/cc/ColorSwitchCC.ts index feaeb43a1cfe..5fbe88d0f576 100644 --- a/packages/cc/src/cc/ColorSwitchCC.ts +++ b/packages/cc/src/cc/ColorSwitchCC.ts @@ -22,6 +22,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName, isEnumMember, @@ -713,7 +714,7 @@ export class ColorSwitchCCSupportedReport extends ColorSwitchCC { @ccValue(ColorSwitchCCValues.supportedColorComponents) public readonly supportedColorComponents: readonly ColorComponent[]; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { this.payload = encodeBitMask( this.supportedColorComponents, 15, // fixed 2 bytes @@ -858,15 +859,15 @@ export class ColorSwitchCCReport extends ColorSwitchCC { @ccValue(ColorSwitchCCValues.duration) public readonly duration: Duration | undefined; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ this.colorComponent, this.currentValue, ]); if (this.targetValue != undefined && this.duration != undefined) { - this.payload = Buffer.concat([ + this.payload = Bytes.concat([ this.payload, - Buffer.from([ + Bytes.from([ this.targetValue ?? 0xfe, (this.duration ?? Duration.default()).serializeReport(), ]), @@ -942,8 +943,8 @@ export class ColorSwitchCCGet extends ColorSwitchCC { this._colorComponent = value; } - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this._colorComponent]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this._colorComponent]); return super.serialize(ctx); } @@ -1023,9 +1024,9 @@ export class ColorSwitchCCSet extends ColorSwitchCC { public colorTable: ColorTable; public duration: Duration | undefined; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { const populatedColorCount = Object.keys(this.colorTable).length; - this.payload = Buffer.allocUnsafe( + this.payload = new Bytes( 1 + populatedColorCount * 2 + 1, ); this.payload[0] = populatedColorCount & 0b11111; @@ -1138,10 +1139,10 @@ export class ColorSwitchCCStartLevelChange extends ColorSwitchCC { public direction: keyof typeof LevelChangeDirection; public colorComponent: ColorComponent; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { const controlByte = (LevelChangeDirection[this.direction] << 6) | (this.ignoreStartLevel ? 0b0010_0000 : 0); - this.payload = Buffer.from([ + this.payload = Bytes.from([ controlByte, this.colorComponent, this.startLevel, @@ -1212,8 +1213,8 @@ export class ColorSwitchCCStopLevelChange extends ColorSwitchCC { public readonly colorComponent: ColorComponent; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.colorComponent]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.colorComponent]); return super.serialize(ctx); } diff --git a/packages/cc/src/cc/ConfigurationCC.ts b/packages/cc/src/cc/ConfigurationCC.ts index c10cc6811adf..01fabfca0ec7 100644 --- a/packages/cc/src/cc/ConfigurationCC.ts +++ b/packages/cc/src/cc/ConfigurationCC.ts @@ -38,6 +38,7 @@ import type { GetSupportedCCVersion, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { distinct } from "alcalzone-shared/arrays"; @@ -279,7 +280,7 @@ function reInterpretSignedValue( targetFormat: ConfigValueFormat, ): ConfigValue { // Re-interpret the value with the new format - const raw = Buffer.allocUnsafe(valueSize); + const raw = new Bytes(valueSize); serializeValue(raw, 0, valueSize, ConfigValueFormat.SignedInteger, value); return parseValue(raw, valueSize, targetFormat); } @@ -1753,10 +1754,10 @@ export class ConfigurationCCReport extends ConfigurationCC { return true; } - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.concat([ - Buffer.from([this.parameter, this.valueSize & 0b111]), - Buffer.allocUnsafe(this.valueSize), + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.concat([ + Bytes.from([this.parameter, this.valueSize & 0b111]), + new Bytes(this.valueSize), ]); serializeValue( this.payload, @@ -1826,8 +1827,8 @@ export class ConfigurationCCGet extends ConfigurationCC { public parameter: number; public allowUnexpectedResponse: boolean; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.parameter]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.parameter]); return super.serialize(ctx); } @@ -1906,10 +1907,10 @@ export class ConfigurationCCSet extends ConfigurationCC { public valueFormat: ConfigValueFormat | undefined; public value: ConfigValue | undefined; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { const valueSize = this.resetToDefault ? 1 : this.valueSize!; const payloadLength = 2 + valueSize; - this.payload = Buffer.alloc(payloadLength, 0); + this.payload = Bytes.alloc(payloadLength, 0); this.payload[0] = this.parameter; this.payload[1] = (this.resetToDefault ? 0b1000_0000 : 0) | (valueSize & 0b111); @@ -2069,10 +2070,10 @@ export class ConfigurationCCBulkSet extends ConfigurationCC { return this._handshake; } - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { const valueSize = this._resetToDefault ? 1 : this.valueSize; const payloadLength = 4 + valueSize * this.parameters.length; - this.payload = Buffer.alloc(payloadLength, 0); + this.payload = Bytes.alloc(payloadLength, 0); this.payload.writeUInt16BE(this.parameters[0], 0); this.payload[2] = this.parameters.length; this.payload[3] = (this._resetToDefault ? 0b1000_0000 : 0) @@ -2320,8 +2321,8 @@ export class ConfigurationCCBulkGet extends ConfigurationCC { return this._parameters; } - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.allocUnsafe(3); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = new Bytes(3); this.payload.writeUInt16BE(this.parameters[0], 0); this.payload[2] = this.parameters.length; return super.serialize(ctx); @@ -2417,12 +2418,12 @@ export class ConfigurationCCNameReport extends ConfigurationCC { return true; } - public serialize(ctx: CCEncodingContext): Buffer { - const nameBuffer = Buffer.from(this.name, "utf8"); - this.payload = Buffer.allocUnsafe(3 + nameBuffer.length); + public serialize(ctx: CCEncodingContext): Bytes { + const nameBuffer = Bytes.from(this.name, "utf8"); + this.payload = new Bytes(3 + nameBuffer.length); this.payload.writeUInt16BE(this.parameter, 0); this.payload[2] = this.reportsToFollow; - nameBuffer.copy(this.payload, 3); + this.payload.set(nameBuffer, 3); return super.serialize(ctx); } @@ -2483,8 +2484,8 @@ export class ConfigurationCCNameGet extends ConfigurationCC { public parameter: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.allocUnsafe(2); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = new Bytes(2); this.payload.writeUInt16BE(this.parameter, 0); return super.serialize(ctx); } @@ -2592,12 +2593,12 @@ export class ConfigurationCCInfoReport extends ConfigurationCC { return true; } - public serialize(ctx: CCEncodingContext): Buffer { - const infoBuffer = Buffer.from(this.info, "utf8"); - this.payload = Buffer.allocUnsafe(3 + infoBuffer.length); + public serialize(ctx: CCEncodingContext): Bytes { + const infoBuffer = Bytes.from(this.info, "utf8"); + this.payload = new Bytes(3 + infoBuffer.length); this.payload.writeUInt16BE(this.parameter, 0); this.payload[2] = this.reportsToFollow; - infoBuffer.copy(this.payload, 3); + this.payload.set(infoBuffer, 3); return super.serialize(ctx); } @@ -2658,8 +2659,8 @@ export class ConfigurationCCInfoGet extends ConfigurationCC { public parameter: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.allocUnsafe(2); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = new Bytes(2); this.payload.writeUInt16BE(this.parameter, 0); return super.serialize(ctx); } @@ -2906,8 +2907,8 @@ export class ConfigurationCCPropertiesReport extends ConfigurationCC { public isAdvanced: MaybeNotKnown; public noBulkSupport: MaybeNotKnown; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.allocUnsafe( + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = new Bytes( 3 // preamble + 3 * this.valueSize // min, max, default value + 2 // next parameter @@ -3020,8 +3021,8 @@ export class ConfigurationCCPropertiesGet extends ConfigurationCC { public parameter: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.allocUnsafe(2); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = new Bytes(2); this.payload.writeUInt16BE(this.parameter, 0); return super.serialize(ctx); } @@ -3063,7 +3064,7 @@ function isSafeValue( /** Interprets values from the payload depending on the value format */ function parseValue( - raw: Buffer, + raw: Bytes, size: number, format: ConfigValueFormat, ): ConfigValue { @@ -3110,7 +3111,7 @@ function tryCatchOutOfBoundsError( /** Serializes values into the payload according to the value format */ function serializeValue( - payload: Buffer, + payload: Bytes, offset: number, size: number, format: ConfigValueFormat, diff --git a/packages/cc/src/cc/DoorLockCC.ts b/packages/cc/src/cc/DoorLockCC.ts index e39fa35a7b8d..64dabab944a9 100644 --- a/packages/cc/src/cc/DoorLockCC.ts +++ b/packages/cc/src/cc/DoorLockCC.ts @@ -21,6 +21,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { isArray } from "alcalzone-shared/typeguards"; @@ -858,8 +859,8 @@ export class DoorLockCCOperationSet extends DoorLockCC { public mode: DoorLockMode; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.mode]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.mode]); return super.serialize(ctx); } @@ -1328,7 +1329,7 @@ export class DoorLockCCConfigurationSet extends DoorLockCC { public twistAssist?: boolean; public blockToBlock?: boolean; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { const insideHandles = isArray( this.insideHandlesCanOpenDoorConfiguration, ) @@ -1358,7 +1359,7 @@ export class DoorLockCCConfigurationSet extends DoorLockCC { const flags = (this.twistAssist ? 0b1 : 0) | (this.blockToBlock ? 0b10 : 0); - this.payload = Buffer.from([ + this.payload = Bytes.from([ this.operationType, handles, lockTimeoutMinutes, diff --git a/packages/cc/src/cc/DoorLockLoggingCC.ts b/packages/cc/src/cc/DoorLockLoggingCC.ts index 7c839b95278d..12849003b794 100644 --- a/packages/cc/src/cc/DoorLockLoggingCC.ts +++ b/packages/cc/src/cc/DoorLockLoggingCC.ts @@ -14,6 +14,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { isPrintableASCII, num2hex } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { CCAPI, PhysicalCCAPI } from "../lib/API"; @@ -429,8 +430,8 @@ export class DoorLockLoggingCCRecordGet extends DoorLockLoggingCC { public recordNumber: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.recordNumber]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.recordNumber]); return super.serialize(ctx); } diff --git a/packages/cc/src/cc/EnergyProductionCC.ts b/packages/cc/src/cc/EnergyProductionCC.ts index a45eb28c93a0..5952c44baaff 100644 --- a/packages/cc/src/cc/EnergyProductionCC.ts +++ b/packages/cc/src/cc/EnergyProductionCC.ts @@ -14,7 +14,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host"; -import { getEnumMemberName, pick } from "@zwave-js/shared"; +import { Bytes, getEnumMemberName, pick } from "@zwave-js/shared"; import { validateArgs } from "@zwave-js/transformers"; import { CCAPI, @@ -250,9 +250,9 @@ export class EnergyProductionCCReport extends EnergyProductionCC { return true; } - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.concat([ - Buffer.from([this.parameter]), + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.concat([ + Bytes.from([this.parameter]), encodeFloatWithScale(this.value, this.scale.key), ]); return super.serialize(ctx); @@ -313,8 +313,8 @@ export class EnergyProductionCCGet extends EnergyProductionCC { public parameter: EnergyProductionParameter; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.parameter]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.parameter]); return super.serialize(ctx); } diff --git a/packages/cc/src/cc/EntryControlCC.ts b/packages/cc/src/cc/EntryControlCC.ts index b7d70a3a7091..f3ae57f3bd91 100644 --- a/packages/cc/src/cc/EntryControlCC.ts +++ b/packages/cc/src/cc/EntryControlCC.ts @@ -19,6 +19,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { buffer2hex, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { @@ -399,7 +400,7 @@ export interface EntryControlCCNotificationOptions { sequenceNumber: number; dataType: EntryControlDataTypes; eventType: EntryControlEventTypes; - eventData?: string | Buffer; + eventData?: string | Bytes; } @CCCommand(EntryControlCommand.Notification) @@ -428,7 +429,7 @@ export class EntryControlCCNotification extends EntryControlCC { validatePayload(eventDataLength >= 0 && eventDataLength <= 32); const offset = 4; validatePayload(raw.payload.length >= offset + eventDataLength); - let eventData: string | Buffer | undefined; + let eventData: string | Bytes | undefined; if (eventDataLength > 0) { // We shouldn't need to check this, since the specs are pretty clear which format to expect. // But as always - manufacturers don't care and send ASCII data with 0 bytes... @@ -438,7 +439,7 @@ export class EntryControlCCNotification extends EntryControlCC { ctx.sourceNodeId, )?.compat?.disableStrictEntryControlDataValidation; - eventData = Buffer.from( + eventData = Bytes.from( raw.payload.subarray(offset, offset + eventDataLength), ); switch (dataType) { @@ -458,15 +459,14 @@ export class EntryControlCCNotification extends EntryControlCC { eventDataLength === 16 || eventDataLength === 32, ); } - // Using toString("ascii") converts the padding bytes 0xff to 0x7f eventData = eventData.toString("ascii"); if (!noStrictValidation) { validatePayload( - /^[\u0000-\u007f]+[\u007f]*$/.test(eventData), + /^[\u0000-\u007f]+[\u00ff]*$/.test(eventData), ); } - // Trim padding - eventData = eventData.replace(/[\u007f]*$/, ""); + // Trim 0xff padding bytes + eventData = eventData.replace(/[\u00ff]*$/, ""); break; case EntryControlDataTypes.MD5: // MD5 16 byte binary data encoded as a MD5 hash value. @@ -491,7 +491,7 @@ export class EntryControlCCNotification extends EntryControlCC { public readonly sequenceNumber: number; public readonly dataType: EntryControlDataTypes; public readonly eventType: EntryControlEventTypes; - public readonly eventData?: Buffer | string; + public readonly eventData?: Uint8Array | string; public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { @@ -786,8 +786,8 @@ export class EntryControlCCConfigurationSet extends EntryControlCC { public readonly keyCacheSize: number; public readonly keyCacheTimeout: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.keyCacheSize, this.keyCacheTimeout]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.keyCacheSize, this.keyCacheTimeout]); return super.serialize(ctx); } diff --git a/packages/cc/src/cc/FirmwareUpdateMetaDataCC.ts b/packages/cc/src/cc/FirmwareUpdateMetaDataCC.ts index 9fb74611ccba..9f6afef71bfe 100644 --- a/packages/cc/src/cc/FirmwareUpdateMetaDataCC.ts +++ b/packages/cc/src/cc/FirmwareUpdateMetaDataCC.ts @@ -15,6 +15,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { type AllOrNone, getEnumMemberName, @@ -190,7 +191,7 @@ export class FirmwareUpdateMetaDataCCAPI extends PhysicalCCAPI { public async sendFirmwareFragment( fragmentNumber: number, isLastFragment: boolean, - data: Buffer, + data: Uint8Array, ): Promise { this.assertSupportsCommand( FirmwareUpdateMetaDataCommand, @@ -436,8 +437,8 @@ export class FirmwareUpdateMetaDataCCMetaDataReport @ccValue(FirmwareUpdateMetaDataCCValues.supportsNonSecureTransfer) public readonly supportsNonSecureTransfer?: MaybeNotKnown; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.alloc( + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.alloc( 12 + 2 * this.additionalFirmwareIDs.length, ); this.payload.writeUInt16BE(this.manufacturerId, 0); @@ -640,8 +641,8 @@ export class FirmwareUpdateMetaDataCCRequestGet public resume?: boolean; public nonSecureTransfer?: boolean; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.alloc(11, 0); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.alloc(11, 0); this.payload.writeUInt16BE(this.manufacturerId, 0); this.payload.writeUInt16BE(this.firmwareId, 2); this.payload.writeUInt16BE(this.checksum, 4); @@ -740,7 +741,7 @@ export class FirmwareUpdateMetaDataCCGet extends FirmwareUpdateMetaDataCC { export interface FirmwareUpdateMetaDataCCReportOptions { isLast: boolean; reportNumber: number; - firmwareData: Buffer; + firmwareData: Uint8Array; } @CCCommand(FirmwareUpdateMetaDataCommand.Report) @@ -772,11 +773,11 @@ export class FirmwareUpdateMetaDataCCReport extends FirmwareUpdateMetaDataCC { public isLast: boolean; public reportNumber: number; - public firmwareData: Buffer; + public firmwareData: Uint8Array; - public serialize(ctx: CCEncodingContext): Buffer { - const commandBuffer = Buffer.concat([ - Buffer.allocUnsafe(2), // placeholder for report number + public serialize(ctx: CCEncodingContext): Bytes { + const commandBuffer = Bytes.concat([ + new Bytes(2), // placeholder for report number this.firmwareData, ]); commandBuffer.writeUInt16BE( @@ -790,11 +791,11 @@ export class FirmwareUpdateMetaDataCCReport extends FirmwareUpdateMetaDataCC { if (ccVersion >= 2) { // Compute and save the CRC16 in the payload // The CC header is included in the CRC computation - let crc = CRC16_CCITT(Buffer.from([this.ccId, this.ccCommand])); + let crc = CRC16_CCITT(Bytes.from([this.ccId, this.ccCommand])); crc = CRC16_CCITT(commandBuffer, crc); - this.payload = Buffer.concat([ + this.payload = Bytes.concat([ commandBuffer, - Buffer.allocUnsafe(2), + new Bytes(2), ]); this.payload.writeUInt16BE(crc, this.payload.length - 2); } else { @@ -1001,8 +1002,8 @@ export class FirmwareUpdateMetaDataCCActivationSet public firmwareTarget: number; public hardwareVersion?: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.allocUnsafe(8); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = new Bytes(8); this.payload.writeUInt16BE(this.manufacturerId, 0); this.payload.writeUInt16BE(this.firmwareId, 2); this.payload.writeUInt16BE(this.checksum, 4); @@ -1123,8 +1124,8 @@ export class FirmwareUpdateMetaDataCCPrepareGet public fragmentSize: number; public hardwareVersion: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.allocUnsafe(8); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = new Bytes(8); this.payload.writeUInt16BE(this.manufacturerId, 0); this.payload.writeUInt16BE(this.firmwareId, 2); this.payload[4] = this.firmwareTarget; diff --git a/packages/cc/src/cc/HumidityControlModeCC.ts b/packages/cc/src/cc/HumidityControlModeCC.ts index 6b1f5db37314..87695b37d73c 100644 --- a/packages/cc/src/cc/HumidityControlModeCC.ts +++ b/packages/cc/src/cc/HumidityControlModeCC.ts @@ -18,6 +18,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { @@ -309,8 +310,8 @@ export class HumidityControlModeCCSet extends HumidityControlModeCC { public mode: HumidityControlMode; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.mode & 0b1111]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.mode & 0b1111]); return super.serialize(ctx); } diff --git a/packages/cc/src/cc/HumidityControlSetpointCC.ts b/packages/cc/src/cc/HumidityControlSetpointCC.ts index 0e1aaf1fdde3..81b5fde9cfc1 100644 --- a/packages/cc/src/cc/HumidityControlSetpointCC.ts +++ b/packages/cc/src/cc/HumidityControlSetpointCC.ts @@ -23,6 +23,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { @@ -551,9 +552,9 @@ export class HumidityControlSetpointCCSet extends HumidityControlSetpointCC { public value: number; public scale: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.concat([ - Buffer.from([this.setpointType & 0b1111]), + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.concat([ + Bytes.from([this.setpointType & 0b1111]), encodeFloatWithScale(this.value, this.scale), ]); return super.serialize(ctx); @@ -716,8 +717,8 @@ export class HumidityControlSetpointCCGet extends HumidityControlSetpointCC { public setpointType: HumidityControlSetpointType; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.setpointType & 0b1111]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.setpointType & 0b1111]); return super.serialize(ctx); } @@ -825,7 +826,7 @@ export class HumidityControlSetpointCCScaleSupportedReport ): HumidityControlSetpointCCScaleSupportedReport { validatePayload(raw.payload.length >= 1); const supportedScales = parseBitMask( - Buffer.from([raw.payload[0] & 0b1111]), + Bytes.from([raw.payload[0] & 0b1111]), 0, ); @@ -886,8 +887,8 @@ export class HumidityControlSetpointCCScaleSupportedGet public setpointType: HumidityControlSetpointType; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.setpointType & 0b1111]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.setpointType & 0b1111]); return super.serialize(ctx); } @@ -1033,8 +1034,8 @@ export class HumidityControlSetpointCCCapabilitiesGet public setpointType: HumidityControlSetpointType; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.setpointType & 0b1111]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.setpointType & 0b1111]); return super.serialize(ctx); } diff --git a/packages/cc/src/cc/InclusionControllerCC.ts b/packages/cc/src/cc/InclusionControllerCC.ts index e295314cb6ac..d8f456521375 100644 --- a/packages/cc/src/cc/InclusionControllerCC.ts +++ b/packages/cc/src/cc/InclusionControllerCC.ts @@ -10,7 +10,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host"; -import { getEnumMemberName } from "@zwave-js/shared"; +import { Bytes, getEnumMemberName } from "@zwave-js/shared"; import { CCAPI } from "../lib/API"; import { type CCRaw, CommandClass } from "../lib/CommandClass"; import { @@ -124,8 +124,8 @@ export class InclusionControllerCCComplete extends InclusionControllerCC { public step: InclusionControllerStep; public status: InclusionControllerStatus; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.step, this.status]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.step, this.status]); return super.serialize(ctx); } @@ -181,8 +181,8 @@ export class InclusionControllerCCInitiate extends InclusionControllerCC { public includedNodeId: number; public step: InclusionControllerStep; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.includedNodeId, this.step]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.includedNodeId, this.step]); return super.serialize(ctx); } diff --git a/packages/cc/src/cc/IndicatorCC.ts b/packages/cc/src/cc/IndicatorCC.ts index e54b09efa770..39c51ec4c6bd 100644 --- a/packages/cc/src/cc/IndicatorCC.ts +++ b/packages/cc/src/cc/IndicatorCC.ts @@ -21,6 +21,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { num2hex } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { clamp, roundTo } from "alcalzone-shared/math"; @@ -951,10 +952,10 @@ export class IndicatorCCSet extends IndicatorCC { public indicator0Value: number | undefined; public values: IndicatorObject[] | undefined; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { if (this.values != undefined) { // V2+ - this.payload = Buffer.alloc(2 + 3 * this.values.length, 0); + this.payload = Bytes.alloc(2 + 3 * this.values.length, 0); // Byte 0 is the legacy value const objCount = this.values.length & MAX_INDICATOR_OBJECTS; this.payload[1] = objCount; @@ -971,7 +972,7 @@ export class IndicatorCCSet extends IndicatorCC { } } else { // V1 - this.payload = Buffer.from([this.indicator0Value ?? 0]); + this.payload = Bytes.from([this.indicator0Value ?? 0]); } return super.serialize(ctx); } @@ -1172,10 +1173,10 @@ export class IndicatorCCReport extends IndicatorCC { this.setValue(ctx, valueV2, value.value); } - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { if (this.values != undefined) { // V2+ - this.payload = Buffer.alloc(2 + 3 * this.values.length, 0); + this.payload = Bytes.alloc(2 + 3 * this.values.length, 0); // Byte 0 is the legacy value const objCount = this.values.length & MAX_INDICATOR_OBJECTS; this.payload[1] = objCount; @@ -1192,7 +1193,7 @@ export class IndicatorCCReport extends IndicatorCC { } } else { // V1 - this.payload = Buffer.from([this.indicator0Value ?? 0]); + this.payload = Bytes.from([this.indicator0Value ?? 0]); } return super.serialize(ctx); } @@ -1251,9 +1252,9 @@ export class IndicatorCCGet extends IndicatorCC { public indicatorId: number | undefined; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { if (this.indicatorId != undefined) { - this.payload = Buffer.from([this.indicatorId]); + this.payload = Bytes.from([this.indicatorId]); } return super.serialize(ctx); } @@ -1334,12 +1335,12 @@ export class IndicatorCCSupportedReport extends IndicatorCC { public readonly nextIndicatorId: number; public readonly supportedProperties: readonly number[]; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { const bitmask = this.supportedProperties.length > 0 ? encodeBitMask(this.supportedProperties, undefined, 0) - : Buffer.from([]); - this.payload = Buffer.concat([ - Buffer.from([ + : new Bytes(); + this.payload = Bytes.concat([ + Bytes.from([ this.indicatorId, this.nextIndicatorId, bitmask.length, @@ -1410,8 +1411,8 @@ export class IndicatorCCSupportedGet extends IndicatorCC { public indicatorId: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.indicatorId]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.indicatorId]); return super.serialize(ctx); } @@ -1478,10 +1479,10 @@ export class IndicatorCCDescriptionReport extends IndicatorCC { return true; } - public serialize(ctx: CCEncodingContext): Buffer { - const description = Buffer.from(this.description, "utf8"); - this.payload = Buffer.concat([ - Buffer.from([this.indicatorId, description.length]), + public serialize(ctx: CCEncodingContext): Bytes { + const description = Bytes.from(this.description, "utf8"); + this.payload = Bytes.concat([ + Bytes.from([this.indicatorId, description.length]), description, ]); return super.serialize(ctx); @@ -1544,8 +1545,8 @@ export class IndicatorCCDescriptionGet extends IndicatorCC { public indicatorId: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.indicatorId]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.indicatorId]); return super.serialize(ctx); } diff --git a/packages/cc/src/cc/IrrigationCC.ts b/packages/cc/src/cc/IrrigationCC.ts index 27d060b5d16d..2e6a76c7811e 100644 --- a/packages/cc/src/cc/IrrigationCC.ts +++ b/packages/cc/src/cc/IrrigationCC.ts @@ -21,6 +21,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { padStart } from "alcalzone-shared/strings"; @@ -1665,7 +1666,7 @@ export class IrrigationCCSystemConfigSet extends IrrigationCC { public rainSensorPolarity?: IrrigationSensorPolarity; public moistureSensorPolarity?: IrrigationSensorPolarity; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { let polarity = 0; if (this.rainSensorPolarity != undefined) polarity |= 0b1; if (this.moistureSensorPolarity != undefined) polarity |= 0b10; @@ -1676,11 +1677,11 @@ export class IrrigationCCSystemConfigSet extends IrrigationCC { // Valid bit polarity |= 0b1000_0000; } - this.payload = Buffer.concat([ - Buffer.from([this.masterValveDelay]), + this.payload = Bytes.concat([ + Bytes.from([this.masterValveDelay]), encodeFloatWithScale(this.highPressureThreshold, 0 /* kPa */), encodeFloatWithScale(this.lowPressureThreshold, 0 /* kPa */), - Buffer.from([polarity]), + Bytes.from([polarity]), ]); return super.serialize(ctx); } @@ -2047,8 +2048,8 @@ export class IrrigationCCValveInfoGet extends IrrigationCC { public valveId: ValveId; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ this.valveId === "master" ? 1 : 0, this.valveId === "master" ? 1 : this.valveId || 1, ]); @@ -2118,9 +2119,9 @@ export class IrrigationCCValveConfigSet extends IrrigationCC { public useRainSensor: boolean; public useMoistureSensor: boolean; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.concat([ - Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.concat([ + Bytes.from([ this.valveId === "master" ? 1 : 0, this.valveId === "master" ? 1 : this.valveId || 1, Math.floor(this.nominalCurrentHighThreshold / 10), @@ -2129,7 +2130,7 @@ export class IrrigationCCValveConfigSet extends IrrigationCC { encodeFloatWithScale(this.maximumFlow, 0 /* l/h */), encodeFloatWithScale(this.highFlowThreshold, 0 /* l/h */), encodeFloatWithScale(this.lowFlowThreshold, 0 /* l/h */), - Buffer.from([ + Bytes.from([ (this.useRainSensor ? 0b1 : 0) | (this.useMoistureSensor ? 0b10 : 0), ]), @@ -2370,8 +2371,8 @@ export class IrrigationCCValveConfigGet extends IrrigationCC { public valveId: ValveId; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ this.valveId === "master" ? 1 : 0, this.valveId === "master" ? 1 : this.valveId || 1, ]); @@ -2423,8 +2424,8 @@ export class IrrigationCCValveRun extends IrrigationCC { public valveId: ValveId; public duration: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ this.valveId === "master" ? 1 : 0, this.valveId === "master" ? 1 : this.valveId || 1, 0, @@ -2485,8 +2486,8 @@ export class IrrigationCCValveTableSet extends IrrigationCC { public tableId: number; public entries: ValveTableEntry[]; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.allocUnsafe(1 + this.entries.length * 3); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = new Bytes(1 + this.entries.length * 3); this.payload[0] = this.tableId; for (let i = 0; i < this.entries.length; i++) { const entry = this.entries[i]; @@ -2621,8 +2622,8 @@ export class IrrigationCCValveTableGet extends IrrigationCC { public tableId: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.tableId]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.tableId]); return super.serialize(ctx); } @@ -2674,8 +2675,8 @@ export class IrrigationCCValveTableRun extends IrrigationCC { public tableIDs: number[]; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from(this.tableIDs); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from(this.tableIDs); return super.serialize(ctx); } @@ -2727,8 +2728,8 @@ export class IrrigationCCSystemShutoff extends IrrigationCC { public duration?: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.duration ?? 255]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.duration ?? 255]); return super.serialize(ctx); } diff --git a/packages/cc/src/cc/LanguageCC.ts b/packages/cc/src/cc/LanguageCC.ts index 87a9221149c6..ddada460ee11 100644 --- a/packages/cc/src/cc/LanguageCC.ts +++ b/packages/cc/src/cc/LanguageCC.ts @@ -18,6 +18,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { CCAPI } from "../lib/API"; @@ -223,10 +224,14 @@ export class LanguageCCSet extends LanguageCC { this._country = value; } - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.allocUnsafe(!!this._country ? 5 : 3); - this.payload.write(this._language, 0, "ascii"); - if (!!this._country) this.payload.write(this._country, 3, "ascii"); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from(this._language, "ascii"); + if (this._country) { + this.payload = Bytes.concat([ + this.payload, + Bytes.from(this._country, "ascii"), + ]); + } return super.serialize(ctx); } @@ -260,10 +265,10 @@ export class LanguageCCReport extends LanguageCC { public static from(raw: CCRaw, ctx: CCParsingContext): LanguageCCReport { validatePayload(raw.payload.length >= 3); - const language = raw.payload.toString("ascii", 0, 3); + const language = raw.payload.subarray(0, 3).toString("ascii"); let country: MaybeNotKnown; if (raw.payload.length >= 5) { - country = raw.payload.toString("ascii", 3, 5); + country = raw.payload.subarray(3, 5).toString("ascii"); } return new LanguageCCReport({ diff --git a/packages/cc/src/cc/LockCC.ts b/packages/cc/src/cc/LockCC.ts index 98fd94d2dd33..cb1029e3c08d 100644 --- a/packages/cc/src/cc/LockCC.ts +++ b/packages/cc/src/cc/LockCC.ts @@ -16,6 +16,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { CCAPI, @@ -210,8 +211,8 @@ export class LockCCSet extends LockCC { public locked: boolean; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.locked ? 1 : 0]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.locked ? 1 : 0]); return super.serialize(ctx); } diff --git a/packages/cc/src/cc/ManufacturerProprietaryCC.ts b/packages/cc/src/cc/ManufacturerProprietaryCC.ts index c6d42b35f5ec..7a3c9546b2a5 100644 --- a/packages/cc/src/cc/ManufacturerProprietaryCC.ts +++ b/packages/cc/src/cc/ManufacturerProprietaryCC.ts @@ -6,6 +6,7 @@ import { validatePayload, } from "@zwave-js/core/safe"; import type { CCEncodingContext, CCParsingContext } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { CCAPI, type CCAPIEndpoint, type CCAPIHost } from "../lib/API"; import { @@ -64,28 +65,28 @@ export class ManufacturerProprietaryCCAPI extends CCAPI { @validateArgs() public async sendData( manufacturerId: number, - data?: Buffer, + data?: Uint8Array, ): Promise { const cc = new ManufacturerProprietaryCC({ nodeId: this.endpoint.nodeId, endpointIndex: this.endpoint.index, manufacturerId, }); - cc.payload = data ?? Buffer.allocUnsafe(0); + cc.payload = data ? Bytes.view(data) : new Bytes(); await this.host.sendCommand(cc, this.commandOptions); } @validateArgs() // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - public async sendAndReceiveData(manufacturerId: number, data?: Buffer) { + public async sendAndReceiveData(manufacturerId: number, data?: Uint8Array) { const cc = new ManufacturerProprietaryCC({ nodeId: this.endpoint.nodeId, endpointIndex: this.endpoint.index, manufacturerId, unspecifiedExpectsResponse: true, }); - cc.payload = data ?? Buffer.allocUnsafe(0); + cc.payload = data ? Bytes.view(data) : new Bytes(); const response = await this.host.sendCommand< ManufacturerProprietaryCC @@ -149,7 +150,7 @@ export class ManufacturerProprietaryCC extends CommandClass { ctx: CCParsingContext, ): ManufacturerProprietaryCC { validatePayload(raw.payload.length >= 1); - const manufacturerId = raw.payload.readUint16BE(0); + const manufacturerId = raw.payload.readUInt16BE(0); // Try to parse the proprietary command const PCConstructor = getManufacturerProprietaryCCConstructor( manufacturerId, @@ -186,14 +187,14 @@ export class ManufacturerProprietaryCC extends CommandClass { return this.manufacturerId; } - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { const manufacturerId = this.getManufacturerIdOrThrow(); // ManufacturerProprietaryCC has no CC command, so the first byte // is stored in ccCommand (this.ccCommand as unknown as number) = (manufacturerId >>> 8) & 0xff; // The 2nd byte is in the payload - this.payload = Buffer.concat([ - Buffer.from([ + this.payload = Bytes.concat([ + Bytes.from([ // 2nd byte of manufacturerId manufacturerId & 0xff, ]), diff --git a/packages/cc/src/cc/ManufacturerSpecificCC.ts b/packages/cc/src/cc/ManufacturerSpecificCC.ts index a84795d789d5..70e06383ec24 100644 --- a/packages/cc/src/cc/ManufacturerSpecificCC.ts +++ b/packages/cc/src/cc/ManufacturerSpecificCC.ts @@ -13,6 +13,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName, num2hex, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { CCAPI, PhysicalCCAPI } from "../lib/API"; @@ -270,8 +271,8 @@ export class ManufacturerSpecificCCReport extends ManufacturerSpecificCC { @ccValue(ManufacturerSpecificCCValues.productId) public readonly productId: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.allocUnsafe(6); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = new Bytes(6); this.payload.writeUInt16BE(this.manufacturerId, 0); this.payload.writeUInt16BE(this.productType, 2); this.payload.writeUInt16BE(this.productId, 4); @@ -388,8 +389,8 @@ export class ManufacturerSpecificCCDeviceSpecificGet public deviceIdType: DeviceIdType; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([(this.deviceIdType || 0) & 0b111]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([(this.deviceIdType || 0) & 0b111]); return super.serialize(ctx); } diff --git a/packages/cc/src/cc/MeterCC.ts b/packages/cc/src/cc/MeterCC.ts index b897946b8a7a..8e7014456bd4 100644 --- a/packages/cc/src/cc/MeterCC.ts +++ b/packages/cc/src/cc/MeterCC.ts @@ -38,6 +38,7 @@ import type { GetSupportedCCVersion, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { type AllOrNone, getEnumMemberName, @@ -196,7 +197,7 @@ function getValueLabel( return ret; } -function parseMeterValueAndInfo(data: Buffer, offset: number): { +function parseMeterValueAndInfo(data: Uint8Array, offset: number): { type: number; rateType: RateType; scale1: number; @@ -231,7 +232,7 @@ function encodeMeterValueAndInfo( rateType: RateType, scale: number, value: number, -): { data: Buffer; floatParams: FloatParameters; scale2: number | undefined } { +): { data: Bytes; floatParams: FloatParameters; scale2: number | undefined } { // We need at least 2 bytes const scale1 = scale >= 7 ? 7 : scale & 0b111; @@ -251,7 +252,7 @@ function encodeMeterValueAndInfo( ); return { - data: Buffer.concat([Buffer.from([typeByte]), valueBytes]), + data: Bytes.concat([Bytes.from([typeByte]), valueBytes]), floatParams: pick(floatParams, ["precision", "size"]), scale2, }; @@ -259,7 +260,7 @@ function encodeMeterValueAndInfo( function parseScale( scale1: number, - data: Buffer, + data: Uint8Array, scale2Offset: number, ): number { if (scale1 === 7) { @@ -932,8 +933,8 @@ export class MeterCCReport extends MeterCC { ) { const { value: prevValue } = parseFloatWithScale( // This float is split in the payload - Buffer.concat([ - Buffer.from([raw.payload[1]]), + Bytes.concat([ + Bytes.from([raw.payload[1]]), raw.payload.subarray(offset), ]), ); @@ -1045,7 +1046,7 @@ export class MeterCCReport extends MeterCC { public rateType: RateType; public deltaTime: MaybeUnknown; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { const { data: typeAndValue, floatParams, scale2 } = encodeMeterValueAndInfo( this.type, @@ -1055,10 +1056,10 @@ export class MeterCCReport extends MeterCC { ); const deltaTime = this.deltaTime ?? 0xffff; - const deltaTimeBytes = Buffer.allocUnsafe(2); + const deltaTimeBytes = new Bytes(2); deltaTimeBytes.writeUInt16BE(deltaTime, 0); - this.payload = Buffer.concat([ + this.payload = Bytes.concat([ typeAndValue, deltaTimeBytes, ]); @@ -1071,16 +1072,16 @@ export class MeterCCReport extends MeterCC { floatParams, ).subarray(1); - this.payload = Buffer.concat([ + this.payload = Bytes.concat([ this.payload, prevValueBytes, ]); } if (scale2 != undefined) { - this.payload = Buffer.concat([ + this.payload = Bytes.concat([ this.payload, - Buffer.from([scale2]), + Bytes.from([scale2]), ]); } @@ -1159,7 +1160,7 @@ export class MeterCCGet extends MeterCC { public rateType: RateType | undefined; public scale: number | undefined; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { let scale1: number; let scale2: number | undefined; let bufferLength = 0; @@ -1187,7 +1188,7 @@ export class MeterCCGet extends MeterCC { bufferLength = Math.max(bufferLength, 1); } - this.payload = Buffer.alloc(bufferLength, 0); + this.payload = Bytes.alloc(bufferLength, 0); this.payload[0] = (rateTypeFlags << 6) | (scale1 << 3); if (scale2) this.payload[1] = scale2; @@ -1257,8 +1258,8 @@ export class MeterCCSupportedReport extends MeterCC { // The bitmask is the original payload byte plus all following bytes // Since the first byte only has 7 bits, we need to reduce all following bits by 1 supportedScales = parseBitMask( - Buffer.concat([ - Buffer.from([raw.payload[1] & 0b0_1111111]), + Bytes.concat([ + Bytes.from([raw.payload[1] & 0b0_1111111]), raw.payload.subarray(3, 3 + extraBytes), ]), 0, @@ -1266,13 +1267,13 @@ export class MeterCCSupportedReport extends MeterCC { } else { // only 7 bits in the bitmask. Bit 7 is 0, so no need to mask it out supportedScales = parseBitMask( - Buffer.from([raw.payload[1]]), + Bytes.from([raw.payload[1]]), 0, ); } // This is only present in V4+ const supportedRateTypes: RateType[] = parseBitMask( - Buffer.from([(raw.payload[0] & 0b0_11_00000) >>> 5]), + Bytes.from([(raw.payload[0] & 0b0_11_00000) >>> 5]), 1, ); @@ -1333,7 +1334,7 @@ export class MeterCCSupportedReport extends MeterCC { return true; } - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { const typeByte = (this.type & 0b0_00_11111) | (this.supportedRateTypes.includes(RateType.Consumed) ? 0b0_01_00000 @@ -1353,15 +1354,15 @@ export class MeterCCSupportedReport extends MeterCC { const scalesByte1 = (supportedScales[0] >>> 1) | (supportedScales.length > 1 ? 0b1000_0000 : 0); - this.payload = Buffer.from([ + this.payload = Bytes.from([ typeByte, scalesByte1, ]); if (supportedScales.length > 1) { - this.payload = Buffer.concat([ + this.payload = Bytes.concat([ this.payload, - Buffer.from([supportedScales.length - 1]), - Buffer.from(supportedScales.subarray(1)), + Bytes.from([supportedScales.length - 1]), + Bytes.from(supportedScales.subarray(1)), ]); } @@ -1446,7 +1447,7 @@ export class MeterCCReset extends MeterCC { public rateType: RateType | undefined; public targetValue: number | undefined; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { if ( this.type != undefined && this.scale != undefined @@ -1463,9 +1464,9 @@ export class MeterCCReset extends MeterCC { this.payload = typeAndValue; if (scale2 != undefined) { - this.payload = Buffer.concat([ + this.payload = Bytes.concat([ this.payload, - Buffer.from([scale2]), + Bytes.from([scale2]), ]); } } diff --git a/packages/cc/src/cc/MultiChannelAssociationCC.ts b/packages/cc/src/cc/MultiChannelAssociationCC.ts index b6ed2b7fac76..c099fd2c7b65 100644 --- a/packages/cc/src/cc/MultiChannelAssociationCC.ts +++ b/packages/cc/src/cc/MultiChannelAssociationCC.ts @@ -21,6 +21,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { CCAPI, PhysicalCCAPI } from "../lib/API"; @@ -110,10 +111,10 @@ const MULTI_CHANNEL_ASSOCIATION_MARKER = 0x00; function serializeMultiChannelAssociationDestination( nodeIds: number[], endpoints: EndpointAddress[], -): Buffer { +): Bytes { const nodeAddressBytes = nodeIds.length; const endpointAddressBytes = endpoints.length * 2; - const payload = Buffer.allocUnsafe( + const payload = new Bytes( // node addresses nodeAddressBytes // endpoint marker @@ -145,7 +146,7 @@ function serializeMultiChannelAssociationDestination( return payload; } -function deserializeMultiChannelAssociationDestination(data: Buffer): { +function deserializeMultiChannelAssociationDestination(data: Uint8Array): { nodeIds: number[]; endpoints: EndpointAddress[]; } { @@ -165,7 +166,7 @@ function deserializeMultiChannelAssociationDestination(data: Buffer): { const isBitMask = !!(data[i + 1] & 0b1000_0000); const destination = data[i + 1] & 0b0111_1111; const endpoint = isBitMask - ? parseBitMask(Buffer.from([destination])) + ? parseBitMask(Bytes.from([destination])) : destination; endpoints.push({ nodeId, endpoint }); @@ -667,9 +668,9 @@ export class MultiChannelAssociationCCSet extends MultiChannelAssociationCC { public nodeIds: number[]; public endpoints: EndpointAddress[]; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.concat([ - Buffer.from([this.groupId]), + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.concat([ + Bytes.from([this.groupId]), serializeMultiChannelAssociationDestination( this.nodeIds, this.endpoints, @@ -738,9 +739,9 @@ export class MultiChannelAssociationCCRemove extends MultiChannelAssociationCC { public nodeIds?: number[]; public endpoints?: EndpointAddress[]; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.concat([ - Buffer.from([this.groupId || 0]), + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.concat([ + Bytes.from([this.groupId || 0]), serializeMultiChannelAssociationDestination( this.nodeIds || [], this.endpoints || [], @@ -858,13 +859,13 @@ export class MultiChannelAssociationCCReport extends MultiChannelAssociationCC { .reduce((prev, cur) => prev.concat(...cur), []); } - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { const destinations = serializeMultiChannelAssociationDestination( this.nodeIds, this.endpoints, ); - this.payload = Buffer.concat([ - Buffer.from([ + this.payload = Bytes.concat([ + Bytes.from([ this.groupId, this.maxNodes, this.reportsToFollow, @@ -923,8 +924,8 @@ export class MultiChannelAssociationCCGet extends MultiChannelAssociationCC { public groupId: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.groupId]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.groupId]); return super.serialize(ctx); } @@ -971,8 +972,8 @@ export class MultiChannelAssociationCCSupportedGroupingsReport @ccValue(MultiChannelAssociationCCValues.groupCount) public readonly groupCount: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.groupCount]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.groupCount]); return super.serialize(ctx); } diff --git a/packages/cc/src/cc/MultiChannelCC.ts b/packages/cc/src/cc/MultiChannelCC.ts index 331d7db88f80..49f190052d2c 100644 --- a/packages/cc/src/cc/MultiChannelCC.ts +++ b/packages/cc/src/cc/MultiChannelCC.ts @@ -24,6 +24,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { distinct } from "alcalzone-shared/arrays"; import { CCAPI } from "../lib/API"; @@ -861,8 +862,8 @@ export class MultiChannelCCEndPointReport extends MultiChannelCC { @ccValue(MultiChannelCCValues.aggregatedEndpointCount) public aggregatedCount: MaybeNotKnown; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ (this.countIsDynamic ? 0b10000000 : 0) | (this.identicalCapabilities ? 0b01000000 : 0), this.individualCount & 0b01111111, @@ -977,9 +978,9 @@ export class MultiChannelCCCapabilityReport extends MultiChannelCC public readonly isDynamic: boolean; public readonly wasRemoved: boolean; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.concat([ - Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.concat([ + Bytes.from([ (this.endpointIndex & 0b01111111) | (this.isDynamic ? 0b10000000 : 0), ]), @@ -1049,8 +1050,8 @@ export class MultiChannelCCCapabilityGet extends MultiChannelCC { public requestedEndpoint: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.requestedEndpoint & 0b01111111]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.requestedEndpoint & 0b01111111]); return super.serialize(ctx); } @@ -1112,14 +1113,14 @@ export class MultiChannelCCEndPointFindReport extends MultiChannelCC { public foundEndpoints: number[]; public reportsToFollow: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.concat([ - Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.concat([ + Bytes.from([ this.reportsToFollow, this.genericClass, this.specificClass, ]), - Buffer.from(this.foundEndpoints.map((e) => e & 0b01111111)), + Bytes.from(this.foundEndpoints.map((e) => e & 0b01111111)), ]); return super.serialize(ctx); } @@ -1199,8 +1200,8 @@ export class MultiChannelCCEndPointFind extends MultiChannelCC { public genericClass: number; public specificClass: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.genericClass, this.specificClass]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.genericClass, this.specificClass]); return super.serialize(ctx); } @@ -1307,8 +1308,8 @@ export class MultiChannelCCAggregatedMembersGet extends MultiChannelCC { public requestedEndpoint: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.requestedEndpoint & 0b0111_1111]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.requestedEndpoint & 0b0111_1111]); return super.serialize(ctx); } @@ -1418,7 +1419,7 @@ export class MultiChannelCCCommandEncapsulation extends MultiChannelCC { destination = raw.payload[1] & 0b0111_1111; if (isBitMask) { destination = parseBitMask( - Buffer.from([destination]), + Bytes.from([destination]), ) as any; } } @@ -1436,7 +1437,7 @@ export class MultiChannelCCCommandEncapsulation extends MultiChannelCC { /** The destination end point (0-127) or an array of destination end points (1-7) */ public destination: MultiChannelCCDestination; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { if ( ctx.getDeviceConfig?.(this.nodeId as number)?.compat ?.treatDestinationEndpointAsSource @@ -1452,8 +1453,8 @@ export class MultiChannelCCCommandEncapsulation extends MultiChannelCC { ? this.destination & 0b0111_1111 // The destination is a bit mask : encodeBitMask(this.destination, 7)[0] | 0b1000_0000; - this.payload = Buffer.concat([ - Buffer.from([this.endpointIndex & 0b0111_1111, destination]), + this.payload = Bytes.concat([ + Bytes.from([this.endpointIndex & 0b0111_1111, destination]), this.encapsulated.serialize(ctx), ]); return super.serialize(ctx); @@ -1564,8 +1565,8 @@ export class MultiChannelCCV1Get extends MultiChannelCC { public requestedCC: CommandClasses; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.requestedCC]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.requestedCC]); return super.serialize(ctx); } @@ -1642,9 +1643,9 @@ export class MultiChannelCCV1CommandEncapsulation extends MultiChannelCC { public encapsulated!: CommandClass; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.concat([ - Buffer.from([this.endpointIndex]), + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.concat([ + Bytes.from([this.endpointIndex]), this.encapsulated.serialize(ctx), ]); return super.serialize(ctx); diff --git a/packages/cc/src/cc/MultiCommandCC.ts b/packages/cc/src/cc/MultiCommandCC.ts index adecb65a6f01..ca20183dbb69 100644 --- a/packages/cc/src/cc/MultiCommandCC.ts +++ b/packages/cc/src/cc/MultiCommandCC.ts @@ -11,6 +11,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { CCAPI } from "../lib/API"; import { type CCRaw, CommandClass } from "../lib/CommandClass"; @@ -145,15 +146,15 @@ export class MultiCommandCCCommandEncapsulation extends MultiCommandCC { public encapsulated: CommandClass[]; - public serialize(ctx: CCEncodingContext): Buffer { - const buffers: Buffer[] = []; - buffers.push(Buffer.from([this.encapsulated.length])); + public serialize(ctx: CCEncodingContext): Bytes { + const buffers: Bytes[] = []; + buffers.push(Bytes.from([this.encapsulated.length])); for (const cmd of this.encapsulated) { const cmdBuffer = cmd.serialize(ctx); - buffers.push(Buffer.from([cmdBuffer.length])); + buffers.push(Bytes.from([cmdBuffer.length])); buffers.push(cmdBuffer); } - this.payload = Buffer.concat(buffers); + this.payload = Bytes.concat(buffers); return super.serialize(ctx); } diff --git a/packages/cc/src/cc/MultilevelSensorCC.ts b/packages/cc/src/cc/MultilevelSensorCC.ts index c83aa03dbff5..97b4bf404055 100644 --- a/packages/cc/src/cc/MultilevelSensorCC.ts +++ b/packages/cc/src/cc/MultilevelSensorCC.ts @@ -40,6 +40,7 @@ import type { GetValueDB, LogNode, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { type AllOrNone, num2hex } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { @@ -752,9 +753,9 @@ export class MultilevelSensorCCReport extends MultilevelSensorCC { public scale: number; public value: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.concat([ - Buffer.from([this.type]), + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.concat([ + Bytes.from([this.type]), encodeFloatWithScale(this.value, this.scale), ]); return super.serialize(ctx); @@ -826,12 +827,12 @@ export class MultilevelSensorCCGet extends MultilevelSensorCC { public sensorType: number | undefined; public scale: number | undefined; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { if ( this.sensorType != undefined && this.scale != undefined ) { - this.payload = Buffer.from([ + this.payload = Bytes.from([ this.sensorType, (this.scale & 0b11) << 3, ]); @@ -896,7 +897,7 @@ export class MultilevelSensorCCSupportedSensorReport @ccValue(MultilevelSensorCCValues.supportedSensorTypes) public supportedSensorTypes: readonly number[]; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { this.payload = encodeBitMask(this.supportedSensorTypes); return super.serialize(ctx); } @@ -941,7 +942,7 @@ export class MultilevelSensorCCSupportedScaleReport extends MultilevelSensorCC { validatePayload(raw.payload.length >= 2); const sensorType = raw.payload[0]; const supportedScales = parseBitMask( - Buffer.from([raw.payload[1] & 0b1111]), + Bytes.from([raw.payload[1] & 0b1111]), 0, ); @@ -961,9 +962,9 @@ export class MultilevelSensorCCSupportedScaleReport extends MultilevelSensorCC { ) public readonly supportedScales: readonly number[]; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.concat([ - Buffer.from([this.sensorType]), + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.concat([ + Bytes.from([this.sensorType]), encodeBitMask(this.supportedScales, 4, 0), ]); return super.serialize(ctx); @@ -1018,8 +1019,8 @@ export class MultilevelSensorCCGetSupportedScale extends MultilevelSensorCC { public sensorType: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.sensorType]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.sensorType]); return super.serialize(ctx); } diff --git a/packages/cc/src/cc/MultilevelSwitchCC.ts b/packages/cc/src/cc/MultilevelSwitchCC.ts index 2c60c409b752..27ad81126dbb 100644 --- a/packages/cc/src/cc/MultilevelSwitchCC.ts +++ b/packages/cc/src/cc/MultilevelSwitchCC.ts @@ -19,6 +19,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { @@ -657,8 +658,8 @@ export class MultilevelSwitchCCSet extends MultilevelSwitchCC { public targetValue: number; public duration: Duration | undefined; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ this.targetValue, (this.duration ?? Duration.default()).serializeSet(), ]); @@ -744,8 +745,8 @@ export class MultilevelSwitchCCReport extends MultilevelSwitchCC { @ccValue(MultilevelSwitchCCValues.currentValue) public currentValue: MaybeUnknown | undefined; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ this.currentValue ?? 0xfe, this.targetValue ?? 0xfe, (this.duration ?? Duration.default()).serializeReport(), @@ -834,10 +835,10 @@ export class MultilevelSwitchCCStartLevelChange extends MultilevelSwitchCC { public ignoreStartLevel: boolean; public direction: keyof typeof LevelChangeDirection; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { const controlByte = (LevelChangeDirection[this.direction] << 6) | (this.ignoreStartLevel ? 0b0010_0000 : 0); - this.payload = Buffer.from([ + this.payload = Bytes.from([ controlByte, this.startLevel, (this.duration ?? Duration.default()).serializeSet(), @@ -915,8 +916,8 @@ export class MultilevelSwitchCCSupportedReport extends MultilevelSwitchCC { return true; } - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.switchType & 0b11111]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.switchType & 0b11111]); return super.serialize(ctx); } diff --git a/packages/cc/src/cc/NodeNamingCC.ts b/packages/cc/src/cc/NodeNamingCC.ts index 0ee09bc68ee4..f6b67269a26e 100644 --- a/packages/cc/src/cc/NodeNamingCC.ts +++ b/packages/cc/src/cc/NodeNamingCC.ts @@ -15,6 +15,11 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host/safe"; +import { + Bytes, + stringToUint8ArrayUTF16BE, + uint8ArrayToStringUTF16BE, +} from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { CCAPI, @@ -298,23 +303,22 @@ export class NodeNamingAndLocationCCNameSet extends NodeNamingAndLocationCC { public name: string; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { const encoding = isASCII(this.name) ? "ascii" : "utf16le"; - this.payload = Buffer.allocUnsafe( + this.payload = new Bytes( 1 + this.name.length * (encoding === "ascii" ? 1 : 2), ); this.payload[0] = encoding === "ascii" ? 0x0 : 0x2; - let nameAsBuffer = Buffer.from(this.name, encoding); + let nameBuffer: Uint8Array; if (encoding === "utf16le") { - // Z-Wave expects UTF16 BE - nameAsBuffer = nameAsBuffer.swap16(); + nameBuffer = stringToUint8ArrayUTF16BE(this.name); + } else { + nameBuffer = Bytes.from(this.name, "ascii"); } - // Copy at max 16 bytes - nameAsBuffer.copy( - this.payload, - 0, + // Copy at most 16 bytes + this.payload.set( + nameBuffer.subarray(0, Math.min(16, nameBuffer.length)), 0, - Math.min(16, nameAsBuffer.length), ); return super.serialize(ctx); } @@ -347,16 +351,18 @@ export class NodeNamingAndLocationCCNameReport extends NodeNamingAndLocationCC { ): NodeNamingAndLocationCCNameReport { validatePayload(raw.payload.length >= 1); const encoding = raw.payload[0] === 2 ? "utf16le" : "ascii"; - let nameBuffer = raw.payload.subarray(1); + const nameBuffer = raw.payload.subarray(1); + let name: string; if (encoding === "utf16le") { validatePayload(nameBuffer.length % 2 === 0); - // Z-Wave expects UTF16 BE - nameBuffer = nameBuffer.swap16(); + name = uint8ArrayToStringUTF16BE(nameBuffer); + } else { + name = nameBuffer.toString("ascii"); } return new NodeNamingAndLocationCCNameReport({ nodeId: ctx.sourceNodeId, - name: nameBuffer.toString(encoding), + name, }); } @@ -409,23 +415,22 @@ export class NodeNamingAndLocationCCLocationSet public location: string; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { const encoding = isASCII(this.location) ? "ascii" : "utf16le"; - this.payload = Buffer.allocUnsafe( + this.payload = new Bytes( 1 + this.location.length * (encoding === "ascii" ? 1 : 2), ); this.payload[0] = encoding === "ascii" ? 0x0 : 0x2; - let locationAsBuffer = Buffer.from(this.location, encoding); + let locationBuffer: Uint8Array; if (encoding === "utf16le") { - // Z-Wave expects UTF16 BE - locationAsBuffer = locationAsBuffer.swap16(); + locationBuffer = stringToUint8ArrayUTF16BE(this.location); + } else { + locationBuffer = Bytes.from(this.location, "ascii"); } - // Copy at max 16 bytes - locationAsBuffer.copy( - this.payload, + // Copy at most 16 bytes + this.payload.set( + locationBuffer.subarray(0, Math.min(16, locationBuffer.length)), 0, - 0, - Math.min(16, locationAsBuffer.length), ); return super.serialize(ctx); } @@ -460,16 +465,18 @@ export class NodeNamingAndLocationCCLocationReport ): NodeNamingAndLocationCCLocationReport { validatePayload(raw.payload.length >= 1); const encoding = raw.payload[0] === 2 ? "utf16le" : "ascii"; - let locationBuffer = raw.payload.subarray(1); + const locationBuffer = raw.payload.subarray(1); + let location: string; if (encoding === "utf16le") { validatePayload(locationBuffer.length % 2 === 0); - // Z-Wave expects UTF16 BE - locationBuffer = locationBuffer.swap16(); + location = uint8ArrayToStringUTF16BE(locationBuffer); + } else { + location = locationBuffer.toString("ascii"); } return new NodeNamingAndLocationCCLocationReport({ nodeId: ctx.sourceNodeId, - location: locationBuffer.toString(encoding), + location, }); } diff --git a/packages/cc/src/cc/NotificationCC.ts b/packages/cc/src/cc/NotificationCC.ts index 7c761041fcaf..5143a9f9134f 100644 --- a/packages/cc/src/cc/NotificationCC.ts +++ b/packages/cc/src/cc/NotificationCC.ts @@ -45,6 +45,7 @@ import type { GetValueDB, LogNode, } from "@zwave-js/host/safe"; +import { Bytes, isUint8Array } from "@zwave-js/shared/safe"; import { buffer2hex, num2hex, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { isArray } from "alcalzone-shared/typeguards"; @@ -953,8 +954,8 @@ export class NotificationCCSet extends NotificationCC { public notificationType: number; public notificationStatus: boolean; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ this.notificationType, this.notificationStatus ? 0xff : 0x00, ]); @@ -979,7 +980,7 @@ export type NotificationCCReportOptions = { notificationType?: number; notificationEvent?: number; notificationStatus?: number; - eventParameters?: Buffer; + eventParameters?: Uint8Array; sequenceNumber?: number; }; @@ -1033,12 +1034,10 @@ export class NotificationCCReport extends NotificationCC { const containsSeqNum = !!(raw.payload[6] & 0b1000_0000); const numEventParams = raw.payload[6] & 0b11111; - let eventParameters: Buffer | undefined; + let eventParameters: Uint8Array | undefined; if (numEventParams > 0) { validatePayload(raw.payload.length >= 7 + numEventParams); - eventParameters = Buffer.from( - raw.payload.subarray(7, 7 + numEventParams), - ); + eventParameters = raw.payload.subarray(7, 7 + numEventParams); } let sequenceNumber: number | undefined; if (containsSeqNum) { @@ -1167,7 +1166,7 @@ export class NotificationCCReport extends NotificationCC { public notificationEvent: number | undefined; public eventParameters: - | Buffer + | Uint8Array | Duration | Record | number @@ -1239,7 +1238,7 @@ export class NotificationCCReport extends NotificationCC { if (!found) { message["state parameters"] = num2hex(this.eventParameters); } - } else if (Buffer.isBuffer(this.eventParameters)) { + } else if (isUint8Array(this.eventParameters)) { message["event parameters"] = buffer2hex(this.eventParameters); } else if (this.eventParameters instanceof Duration) { message["event parameters"] = this.eventParameters.toString(); @@ -1288,7 +1287,7 @@ export class NotificationCCReport extends NotificationCC { // Parse the event parameters if possible if (valueConfig.parameter?.type === "duration") { // This only makes sense if the event parameters are a buffer - if (!Buffer.isBuffer(this.eventParameters)) { + if (!isUint8Array(this.eventParameters)) { return; } // The parameters contain a Duration @@ -1297,7 +1296,7 @@ export class NotificationCCReport extends NotificationCC { ); } else if (valueConfig.parameter?.type === "commandclass") { // This only makes sense if the event parameters are a buffer - if (!Buffer.isBuffer(this.eventParameters)) { + if (!isUint8Array(this.eventParameters)) { return; } // The parameters **should** contain a CC, however there might be some exceptions @@ -1352,7 +1351,7 @@ export class NotificationCCReport extends NotificationCC { isZWaveError(e) && e.code === ZWaveErrorCodes.PacketFormat_InvalidPayload - && Buffer.isBuffer(this.eventParameters) + && isUint8Array(this.eventParameters) ) { const { ccId, ccCommand } = CCRaw.parse( this.eventParameters, @@ -1382,12 +1381,14 @@ export class NotificationCCReport extends NotificationCC { } } else if (valueConfig.parameter?.type === "value") { // This only makes sense if the event parameters are a buffer - if (!Buffer.isBuffer(this.eventParameters)) { + if (!isUint8Array(this.eventParameters)) { return; } // The parameters contain a named value this.eventParameters = { - [valueConfig.parameter.propertyName]: this.eventParameters + [valueConfig.parameter.propertyName]: Bytes.view( + this.eventParameters, + ) .readUIntBE( 0, this.eventParameters.length, @@ -1395,7 +1396,7 @@ export class NotificationCCReport extends NotificationCC { }; } else if (valueConfig.parameter?.type === "enum") { // The parameters may contain an enum value - this.eventParameters = Buffer.isBuffer(this.eventParameters) + this.eventParameters = isUint8Array(this.eventParameters) && this.eventParameters.length === 1 ? this.eventParameters[0] : undefined; @@ -1412,7 +1413,7 @@ export class NotificationCCReport extends NotificationCC { } } - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { if (this.notificationType != undefined) { if ( this.notificationEvent == undefined @@ -1423,7 +1424,7 @@ export class NotificationCCReport extends NotificationCC { ); } else if ( this.eventParameters != undefined - && !Buffer.isBuffer(this.eventParameters) + && !isUint8Array(this.eventParameters) ) { throw new ZWaveError( `When sending Notification CC reports, the event parameters can only be buffers!`, @@ -1434,7 +1435,7 @@ export class NotificationCCReport extends NotificationCC { const controlByte = (this.sequenceNumber != undefined ? 0b1000_0000 : 0) | ((this.eventParameters?.length ?? 0) & 0b11111); - this.payload = Buffer.from([ + this.payload = Bytes.from([ 0, 0, 0, @@ -1444,19 +1445,19 @@ export class NotificationCCReport extends NotificationCC { controlByte, ]); if (this.eventParameters) { - this.payload = Buffer.concat([ + this.payload = Bytes.concat([ this.payload, this.eventParameters, ]); } if (this.sequenceNumber != undefined) { - this.payload = Buffer.concat([ + this.payload = Bytes.concat([ this.payload, - Buffer.from([this.sequenceNumber]), + Bytes.from([this.sequenceNumber]), ]); } } else { - this.payload = Buffer.from([ + this.payload = Bytes.from([ this.alarmType ?? 0x00, this.alarmLevel ?? 0x00, ]); @@ -1520,11 +1521,11 @@ export class NotificationCCGet extends NotificationCC { public notificationType: number | undefined; public notificationEvent: number | undefined; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { const notificationEvent = this.notificationEvent === 0xff ? 0x00 : this.notificationEvent; - this.payload = Buffer.from([ + this.payload = Bytes.from([ this.alarmType ?? 0x00, this.notificationType ?? 0xff, notificationEvent ?? 0x00, @@ -1606,14 +1607,14 @@ export class NotificationCCSupportedReport extends NotificationCC { @ccValue(NotificationCCValues.supportedNotificationTypes) public supportedNotificationTypes: number[]; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { const bitMask = encodeBitMask( this.supportedNotificationTypes, Math.max(...this.supportedNotificationTypes), 0, ); - this.payload = Buffer.concat([ - Buffer.from([ + this.payload = Bytes.concat([ + Bytes.from([ (this.supportsV1Alarm ? 0b1000_0000 : 0) | bitMask.length, ]), bitMask, @@ -1747,8 +1748,8 @@ export class NotificationCCEventSupportedReport extends NotificationCC { public notificationType: number; public supportedEvents: number[]; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.notificationType, 0]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.notificationType, 0]); if (this.supportedEvents.length > 0) { const bitMask = encodeBitMask( this.supportedEvents, @@ -1756,7 +1757,7 @@ export class NotificationCCEventSupportedReport extends NotificationCC { 0, ); this.payload[1] = bitMask.length; - this.payload = Buffer.concat([this.payload, bitMask]); + this.payload = Bytes.concat([this.payload, bitMask]); } return super.serialize(ctx); @@ -1813,8 +1814,8 @@ export class NotificationCCEventSupportedGet extends NotificationCC { public notificationType: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.notificationType]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.notificationType]); return super.serialize(ctx); } diff --git a/packages/cc/src/cc/PowerlevelCC.ts b/packages/cc/src/cc/PowerlevelCC.ts index ce06831e3180..6d8dc7756eea 100644 --- a/packages/cc/src/cc/PowerlevelCC.ts +++ b/packages/cc/src/cc/PowerlevelCC.ts @@ -15,6 +15,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { PhysicalCCAPI } from "../lib/API"; @@ -259,8 +260,8 @@ export class PowerlevelCCSet extends PowerlevelCC { public powerlevel: Powerlevel; public timeout?: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.powerlevel, this.timeout ?? 0x00]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.powerlevel, this.timeout ?? 0x00]); return super.serialize(ctx); } @@ -321,8 +322,8 @@ export class PowerlevelCCReport extends PowerlevelCC { public readonly powerlevel: Powerlevel; public readonly timeout?: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.powerlevel, this.timeout ?? 0x00]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.powerlevel, this.timeout ?? 0x00]); return super.serialize(ctx); } @@ -384,8 +385,8 @@ export class PowerlevelCCTestNodeSet extends PowerlevelCC { public powerlevel: Powerlevel; public testFrameCount: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.testNodeId, this.powerlevel, 0, 0]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.testNodeId, this.powerlevel, 0, 0]); this.payload.writeUInt16BE(this.testFrameCount, 2); return super.serialize(ctx); } @@ -442,8 +443,8 @@ export class PowerlevelCCTestNodeReport extends PowerlevelCC { public status: PowerlevelTestStatus; public acknowledgedFrames: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ this.testNodeId, this.status, // Placeholder for acknowledged frames diff --git a/packages/cc/src/cc/ProtectionCC.ts b/packages/cc/src/cc/ProtectionCC.ts index d935417e5425..30b9905c0ff1 100644 --- a/packages/cc/src/cc/ProtectionCC.ts +++ b/packages/cc/src/cc/ProtectionCC.ts @@ -20,6 +20,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { padStart } from "alcalzone-shared/strings"; @@ -530,8 +531,8 @@ export class ProtectionCCSet extends ProtectionCC { public local: LocalProtectionState; public rf?: RFProtectionState; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ this.local & 0b1111, (this.rf ?? RFProtectionState.Unprotected) & 0b1111, ]); @@ -808,8 +809,8 @@ export class ProtectionCCExclusiveControlSet extends ProtectionCC { public exclusiveControlNodeId: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.exclusiveControlNodeId]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.exclusiveControlNodeId]); return super.serialize(ctx); } @@ -900,8 +901,8 @@ export class ProtectionCCTimeoutSet extends ProtectionCC { public timeout: Timeout; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.timeout.serialize()]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.timeout.serialize()]); return super.serialize(ctx); } diff --git a/packages/cc/src/cc/SceneActivationCC.ts b/packages/cc/src/cc/SceneActivationCC.ts index 1800d3df1bf7..5500fb3dc225 100644 --- a/packages/cc/src/cc/SceneActivationCC.ts +++ b/packages/cc/src/cc/SceneActivationCC.ts @@ -16,6 +16,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { CCAPI, @@ -169,8 +170,8 @@ export class SceneActivationCCSet extends SceneActivationCC { @ccValue(SceneActivationCCValues.dimmingDuration) public dimmingDuration: Duration | undefined; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ this.sceneId, this.dimmingDuration?.serializeSet() ?? 0xff, ]); diff --git a/packages/cc/src/cc/SceneActuatorConfigurationCC.ts b/packages/cc/src/cc/SceneActuatorConfigurationCC.ts index 80e2dbff7475..eb941c974003 100644 --- a/packages/cc/src/cc/SceneActuatorConfigurationCC.ts +++ b/packages/cc/src/cc/SceneActuatorConfigurationCC.ts @@ -17,6 +17,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { @@ -390,8 +391,8 @@ export class SceneActuatorConfigurationCCSet public dimmingDuration: Duration; public level?: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ this.sceneId, this.dimmingDuration.serializeSet(), this.level != undefined ? 0b1000_0000 : 0, @@ -556,8 +557,8 @@ export class SceneActuatorConfigurationCCGet public sceneId: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.sceneId]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.sceneId]); return super.serialize(ctx); } diff --git a/packages/cc/src/cc/SceneControllerConfigurationCC.ts b/packages/cc/src/cc/SceneControllerConfigurationCC.ts index 7e500881e206..f2a35633e53b 100644 --- a/packages/cc/src/cc/SceneControllerConfigurationCC.ts +++ b/packages/cc/src/cc/SceneControllerConfigurationCC.ts @@ -19,6 +19,7 @@ import type { GetDeviceConfig, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { @@ -523,8 +524,8 @@ export class SceneControllerConfigurationCCSet public sceneId: number; public dimmingDuration: Duration; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ this.groupId, this.sceneId, this.dimmingDuration.serializeSet(), @@ -666,8 +667,8 @@ export class SceneControllerConfigurationCCGet public groupId: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.groupId]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.groupId]); return super.serialize(ctx); } diff --git a/packages/cc/src/cc/ScheduleEntryLockCC.ts b/packages/cc/src/cc/ScheduleEntryLockCC.ts index 5ac6c9150d1d..d88bd4b8b61e 100644 --- a/packages/cc/src/cc/ScheduleEntryLockCC.ts +++ b/packages/cc/src/cc/ScheduleEntryLockCC.ts @@ -21,6 +21,7 @@ import type { } from "@zwave-js/host"; import { type AllOrNone, + Bytes, formatDate, formatTime, getEnumMemberName, @@ -956,8 +957,8 @@ export class ScheduleEntryLockCCEnableSet extends ScheduleEntryLockCC { public userId: number; public enabled: boolean; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.userId, this.enabled ? 0x01 : 0x00]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.userId, this.enabled ? 0x01 : 0x00]); return super.serialize(ctx); } @@ -1002,8 +1003,8 @@ export class ScheduleEntryLockCCEnableAllSet extends ScheduleEntryLockCC { public enabled: boolean; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.enabled ? 0x01 : 0x00]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.enabled ? 0x01 : 0x00]); return super.serialize(ctx); } @@ -1062,8 +1063,8 @@ export class ScheduleEntryLockCCSupportedReport extends ScheduleEntryLockCC { @ccValue(ScheduleEntryLockCCValues.numDailyRepeatingSlots) public numDailyRepeatingSlots: number | undefined; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ this.numWeekDaySlots, this.numYearDaySlots, this.numDailyRepeatingSlots ?? 0, @@ -1176,8 +1177,8 @@ export class ScheduleEntryLockCCWeekDayScheduleSet extends ScheduleEntryLockCC { public stopHour?: number; public stopMinute?: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ this.action, this.userId, this.slotId, @@ -1340,8 +1341,8 @@ export class ScheduleEntryLockCCWeekDayScheduleReport return true; } - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ this.userId, this.slotId, this.weekday ?? 0xff, @@ -1419,8 +1420,8 @@ export class ScheduleEntryLockCCWeekDayScheduleGet extends ScheduleEntryLockCC { public userId: number; public slotId: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.userId, this.slotId]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.userId, this.slotId]); return super.serialize(ctx); } @@ -1540,8 +1541,8 @@ export class ScheduleEntryLockCCYearDayScheduleSet extends ScheduleEntryLockCC { public stopHour?: number; public stopMinute?: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ this.action, this.userId, this.slotId, @@ -1756,8 +1757,8 @@ export class ScheduleEntryLockCCYearDayScheduleReport return true; } - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ this.userId, this.slotId, this.startYear ?? 0xff, @@ -1843,8 +1844,8 @@ export class ScheduleEntryLockCCYearDayScheduleGet extends ScheduleEntryLockCC { public userId: number; public slotId: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.userId, this.slotId]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.userId, this.slotId]); return super.serialize(ctx); } @@ -1892,7 +1893,7 @@ export class ScheduleEntryLockCCTimeOffsetSet extends ScheduleEntryLockCC { public standardOffset: number; public dstOffset: number; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { this.payload = encodeTimezone({ standardOffset: this.standardOffset, dstOffset: this.dstOffset, @@ -1943,7 +1944,7 @@ export class ScheduleEntryLockCCTimeOffsetReport extends ScheduleEntryLockCC { public standardOffset: number; public dstOffset: number; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { this.payload = encodeTimezone({ standardOffset: this.standardOffset, dstOffset: this.dstOffset, @@ -2058,17 +2059,17 @@ export class ScheduleEntryLockCCDailyRepeatingScheduleSet public durationHour?: number; public durationMinute?: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.action, this.userId, this.slotId]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.action, this.userId, this.slotId]); if (this.action === ScheduleEntryLockSetAction.Set) { - this.payload = Buffer.concat([ + this.payload = Bytes.concat([ this.payload, encodeBitMask( this.weekdays!, ScheduleEntryLockWeekday.Saturday, ScheduleEntryLockWeekday.Sunday, ), - Buffer.from([ + Bytes.from([ this.startHour!, this.startMinute!, this.durationHour!, @@ -2077,7 +2078,7 @@ export class ScheduleEntryLockCCDailyRepeatingScheduleSet ]); } else { // Not sure if this is correct - this.payload = Buffer.concat([this.payload, Buffer.alloc(5, 0xff)]); + this.payload = Bytes.concat([this.payload, Bytes.alloc(5, 0xff)]); } return super.serialize(ctx); @@ -2210,17 +2211,17 @@ export class ScheduleEntryLockCCDailyRepeatingScheduleReport return true; } - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.userId, this.slotId]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.userId, this.slotId]); if (this.weekdays) { - this.payload = Buffer.concat([ + this.payload = Bytes.concat([ this.payload, encodeBitMask( this.weekdays, ScheduleEntryLockWeekday.Saturday, ScheduleEntryLockWeekday.Sunday, ), - Buffer.from([ + Bytes.from([ this.startHour!, this.startMinute!, this.durationHour!, @@ -2229,7 +2230,7 @@ export class ScheduleEntryLockCCDailyRepeatingScheduleReport ]); } else { // Not sure if this is correct, but at least we won't parse it incorrectly ourselves when setting everything to 0 - this.payload = Buffer.concat([this.payload, Buffer.alloc(5, 0)]); + this.payload = Bytes.concat([this.payload, Bytes.alloc(5, 0)]); } return super.serialize(ctx); @@ -2305,8 +2306,8 @@ export class ScheduleEntryLockCCDailyRepeatingScheduleGet public userId: number; public slotId: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.userId, this.slotId]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.userId, this.slotId]); return super.serialize(ctx); } diff --git a/packages/cc/src/cc/Security2CC.ts b/packages/cc/src/cc/Security2CC.ts index 996fad24b8db..292a8a6a4cff 100644 --- a/packages/cc/src/cc/Security2CC.ts +++ b/packages/cc/src/cc/Security2CC.ts @@ -42,6 +42,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { buffer2hex, getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { isArray } from "alcalzone-shared/typeguards"; import { setTimeout as wait } from "node:timers/promises"; @@ -75,7 +76,7 @@ import { MultiChannelCC } from "./MultiChannelCC"; import { SecurityCC } from "./SecurityCC"; import { TransportServiceCC } from "./TransportServiceCC"; -function securityClassToBitMask(key: SecurityClass): Buffer { +function securityClassToBitMask(key: SecurityClass): Bytes { return encodeBitMask( [key], SecurityClass.S0_Legacy, @@ -83,7 +84,10 @@ function securityClassToBitMask(key: SecurityClass): Buffer { ); } -function bitMaskToSecurityClass(buffer: Buffer, offset: number): SecurityClass { +function bitMaskToSecurityClass( + buffer: Uint8Array, + offset: number, +): SecurityClass { const keys = parseBitMask( buffer.subarray(offset, offset + 1), SecurityClass.S2_Unauthenticated, @@ -97,13 +101,13 @@ function getAuthenticationData( destination: number, homeId: number, commandLength: number, - unencryptedPayload: Buffer, -): Buffer { + unencryptedPayload: Uint8Array, +): Uint8Array { const nodeIdSize = isLongRangeNodeId(sendingNodeId) || isLongRangeNodeId(destination) ? 2 : 1; - const ret = Buffer.allocUnsafe( + const ret = new Bytes( 2 * nodeIdSize + 6 + unencryptedPayload.length, ); let offset = 0; @@ -116,7 +120,7 @@ function getAuthenticationData( ret.writeUInt16BE(commandLength, offset); offset += 2; // This includes the sequence number and all unencrypted extensions - unencryptedPayload.copy(ret, offset, 0); + ret.set(unencryptedPayload, offset); return ret; } function getSecurityManager( @@ -160,10 +164,10 @@ const MAX_DECRYPT_ATTEMPTS_SC_FOLLOWUP = 1; // @publicAPI export interface DecryptionResult { - plaintext: Buffer; + plaintext: Uint8Array; authOK: boolean; - key?: Buffer; - iv?: Buffer; + key?: Uint8Array; + iv?: Uint8Array; securityClass: SecurityClass | undefined; } @@ -218,15 +222,15 @@ function decryptSinglecast( sendingNodeId: number, curSequenceNumber: number, prevSequenceNumber: number, - ciphertext: Buffer, - authData: Buffer, - authTag: Buffer, + ciphertext: Uint8Array, + authData: Uint8Array, + authTag: Uint8Array, spanState: SPANTableEntry & { type: SPANState.SPAN | SPANState.LocalEI; }, extensions: Security2Extension[], ): DecryptionResult { - const decryptWithNonce = (nonce: Buffer) => { + const decryptWithNonce = (nonce: Uint8Array) => { const { keyCCM: key } = securityManager.getKeysForNode( sendingNodeId, ); @@ -378,7 +382,7 @@ function decryptSinglecast( // Nothing worked, fail the decryption return { - plaintext: Buffer.from([]), + plaintext: new Uint8Array(), authOK: false, securityClass: undefined, }; @@ -388,9 +392,9 @@ function decryptMulticast( sendingNodeId: number, securityManager: SecurityManager2, groupId: number, - ciphertext: Buffer, - authData: Buffer, - authTag: Buffer, + ciphertext: Uint8Array, + authData: Uint8Array, + authTag: Uint8Array, ): DecryptionResult { const iv = securityManager.nextPeerMPAN( sendingNodeId, @@ -451,7 +455,7 @@ function getMulticastGroupId( } /** Returns the Sender's Entropy Input if this command contains an SPAN extension */ -function getSenderEI(extensions: Security2Extension[]): Buffer | undefined { +function getSenderEI(extensions: Security2Extension[]): Uint8Array | undefined { const spanExtension = extensions.find( (e) => e instanceof SPANExtension, ); @@ -578,7 +582,7 @@ export class Security2CCAPI extends CCAPI { /** Sends the given MPAN to the node */ public async sendMPAN( groupId: number, - innerMPANState: Buffer, + innerMPANState: Uint8Array, ): Promise { this.assertSupportsCommand( Security2Command, @@ -795,7 +799,7 @@ export class Security2CCAPI extends CCAPI { } public async sendPublicKey( - publicKey: Buffer, + publicKey: Uint8Array, includingNode: boolean = true, ): Promise { this.assertSupportsCommand( @@ -830,7 +834,7 @@ export class Security2CCAPI extends CCAPI { public async sendNetworkKey( securityClass: SecurityClass, - networkKey: Buffer, + networkKey: Uint8Array, ): Promise { this.assertSupportsCommand( Security2Command, @@ -1375,7 +1379,7 @@ export class Security2CCMessageEncapsulation extends Security2CC { const extensions: Security2Extension[] = []; let mustDiscardCommand = false; - const parseExtensions = (buffer: Buffer, wasEncrypted: boolean) => { + const parseExtensions = (buffer: Uint8Array, wasEncrypted: boolean) => { while (true) { if (buffer.length < offset + 2) { // An S2 extension was expected, but the buffer is too short @@ -1574,10 +1578,10 @@ export class Security2CCMessageEncapsulation extends Security2CC { ); } - let plaintext: Buffer | undefined; + let plaintext: Uint8Array | undefined; let authOK = false; - let key: Buffer | undefined; - let iv: Buffer | undefined; + let key: Uint8Array | undefined; + let iv: Uint8Array | undefined; let decryptionSecurityClass: SecurityClass | undefined; // If the Receiver is unable to authenticate the singlecast message with the current SPAN, @@ -1692,12 +1696,12 @@ export class Security2CCMessageEncapsulation extends Security2CC { public readonly securityClass?: SecurityClass; // Only used for testing/debugging purposes - private key?: Buffer; - private iv?: Buffer; - private authData?: Buffer; - private authTag?: Buffer; - private ciphertext?: Buffer; - private plaintext?: Buffer; + private key?: Uint8Array; + private iv?: Uint8Array; + private authData?: Uint8Array; + private authTag?: Uint8Array; + private ciphertext?: Uint8Array; + private plaintext?: Uint8Array; public readonly verifyDelivery: boolean = true; @@ -1735,7 +1739,7 @@ export class Security2CCMessageEncapsulation extends Security2CC { } /** Returns the Sender's Entropy Input if this command contains an SPAN extension */ - public getSenderEI(): Buffer | undefined { + public getSenderEI(): Uint8Array | undefined { return getSenderEI(this.extensions); } @@ -1813,7 +1817,7 @@ export class Security2CCMessageEncapsulation extends Security2CC { } } - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { const securityManager = assertSecurityTX(ctx, this.nodeId); this.ensureSequenceNumber(securityManager); @@ -1827,8 +1831,8 @@ export class Security2CCMessageEncapsulation extends Security2CC { e.isEncrypted() ); - const unencryptedPayload = Buffer.concat([ - Buffer.from([ + const unencryptedPayload = Bytes.concat([ + Bytes.from([ this.sequenceNumber, (encryptedExtensions.length > 0 ? 0b10 : 0) | (unencryptedExtensions.length > 0 ? 1 : 0), @@ -1838,8 +1842,8 @@ export class Security2CCMessageEncapsulation extends Security2CC { ), ]); const serializedCC = this.encapsulated?.serialize(ctx) - ?? Buffer.from([]); - const plaintextPayload = Buffer.concat([ + ?? new Bytes(); + const plaintextPayload = Bytes.concat([ ...encryptedExtensions.map((e, index) => e.serialize(index < encryptedExtensions.length - 1) ), @@ -1860,8 +1864,8 @@ export class Security2CCMessageEncapsulation extends Security2CC { unencryptedPayload, ); - let key: Buffer; - let iv: Buffer; + let key: Uint8Array; + let iv: Uint8Array; if (this.isSinglecast()) { // Singlecast: @@ -1900,7 +1904,7 @@ export class Security2CCMessageEncapsulation extends Security2CC { this.authTag = authTag; this.ciphertext = ciphertextPayload; - this.payload = Buffer.concat([ + this.payload = Bytes.concat([ unencryptedPayload, ciphertextPayload, authTag, @@ -1982,7 +1986,7 @@ export type Security2CCNonceReportOptions = | { MOS: boolean; SOS: true; - receiverEI: Buffer; + receiverEI: Uint8Array; } | { MOS: true; @@ -2069,18 +2073,18 @@ export class Security2CCNonceReport extends Security2CC { public readonly SOS: boolean; public readonly MOS: boolean; - public readonly receiverEI?: Buffer; + public readonly receiverEI?: Uint8Array; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { const securityManager = assertSecurityTX(ctx, this.nodeId); this.ensureSequenceNumber(securityManager); - this.payload = Buffer.from([ + this.payload = Bytes.from([ this.sequenceNumber, (this.MOS ? 0b10 : 0) + (this.SOS ? 0b1 : 0), ]); if (this.SOS) { - this.payload = Buffer.concat([this.payload, this.receiverEI!]); + this.payload = Bytes.concat([this.payload, this.receiverEI!]); } return super.serialize(ctx); } @@ -2151,11 +2155,11 @@ export class Security2CCNonceGet extends Security2CC { } } - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { const securityManager = assertSecurityTX(ctx, this.nodeId); this.ensureSequenceNumber(securityManager); - this.payload = Buffer.from([this.sequenceNumber]); + this.payload = Bytes.from([this.sequenceNumber]); return super.serialize(ctx); } @@ -2236,9 +2240,9 @@ export class Security2CCKEXReport extends Security2CC { public readonly supportedECDHProfiles: readonly ECDHProfiles[]; public readonly requestedKeys: readonly SecurityClass[]; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.concat([ - Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.concat([ + Bytes.from([ this._reserved + (this.requestCSA ? 0b10 : 0) + (this.echo ? 0b1 : 0), @@ -2372,9 +2376,9 @@ export class Security2CCKEXSet extends Security2CC { public selectedECDHProfile: ECDHProfiles; public grantedKeys: SecurityClass[]; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.concat([ - Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.concat([ + Bytes.from([ this._reserved + (this.permitCSA ? 0b10 : 0) + (this.echo ? 0b1 : 0), @@ -2443,8 +2447,8 @@ export class Security2CCKEXFail extends Security2CC { public failType: KEXFailType; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.failType]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.failType]); return super.serialize(ctx); } @@ -2459,7 +2463,7 @@ export class Security2CCKEXFail extends Security2CC { // @publicAPI export interface Security2CCPublicKeyReportOptions { includingNode: boolean; - publicKey: Buffer; + publicKey: Uint8Array; } @CCCommand(Security2Command.PublicKeyReport) @@ -2478,7 +2482,7 @@ export class Security2CCPublicKeyReport extends Security2CC { ): Security2CCPublicKeyReport { validatePayload(raw.payload.length >= 17); const includingNode = !!(raw.payload[0] & 0b1); - const publicKey: Buffer = raw.payload.subarray(1); + const publicKey: Uint8Array = raw.payload.subarray(1); return new Security2CCPublicKeyReport({ nodeId: ctx.sourceNodeId, @@ -2488,11 +2492,11 @@ export class Security2CCPublicKeyReport extends Security2CC { } public includingNode: boolean; - public publicKey: Buffer; + public publicKey: Uint8Array; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.concat([ - Buffer.from([this.includingNode ? 1 : 0]), + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.concat([ + Bytes.from([this.includingNode ? 1 : 0]), this.publicKey, ]); return super.serialize(ctx); @@ -2512,7 +2516,7 @@ export class Security2CCPublicKeyReport extends Security2CC { // @publicAPI export interface Security2CCNetworkKeyReportOptions { grantedKey: SecurityClass; - networkKey: Buffer; + networkKey: Uint8Array; } @CCCommand(Security2Command.NetworkKeyReport) @@ -2544,10 +2548,10 @@ export class Security2CCNetworkKeyReport extends Security2CC { } public grantedKey: SecurityClass; - public networkKey: Buffer; + public networkKey: Uint8Array; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.concat([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.concat([ securityClassToBitMask(this.grantedKey), this.networkKey, ]); @@ -2603,7 +2607,7 @@ export class Security2CCNetworkKeyGet extends Security2CC { public requestedKey: SecurityClass; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { this.payload = securityClassToBitMask(this.requestedKey); return super.serialize(ctx); } @@ -2658,8 +2662,8 @@ export class Security2CCTransferEnd extends Security2CC { public keyVerified: boolean; public keyRequestComplete: boolean; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ (this.keyVerified ? 0b10 : 0) + (this.keyRequestComplete ? 0b1 : 0), ]); return super.serialize(ctx); @@ -2709,7 +2713,7 @@ export class Security2CCCommandsSupportedReport extends Security2CC { public readonly supportedCCs: CommandClasses[]; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { this.payload = encodeCCList(this.supportedCCs, []); return super.serialize(ctx); } diff --git a/packages/cc/src/cc/SecurityCC.ts b/packages/cc/src/cc/SecurityCC.ts index 762f8ceef0aa..49a604eeec21 100644 --- a/packages/cc/src/cc/SecurityCC.ts +++ b/packages/cc/src/cc/SecurityCC.ts @@ -27,6 +27,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { buffer2hex, num2hex, pick } from "@zwave-js/shared/safe"; import { randomBytes } from "node:crypto"; import { setTimeout as wait } from "node:timers/promises"; @@ -51,17 +52,17 @@ import { TransportServiceCC } from "./TransportServiceCC"; // @noSetValueAPI This is an encapsulation CC function getAuthenticationData( - senderNonce: Buffer, - receiverNonce: Buffer, + senderNonce: Uint8Array, + receiverNonce: Uint8Array, ccCommand: SecurityCommand, sendingNodeId: number, receivingNodeId: number, - encryptedPayload: Buffer, -): Buffer { - return Buffer.concat([ + encryptedPayload: Uint8Array, +): Uint8Array { + return Bytes.concat([ senderNonce, receiverNonce, - Buffer.from([ + Bytes.from([ ccCommand, sendingNodeId, receivingNodeId, @@ -158,7 +159,7 @@ export class SecurityCCAPI extends PhysicalCCAPI { /** * Requests a new nonce for Security CC encapsulation which is not directly linked to a specific command. */ - public async getNonce(): Promise { + public async getNonce(): Promise { this.assertSupportsCommand(SecurityCommand, SecurityCommand.NonceGet); const cc = new SecurityCCNonceGet({ @@ -289,7 +290,7 @@ export class SecurityCCAPI extends PhysicalCCAPI { // There is only one scheme, so we don't return anything here } - public async setNetworkKey(networkKey: Buffer): Promise { + public async setNetworkKey(networkKey: Uint8Array): Promise { this.assertSupportsCommand( SecurityCommand, SecurityCommand.NetworkKeySet, @@ -304,7 +305,7 @@ export class SecurityCCAPI extends PhysicalCCAPI { nodeId: this.endpoint.nodeId, endpointIndex: this.endpoint.index, encapsulated: keySet, - alternativeNetworkKey: Buffer.alloc(16, 0), + alternativeNetworkKey: new Uint8Array(16).fill(0), }); await this.host.sendCommand(cc, this.commandOptions); } @@ -549,7 +550,7 @@ export class SecurityCC extends CommandClass { } interface SecurityCCNonceReportOptions { - nonce: Buffer; + nonce: Uint8Array; } @CCCommand(SecurityCommand.NonceReport) @@ -581,10 +582,10 @@ export class SecurityCCNonceReport extends SecurityCC { }); } - public nonce: Buffer; + public nonce: Uint8Array; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = this.nonce; + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.view(this.nonce); return super.serialize(ctx); } @@ -603,12 +604,12 @@ export class SecurityCCNonceGet extends SecurityCC {} // @publicAPI export type SecurityCCCommandEncapsulationOptions = & { - alternativeNetworkKey?: Buffer; + alternativeNetworkKey?: Uint8Array; } & ({ encapsulated: CommandClass; } | { - decryptedCCBytes: Buffer; + decryptedCCBytes: Uint8Array; sequenced: boolean; secondFrame: boolean; sequenceCounter: number; @@ -697,14 +698,15 @@ export class SecurityCCCommandEncapsulation extends SecurityCC { const frameControlAndDecryptedCC = decryptAES128OFB( encryptedPayload, ctx.securityManager.encryptionKey, - Buffer.concat([iv, nonce]), + Bytes.concat([iv, nonce]), ); const frameControl = frameControlAndDecryptedCC[0]; const sequenceCounter = frameControl & 0b1111; const sequenced = !!(frameControl & 0b1_0000); const secondFrame = !!(frameControl & 0b10_0000); - const decryptedCCBytes: Buffer | undefined = frameControlAndDecryptedCC - .subarray(1); + const decryptedCCBytes: Uint8Array | undefined = + frameControlAndDecryptedCC + .subarray(1); const ret = new SecurityCCCommandEncapsulation({ nodeId: ctx.sourceNodeId, @@ -725,21 +727,21 @@ export class SecurityCCCommandEncapsulation extends SecurityCC { private secondFrame: boolean | undefined; private sequenceCounter: number | undefined; - private decryptedCCBytes: Buffer | undefined; + private decryptedCCBytes: Uint8Array | undefined; public encapsulated!: CommandClass; - private alternativeNetworkKey?: Buffer; + private alternativeNetworkKey?: Uint8Array; public get nonceId(): number | undefined { return this.nonce?.[0]; } - public nonce: Buffer | undefined; + public nonce: Uint8Array | undefined; // Only used testing/for debugging purposes - private iv?: Buffer; - private authData?: Buffer; - private authCode?: Buffer; - private ciphertext?: Buffer; + private iv?: Uint8Array; + private authData?: Uint8Array; + private authCode?: Uint8Array; + private ciphertext?: Uint8Array; public getPartialCCSessionId(): Record | undefined { if (this.sequenced) { @@ -765,7 +767,7 @@ export class SecurityCCCommandEncapsulation extends SecurityCC { ctx: CCParsingContext, ): void { // Concat the CC buffers - this.decryptedCCBytes = Buffer.concat( + this.decryptedCCBytes = Bytes.concat( [...partials, this].map((cc) => cc.decryptedCCBytes!), ); // make sure this contains a complete CC command that's worth splitting @@ -775,15 +777,15 @@ export class SecurityCCCommandEncapsulation extends SecurityCC { this.encapsulated.encapsulatingCC = this as any; } - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { if (!this.nonce) throwNoNonce(); if (this.nonce.length !== HALF_NONCE_SIZE) { throwNoNonce("Invalid nonce size"); } assertSecurityTX(ctx); - let authKey: Buffer; - let encryptionKey: Buffer; + let authKey: Uint8Array; + let encryptionKey: Uint8Array; if (this.alternativeNetworkKey) { authKey = generateAuthKey(this.alternativeNetworkKey); encryptionKey = generateEncryptionKey( @@ -795,13 +797,13 @@ export class SecurityCCCommandEncapsulation extends SecurityCC { } const serializedCC = this.encapsulated.serialize(ctx); - const plaintext = Buffer.concat([ - Buffer.from([0]), // TODO: frame control + const plaintext = Bytes.concat([ + Bytes.from([0]), // TODO: frame control serializedCC, ]); // Encrypt the payload const senderNonce = randomBytes(HALF_NONCE_SIZE); - const iv = Buffer.concat([senderNonce, this.nonce]); + const iv = Bytes.concat([senderNonce, this.nonce]); const ciphertext = encryptAES128OFB(plaintext, encryptionKey, iv); // And generate the auth code const authData = getAuthenticationData( @@ -820,10 +822,10 @@ export class SecurityCCCommandEncapsulation extends SecurityCC { this.authCode = authCode; this.ciphertext = ciphertext; - this.payload = Buffer.concat([ + this.payload = Bytes.concat([ senderNonce, ciphertext, - Buffer.from([this.nonceId!]), + Bytes.from([this.nonceId!]), authCode, ]); return super.serialize(ctx); @@ -896,9 +898,9 @@ export class SecurityCCSchemeReport extends SecurityCC { }); } - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { // Since it is unlikely that any more schemes will be added to S0, we hardcode the default scheme here (bit 0 = 0) - this.payload = Buffer.from([0]); + this.payload = Bytes.from([0]); return super.serialize(ctx); } @@ -914,9 +916,9 @@ export class SecurityCCSchemeReport extends SecurityCC { @CCCommand(SecurityCommand.SchemeGet) @expectedCCResponse(SecurityCCSchemeReport) export class SecurityCCSchemeGet extends SecurityCC { - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { // Since it is unlikely that any more schemes will be added to S0, we hardcode the default scheme here (bit 0 = 0) - this.payload = Buffer.from([0]); + this.payload = Bytes.from([0]); return super.serialize(ctx); } @@ -932,9 +934,9 @@ export class SecurityCCSchemeGet extends SecurityCC { @CCCommand(SecurityCommand.SchemeInherit) @expectedCCResponse(SecurityCCSchemeReport) export class SecurityCCSchemeInherit extends SecurityCC { - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { // Since it is unlikely that any more schemes will be added to S0, we hardcode the default scheme here (bit 0 = 0) - this.payload = Buffer.from([0]); + this.payload = Bytes.from([0]); return super.serialize(ctx); } @@ -952,7 +954,7 @@ export class SecurityCCNetworkKeyVerify extends SecurityCC {} // @publicAPI export interface SecurityCCNetworkKeySetOptions { - networkKey: Buffer; + networkKey: Uint8Array; } @CCCommand(SecurityCommand.NetworkKeySet) @@ -976,7 +978,7 @@ export class SecurityCCNetworkKeySet extends SecurityCC { ctx: CCParsingContext, ): SecurityCCNetworkKeySet { validatePayload(raw.payload.length >= 16); - const networkKey: Buffer = raw.payload.subarray(0, 16); + const networkKey: Uint8Array = raw.payload.subarray(0, 16); return new SecurityCCNetworkKeySet({ nodeId: ctx.sourceNodeId, @@ -984,10 +986,10 @@ export class SecurityCCNetworkKeySet extends SecurityCC { }); } - public networkKey: Buffer; + public networkKey: Uint8Array; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = this.networkKey; + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.view(this.networkKey); return super.serialize(ctx); } @@ -1061,9 +1063,9 @@ export class SecurityCCCommandsSupportedReport extends SecurityCC { .reduce((prev, cur) => prev.concat(...cur), []); } - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.concat([ - Buffer.from([this.reportsToFollow]), + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.concat([ + Bytes.from([this.reportsToFollow]), encodeCCList(this.supportedCCs, this.controlledCCs), ]); return super.serialize(ctx); diff --git a/packages/cc/src/cc/SoundSwitchCC.ts b/packages/cc/src/cc/SoundSwitchCC.ts index f382abe19903..840f00e7dab5 100644 --- a/packages/cc/src/cc/SoundSwitchCC.ts +++ b/packages/cc/src/cc/SoundSwitchCC.ts @@ -17,6 +17,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { clamp } from "alcalzone-shared/math"; @@ -498,8 +499,8 @@ export class SoundSwitchCCTonesNumberReport extends SoundSwitchCC { public toneCount: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.toneCount]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.toneCount]); return super.serialize(ctx); } @@ -559,10 +560,10 @@ export class SoundSwitchCCToneInfoReport extends SoundSwitchCC { public readonly duration: number; public readonly name: string; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.concat([ - Buffer.from([this.toneId, 0, 0, this.name.length]), - Buffer.from(this.name, "utf8"), + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.concat([ + Bytes.from([this.toneId, 0, 0, this.name.length]), + Bytes.from(this.name, "utf8"), ]); this.payload.writeUInt16BE(this.duration, 1); return super.serialize(ctx); @@ -620,8 +621,8 @@ export class SoundSwitchCCToneInfoGet extends SoundSwitchCC { public toneId: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.toneId]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.toneId]); return super.serialize(ctx); } @@ -668,8 +669,8 @@ export class SoundSwitchCCConfigurationSet extends SoundSwitchCC { public defaultVolume: number; public defaultToneId: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.defaultVolume, this.defaultToneId]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.defaultVolume, this.defaultToneId]); return super.serialize(ctx); } @@ -721,8 +722,8 @@ export class SoundSwitchCCConfigurationReport extends SoundSwitchCC { @ccValue(SoundSwitchCCValues.defaultToneId) public defaultToneId: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.defaultVolume, this.defaultToneId]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.defaultVolume, this.defaultToneId]); return super.serialize(ctx); } @@ -780,8 +781,8 @@ export class SoundSwitchCCTonePlaySet extends SoundSwitchCC { public toneId: ToneId | number; public volume?: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.toneId, this.volume ?? 0]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.toneId, this.volume ?? 0]); return super.serialize(ctx); } @@ -841,8 +842,8 @@ export class SoundSwitchCCTonePlayReport extends SoundSwitchCC { @ccValue(SoundSwitchCCValues.volume) public volume?: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.toneId, this.volume ?? 0]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.toneId, this.volume ?? 0]); return super.serialize(ctx); } diff --git a/packages/cc/src/cc/SupervisionCC.ts b/packages/cc/src/cc/SupervisionCC.ts index b9c1343c95a3..25321e381fc1 100644 --- a/packages/cc/src/cc/SupervisionCC.ts +++ b/packages/cc/src/cc/SupervisionCC.ts @@ -26,6 +26,7 @@ import type { GetNode, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName } from "@zwave-js/shared/safe"; import { PhysicalCCAPI } from "../lib/API"; import { type CCRaw, CommandClass } from "../lib/CommandClass"; @@ -338,9 +339,9 @@ export class SupervisionCCReport extends SupervisionCC { public readonly status: SupervisionStatus; public readonly duration: Duration | undefined; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.concat([ - Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.concat([ + Bytes.from([ (this.moreUpdatesFollow ? 0b1_0_000000 : 0) | (this.requestWakeUpOnDemand ? 0b0_1_000000 : 0) | (this.sessionId & 0b111111), @@ -349,9 +350,9 @@ export class SupervisionCCReport extends SupervisionCC { ]); if (this.duration) { - this.payload = Buffer.concat([ + this.payload = Bytes.concat([ this.payload, - Buffer.from([this.duration.serializeReport()]), + Bytes.from([this.duration.serializeReport()]), ]); } return super.serialize(ctx); @@ -433,10 +434,10 @@ export class SupervisionCCGet extends SupervisionCC { public sessionId: number; public encapsulated: CommandClass; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { const encapCC = this.encapsulated.serialize(ctx); - this.payload = Buffer.concat([ - Buffer.from([ + this.payload = Bytes.concat([ + Bytes.from([ (this.requestStatusUpdates ? 0b10_000000 : 0) | (this.sessionId & 0b111111), encapCC.length, diff --git a/packages/cc/src/cc/ThermostatFanModeCC.ts b/packages/cc/src/cc/ThermostatFanModeCC.ts index d23c88dea711..2dfaf45dfca6 100644 --- a/packages/cc/src/cc/ThermostatFanModeCC.ts +++ b/packages/cc/src/cc/ThermostatFanModeCC.ts @@ -19,6 +19,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { @@ -369,8 +370,8 @@ export class ThermostatFanModeCCSet extends ThermostatFanModeCC { public mode: ThermostatFanMode; public off: boolean | undefined; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ (this.off ? 0b1000_0000 : 0) | (this.mode & 0b1111), ]); diff --git a/packages/cc/src/cc/ThermostatModeCC.ts b/packages/cc/src/cc/ThermostatModeCC.ts index 5a7a15185c45..e8e09adb793a 100644 --- a/packages/cc/src/cc/ThermostatModeCC.ts +++ b/packages/cc/src/cc/ThermostatModeCC.ts @@ -20,6 +20,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { buffer2hex, getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { @@ -156,13 +157,13 @@ export class ThermostatModeCCAPI extends CCAPI { ): Promise; public async set( mode: (typeof ThermostatMode)["Manufacturer specific"], - manufacturerData: Buffer | string, + manufacturerData: Uint8Array | string, ): Promise; @validateArgs({ strictEnums: true }) public async set( mode: ThermostatMode, - manufacturerData?: Buffer | string, + manufacturerData?: Uint8Array | string, ): Promise { this.assertSupportsCommand( ThermostatModeCommand, @@ -180,7 +181,7 @@ export class ThermostatModeCCAPI extends CCAPI { ZWaveErrorCodes.Argument_Invalid, ); } - manufacturerData = Buffer.from(manufacturerData, "hex"); + manufacturerData = Bytes.from(manufacturerData, "hex"); } const cc = new ThermostatModeCCSet({ @@ -317,7 +318,7 @@ export type ThermostatModeCCSetOptions = } | { mode: (typeof ThermostatMode)["Manufacturer specific"]; - manufacturerData: Buffer; + manufacturerData: Uint8Array; }; @CCCommand(ThermostatModeCommand.Set) @@ -360,17 +361,17 @@ export class ThermostatModeCCSet extends ThermostatModeCC { } public mode: ThermostatMode; - public manufacturerData?: Buffer; + public manufacturerData?: Uint8Array; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { const manufacturerData = this.mode === ThermostatMode["Manufacturer specific"] && this.manufacturerData ? this.manufacturerData - : Buffer.from([]); + : new Uint8Array(); const manufacturerDataLength = manufacturerData.length; - this.payload = Buffer.concat([ - Buffer.from([ + this.payload = Bytes.concat([ + Bytes.from([ ((manufacturerDataLength & 0b111) << 5) + (this.mode & 0b11111), ]), manufacturerData, @@ -403,7 +404,7 @@ export type ThermostatModeCCReportOptions = } | { mode: (typeof ThermostatMode)["Manufacturer specific"]; - manufacturerData?: Buffer; + manufacturerData?: Uint8Array; }; @CCCommand(ThermostatModeCommand.Report) @@ -485,22 +486,20 @@ export class ThermostatModeCCReport extends ThermostatModeCC { public readonly mode: ThermostatMode; @ccValue(ThermostatModeCCValues.manufacturerData) - public readonly manufacturerData: Buffer | undefined; + public readonly manufacturerData: Uint8Array | undefined; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { const manufacturerDataLength = this.mode === ThermostatMode["Manufacturer specific"] && this.manufacturerData ? Math.min(0b111, this.manufacturerData.length) : 0; - this.payload = Buffer.allocUnsafe(1 + manufacturerDataLength); + this.payload = new Bytes(1 + manufacturerDataLength); this.payload[0] = (manufacturerDataLength << 5) + (this.mode & 0b11111); - if (manufacturerDataLength) { - this.manufacturerData!.copy( - this.payload, + if (manufacturerDataLength && this.manufacturerData) { + this.payload.set( + this.manufacturerData.subarray(0, manufacturerDataLength), 1, - 0, - manufacturerDataLength, ); } return super.serialize(ctx); @@ -572,7 +571,7 @@ export class ThermostatModeCCSupportedReport extends ThermostatModeCC { @ccValue(ThermostatModeCCValues.supportedModes) public readonly supportedModes: ThermostatMode[]; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { this.payload = encodeBitMask( this.supportedModes, ThermostatMode["Manufacturer specific"], diff --git a/packages/cc/src/cc/ThermostatSetbackCC.ts b/packages/cc/src/cc/ThermostatSetbackCC.ts index fab9ddada914..01a77c607459 100644 --- a/packages/cc/src/cc/ThermostatSetbackCC.ts +++ b/packages/cc/src/cc/ThermostatSetbackCC.ts @@ -12,6 +12,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { @@ -208,8 +209,8 @@ export class ThermostatSetbackCCSet extends ThermostatSetbackCC { /** The offset from the setpoint in 0.1 Kelvin or a special mode */ public setbackState: SetbackState; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ this.setbackType & 0b11, encodeSetbackState(this.setbackState), ]); @@ -272,8 +273,8 @@ export class ThermostatSetbackCCReport extends ThermostatSetbackCC { /** The offset from the setpoint in 0.1 Kelvin or a special mode */ public readonly setbackState: SetbackState; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ this.setbackType & 0b11, encodeSetbackState(this.setbackState), ]); diff --git a/packages/cc/src/cc/ThermostatSetpointCC.ts b/packages/cc/src/cc/ThermostatSetpointCC.ts index 3c39c854c610..2297a9f7363b 100644 --- a/packages/cc/src/cc/ThermostatSetpointCC.ts +++ b/packages/cc/src/cc/ThermostatSetpointCC.ts @@ -24,6 +24,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { @@ -585,12 +586,12 @@ export class ThermostatSetpointCCSet extends ThermostatSetpointCC { public value: number; public scale: number; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { // If a config file overwrites how the float should be encoded, use that information const override = ctx.getDeviceConfig?.(this.nodeId as number) ?.compat?.overrideFloatEncoding; - this.payload = Buffer.concat([ - Buffer.from([this.setpointType & 0b1111]), + this.payload = Bytes.concat([ + Bytes.from([this.setpointType & 0b1111]), encodeFloatWithScale(this.value, this.scale, override), ]); return super.serialize(ctx); @@ -693,9 +694,9 @@ export class ThermostatSetpointCCReport extends ThermostatSetpointCC { public scale: number; public value: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.concat([ - Buffer.from([this.type & 0b1111]), + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.concat([ + Bytes.from([this.type & 0b1111]), encodeFloatWithScale(this.value, this.scale), ]); return super.serialize(ctx); @@ -757,8 +758,8 @@ export class ThermostatSetpointCCGet extends ThermostatSetpointCC { public setpointType: ThermostatSetpointType; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.setpointType & 0b1111]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.setpointType & 0b1111]); return super.serialize(ctx); } @@ -848,10 +849,10 @@ export class ThermostatSetpointCCCapabilitiesReport public minValueScale: number; public maxValueScale: number; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { const min = encodeFloatWithScale(this.minValue, this.minValueScale); const max = encodeFloatWithScale(this.maxValue, this.maxValueScale); - this.payload = Buffer.concat([Buffer.from([this.type]), min, max]); + this.payload = Bytes.concat([Bytes.from([this.type]), min, max]); return super.serialize(ctx); } @@ -902,8 +903,8 @@ export class ThermostatSetpointCCCapabilitiesGet extends ThermostatSetpointCC { public setpointType: ThermostatSetpointType; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.setpointType & 0b1111]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.setpointType & 0b1111]); return super.serialize(ctx); } @@ -966,7 +967,7 @@ export class ThermostatSetpointCCSupportedReport extends ThermostatSetpointCC { @ccValue(ThermostatSetpointCCValues.supportedSetpointTypes) public readonly supportedSetpointTypes: readonly ThermostatSetpointType[]; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { this.payload = encodeBitMask( // Encode as interpretation A this.supportedSetpointTypes diff --git a/packages/cc/src/cc/TimeCC.ts b/packages/cc/src/cc/TimeCC.ts index d0e0b0eba556..a6f0899f522a 100644 --- a/packages/cc/src/cc/TimeCC.ts +++ b/packages/cc/src/cc/TimeCC.ts @@ -17,6 +17,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { padStart } from "alcalzone-shared/strings"; @@ -272,8 +273,8 @@ export class TimeCCTimeReport extends TimeCC { public minute: number; public second: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ this.hour & 0b11111, this.minute, this.second, @@ -337,8 +338,8 @@ export class TimeCCDateReport extends TimeCC { public month: number; public day: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ // 2 bytes placeholder for year 0, 0, @@ -410,13 +411,13 @@ export class TimeCCTimeOffsetSet extends TimeCC { public dstStartDate: Date; public dstEndDate: Date; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.concat([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.concat([ encodeTimezone({ standardOffset: this.standardOffset, dstOffset: this.dstOffset, }), - Buffer.from([ + Bytes.from([ this.dstStartDate.getUTCMonth() + 1, this.dstStartDate.getUTCDate(), this.dstStartDate.getUTCHours(), @@ -499,13 +500,13 @@ export class TimeCCTimeOffsetReport extends TimeCC { public dstStartDate: Date; public dstEndDate: Date; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.concat([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.concat([ encodeTimezone({ standardOffset: this.standardOffset, dstOffset: this.dstOffset, }), - Buffer.from([ + Bytes.from([ this.dstStartDate.getUTCMonth() + 1, this.dstStartDate.getUTCDate(), this.dstStartDate.getUTCHours(), diff --git a/packages/cc/src/cc/TimeParametersCC.ts b/packages/cc/src/cc/TimeParametersCC.ts index bef39dba2b50..b458086f2bea 100644 --- a/packages/cc/src/cc/TimeParametersCC.ts +++ b/packages/cc/src/cc/TimeParametersCC.ts @@ -20,6 +20,7 @@ import type { GetDeviceConfig, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { CCAPI, @@ -391,12 +392,12 @@ export class TimeParametersCCSet extends TimeParametersCC { public dateAndTime: Date; private useLocalTime?: boolean; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { const dateSegments = dateToSegments( this.dateAndTime, !!this.useLocalTime, ); - this.payload = Buffer.from([ + this.payload = Bytes.from([ // 2 bytes placeholder for year 0, 0, diff --git a/packages/cc/src/cc/TransportServiceCC.ts b/packages/cc/src/cc/TransportServiceCC.ts index 92f7b8f9cbcd..a5df23ce0fd0 100644 --- a/packages/cc/src/cc/TransportServiceCC.ts +++ b/packages/cc/src/cc/TransportServiceCC.ts @@ -13,6 +13,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { buffer2hex } from "@zwave-js/shared/safe"; import { type CCRaw, @@ -61,8 +62,8 @@ export class TransportServiceCC extends CommandClass export interface TransportServiceCCFirstSegmentOptions { datagramSize: number; sessionId: number; - headerExtension?: Buffer | undefined; - partialDatagram: Buffer; + headerExtension?: Uint8Array | undefined; + partialDatagram: Uint8Array; } /** @publicAPI */ @@ -102,7 +103,7 @@ export class TransportServiceCCFirstSegment extends TransportServiceCC { validatePayload(raw.payload.length >= 6); // 2 bytes dgram size, 1 byte sessid/ext, 1+ bytes payload, 2 bytes checksum // Verify the CRC - const headerBuffer = Buffer.from([ + const headerBuffer = Bytes.from([ CommandClasses["Transport Service"], TransportServiceCommand.FirstSegment | raw.payload[0], ]); @@ -119,14 +120,17 @@ export class TransportServiceCCFirstSegment extends TransportServiceCC { // If there is a header extension, read it const hasHeaderExtension = !!(raw.payload[2] & 0b1000); - let headerExtension: Buffer | undefined; + let headerExtension: Uint8Array | undefined; if (hasHeaderExtension) { const extLength = raw.payload[3]; headerExtension = raw.payload.subarray(4, 4 + extLength); payloadOffset += 1 + extLength; } - const partialDatagram: Buffer = raw.payload.subarray(payloadOffset, -2); + const partialDatagram: Uint8Array = raw.payload.subarray( + payloadOffset, + -2, + ); // A node supporting the Transport Service Command Class, version 2 // MUST NOT send Transport Service segments with the Payload field longer than 39 bytes. @@ -143,36 +147,36 @@ export class TransportServiceCCFirstSegment extends TransportServiceCC { public datagramSize: number; public sessionId: number; - public headerExtension: Buffer | undefined; - public partialDatagram: Buffer; + public headerExtension: Uint8Array | undefined; + public partialDatagram: Uint8Array; public encapsulated!: CommandClass; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { // Transport Service re-uses the lower 3 bits of the ccCommand as payload this.ccCommand = (this.ccCommand & 0b11111_000) | ((this.datagramSize >>> 8) & 0b111); const ext = !!this.headerExtension && this.headerExtension.length >= 1; - this.payload = Buffer.from([ + this.payload = Bytes.from([ this.datagramSize & 0xff, ((this.sessionId & 0b1111) << 4) | (ext ? 0b1000 : 0), ]); if (ext) { - this.payload = Buffer.concat([ + this.payload = Bytes.concat([ this.payload, - Buffer.from([this.headerExtension!.length]), + Bytes.from([this.headerExtension!.length]), this.headerExtension!, ]); } - this.payload = Buffer.concat([ + this.payload = Bytes.concat([ this.payload, this.partialDatagram, - Buffer.alloc(2, 0), // checksum + Bytes.alloc(2, 0), // checksum ]); // Compute and save the CRC16 in the payload // The CC header is included in the CRC computation - const headerBuffer = Buffer.from([this.ccId, this.ccCommand]); + const headerBuffer = Bytes.from([this.ccId, this.ccCommand]); let crc = CRC16_CCITT(headerBuffer); crc = CRC16_CCITT(this.payload.subarray(0, -2), crc); // Write the checksum into the last two bytes of the payload @@ -245,7 +249,7 @@ export class TransportServiceCCSubsequentSegment extends TransportServiceCC { validatePayload(raw.payload.length >= 7); // 2 bytes dgram size, 1 byte sessid/ext/offset, 1 byte offset, 1+ bytes payload, 2 bytes checksum // Verify the CRC - const headerBuffer = Buffer.from([ + const headerBuffer = Bytes.from([ CommandClasses["Transport Service"], TransportServiceCommand.SubsequentSegment | raw.payload[0], ]); @@ -264,14 +268,14 @@ export class TransportServiceCCSubsequentSegment extends TransportServiceCC { // If there is a header extension, read it const hasHeaderExtension = !!(raw.payload[2] & 0b1000); - let headerExtension: Buffer | undefined; + let headerExtension: Bytes | undefined; if (hasHeaderExtension) { const extLength = raw.payload[4]; headerExtension = raw.payload.subarray(5, 5 + extLength); payloadOffset += 1 + extLength; } - const partialDatagram: Buffer = raw.payload.subarray(payloadOffset, -2); + const partialDatagram: Bytes = raw.payload.subarray(payloadOffset, -2); // A node supporting the Transport Service Command Class, version 2 // MUST NOT send Transport Service segments with the Payload field longer than 39 bytes. @@ -290,8 +294,8 @@ export class TransportServiceCCSubsequentSegment extends TransportServiceCC { public datagramSize: number; public datagramOffset: number; public sessionId: number; - public headerExtension: Buffer | undefined; - public partialDatagram: Buffer; + public headerExtension: Uint8Array | undefined; + public partialDatagram: Uint8Array; // This can only be received private _encapsulated!: CommandClass; @@ -340,7 +344,7 @@ export class TransportServiceCCSubsequentSegment extends TransportServiceCC { ctx: CCParsingContext, ): void { // Concat the CC buffers - const datagram = Buffer.allocUnsafe(this.datagramSize); + const datagram = new Bytes(this.datagramSize); for (const partial of [...partials, this]) { // Ensure that we don't try to write out-of-bounds const offset = partial instanceof TransportServiceCCFirstSegment @@ -352,7 +356,7 @@ export class TransportServiceCCSubsequentSegment extends TransportServiceCC { ZWaveErrorCodes.PacketFormat_InvalidPayload, ); } - partial.partialDatagram.copy(datagram, offset); + datagram.set(partial.partialDatagram, offset); } // and deserialize the CC @@ -360,13 +364,13 @@ export class TransportServiceCCSubsequentSegment extends TransportServiceCC { this._encapsulated.encapsulatingCC = this as any; } - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { // Transport Service re-uses the lower 3 bits of the ccCommand as payload this.ccCommand = (this.ccCommand & 0b11111_000) | ((this.datagramSize >>> 8) & 0b111); const ext = !!this.headerExtension && this.headerExtension.length >= 1; - this.payload = Buffer.from([ + this.payload = Bytes.from([ this.datagramSize & 0xff, ((this.sessionId & 0b1111) << 4) | (ext ? 0b1000 : 0) @@ -374,21 +378,21 @@ export class TransportServiceCCSubsequentSegment extends TransportServiceCC { this.datagramOffset & 0xff, ]); if (ext) { - this.payload = Buffer.concat([ + this.payload = Bytes.concat([ this.payload, - Buffer.from([this.headerExtension!.length]), + Bytes.from([this.headerExtension!.length]), this.headerExtension!, ]); } - this.payload = Buffer.concat([ + this.payload = Bytes.concat([ this.payload, this.partialDatagram, - Buffer.alloc(2, 0), // checksum + Bytes.alloc(2, 0), // checksum ]); // Compute and save the CRC16 in the payload // The CC header is included in the CRC computation - const headerBuffer = Buffer.from([this.ccId, this.ccCommand]); + const headerBuffer = Bytes.from([this.ccId, this.ccCommand]); let crc = CRC16_CCITT(headerBuffer); crc = CRC16_CCITT(this.payload.subarray(0, -2), crc); // Write the checksum into the last two bytes of the payload @@ -474,8 +478,8 @@ export class TransportServiceCCSegmentRequest extends TransportServiceCC { public sessionId: number; public datagramOffset: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ ((this.sessionId & 0b1111) << 4) | ((this.datagramOffset >>> 8) & 0b111), this.datagramOffset & 0xff, @@ -523,8 +527,8 @@ export class TransportServiceCCSegmentComplete extends TransportServiceCC { public sessionId: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([(this.sessionId & 0b1111) << 4]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([(this.sessionId & 0b1111) << 4]); return super.serialize(ctx); } @@ -565,8 +569,8 @@ export class TransportServiceCCSegmentWait extends TransportServiceCC { public pendingSegments: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.pendingSegments]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.pendingSegments]); return super.serialize(ctx); } diff --git a/packages/cc/src/cc/UserCodeCC.ts b/packages/cc/src/cc/UserCodeCC.ts index 0e70a3b65001..9ed1717f1dad 100644 --- a/packages/cc/src/cc/UserCodeCC.ts +++ b/packages/cc/src/cc/UserCodeCC.ts @@ -22,6 +22,7 @@ import type { GetSupportedCCVersion, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes, isUint8Array, uint8ArrayToString } from "@zwave-js/shared/safe"; import { getEnumMemberName, isPrintableASCII, @@ -161,7 +162,7 @@ export const UserCodeCCValues = Object.freeze({ }), }); -function parseExtendedUserCode(payload: Buffer): { +function parseExtendedUserCode(payload: Bytes): { code: UserCode; bytesRead: number; } { @@ -190,7 +191,7 @@ function setUserCodeMetadata( this: UserCodeCC, ctx: GetValueDB & GetSupportedCCVersion, userId: number, - userCode?: string | Buffer, + userCode?: string | Uint8Array, ) { const statusValue = UserCodeCCValues.userIdStatus(userId); const codeValue = UserCodeCCValues.userCode(userId); @@ -222,7 +223,7 @@ function setUserCodeMetadata( }); const codeMetadata: ValueMetadata = { - ...(Buffer.isBuffer(userCode) + ...(isUint8Array(userCode) ? ValueMetadata.Buffer : ValueMetadata.String), minLength: 4, @@ -239,7 +240,7 @@ function persistUserCode( ctx: GetValueDB & GetSupportedCCVersion, userId: number, userIdStatus: UserIDStatus, - userCode: string | Buffer, + userCode: string | Bytes, ) { const statusValue = UserCodeCCValues.userIdStatus(userId); const codeValue = UserCodeCCValues.userCode(userId); @@ -262,8 +263,8 @@ function persistUserCode( } /** Formats a user code in a way that's safe to print in public logs */ -export function userCodeToLogString(userCode: string | Buffer): string { - if (userCode === "") return "(empty)"; +export function userCodeToLogString(userCode: string | Uint8Array): string { + if (userCode.length === 0) return "(empty)"; return "*".repeat(userCode.length); } @@ -377,7 +378,7 @@ export class UserCodeCCAPI extends PhysicalCCAPI { propertyKey, ); } - if (typeof value !== "string" && !Buffer.isBuffer(value)) { + if (typeof value !== "string" && !isUint8Array(value)) { throwWrongValueType( this.ccId, property, @@ -529,7 +530,7 @@ export class UserCodeCCAPI extends PhysicalCCAPI { UserIDStatus, UserIDStatus.Available | UserIDStatus.StatusNotAvailable >, - userCode: string | Buffer, + userCode: string | Uint8Array, ): Promise { if (userId > 255) { return this.setMany([{ userId, userIdStatus, userCode }]); @@ -646,9 +647,12 @@ export class UserCodeCCAPI extends PhysicalCCAPI { } else if (code.userIdStatus === UserIDStatus.Available) { code.userCode = undefined; } else if (supportedASCIIChars) { + const userCodeString = typeof code.userCode === "string" + ? code.userCode + : uint8ArrayToString(code.userCode); if ( !validateCode( - code.userCode.toString("ascii"), + userCodeString, supportedASCIIChars, ) ) { @@ -1200,10 +1204,10 @@ export class UserCodeCC extends CommandClass { ctx: GetValueDB, endpoint: EndpointId, userId: number, - ): MaybeNotKnown { + ): MaybeNotKnown { return ctx .getValueDB(endpoint.nodeId) - .getValue( + .getValue( UserCodeCCValues.userCode(userId).endpoint(endpoint.index), ); } @@ -1227,7 +1231,7 @@ export type UserCodeCCSetOptions = UserIDStatus, UserIDStatus.Available | UserIDStatus.StatusNotAvailable >; - userCode: string | Buffer; + userCode: string | Uint8Array; }; @CCCommand(UserCodeCommand.Set) @@ -1300,13 +1304,13 @@ export class UserCodeCCSet extends UserCodeCC { public userId: number; public userIdStatus: UserIDStatus; - public userCode: string | Buffer; + public userCode: string | Uint8Array; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.concat([ - Buffer.from([this.userId, this.userIdStatus]), + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.concat([ + Bytes.from([this.userId, this.userIdStatus]), typeof this.userCode === "string" - ? Buffer.from(this.userCode, "ascii") + ? Bytes.from(this.userCode, "ascii") : this.userCode, ]); return super.serialize(ctx); @@ -1328,7 +1332,7 @@ export class UserCodeCCSet extends UserCodeCC { export interface UserCodeCCReportOptions { userId: number; userIdStatus: UserIDStatus; - userCode?: string | Buffer; + userCode?: string | Bytes; } @CCCommand(UserCodeCommand.Report) @@ -1349,7 +1353,7 @@ export class UserCodeCCReport extends UserCodeCC validatePayload(raw.payload.length >= 2); const userId = raw.payload[0]; const userIdStatus: UserIDStatus = raw.payload[1]; - let userCode: string | Buffer; + let userCode: string | Bytes; if ( raw.payload.length === 2 @@ -1390,7 +1394,7 @@ export class UserCodeCCReport extends UserCodeCC public readonly userId: number; public readonly userIdStatus: UserIDStatus; - public readonly userCode: string | Buffer; + public readonly userCode: string | Bytes; public persistValues(ctx: PersistValuesContext): boolean { if (!super.persistValues(ctx)) return false; @@ -1405,16 +1409,16 @@ export class UserCodeCCReport extends UserCodeCC return true; } - public serialize(ctx: CCEncodingContext): Buffer { - let userCodeBuffer: Buffer; + public serialize(ctx: CCEncodingContext): Bytes { + let userCodeBuffer: Bytes; if (typeof this.userCode === "string") { - userCodeBuffer = Buffer.from(this.userCode, "ascii"); + userCodeBuffer = Bytes.from(this.userCode, "ascii"); } else { userCodeBuffer = this.userCode; } - this.payload = Buffer.concat([ - Buffer.from([this.userId, this.userIdStatus]), + this.payload = Bytes.concat([ + Bytes.from([this.userId, this.userIdStatus]), userCodeBuffer, ]); return super.serialize(ctx); @@ -1464,8 +1468,8 @@ export class UserCodeCCGet extends UserCodeCC { public userId: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.userId]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.userId]); return super.serialize(ctx); } @@ -1517,8 +1521,8 @@ export class UserCodeCCUsersNumberReport extends UserCodeCC { @ccValue(UserCodeCCValues.supportedUsers) public readonly supportedUsers: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.allocUnsafe(3); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = new Bytes(3); // If the node implements more than 255 users, this field MUST be set to 255 this.payload[0] = Math.min(255, this.supportedUsers); this.payload.writeUInt16BE(this.supportedUsers, 1); @@ -1620,7 +1624,7 @@ export class UserCodeCCCapabilitiesReport extends UserCodeCC { offset += 1; validatePayload(raw.payload.length >= offset + keysBitMaskLength); - const supportedASCIIChars = Buffer.from( + const supportedASCIIChars = Bytes.from( parseBitMask( raw.payload.subarray(offset, offset + keysBitMaskLength), 0, @@ -1664,7 +1668,7 @@ export class UserCodeCCCapabilitiesReport extends UserCodeCC { @ccValue(UserCodeCCValues.supportedASCIIChars) public readonly supportedASCIIChars: string; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { const supportedStatusesBitmask = encodeBitMask( this.supportedUserIDStatuses, undefined, @@ -1690,12 +1694,12 @@ export class UserCodeCCCapabilitiesReport extends UserCodeCC { const supportedKeysBitmask = encodeBitMask(keysAsNumbers, undefined, 0); const controlByte3 = supportedKeysBitmask.length & 0b000_11111; - this.payload = Buffer.concat([ - Buffer.from([controlByte1]), + this.payload = Bytes.concat([ + Bytes.from([controlByte1]), supportedStatusesBitmask, - Buffer.from([controlByte2]), + Bytes.from([controlByte2]), supportedKeypadModesBitmask, - Buffer.from([controlByte3]), + Bytes.from([controlByte3]), supportedKeysBitmask, ]); return super.serialize(ctx); @@ -1762,8 +1766,8 @@ export class UserCodeCCKeypadModeSet extends UserCodeCC { public keypadMode: KeypadMode; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.keypadMode]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.keypadMode]); return super.serialize(ctx); } @@ -1826,8 +1830,8 @@ export class UserCodeCCKeypadModeReport extends UserCodeCC { @ccValue(UserCodeCCValues.keypadMode) public readonly keypadMode: KeypadMode; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.keypadMode]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.keypadMode]); return super.serialize(ctx); } @@ -1879,10 +1883,10 @@ export class UserCodeCCAdminCodeSet extends UserCodeCC { public adminCode: string; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.concat([ - Buffer.from([this.adminCode.length & 0b1111]), - Buffer.from(this.adminCode, "ascii"), + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.concat([ + Bytes.from([this.adminCode.length & 0b1111]), + Bytes.from(this.adminCode, "ascii"), ]); return super.serialize(ctx); } @@ -1929,10 +1933,10 @@ export class UserCodeCCAdminCodeReport extends UserCodeCC { @ccValue(UserCodeCCValues.adminCode) public readonly adminCode: string; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.concat([ - Buffer.from([this.adminCode.length & 0b1111]), - Buffer.from(this.adminCode, "ascii"), + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.concat([ + Bytes.from([this.adminCode.length & 0b1111]), + Bytes.from(this.adminCode, "ascii"), ]); return super.serialize(ctx); } @@ -1979,8 +1983,8 @@ export class UserCodeCCUserCodeChecksumReport extends UserCodeCC { @ccValue(UserCodeCCValues.userCodeChecksum) public readonly userCodeChecksum: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.allocUnsafe(2); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = new Bytes(2); this.payload.writeUInt16BE(this.userCodeChecksum, 0); return super.serialize(ctx); } @@ -2035,24 +2039,24 @@ export class UserCodeCCExtendedUserCodeSet extends UserCodeCC { public userCodes: UserCodeCCSetOptions[]; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { const userCodeBuffers = this.userCodes.map((code) => { - const ret = Buffer.concat([ - Buffer.from([ + const ret = Bytes.concat([ + Bytes.from([ 0, 0, code.userIdStatus, code.userCode?.length ?? 0, ]), - Buffer.isBuffer(code.userCode) + isUint8Array(code.userCode) ? code.userCode - : Buffer.from(code.userCode ?? "", "ascii"), + : Bytes.from(code.userCode ?? "", "ascii"), ]); ret.writeUInt16BE(code.userId, 0); return ret; }); - this.payload = Buffer.concat([ - Buffer.from([this.userCodes.length]), + this.payload = Bytes.concat([ + Bytes.from([this.userCodes.length]), ...userCodeBuffers, ]); return super.serialize(ctx); @@ -2188,8 +2192,8 @@ export class UserCodeCCExtendedUserCodeGet extends UserCodeCC { public userId: number; public reportMore: boolean; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([0, 0, this.reportMore ? 1 : 0]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([0, 0, this.reportMore ? 1 : 0]); this.payload.writeUInt16BE(this.userId, 0); return super.serialize(ctx); } diff --git a/packages/cc/src/cc/VersionCC.ts b/packages/cc/src/cc/VersionCC.ts index 13c0ac0a251b..15241644b307 100644 --- a/packages/cc/src/cc/VersionCC.ts +++ b/packages/cc/src/cc/VersionCC.ts @@ -21,6 +21,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName, num2hex, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { CCAPI, PhysicalCCAPI } from "../lib/API"; @@ -206,7 +207,7 @@ export const VersionCCValues = Object.freeze({ }), }); -function parseVersion(buffer: Buffer): string { +function parseVersion(buffer: Uint8Array): string { if (buffer[0] === 0 && buffer[1] === 0 && buffer[2] === 0) return "unused"; return `${buffer[0]}.${buffer[1]}.${buffer[2]}`; } @@ -722,8 +723,8 @@ export class VersionCCReport extends VersionCC { @ccValue(VersionCCValues.hardwareVersion) public readonly hardwareVersion: number | undefined; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ this.libraryType, ...this.protocolVersion .split(".") @@ -738,7 +739,7 @@ export class VersionCCReport extends VersionCC { ]); if (this.firmwareVersions.length > 1) { - const firmwaresBuffer = Buffer.allocUnsafe( + const firmwaresBuffer = new Bytes( (this.firmwareVersions.length - 1) * 2, ); for (let i = 1; i < this.firmwareVersions.length; i++) { @@ -748,7 +749,7 @@ export class VersionCCReport extends VersionCC { firmwaresBuffer[2 * (i - 1)] = major; firmwaresBuffer[2 * (i - 1) + 1] = minor; } - this.payload = Buffer.concat([this.payload, firmwaresBuffer]); + this.payload = Bytes.concat([this.payload, firmwaresBuffer]); } return super.serialize(ctx); @@ -811,8 +812,8 @@ export class VersionCCCommandClassReport extends VersionCC { public ccVersion: number; public requestedCC: CommandClasses; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.requestedCC, this.ccVersion]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.requestedCC, this.ccVersion]); return super.serialize(ctx); } @@ -868,8 +869,8 @@ export class VersionCCCommandClassGet extends VersionCC { public requestedCC: CommandClasses; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.requestedCC]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.requestedCC]); return super.serialize(ctx); } @@ -913,8 +914,8 @@ export class VersionCCCapabilitiesReport extends VersionCC { @ccValue(VersionCCValues.supportsZWaveSoftwareGet) public supportsZWaveSoftwareGet: boolean; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ (this.supportsZWaveSoftwareGet ? 0b100 : 0) | 0b11, ]); return super.serialize(ctx); diff --git a/packages/cc/src/cc/WakeUpCC.ts b/packages/cc/src/cc/WakeUpCC.ts index d209b426df5a..58f73ac5898a 100644 --- a/packages/cc/src/cc/WakeUpCC.ts +++ b/packages/cc/src/cc/WakeUpCC.ts @@ -15,6 +15,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { clamp } from "alcalzone-shared/math"; @@ -385,8 +386,8 @@ export class WakeUpCCIntervalSet extends WakeUpCC { public wakeUpInterval: number; public controllerNodeId: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ 0, 0, 0, // placeholder diff --git a/packages/cc/src/cc/WindowCoveringCC.ts b/packages/cc/src/cc/WindowCoveringCC.ts index eb5e026b001c..b471311e58bb 100644 --- a/packages/cc/src/cc/WindowCoveringCC.ts +++ b/packages/cc/src/cc/WindowCoveringCC.ts @@ -17,6 +17,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host"; +import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { @@ -716,7 +717,7 @@ export class WindowCoveringCCSupportedReport extends WindowCoveringCC { @ccValue(WindowCoveringCCValues.supportedParameters) public readonly supportedParameters: readonly WindowCoveringParameter[]; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { const bitmask = encodeBitMask( this.supportedParameters, undefined, @@ -724,8 +725,8 @@ export class WindowCoveringCCSupportedReport extends WindowCoveringCC { ).subarray(0, 15); const numBitmaskBytes = bitmask.length & 0b1111; - this.payload = Buffer.concat([ - Buffer.from([numBitmaskBytes]), + this.payload = Bytes.concat([ + Bytes.from([numBitmaskBytes]), bitmask.subarray(0, numBitmaskBytes), ]); @@ -866,8 +867,8 @@ export class WindowCoveringCCGet extends WindowCoveringCC { public parameter: WindowCoveringParameter; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.parameter]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.parameter]); return super.serialize(ctx); } @@ -940,9 +941,9 @@ export class WindowCoveringCCSet extends WindowCoveringCC { }[]; public duration: Duration | undefined; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { const numEntries = this.targetValues.length & 0b11111; - this.payload = Buffer.allocUnsafe(2 + numEntries * 2); + this.payload = new Bytes(2 + numEntries * 2); this.payload[0] = numEntries; for (let i = 0; i < numEntries; i++) { @@ -1020,8 +1021,8 @@ export class WindowCoveringCCStartLevelChange extends WindowCoveringCC { public direction: keyof typeof LevelChangeDirection; public duration: Duration | undefined; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ this.direction === "down" ? 0b0100_0000 : 0b0000_0000, this.parameter, (this.duration ?? Duration.default()).serializeSet(), @@ -1077,8 +1078,8 @@ export class WindowCoveringCCStopLevelChange extends WindowCoveringCC { public parameter: WindowCoveringParameter; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.parameter]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.parameter]); return super.serialize(ctx); } diff --git a/packages/cc/src/cc/ZWavePlusCC.ts b/packages/cc/src/cc/ZWavePlusCC.ts index d8bf67d13daf..4db72a190cbf 100644 --- a/packages/cc/src/cc/ZWavePlusCC.ts +++ b/packages/cc/src/cc/ZWavePlusCC.ts @@ -11,6 +11,7 @@ import type { CCParsingContext, GetValueDB, } from "@zwave-js/host/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { getEnumMemberName, num2hex, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { CCAPI, PhysicalCCAPI } from "../lib/API"; @@ -221,8 +222,8 @@ export class ZWavePlusCCReport extends ZWavePlusCC { @ccValue(ZWavePlusCCValues.userIcon) public userIcon: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ this.zwavePlusVersion, this.roleType, this.nodeType, diff --git a/packages/cc/src/cc/ZWaveProtocolCC.ts b/packages/cc/src/cc/ZWaveProtocolCC.ts index f9b1d4a378a5..26c32ca1212d 100644 --- a/packages/cc/src/cc/ZWaveProtocolCC.ts +++ b/packages/cc/src/cc/ZWaveProtocolCC.ts @@ -22,6 +22,7 @@ import { validatePayload, } from "@zwave-js/core"; import type { CCEncodingContext, CCParsingContext } from "@zwave-js/host"; +import { Bytes } from "@zwave-js/shared/safe"; import { type CCRaw, CommandClass } from "../lib/CommandClass"; import { CCCommand, @@ -120,7 +121,7 @@ export class ZWaveProtocolCCNodeInformationFrame extends ZWaveProtocolCC public supportsBeaming: boolean; public supportedCCs: CommandClasses[]; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { this.payload = encodeNodeInformationFrame(this); return super.serialize(ctx); } @@ -166,8 +167,8 @@ export class ZWaveProtocolCCAssignIDs extends ZWaveProtocolCC { public assignedNodeId: number; public homeId: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.allocUnsafe(5); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = new Bytes(5); this.payload[0] = this.assignedNodeId; this.payload.writeUInt32BE(this.homeId, 1); return super.serialize(ctx); @@ -239,13 +240,13 @@ export class ZWaveProtocolCCFindNodesInRange extends ZWaveProtocolCC { public wakeUpTime: WakeUpTime; public dataRate: ZWaveDataRate; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { const nodesBitmask = encodeBitMask(this.candidateNodeIds, MAX_NODES); const speedAndLength = 0b1000_0000 | nodesBitmask.length; - this.payload = Buffer.concat([ - Buffer.from([speedAndLength]), + this.payload = Bytes.concat([ + Bytes.from([speedAndLength]), nodesBitmask, - Buffer.from([this.wakeUpTime, this.dataRate]), + Bytes.from([this.wakeUpTime, this.dataRate]), ]); return super.serialize(ctx); } @@ -296,14 +297,14 @@ export class ZWaveProtocolCCRangeInfo extends ZWaveProtocolCC { public neighborNodeIds: number[]; public wakeUpTime?: WakeUpTime; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { const nodesBitmask = encodeBitMask(this.neighborNodeIds, MAX_NODES); - this.payload = Buffer.concat([ - Buffer.from([nodesBitmask.length]), + this.payload = Bytes.concat([ + Bytes.from([nodesBitmask.length]), nodesBitmask, this.wakeUpTime != undefined - ? Buffer.from([this.wakeUpTime]) - : Buffer.alloc(0), + ? Bytes.from([this.wakeUpTime]) + : new Bytes(), ]); return super.serialize(ctx); } @@ -342,8 +343,8 @@ export class ZWaveProtocolCCCommandComplete extends ZWaveProtocolCC { public sequenceNumber: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.sequenceNumber]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.sequenceNumber]); return super.serialize(ctx); } } @@ -394,8 +395,8 @@ export class ZWaveProtocolCCTransferPresentation extends ZWaveProtocolCC { public includeNode: boolean; public excludeNode: boolean; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ (this.supportsNWI ? 0b0001 : 0) | (this.excludeNode ? 0b0010 : 0) | (this.includeNode ? 0b0100 : 0), @@ -473,9 +474,9 @@ export class ZWaveProtocolCCTransferNodeInformation extends ZWaveProtocolCC public supportsSecurity: boolean; public supportsBeaming: boolean; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.concat([ - Buffer.from([this.sequenceNumber, this.sourceNodeId]), + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.concat([ + Bytes.from([this.sequenceNumber, this.sourceNodeId]), encodeNodeProtocolInfoAndDeviceClass(this), ]); return super.serialize(ctx); @@ -526,10 +527,10 @@ export class ZWaveProtocolCCTransferRangeInformation extends ZWaveProtocolCC { public testedNodeId: number; public neighborNodeIds: number[]; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { const nodesBitmask = encodeBitMask(this.neighborNodeIds, MAX_NODES); - this.payload = Buffer.concat([ - Buffer.from([ + this.payload = Bytes.concat([ + Bytes.from([ this.sequenceNumber, this.testedNodeId, nodesBitmask.length, @@ -569,8 +570,8 @@ export class ZWaveProtocolCCTransferEnd extends ZWaveProtocolCC { public status: NetworkTransferStatus; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.status]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.status]); return super.serialize(ctx); } } @@ -635,11 +636,11 @@ export class ZWaveProtocolCCAssignReturnRoute extends ZWaveProtocolCC { public destinationWakeUp: WakeUpTime; public destinationSpeed: ZWaveDataRate; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { const routeByte = (this.routeIndex << 4) | this.repeaters.length; const speedMask = dataRate2Bitmask(this.destinationSpeed); const speedByte = (speedMask << 3) | (this.destinationWakeUp << 1); - this.payload = Buffer.from([ + this.payload = Bytes.from([ this.destinationNodeId, routeByte, ...this.repeaters, @@ -712,9 +713,9 @@ export class ZWaveProtocolCCNewNodeRegistered extends ZWaveProtocolCC public supportsBeaming: boolean; public supportedCCs: CommandClasses[]; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.concat([ - Buffer.from([this.newNodeId]), + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.concat([ + Bytes.from([this.newNodeId]), encodeNodeInformationFrame(this), ]); return super.serialize(ctx); @@ -758,10 +759,10 @@ export class ZWaveProtocolCCNewRangeRegistered extends ZWaveProtocolCC { public testedNodeId: number; public neighborNodeIds: number[]; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { const nodesBitmask = encodeBitMask(this.neighborNodeIds, MAX_NODES); - this.payload = Buffer.concat([ - Buffer.from([this.testedNodeId, nodesBitmask.length]), + this.payload = Bytes.concat([ + Bytes.from([this.testedNodeId, nodesBitmask.length]), nodesBitmask, ]); return super.serialize(ctx); @@ -801,8 +802,8 @@ export class ZWaveProtocolCCTransferNewPrimaryControllerComplete public genericDeviceClass: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.genericDeviceClass]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.genericDeviceClass]); return super.serialize(ctx); } } @@ -847,8 +848,8 @@ export class ZWaveProtocolCCSUCNodeID extends ZWaveProtocolCC { public sucNodeId: number; public isSIS: boolean; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.sucNodeId, this.isSIS ? 0b1 : 0]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.sucNodeId, this.isSIS ? 0b1 : 0]); return super.serialize(ctx); } } @@ -884,8 +885,8 @@ export class ZWaveProtocolCCSetSUC extends ZWaveProtocolCC { public enableSIS: boolean; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([0x01, this.enableSIS ? 0b1 : 0]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([0x01, this.enableSIS ? 0b1 : 0]); return super.serialize(ctx); } } @@ -925,8 +926,8 @@ export class ZWaveProtocolCCSetSUCAck extends ZWaveProtocolCC { public accepted: boolean; public isSIS: boolean; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ this.accepted ? 0x01 : 0x00, this.isSIS ? 0b1 : 0, ]); @@ -976,8 +977,8 @@ export class ZWaveProtocolCCStaticRouteRequest extends ZWaveProtocolCC { public nodeIds: number[]; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.alloc(5, 0); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.alloc(5, 0); for (let i = 0; i < this.nodeIds.length && i < 5; i++) { this.payload[i] = this.nodeIds[i]; } @@ -1011,8 +1012,8 @@ export class ZWaveProtocolCCLost extends ZWaveProtocolCC { public lostNodeId: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.lostNodeId]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.lostNodeId]); return super.serialize(ctx); } } @@ -1049,8 +1050,8 @@ export class ZWaveProtocolCCAcceptLost extends ZWaveProtocolCC { public accepted: boolean; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.accepted ? 0x05 : 0x04]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.accepted ? 0x05 : 0x04]); return super.serialize(ctx); } } @@ -1116,8 +1117,8 @@ export class ZWaveProtocolCCNOPPower extends ZWaveProtocolCC { // Power dampening in (negative) dBm. A value of 2 means -2 dBm. public powerDampening: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([0, this.powerDampening]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([0, this.powerDampening]); return super.serialize(ctx); } } @@ -1155,8 +1156,8 @@ export class ZWaveProtocolCCReservedIDs extends ZWaveProtocolCC { public reservedNodeIDs: number[]; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ this.reservedNodeIDs.length, ...this.reservedNodeIDs, ]); @@ -1194,8 +1195,8 @@ export class ZWaveProtocolCCReserveNodeIDs extends ZWaveProtocolCC { public numNodeIDs: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.numNodeIDs]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.numNodeIDs]); return super.serialize(ctx); } } @@ -1234,8 +1235,8 @@ export class ZWaveProtocolCCNodesExistReply extends ZWaveProtocolCC { public nodeMaskType: number; public nodeListUpdated: boolean; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ this.nodeMaskType, this.nodeListUpdated ? 0x01 : 0x00, ]); @@ -1290,8 +1291,8 @@ export class ZWaveProtocolCCNodesExist extends ZWaveProtocolCC { public nodeMaskType: number; public nodeIDs: number[]; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ this.nodeMaskType, this.nodeIDs.length, ...this.nodeIDs, @@ -1334,8 +1335,8 @@ export class ZWaveProtocolCCSetNWIMode extends ZWaveProtocolCC { public enabled: boolean; public timeoutMinutes?: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([ this.enabled ? 0x01 : 0x00, this.timeoutMinutes ?? 0x00, ]); @@ -1382,8 +1383,8 @@ export class ZWaveProtocolCCAssignReturnRoutePriority extends ZWaveProtocolCC { public targetNodeId: number; public routeNumber: number; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from([this.targetNodeId, this.routeNumber]); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from([this.targetNodeId, this.routeNumber]); return super.serialize(ctx); } } @@ -1395,7 +1396,7 @@ export class ZWaveProtocolCCAssignSUCReturnRoutePriority // @publicAPI export interface ZWaveProtocolCCSmartStartIncludedNodeInformationOptions { - nwiHomeId: Buffer; + nwiHomeId: Uint8Array; } @CCCommand(ZWaveProtocolCommand.SmartStartIncludedNodeInformation) @@ -1422,7 +1423,7 @@ export class ZWaveProtocolCCSmartStartIncludedNodeInformation ctx: CCParsingContext, ): ZWaveProtocolCCSmartStartIncludedNodeInformation { validatePayload(raw.payload.length >= 4); - const nwiHomeId: Buffer = raw.payload.subarray(0, 4); + const nwiHomeId = raw.payload.subarray(0, 4); return new ZWaveProtocolCCSmartStartIncludedNodeInformation({ nodeId: ctx.sourceNodeId, @@ -1430,10 +1431,10 @@ export class ZWaveProtocolCCSmartStartIncludedNodeInformation }); } - public nwiHomeId: Buffer; + public nwiHomeId: Uint8Array; - public serialize(ctx: CCEncodingContext): Buffer { - this.payload = Buffer.from(this.nwiHomeId); + public serialize(ctx: CCEncodingContext): Bytes { + this.payload = Bytes.from(this.nwiHomeId); return super.serialize(ctx); } } diff --git a/packages/cc/src/cc/manufacturerProprietary/FibaroCC.ts b/packages/cc/src/cc/manufacturerProprietary/FibaroCC.ts index b7d2a5283568..9b90f9fc4268 100644 --- a/packages/cc/src/cc/manufacturerProprietary/FibaroCC.ts +++ b/packages/cc/src/cc/manufacturerProprietary/FibaroCC.ts @@ -17,7 +17,7 @@ import type { GetDeviceConfig, GetValueDB, } from "@zwave-js/host/safe"; -import { pick } from "@zwave-js/shared"; +import { Bytes, pick } from "@zwave-js/shared"; import { validateArgs } from "@zwave-js/transformers"; import { isArray } from "alcalzone-shared/typeguards"; import { @@ -283,7 +283,7 @@ export class FibaroCC extends ManufacturerProprietaryCC { } } - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { if (this.fibaroCCId == undefined) { throw new ZWaveError( "Cannot serialize a Fibaro CC without a Fibaro CC ID", @@ -295,8 +295,8 @@ export class FibaroCC extends ManufacturerProprietaryCC { ZWaveErrorCodes.CC_Invalid, ); } - this.payload = Buffer.concat([ - Buffer.from([this.fibaroCCId, this.fibaroCCCommand]), + this.payload = Bytes.concat([ + Bytes.from([this.fibaroCCId, this.fibaroCCCommand]), this.payload, ]); return super.serialize(ctx); @@ -398,10 +398,10 @@ export class FibaroVenetianBlindCCSet extends FibaroVenetianBlindCC { public position: number | undefined; public tilt: number | undefined; - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { const controlByte = (this.position != undefined ? 0b10 : 0) | (this.tilt != undefined ? 0b01 : 0); - this.payload = Buffer.from([ + this.payload = Bytes.from([ controlByte, this.position ?? 0, this.tilt ?? 0, diff --git a/packages/cc/src/lib/CommandClass.ts b/packages/cc/src/lib/CommandClass.ts index 7ae4b2f9668b..5be7e2358499 100644 --- a/packages/cc/src/lib/CommandClass.ts +++ b/packages/cc/src/lib/CommandClass.ts @@ -47,6 +47,7 @@ import type { LookupManufacturer, } from "@zwave-js/host"; import { + Bytes, type JSONObject, buffer2hex, getEnumMemberName, @@ -81,7 +82,7 @@ import { export interface CommandClassOptions extends CCAddress { ccId?: number; // Used to overwrite the declared CC ID ccCommand?: number; // undefined = NoOp - payload?: Buffer; + payload?: Uint8Array; } // Defines the necessary traits an endpoint passed to a CC instance must have @@ -156,30 +157,30 @@ export class CCRaw { public constructor( public ccId: CommandClasses, public ccCommand: number | undefined, - public payload: Buffer, + public payload: Bytes, ) {} - public static parse(data: Buffer): CCRaw { + public static parse(data: Uint8Array): CCRaw { const { ccId, bytesRead: ccIdLength } = parseCCId(data); // There are so few exceptions that we can handle them here manually if (ccId === CommandClasses["No Operation"]) { - return new CCRaw(ccId, undefined, Buffer.allocUnsafe(0)); + return new CCRaw(ccId, undefined, new Bytes()); } let ccCommand: number | undefined = data[ccIdLength]; - let payload = data.subarray(ccIdLength + 1); + let payload = Bytes.view(data.subarray(ccIdLength + 1)); if (ccId === CommandClasses["Transport Service"]) { // Transport Service only uses the higher 5 bits for the command // and re-uses the lower 3 bits of the ccCommand as payload - payload = Buffer.concat([ - Buffer.from([ccCommand & 0b111]), + payload = Bytes.concat([ + Bytes.from([ccCommand & 0b111]), payload, ]); ccCommand = ccCommand & 0b11111_000; } else if (ccId === CommandClasses["Manufacturer Proprietary"]) { // ManufacturerProprietaryCC has no CC command, so the first // payload byte is stored in ccCommand. - payload = Buffer.concat([ - Buffer.from([ccCommand]), + payload = Bytes.concat([ + Bytes.from([ccCommand]), payload, ]); ccCommand = undefined; @@ -188,7 +189,7 @@ export class CCRaw { return new CCRaw(ccId, ccCommand, payload); } - public withPayload(payload: Buffer): CCRaw { + public withPayload(payload: Bytes): CCRaw { return new CCRaw(this.ccId, this.ccCommand, payload); } } @@ -202,18 +203,18 @@ export class CommandClass implements CCId { endpointIndex = 0, ccId = getCommandClass(this), ccCommand = getCCCommand(this), - payload = Buffer.allocUnsafe(0), + payload = new Uint8Array(), } = options; this.nodeId = nodeId; this.endpointIndex = endpointIndex; this.ccId = ccId; this.ccCommand = ccCommand; - this.payload = payload; + this.payload = Bytes.view(payload); } public static parse( - data: Buffer, + data: Uint8Array, ctx: CCParsingContext, ): CommandClass { const raw = CCRaw.parse(data); @@ -288,8 +289,7 @@ export class CommandClass implements CCId { /** The ID of the target node(s) */ public nodeId!: number | MulticastDestination; - // Work around https://github.com/Microsoft/TypeScript/issues/27555 - public payload!: Buffer; + public payload: Bytes; /** Which endpoint of the node this CC belongs to. 0 for the root device. */ public endpointIndex: number; @@ -352,10 +352,10 @@ export class CommandClass implements CCId { * Serializes this CommandClass to be embedded in a message payload or another CC */ // eslint-disable-next-line @typescript-eslint/no-unused-vars - public serialize(ctx: CCEncodingContext): Buffer { + public serialize(ctx: CCEncodingContext): Bytes { // NoOp CCs have no command and no payload if (this.ccId === CommandClasses["No Operation"]) { - return Buffer.from([this.ccId]); + return Bytes.from([this.ccId]); } else if (this.ccCommand == undefined) { throw new ZWaveError( "Cannot serialize a Command Class without a command", @@ -365,11 +365,11 @@ export class CommandClass implements CCId { const payloadLength = this.payload.length; const ccIdLength = this.isExtended() ? 2 : 1; - const data = Buffer.allocUnsafe(ccIdLength + 1 + payloadLength); + const data = new Bytes(ccIdLength + 1 + payloadLength); data.writeUIntBE(this.ccId, 0, ccIdLength); data[ccIdLength] = this.ccCommand; if (payloadLength > 0 /* implies payload != undefined */) { - this.payload.copy(data, 1 + ccIdLength); + data.set(this.payload, 1 + ccIdLength); } return data; } @@ -435,7 +435,7 @@ export class CommandClass implements CCId { ret.ccCommand = num2hex(this.ccCommand); } if (this.payload.length > 0) { - ret.payload = "0x" + this.payload.toString("hex"); + ret.payload = buffer2hex(this.payload); } return ret; } diff --git a/packages/cc/src/lib/NotificationEventPayload.ts b/packages/cc/src/lib/NotificationEventPayload.ts index b5990aef4ce6..b4e05223cfc2 100644 --- a/packages/cc/src/lib/NotificationEventPayload.ts +++ b/packages/cc/src/lib/NotificationEventPayload.ts @@ -2,7 +2,10 @@ import type { Duration } from "@zwave-js/core"; import type { CommandClass } from "./CommandClass"; export interface NotificationEventPayload { - toNotificationEventParameters(): Buffer | Duration | Record; + toNotificationEventParameters(): + | Uint8Array + | Duration + | Record; } /** diff --git a/packages/cc/src/lib/Security2/Extension.ts b/packages/cc/src/lib/Security2/Extension.ts index d0a3771f783f..c579b71639d1 100644 --- a/packages/cc/src/lib/Security2/Extension.ts +++ b/packages/cc/src/lib/Security2/Extension.ts @@ -5,9 +5,11 @@ import { validatePayload, } from "@zwave-js/core/safe"; import { + Bytes, type TypedClassDecorator, buffer2hex, getEnumMemberName, + isUint8Array, } from "@zwave-js/shared/safe"; import "reflect-metadata"; @@ -138,11 +140,11 @@ export function validateS2Extension( interface Security2ExtensionCreationOptions { critical: boolean; - payload?: Buffer; + payload?: Uint8Array; } interface Security2ExtensionDeserializationOptions { - data: Buffer; + data: Uint8Array; } type Security2ExtensionOptions = @@ -152,7 +154,7 @@ type Security2ExtensionOptions = function gotDeserializationOptions( options: Record, ): options is Security2ExtensionDeserializationOptions { - return "data" in options && Buffer.isBuffer(options.data); + return "data" in options && isUint8Array(options.data); } export class Security2Extension { @@ -168,22 +170,22 @@ export class Security2Extension { } else { this.type = getExtensionType(this); this.critical = options.critical; - this.payload = options.payload ?? Buffer.allocUnsafe(0); + this.payload = options.payload ?? new Uint8Array(); } } public type: S2ExtensionType; public critical: boolean; public readonly moreToFollow?: boolean; - public payload: Buffer; + public payload: Uint8Array; public isEncrypted(): boolean { return false; } - public serialize(moreToFollow: boolean): Buffer { - return Buffer.concat([ - Buffer.from([ + public serialize(moreToFollow: boolean): Bytes { + return Bytes.concat([ + Bytes.from([ 2 + this.payload.length, (moreToFollow ? 0b1000_0000 : 0) | (this.critical ? 0b0100_0000 : 0) @@ -195,7 +197,7 @@ export class Security2Extension { /** Returns the number of bytes the first extension in the buffer occupies */ public static getExtensionLength( - data: Buffer, + data: Uint8Array, ): { expected?: number; actual: number } { const actual = data[0]; let expected: number | undefined; @@ -230,14 +232,14 @@ export class Security2Extension { * It is assumed that the buffer has been checked beforehand */ public static getConstructor( - data: Buffer, + data: Uint8Array, ): Security2ExtensionConstructor { const type = data[1] & 0b11_1111; return getS2ExtensionConstructor(type) ?? Security2Extension; } /** Creates an instance of the S2 extension that is serialized in the given buffer */ - public static from(data: Buffer): Security2Extension { + public static from(data: Uint8Array): Security2Extension { const Constructor = Security2Extension.getConstructor(data); try { const ret = new Constructor({ data }); @@ -258,7 +260,7 @@ export class Security2Extension { · type: ${getEnumMemberName(S2ExtensionType, this.type)}`; if (this.payload.length > 0) { ret += ` - payload: 0x${this.payload.toString("hex")}`; + payload: ${buffer2hex(this.payload)}`; } return ret; } @@ -268,7 +270,7 @@ export class InvalidExtension extends Security2Extension { } interface SPANExtensionOptions { - senderEI: Buffer; + senderEI: Uint8Array; } @extensionType(S2ExtensionType.SPAN) @@ -294,25 +296,25 @@ export class SPANExtension extends Security2Extension { } } - public senderEI: Buffer; + public senderEI: Uint8Array; public static readonly expectedLength = 18; - public serialize(moreToFollow: boolean): Buffer { + public serialize(moreToFollow: boolean): Bytes { this.payload = this.senderEI; return super.serialize(moreToFollow); } public toLogEntry(): string { let ret = super.toLogEntry().replace(/^ payload:.+$/m, ""); - ret += ` sender EI: 0x${this.senderEI.toString("hex")}`; + ret += ` sender EI: ${buffer2hex(this.senderEI)}`; return ret; } } interface MPANExtensionOptions { groupId: number; - innerMPANState: Buffer; + innerMPANState: Uint8Array; } @extensionType(S2ExtensionType.MPAN) @@ -341,7 +343,7 @@ export class MPANExtension extends Security2Extension { } public groupId: number; - public innerMPANState: Buffer; + public innerMPANState: Uint8Array; public isEncrypted(): boolean { return true; @@ -349,9 +351,9 @@ export class MPANExtension extends Security2Extension { public static readonly expectedLength = 19; - public serialize(moreToFollow: boolean): Buffer { - this.payload = Buffer.concat([ - Buffer.from([this.groupId]), + public serialize(moreToFollow: boolean): Bytes { + this.payload = Bytes.concat([ + [this.groupId], this.innerMPANState, ]); return super.serialize(moreToFollow); @@ -394,8 +396,8 @@ export class MGRPExtension extends Security2Extension { public static readonly expectedLength = 3; - public serialize(moreToFollow: boolean): Buffer { - this.payload = Buffer.from([this.groupId]); + public serialize(moreToFollow: boolean): Bytes { + this.payload = Bytes.from([this.groupId]); return super.serialize(moreToFollow); } diff --git a/packages/cc/src/lib/_Types.ts b/packages/cc/src/lib/_Types.ts index 870ed6749a97..3e947e5c61d7 100644 --- a/packages/cc/src/lib/_Types.ts +++ b/packages/cc/src/lib/_Types.ts @@ -676,7 +676,7 @@ export interface DoorLockLoggingRecord { eventType: DoorLockLoggingEventType; label: string; userId?: number; - userCode?: string | Buffer; + userCode?: string | Uint8Array; } export enum DoorLockLoggingRecordStatus { diff --git a/packages/cc/src/lib/serializers.test.ts b/packages/cc/src/lib/serializers.test.ts index 167b28e3baa6..d4e0c715112b 100644 --- a/packages/cc/src/lib/serializers.test.ts +++ b/packages/cc/src/lib/serializers.test.ts @@ -78,7 +78,7 @@ test("encodeSwitchpoint() should throw when the switchpoint state is undefined", }); test("decodeSwitchpoint() should work correctly", (t) => { - t.deepEqual(decodeSwitchpoint(Buffer.from([15, 37, 0])), { + t.deepEqual(decodeSwitchpoint(Uint8Array.from([15, 37, 0])), { hour: 15, minute: 37, state: 0, diff --git a/packages/cc/src/lib/serializers.ts b/packages/cc/src/lib/serializers.ts index 769ef827664a..f7ac9f79e369 100644 --- a/packages/cc/src/lib/serializers.ts +++ b/packages/cc/src/lib/serializers.ts @@ -1,4 +1,5 @@ import { ZWaveError, ZWaveErrorCodes } from "@zwave-js/core"; +import { Bytes } from "@zwave-js/shared/safe"; import { clamp } from "alcalzone-shared/math"; import type { SetbackSpecialState, @@ -44,7 +45,7 @@ export function decodeSetbackState(val: number): SetbackState | undefined { * @publicAPI * Decodes a switch point used in a ClimateControlScheduleCC */ -export function decodeSwitchpoint(data: Buffer): Switchpoint { +export function decodeSwitchpoint(data: Uint8Array): Switchpoint { return { hour: data[0] & 0b000_11111, minute: data[1] & 0b00_111111, @@ -56,14 +57,14 @@ export function decodeSwitchpoint(data: Buffer): Switchpoint { * @publicAPI * Encodes a switch point to use in a ClimateControlScheduleCC */ -export function encodeSwitchpoint(point: Switchpoint): Buffer { +export function encodeSwitchpoint(point: Switchpoint): Bytes { if (point.state == undefined) { throw new ZWaveError( "The given Switchpoint is not valid!", ZWaveErrorCodes.CC_Invalid, ); } - return Buffer.from([ + return Bytes.from([ point.hour & 0b000_11111, point.minute & 0b00_111111, encodeSetbackState(point.state), @@ -74,7 +75,7 @@ export function encodeSwitchpoint(point: Switchpoint): Buffer { * @publicAPI * Decodes timezone information used in time related CCs */ -export function parseTimezone(data: Buffer): Timezone { +export function parseTimezone(data: Uint8Array): Timezone { const hourSign = !!(data[0] & 0b1000_0000); const hour = data[0] & 0b0111_1111; const minute = data[1]; @@ -93,7 +94,7 @@ export function parseTimezone(data: Buffer): Timezone { * @publicAPI * Decodes timezone information used in time related CCs */ -export function encodeTimezone(tz: Timezone): Buffer { +export function encodeTimezone(tz: Timezone): Bytes { if ( Math.abs(tz.standardOffset) >= 24 * 60 || Math.abs(tz.dstOffset) >= 24 * 60 @@ -111,7 +112,7 @@ export function encodeTimezone(tz: Timezone): Buffer { const deltaMinutes = Math.abs(delta); const deltaSign = delta < 0 ? 1 : 0; - return Buffer.from([ + return Bytes.from([ (hourSign << 7) | (hour & 0b0111_1111), minutes, (deltaSign << 7) | (deltaMinutes & 0b0111_1111), diff --git a/packages/config/src/devices/DeviceConfig.hash.test.ts b/packages/config/src/devices/DeviceConfig.hash.test.ts index 2c79292cfe64..911080f60608 100644 --- a/packages/config/src/devices/DeviceConfig.hash.test.ts +++ b/packages/config/src/devices/DeviceConfig.hash.test.ts @@ -1,4 +1,5 @@ import { CommandClasses } from "@zwave-js/core"; +import { isUint8Array } from "@zwave-js/shared"; import test from "ava"; import path from "node:path"; import { ConfigManager } from "../ConfigManager"; @@ -19,7 +20,7 @@ test("hash() works", async (t) => { t.not(config, undefined); const hash = config.getHash(); - t.true(Buffer.isBuffer(hash)); + t.true(isUint8Array(hash)); }); test("hash() changes when changing a parameter info", async (t) => { diff --git a/packages/config/src/devices/DeviceConfig.ts b/packages/config/src/devices/DeviceConfig.ts index 012104c35c85..4692b3729457 100644 --- a/packages/config/src/devices/DeviceConfig.ts +++ b/packages/config/src/devices/DeviceConfig.ts @@ -1,5 +1,6 @@ import { ZWaveError, ZWaveErrorCodes } from "@zwave-js/core"; import { + Bytes, type JSONObject, enumFilesRecursive, formatId, @@ -763,7 +764,7 @@ export class DeviceConfig { /** * Returns a hash code that can be used to check whether a device config has changed enough to require a re-interview. */ - public getHash(): Buffer { + public getHash(): Uint8Array { // We only need to compare the information that is persisted elsewhere: // - config parameters // - functional association settings @@ -902,7 +903,7 @@ export class DeviceConfig { hashable = sortObject(hashable); // And create a hash from it. This does not need to be cryptographically secure, just good enough to detect changes. - const buffer = Buffer.from(JSON.stringify(hashable), "utf8"); + const buffer = Bytes.from(JSON.stringify(hashable), "utf8"); const md5 = createHash("md5"); return md5.update(buffer).digest(); } diff --git a/packages/core/src/capabilities/NodeInfo.test.ts b/packages/core/src/capabilities/NodeInfo.test.ts index c3df00bec1f0..3b13d6f168ac 100644 --- a/packages/core/src/capabilities/NodeInfo.test.ts +++ b/packages/core/src/capabilities/NodeInfo.test.ts @@ -1,3 +1,4 @@ +import { Bytes } from "@zwave-js/shared/safe"; import test from "ava"; import { CommandClasses } from "./CommandClasses"; import { @@ -6,7 +7,7 @@ import { } from "./NodeInfo"; test("parseApplicationNodeInformation() should parse correctly", (t) => { - const payload = Buffer.from([ + const payload = Bytes.from([ 0x01, // Remote Controller 0x02, // Portable Scene Controller // Supported CCs @@ -27,7 +28,7 @@ test("parseApplicationNodeInformation() should parse correctly", (t) => { }); test("parseNodeUpdatePayload() should parse correctly", (t) => { - const payload = Buffer.from([ + const payload = Bytes.from([ 5, // NodeID 5, // remaining length 0x03, // Slave @@ -51,7 +52,7 @@ test("parseNodeUpdatePayload() should parse correctly", (t) => { }); test("parseNodeUpdatePayload() parses extended CCs correctly", (t) => { - const payload = Buffer.from([ + const payload = Bytes.from([ 5, // NodeID 9, // remaining length 0x03, diff --git a/packages/core/src/capabilities/NodeInfo.ts b/packages/core/src/capabilities/NodeInfo.ts index 3f74674b0148..5a1897eed2a9 100644 --- a/packages/core/src/capabilities/NodeInfo.ts +++ b/packages/core/src/capabilities/NodeInfo.ts @@ -1,3 +1,4 @@ +import { Bytes } from "@zwave-js/shared/safe"; import { sum } from "@zwave-js/shared/safe"; import { NodeIDType } from "../consts"; import { type BasicDeviceClass } from "../index_safe"; @@ -11,7 +12,7 @@ export interface ApplicationNodeInformation { } export function parseApplicationNodeInformation( - nif: Buffer, + nif: Uint8Array, ): ApplicationNodeInformation { validatePayload(nif.length >= 2); return { @@ -23,10 +24,10 @@ export function parseApplicationNodeInformation( export function encodeApplicationNodeInformation( nif: ApplicationNodeInformation, -): Buffer { +): Bytes { const ccList = encodeCCList(nif.supportedCCs, []); - return Buffer.concat([ - Buffer.from([nif.genericDeviceClass, nif.specificDeviceClass]), + return Bytes.concat([ + Bytes.from([nif.genericDeviceClass, nif.specificDeviceClass]), ccList, ]); } @@ -37,7 +38,7 @@ export interface NodeUpdatePayload extends ApplicationNodeInformation { } export function parseNodeUpdatePayload( - nif: Buffer, + nif: Uint8Array, nodeIdType: NodeIDType = NodeIDType.Short, ): NodeUpdatePayload { let offset = 0; @@ -61,12 +62,12 @@ export function parseNodeUpdatePayload( export function encodeNodeUpdatePayload( nif: NodeUpdatePayload, nodeIdType: NodeIDType = NodeIDType.Short, -): Buffer { +): Bytes { const ccList = encodeCCList(nif.supportedCCs, []); const nodeId = encodeNodeID(nif.nodeId, nodeIdType); - return Buffer.concat([ + return Bytes.concat([ nodeId, - Buffer.from([ + Bytes.from([ 3 + ccList.length, nif.basicDeviceClass, nif.genericDeviceClass, @@ -85,15 +86,16 @@ export function isExtendedCCId(ccId: CommandClasses): boolean { * @param offset The offset at which the CC id is located */ export function parseCCId( - payload: Buffer, + payload: Uint8Array, offset: number = 0, ): { ccId: CommandClasses; bytesRead: number } { const isExtended = isExtendedCCId(payload[offset]); validatePayload(payload.length >= offset + (isExtended ? 2 : 1)); + const view = Bytes.view(payload); if (isExtended) { - return { ccId: payload.readUInt16BE(offset), bytesRead: 2 }; + return { ccId: view.readUInt16BE(offset), bytesRead: 2 }; } else { - return { ccId: payload.readUInt8(offset), bytesRead: 1 }; + return { ccId: view.readUInt8(offset), bytesRead: 1 }; } } @@ -103,7 +105,7 @@ export function parseCCId( */ export function encodeCCId( ccId: CommandClasses, - payload: Buffer, + payload: Bytes, offset: number = 0, ): number { if (isExtendedCCId(ccId)) { @@ -115,7 +117,7 @@ export function encodeCCId( } } -export function parseCCList(payload: Buffer): { +export function parseCCList(payload: Uint8Array): { supportedCCs: CommandClasses[]; controlledCCs: CommandClasses[]; } { @@ -143,13 +145,13 @@ export function parseCCList(payload: Buffer): { export function encodeCCList( supportedCCs: readonly CommandClasses[], controlledCCs: readonly CommandClasses[], -): Buffer { +): Bytes { const bufferLength = sum(supportedCCs.map((cc) => (isExtendedCCId(cc) ? 2 : 1))) + (controlledCCs.length > 0 ? 1 : 0) // support/control mark + sum(controlledCCs.map((cc) => (isExtendedCCId(cc) ? 2 : 1))); - const ret = Buffer.allocUnsafe(bufferLength); + const ret = new Bytes(bufferLength); let offset = 0; for (const cc of supportedCCs) { offset += encodeCCId(cc, ret, offset); @@ -214,7 +216,7 @@ export type NodeInformationFrame = & ApplicationNodeInformation; export function parseNodeProtocolInfo( - buffer: Buffer, + buffer: Uint8Array, offset: number, isLongRange: boolean = false, ): NodeProtocolInfo { @@ -298,10 +300,10 @@ export function parseNodeProtocolInfo( export function encodeNodeProtocolInfo( info: NodeProtocolInfo, isLongRange: boolean = false, -): Buffer { +): Bytes { // Technically a lot of these fields are reserved/unused in Z-Wave Long Range, // but the only thing where it really matters is the speed bitmask. - const ret = Buffer.alloc(3, 0); + const ret = Bytes.alloc(3, 0); // Byte 0 and 2 if (info.isListening) ret[0] |= 0b10_000_000; if (info.isRouting) ret[0] |= 0b01_000_000; @@ -330,7 +332,7 @@ export function encodeNodeProtocolInfo( } export function parseNodeProtocolInfoAndDeviceClass( - buffer: Buffer, + buffer: Uint8Array, isLongRange: boolean = false, ): { info: NodeProtocolInfoAndDeviceClass; @@ -365,13 +367,13 @@ export function parseNodeProtocolInfoAndDeviceClass( export function encodeNodeProtocolInfoAndDeviceClass( info: NodeProtocolInfoAndDeviceClass, isLongRange: boolean = false, -): Buffer { - return Buffer.concat([ +): Bytes { + return Bytes.concat([ encodeNodeProtocolInfo( { ...info, hasSpecificDeviceClass: true }, isLongRange, ), - Buffer.from([ + Bytes.from([ info.basicDeviceClass, info.genericDeviceClass, info.specificDeviceClass, @@ -380,7 +382,7 @@ export function encodeNodeProtocolInfoAndDeviceClass( } export function parseNodeInformationFrame( - buffer: Buffer, + buffer: Uint8Array, isLongRange: boolean = false, ): NodeInformationFrame { const result = parseNodeProtocolInfoAndDeviceClass( @@ -390,7 +392,7 @@ export function parseNodeInformationFrame( const info = result.info; let offset = result.bytesRead; - let ccList: Buffer; + let ccList: Uint8Array; if (isLongRange) { const ccListLength = buffer[offset]; offset += 1; @@ -411,7 +413,7 @@ export function parseNodeInformationFrame( export function encodeNodeInformationFrame( info: NodeInformationFrame, isLongRange: boolean = false, -): Buffer { +): Bytes { const protocolInfo = encodeNodeProtocolInfoAndDeviceClass( info, isLongRange, @@ -419,14 +421,14 @@ export function encodeNodeInformationFrame( let ccList = encodeCCList(info.supportedCCs, []); if (isLongRange) { - ccList = Buffer.concat([Buffer.from([ccList.length]), ccList]); + ccList = Bytes.concat([Bytes.from([ccList.length]), ccList]); } - return Buffer.concat([protocolInfo, ccList]); + return Bytes.concat([protocolInfo, ccList]); } export function parseNodeID( - buffer: Buffer, + buffer: Uint8Array, type: NodeIDType = NodeIDType.Short, offset: number = 0, ): { @@ -434,15 +436,15 @@ export function parseNodeID( bytesRead: number; } { validatePayload(buffer.length >= offset + type); - const nodeId = buffer.readUIntBE(offset, type); + const nodeId = Bytes.view(buffer).readUIntBE(offset, type); return { nodeId, bytesRead: type }; } export function encodeNodeID( nodeId: number, type: NodeIDType = NodeIDType.Short, -): Buffer { - const ret = Buffer.allocUnsafe(type); +): Bytes { + const ret = new Bytes(type); ret.writeUIntBE(nodeId, 0, type); return ret; } diff --git a/packages/core/src/security/DSK.ts b/packages/core/src/security/DSK.ts index f5b04ca269aa..e72639c00ba3 100644 --- a/packages/core/src/security/DSK.ts +++ b/packages/core/src/security/DSK.ts @@ -1,8 +1,9 @@ +import { Bytes } from "@zwave-js/shared/safe"; import { padStart } from "alcalzone-shared/strings"; import { ZWaveError, ZWaveErrorCodes } from "../error/ZWaveError"; import { isValidDSK } from "./shared_safe"; -export function dskToString(dsk: Buffer): string { +export function dskToString(dsk: Uint8Array): string { if (dsk.length !== 16) { throw new ZWaveError( `DSK length must be 16 bytes, got ${dsk.length}`, @@ -12,12 +13,12 @@ export function dskToString(dsk: Buffer): string { let ret = ""; for (let i = 0; i < 16; i += 2) { if (i > 0) ret += "-"; - ret += padStart(dsk.readUInt16BE(i).toString(10), 5, "0"); + ret += padStart(Bytes.view(dsk).readUInt16BE(i).toString(10), 5, "0"); } return ret; } -export function dskFromString(dsk: string): Buffer { +export function dskFromString(dsk: string): Uint8Array { if (!isValidDSK(dsk)) { throw new ZWaveError( `The DSK must be in the form "aaaaa-bbbbb-ccccc-ddddd-eeeee-fffff-11111-22222"`, @@ -25,33 +26,34 @@ export function dskFromString(dsk: string): Buffer { ); } - const ret = Buffer.allocUnsafe(16); + const ret = new Uint8Array(16); + const view = Bytes.view(ret); const parts = dsk.split("-"); for (let i = 0; i < 8; i++) { const partAsNumber = parseInt(parts[i], 10); - ret.writeUInt16BE(partAsNumber, i * 2); + view.writeUInt16BE(partAsNumber, i * 2); } return ret; } -export function nwiHomeIdFromDSK(dsk: Buffer): Buffer { +export function nwiHomeIdFromDSK(dsk: Uint8Array): Uint8Array { // NWI HomeID 1..4 shall match byte 9..12 of the S2 DSK. // Additionally: // • Bits 7 and 6 of the NWI HomeID 1 shall be set to 1. // • Bit 0 of the NWI HomeID 4 byte shall be set to 0. - const ret = Buffer.allocUnsafe(4); - dsk.copy(ret, 0, 8, 12); + const ret = new Uint8Array(4); + ret.set(dsk.subarray(8, 12), 0); ret[0] |= 0b11000000; ret[3] &= 0b11111110; return ret; } -export function authHomeIdFromDSK(dsk: Buffer): Buffer { +export function authHomeIdFromDSK(dsk: Uint8Array): Uint8Array { // Auth HomeID 1..4 shall match byte 13..16 of the S2 DSK. // • Bits 7 and 6 of the Auth HomeID 1 shall be set to 0. (Error in the specs, they say it should be 1) // • Bit 0 of the Auth HomeID 4 byte shall be set to 1. (Error in the specs, they say it should be 0) - const ret = Buffer.allocUnsafe(4); - dsk.copy(ret, 0, 12, 16); + const ret = new Uint8Array(4); + ret.set(dsk.subarray(12, 16), 0); ret[0] &= 0b00111111; ret[3] |= 0b00000001; return ret; diff --git a/packages/core/src/security/Manager.test.ts b/packages/core/src/security/Manager.test.ts index f376635e20be..bea9e97b5795 100644 --- a/packages/core/src/security/Manager.test.ts +++ b/packages/core/src/security/Manager.test.ts @@ -1,9 +1,11 @@ +/* eslint-disable no-restricted-globals -- crypto methods return Buffers */ +import { isUint8Array } from "@zwave-js/shared"; import test from "ava"; import crypto, { randomBytes } from "node:crypto"; import sinon from "sinon"; import { SecurityManager } from "./Manager"; -const networkKey = Buffer.from([ +const networkKey = Uint8Array.from([ 1, 2, 3, @@ -27,9 +29,9 @@ const options = { networkKey, ownNodeId, nonceTimeout: 500 }; test("constructor() -> should set the network key, auth key and encryption key", (t) => { const man = new SecurityManager(options); t.deepEqual(man.networkKey, networkKey); - t.true(Buffer.isBuffer(man.authKey)); + t.true(isUint8Array(man.authKey)); t.is(man.authKey.length, 16); - t.true(Buffer.isBuffer(man.encryptionKey)); + t.true(isUint8Array(man.encryptionKey)); t.is(man.encryptionKey.length, 16); }); @@ -37,7 +39,7 @@ test("constructor() -> should throw if the network key doesn't have length 16", t.throws( () => new SecurityManager({ - networkKey: Buffer.from([]), + networkKey: new Uint8Array(), ownNodeId: 1, nonceTimeout: 500, }), @@ -52,9 +54,9 @@ test("generateNonce() should return a random Buffer of the given length", (t) => const nonce2 = man.generateNonce(2, 8); const nonce3 = man.generateNonce(2, 8); - t.true(Buffer.isBuffer(nonce1)); - t.true(Buffer.isBuffer(nonce2)); - t.true(Buffer.isBuffer(nonce3)); + t.true(isUint8Array(nonce1)); + t.true(isUint8Array(nonce2)); + t.true(isUint8Array(nonce3)); t.is(nonce1.length, 8); t.is(nonce2.length, 8); @@ -162,7 +164,7 @@ test("setNonce() -> should store a given nonce to be retrieved later", (t) => { const man = new SecurityManager(options); t.is(man.getNonce(1), undefined); - const nonce: Buffer = randomBytes(8); + const nonce = randomBytes(8); nonce[0] = 1; man.setNonce(1, { nonce, receiver: 2 }); t.deepEqual(man.getNonce(1), nonce); @@ -171,7 +173,7 @@ test("setNonce() -> should store a given nonce to be retrieved later", (t) => { test("setNonce -> the nonces should timeout after the given timeout", (t) => { const clock = sinon.useFakeTimers(Date.now()); const man = new SecurityManager(options); - const nonce: Buffer = randomBytes(8); + const nonce = randomBytes(8); const nonceId = nonce[0]; man.setNonce(nonceId, { nonce, receiver: 2 }); t.deepEqual(man.getNonce(nonceId), nonce); @@ -183,7 +185,7 @@ test("setNonce -> the nonces should timeout after the given timeout", (t) => { test("setNonce -> should mark the nonce as free", (t) => { const man = new SecurityManager(options); - const nonce: Buffer = randomBytes(8); + const nonce = randomBytes(8); nonce[0] = 1; man.setNonce( { @@ -200,7 +202,7 @@ test("setNonce -> should mark the nonce as free", (t) => { test("setNonce -> when a free nonce expires, it should no longer be free", (t) => { const clock = sinon.useFakeTimers(Date.now()); const man = new SecurityManager(options); - const nonce: Buffer = randomBytes(8); + const nonce = randomBytes(8); man.setNonce( { issuer: 2, @@ -220,7 +222,7 @@ test("hasNonce() -> should return whether a nonce id is in the database", (t) => // Manually set t.false(man.hasNonce(1)); - const nonce1: Buffer = randomBytes(8); + const nonce1 = randomBytes(8); nonce1[0] = 1; man.setNonce(1, { nonce: nonce1, receiver: 2 }); t.true(man.hasNonce(1)); @@ -279,7 +281,7 @@ test("deleteAllNoncesForReceiver -> should only delete the nonces for the given test("getFreeNonce() -> should reserve the nonce", (t) => { const man = new SecurityManager(options); - const nonce: Buffer = randomBytes(8); + const nonce = randomBytes(8); nonce[0] = 1; man.setNonce( { @@ -298,7 +300,7 @@ test("nonces should be stored separately for each node", (t) => { const nonce1 = man.generateNonce(3, 8); const nonceId1 = man.getNonceId(nonce1); // Create a nonce with the same nonceId but with another issuer - const nonce2: Buffer = randomBytes(8); + const nonce2 = randomBytes(8); nonce2[0] = nonceId1; const id2 = { issuer: 4, nonceId: nonceId1 }; diff --git a/packages/core/src/security/Manager.ts b/packages/core/src/security/Manager.ts index 2528f7cf1404..9fba228c2373 100644 --- a/packages/core/src/security/Manager.ts +++ b/packages/core/src/security/Manager.ts @@ -4,14 +4,14 @@ import { randomBytes } from "node:crypto"; import { ZWaveError, ZWaveErrorCodes } from "../error/ZWaveError"; import { encryptAES128ECB } from "./crypto"; -const authKeyBase = Buffer.alloc(16, 0x55); -const encryptionKeyBase = Buffer.alloc(16, 0xaa); +const authKeyBase = new Uint8Array(16).fill(0x55); +const encryptionKeyBase = new Uint8Array(16).fill(0xaa); -export function generateAuthKey(networkKey: Buffer): Buffer { +export function generateAuthKey(networkKey: Uint8Array): Uint8Array { return encryptAES128ECB(authKeyBase, networkKey); } -export function generateEncryptionKey(networkKey: Buffer): Buffer { +export function generateEncryptionKey(networkKey: Uint8Array): Uint8Array { return encryptAES128ECB(encryptionKeyBase, networkKey); } @@ -22,13 +22,13 @@ interface NonceKey { } interface NonceEntry { - nonce: Buffer; + nonce: Uint8Array; /** The node this nonce was created for */ receiver: number; } export interface SecurityManagerOptions { - networkKey: Buffer; + networkKey: Uint8Array; ownNodeId: number; nonceTimeout: number; } @@ -47,11 +47,11 @@ export class SecurityManager { private ownNodeId: number; private nonceTimeout: number; - private _networkKey!: Buffer; - public get networkKey(): Buffer { + private _networkKey!: Uint8Array; + public get networkKey(): Uint8Array { return this._networkKey; } - public set networkKey(v: Buffer) { + public set networkKey(v: Uint8Array) { if (v.length !== 16) { throw new ZWaveError( `The network key must be 16 bytes long!`, @@ -63,13 +63,13 @@ export class SecurityManager { this._encryptionKey = generateEncryptionKey(this._networkKey); } - private _authKey!: Buffer; - public get authKey(): Buffer { + private _authKey!: Uint8Array; + public get authKey(): Uint8Array { return this._authKey; } - private _encryptionKey!: Buffer; - public get encryptionKey(): Buffer { + private _encryptionKey!: Uint8Array; + public get encryptionKey(): Uint8Array { return this._encryptionKey; } @@ -94,8 +94,8 @@ export class SecurityManager { } /** Generates a nonce for the current node */ - public generateNonce(receiver: number, length: number): Buffer { - let nonce: Buffer; + public generateNonce(receiver: number, length: number): Uint8Array { + let nonce: Uint8Array; let nonceId: number; do { nonce = randomBytes(length); @@ -106,7 +106,7 @@ export class SecurityManager { return nonce; } - public getNonceId(nonce: Buffer): number { + public getNonceId(nonce: Uint8Array): number { return nonce[0]; } @@ -162,7 +162,7 @@ export class SecurityManager { this.deleteNonceInternal(key); } - public getNonce(id: number | NonceKey): Buffer | undefined { + public getNonce(id: number | NonceKey): Uint8Array | undefined { return this._nonceStore.get(this.normalizeId(id))?.nonce; } @@ -170,7 +170,7 @@ export class SecurityManager { return this._nonceStore.has(this.normalizeId(id)); } - public getFreeNonce(nodeId: number): Buffer | undefined { + public getFreeNonce(nodeId: number): Uint8Array | undefined { // Iterate through the known free nonce IDs to find one for the given node for (const key of this._freeNonceIDs) { const nonceKey = JSON.parse(key) as NonceKey; diff --git a/packages/core/src/security/Manager2.test.ts b/packages/core/src/security/Manager2.test.ts index 985661f09edf..4cef371fc1e8 100644 --- a/packages/core/src/security/Manager2.test.ts +++ b/packages/core/src/security/Manager2.test.ts @@ -1,3 +1,4 @@ +import { isUint8Array } from "@zwave-js/shared"; import test from "ava"; import * as crypto from "node:crypto"; import { ZWaveErrorCodes } from "../error/ZWaveError"; @@ -46,7 +47,7 @@ test("nextNonce() -> should generate a 13-byte nonce otherwise", (t) => { }); const ret = man.nextNonce(2); - t.true(Buffer.isBuffer(ret)); + t.true(isUint8Array(ret)); t.is(ret.length, 13); }); @@ -71,8 +72,8 @@ test("initializeSPAN() -> should throw if either entropy input does not have len man.initializeSPAN( nodeId, SecurityClass.S2_Authenticated, - Buffer.alloc(15), - Buffer.alloc(16), + new Uint8Array(15), + new Uint8Array(16), ), { errorCode: ZWaveErrorCodes.Argument_Invalid, @@ -86,8 +87,8 @@ test("initializeSPAN() -> should throw if either entropy input does not have len man.initializeSPAN( nodeId, SecurityClass.S2_Authenticated, - Buffer.alloc(16), - Buffer.alloc(1), + new Uint8Array(16), + new Uint8Array(1), ), { errorCode: ZWaveErrorCodes.Argument_Invalid, @@ -106,8 +107,8 @@ test("initializeSPAN() -> should throw if the node has not been assigned a secur man.initializeSPAN( nodeId, SecurityClass.S2_Authenticated, - Buffer.alloc(16), - Buffer.alloc(16), + new Uint8Array(16), + new Uint8Array(16), ), { errorCode: ZWaveErrorCodes.Security2CC_NotInitialized, @@ -126,8 +127,8 @@ test("initializeSPAN() -> should throw if the keys for the node's security class man.initializeSPAN( nodeId, SecurityClass.S2_Authenticated, - Buffer.alloc(16), - Buffer.alloc(16), + new Uint8Array(16), + new Uint8Array(16), ), { errorCode: ZWaveErrorCodes.Security2CC_NotInitialized, @@ -148,8 +149,8 @@ test("initializeSPAN() -> should not throw otherwise", (t) => { man.initializeSPAN( nodeId, SecurityClass.S2_Authenticated, - Buffer.alloc(16), - Buffer.alloc(16), + new Uint8Array(16), + new Uint8Array(16), ) ); }); @@ -158,7 +159,7 @@ test("setKeys() -> throws if the network key does not have length 16", (t) => { const man = new SecurityManager2(); assertZWaveError( t, - () => man.setKey(SecurityClass.S2_Authenticated, Buffer.alloc(15)), + () => man.setKey(SecurityClass.S2_Authenticated, new Uint8Array(15)), { errorCode: ZWaveErrorCodes.Argument_Invalid, messageMatches: "16 bytes", @@ -169,7 +170,7 @@ test("setKeys() -> throws if the network key does not have length 16", (t) => { test("setKeys() -> throws if the security class is not valid", (t) => { const man = new SecurityManager2(); - assertZWaveError(t, () => man.setKey(-1 as any, Buffer.alloc(16)), { + assertZWaveError(t, () => man.setKey(-1 as any, new Uint8Array(16)), { errorCode: ZWaveErrorCodes.Argument_Invalid, messageMatches: "security class", }); @@ -279,7 +280,7 @@ test("getMulticastKeyAndIV() -> should generate a 13-byte IV otherwise", (t) => const ret = man.getMulticastKeyAndIV(groupId).iv; - t.true(Buffer.isBuffer(ret)); + t.true(isUint8Array(ret)); t.is(ret.length, 13); }); diff --git a/packages/core/src/security/Manager2.ts b/packages/core/src/security/Manager2.ts index afb9e9ae4a09..cb4b6666a6ed 100644 --- a/packages/core/src/security/Manager2.ts +++ b/packages/core/src/security/Manager2.ts @@ -17,15 +17,15 @@ import { import { CtrDRBG } from "./ctr_drbg"; interface NetworkKeys { - pnk: Buffer; - keyCCM: Buffer; - keyMPAN: Buffer; - personalizationString: Buffer; + pnk: Uint8Array; + keyCCM: Uint8Array; + keyMPAN: Uint8Array; + personalizationString: Uint8Array; } interface TempNetworkKeys { - keyCCM: Buffer; - personalizationString: Buffer; + keyCCM: Uint8Array; + personalizationString: Uint8Array; } export enum SPANState { @@ -52,12 +52,12 @@ export type SPANTableEntry = | { // We know the other node's receiver's entropy input, but we didn't send it our sender's EI yet type: SPANState.RemoteEI; - receiverEI: Buffer; + receiverEI: Uint8Array; } | { // We've sent the other node our receiver's entropy input, but we didn't receive its sender's EI yet type: SPANState.LocalEI; - receiverEI: Buffer; + receiverEI: Uint8Array; } | { // We've established an SPAN with the other node @@ -66,7 +66,7 @@ export type SPANTableEntry = rng: CtrDRBG; /** The most recent generated SPAN */ currentSPAN?: { - nonce: Buffer; + nonce: Uint8Array; expires: number; }; }; @@ -77,7 +77,7 @@ export type MPANTableEntry = } | { type: MPANState.MPAN; - currentMPAN: Buffer; + currentMPAN: Uint8Array; }; export interface MulticastGroup { @@ -98,7 +98,7 @@ export class SecurityManager2 { false, crypto.randomBytes(32), undefined, - Buffer.alloc(32, 0), + new Uint8Array(32).fill(0), ); } @@ -113,7 +113,7 @@ export class SecurityManager2 { private ownSequenceNumbers = new Map(); private peerSequenceNumbers = new Map(); /** A map of the inner MPAN states for each multicast group we manage */ - private mpanStates = new Map(); + private mpanStates = new Map(); /** MPANs used to decrypt multicast messages from other nodes. Peer Node ID -> Multicast Group -> MPAN */ private peerMPANs = new Map>(); /** A map of permanent network keys per security class */ @@ -126,7 +126,7 @@ export class SecurityManager2 { private getNextMulticastGroupId = createWrappingCounter(255); /** Sets the PNK for a given security class and derives the encryption keys from it */ - public setKey(securityClass: SecurityClass, key: Buffer): void { + public setKey(securityClass: SecurityClass, key: Uint8Array): void { if (key.length !== 16) { throw new ZWaveError( `The network key must consist of 16 bytes!`, @@ -257,7 +257,7 @@ export class SecurityManager2 { * Prepares the generation of a new SPAN by creating a random sequence number and (local) entropy input * @param receiver The node this nonce is for. If none is given, the nonce is not stored. */ - public generateNonce(receiver: number | undefined): Buffer { + public generateNonce(receiver: number | undefined): Uint8Array { const receiverEI = this.rng.generate(16); if (receiver != undefined) { this.spanTable.set(receiver, { @@ -293,8 +293,8 @@ export class SecurityManager2 { public initializeSPAN( peerNodeId: number, securityClass: SecurityClass, - senderEI: Buffer, - receiverEI: Buffer, + senderEI: Uint8Array, + receiverEI: Uint8Array, ): void { if (senderEI.length !== 16 || receiverEI.length !== 16) { throw new ZWaveError( @@ -323,8 +323,8 @@ export class SecurityManager2 { /** Initializes the singlecast PAN generator for a given node based on the given entropy inputs */ public initializeTempSPAN( peerNodeId: number, - senderEI: Buffer, - receiverEI: Buffer, + senderEI: Uint8Array, + receiverEI: Uint8Array, ): void { if (senderEI.length !== 16 || receiverEI.length !== 16) { throw new ZWaveError( @@ -379,7 +379,7 @@ export class SecurityManager2 { } } - public storeRemoteEI(peerNodeId: number, remoteEI: Buffer): void { + public storeRemoteEI(peerNodeId: number, remoteEI: Uint8Array): void { if (remoteEI.length !== 16) { throw new ZWaveError( `The entropy input must consist of 16 bytes`, @@ -396,7 +396,7 @@ export class SecurityManager2 { * Generates the next nonce for the given peer and returns it. * @param store - Whether the nonce should be stored/remembered as the current SPAN. */ - public nextNonce(peerNodeId: number, store?: boolean): Buffer { + public nextNonce(peerNodeId: number, store?: boolean): Uint8Array { const spanState = this.spanTable.get(peerNodeId); if (spanState?.type !== SPANState.SPAN) { throw new ZWaveError( @@ -442,13 +442,13 @@ export class SecurityManager2 { return seq; } - public getInnerMPANState(groupId: number): Buffer | undefined { + public getInnerMPANState(groupId: number): Uint8Array | undefined { return this.mpanStates.get(groupId); } public getMulticastKeyAndIV(groupId: number): { - key: Buffer; - iv: Buffer; + key: Uint8Array; + iv: Uint8Array; } { const group = this.getMulticastGroup(groupId); @@ -488,7 +488,7 @@ export class SecurityManager2 { /** * Generates the next nonce for the given peer and returns it. */ - public nextPeerMPAN(peerNodeId: number, groupId: number): Buffer { + public nextPeerMPAN(peerNodeId: number, groupId: number): Uint8Array { const mpanState = this.getPeerMPAN(peerNodeId, groupId); if (mpanState.type !== MPANState.MPAN) { throw new ZWaveError( diff --git a/packages/core/src/security/QR.ts b/packages/core/src/security/QR.ts index 5832b7a381cf..e5398bc20518 100644 --- a/packages/core/src/security/QR.ts +++ b/packages/core/src/security/QR.ts @@ -1,3 +1,4 @@ +import { Bytes } from "@zwave-js/shared/safe"; import { createHash } from "node:crypto"; import { Protocols } from "../capabilities/Protocols"; import { ZWaveError, ZWaveErrorCodes } from "../error/ZWaveError"; @@ -144,7 +145,7 @@ function parseTLVData(type: ProvisioningInformationType, data: string) { } case ProvisioningInformationType.UUID16: { - const buffer = Buffer.allocUnsafe(16); + const buffer = new Bytes(16); // Only format 0 is supported here const presentationFormat = readLevel(data, 0); if (presentationFormat !== 0) return; @@ -160,7 +161,7 @@ function parseTLVData(type: ProvisioningInformationType, data: string) { } case ProvisioningInformationType.SupportedProtocols: { - const bitMask = Buffer.from([ + const bitMask = Uint8Array.from([ data.length === 2 ? readLevel(data, 0) : data.length === 3 @@ -234,13 +235,13 @@ export function parseQRCodeString(qr: string): QRProvisioningInformation { const checksum = readUInt16(qr, 4); // The checksum covers the remaining data const hash = createHash("sha1"); - hash.update(Buffer.from(qr.slice(9), "ascii")); + hash.update(Bytes.from(qr.slice(9), "ascii")); const expectedChecksum = hash.digest().readUInt16BE(0); if (checksum !== expectedChecksum) fail("invalid checksum"); const requestedKeysBitmask = readUInt8(qr, 9); const requestedSecurityClasses = parseBitMask( - Buffer.from([requestedKeysBitmask]), + [requestedKeysBitmask], SecurityClass.S2_Unauthenticated, ); if (!requestedSecurityClasses.every((k) => k in SecurityClass)) { @@ -248,7 +249,7 @@ export function parseQRCodeString(qr: string): QRProvisioningInformation { } let offset = 12; - const dsk = Buffer.allocUnsafe(16); + const dsk = new Bytes(16); for (let dskBlock = 0; dskBlock < 8; dskBlock++) { const block = readUInt16(qr, offset); dsk.writeUInt16BE(block, dskBlock * 2); diff --git a/packages/core/src/security/bufferUtils.ts b/packages/core/src/security/bufferUtils.ts index 20d6092d0cda..86e8c5eefb66 100644 --- a/packages/core/src/security/bufferUtils.ts +++ b/packages/core/src/security/bufferUtils.ts @@ -1,19 +1,19 @@ export function zeroPad( - input: Buffer, + input: Uint8Array, blockSize: number, -): { output: Buffer; paddingLength: number } { - const padding = input.length % blockSize === 0 - ? Buffer.from([]) - : Buffer.alloc(blockSize - (input.length % blockSize), 0); +): { output: Uint8Array; paddingLength: number } { + const desiredLength = Math.ceil(input.length / blockSize) * blockSize; + const ret = new Uint8Array(desiredLength); + ret.set(input, 0); return { - output: Buffer.concat([input, padding]), - paddingLength: padding.length, + output: ret, + paddingLength: ret.length - input.length, }; } /** Left-Shifts a buffer by 1 bit */ -export function leftShift1(input: Buffer): Buffer { - if (input.length === 0) return Buffer.from([]); - const ret = Buffer.allocUnsafe(input.length); +export function leftShift1(input: Uint8Array): Uint8Array { + if (input.length === 0) return new Uint8Array(); + const ret = new Uint8Array(input.length); for (let i = 0; i < input.length - 1; i++) { ret[i] = (input[i] << 1) + (!!(input[i + 1] & 0x80) ? 1 : 0); } @@ -22,11 +22,11 @@ export function leftShift1(input: Buffer): Buffer { } /** Computes the byte-wise XOR of two buffers with the same length */ -export function xor(b1: Buffer, b2: Buffer): Buffer { +export function xor(b1: Uint8Array, b2: Uint8Array): Uint8Array { if (b1.length !== b2.length) { throw new Error("The buffers must have the same length"); } - const ret = Buffer.allocUnsafe(b1.length); + const ret = new Uint8Array(b1.length); for (let i = 0; i < b1.length; i++) { ret[i] = b1[i] ^ b2[i]; } @@ -34,7 +34,7 @@ export function xor(b1: Buffer, b2: Buffer): Buffer { } /** Increments a multi-byte integer in a buffer */ -export function increment(buffer: Buffer): void { +export function increment(buffer: Uint8Array): void { for (let i = buffer.length - 1; i >= 0; i--) { buffer[i] += 1; if (buffer[i] !== 0x00) break; diff --git a/packages/core/src/security/crypto.test.ts b/packages/core/src/security/crypto.test.ts index 06f0488384a0..96c557163c16 100644 --- a/packages/core/src/security/crypto.test.ts +++ b/packages/core/src/security/crypto.test.ts @@ -1,3 +1,4 @@ +import { Bytes } from "@zwave-js/shared/safe"; import test from "ava"; import * as crypto from "node:crypto"; import { @@ -13,25 +14,25 @@ test("encryptAES128OFB() / decryptAES128OFB() -> should be able to en- and decry "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam"; const key = crypto.randomBytes(16); const iv = crypto.randomBytes(16); - const ciphertext = encryptAES128OFB(Buffer.from(plaintextIn), key, iv); + const ciphertext = encryptAES128OFB(Bytes.from(plaintextIn), key, iv); const plaintextOut = decryptAES128OFB(ciphertext, key, iv).toString(); t.is(plaintextOut, plaintextIn); }); test("encryptAES128ECB() -> should work correctly", (t) => { // // Test vector taken from https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf - const key = Buffer.from("2b7e151628aed2a6abf7158809cf4f3c", "hex"); - const plaintext = Buffer.from("6bc1bee22e409f96e93d7e117393172a", "hex"); - const expected = Buffer.from("3ad77bb40d7a3660a89ecaf32466ef97", "hex"); + const key = Bytes.from("2b7e151628aed2a6abf7158809cf4f3c", "hex"); + const plaintext = Bytes.from("6bc1bee22e409f96e93d7e117393172a", "hex"); + const expected = Bytes.from("3ad77bb40d7a3660a89ecaf32466ef97", "hex"); t.deepEqual(encryptAES128ECB(plaintext, key), expected); }); test("encryptAES128OFB() -> should work correctly", (t) => { // Test vector taken from https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf - const key = Buffer.from("2b7e151628aed2a6abf7158809cf4f3c", "hex"); - const iv = Buffer.from("000102030405060708090a0b0c0d0e0f", "hex"); - const plaintext = Buffer.from("6bc1bee22e409f96e93d7e117393172a", "hex"); - const expected = Buffer.from("3b3fd92eb72dad20333449f8e83cfb4a", "hex"); + const key = Bytes.from("2b7e151628aed2a6abf7158809cf4f3c", "hex"); + const iv = Bytes.from("000102030405060708090a0b0c0d0e0f", "hex"); + const plaintext = Bytes.from("6bc1bee22e409f96e93d7e117393172a", "hex"); + const expected = Bytes.from("3b3fd92eb72dad20333449f8e83cfb4a", "hex"); t.deepEqual(encryptAES128OFB(plaintext, key, iv), expected); }); @@ -43,16 +44,16 @@ test("encryptAES128OFB() -> should correctly decrypt a real packet", (t) => { // Network Key: 0x0102030405060708090a0b0c0d0e0f10 const key = encryptAES128ECB( - Buffer.alloc(16, 0xaa), - Buffer.from("0102030405060708090a0b0c0d0e0f10", "hex"), + new Uint8Array(16).fill(0xaa), + Bytes.from("0102030405060708090a0b0c0d0e0f10", "hex"), ); - const iv = Buffer.from("78193fd7b91995ba2866211bff3783d6", "hex"); - const ciphertext = Buffer.from( + const iv = Bytes.from("78193fd7b91995ba2866211bff3783d6", "hex"); + const ciphertext = Bytes.from( "47645ec33fcdb3994b104ebd712e8b7fbd9120d049", "hex", ); const plaintext = decryptAES128OFB(ciphertext, key, iv); - const expected = Buffer.from( + const expected = Bytes.from( "009803008685598e60725a845b7170807aef2526ef", "hex", ); @@ -61,65 +62,65 @@ test("encryptAES128OFB() -> should correctly decrypt a real packet", (t) => { test("computeMAC() -> should work correctly", (t) => { // Test vector taken from https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf - const key = Buffer.from("2b7e151628aed2a6abf7158809cf4f3c", "hex"); + const key = Bytes.from("2b7e151628aed2a6abf7158809cf4f3c", "hex"); // The Z-Wave specs use 16 zeros, but we only found test vectors for this - const iv = Buffer.from("000102030405060708090a0b0c0d0e0f", "hex"); - const plaintext = Buffer.from("6bc1bee22e409f96e93d7e117393172a", "hex"); - const expected = Buffer.from("7649abac8119b246", "hex"); + const iv = Bytes.from("000102030405060708090a0b0c0d0e0f", "hex"); + const plaintext = Bytes.from("6bc1bee22e409f96e93d7e117393172a", "hex"); + const expected = Bytes.from("7649abac8119b246", "hex"); t.deepEqual(computeMAC(plaintext, key, iv), expected); }); test("computeMAC() -> should work correctly (part 2)", (t) => { // Taken from real Z-Wave communication - if anything must be changed, this is the test case to keep! - const key = Buffer.from("c5fe1ca17d36c992731a0c0c468c1ef9", "hex"); - const plaintext = Buffer.from( + const key = Bytes.from("c5fe1ca17d36c992731a0c0c468c1ef9", "hex"); + const plaintext = Bytes.from( "ddd360c382a437514392826cbba0b3128114010cf3fb762d6e82126681c18597", "hex", ); - const expected = Buffer.from("2bc20a8aa9bbb371", "hex"); + const expected = Bytes.from("2bc20a8aa9bbb371", "hex"); t.deepEqual(computeMAC(plaintext, key), expected); }); test("computeCMAC() -> should work correctly (part 1)", (t) => { // Test vector taken from https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/AES_CMAC.pdf - const key = Buffer.from("2B7E151628AED2A6ABF7158809CF4F3C", "hex"); - const plaintext = Buffer.from([]); - const expected = Buffer.from("BB1D6929E95937287FA37D129B756746", "hex"); + const key = Bytes.from("2B7E151628AED2A6ABF7158809CF4F3C", "hex"); + const plaintext = new Bytes(); + const expected = Bytes.from("BB1D6929E95937287FA37D129B756746", "hex"); t.deepEqual(computeCMAC(plaintext, key), expected); }); test("computeCMAC() -> should work correctly (part 2)", (t) => { // Test vector taken from https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/AES_CMAC.pdf - const key = Buffer.from("2B7E151628AED2A6ABF7158809CF4F3C", "hex"); - const plaintext = Buffer.from("6BC1BEE22E409F96E93D7E117393172A", "hex"); - const expected = Buffer.from("070A16B46B4D4144F79BDD9DD04A287C", "hex"); + const key = Bytes.from("2B7E151628AED2A6ABF7158809CF4F3C", "hex"); + const plaintext = Bytes.from("6BC1BEE22E409F96E93D7E117393172A", "hex"); + const expected = Bytes.from("070A16B46B4D4144F79BDD9DD04A287C", "hex"); t.deepEqual(computeCMAC(plaintext, key), expected); }); test("computeCMAC() -> should work correctly (part 3)", (t) => { // Test vector taken from https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/AES_CMAC.pdf - const key = Buffer.from("2B7E151628AED2A6ABF7158809CF4F3C", "hex"); - const plaintext = Buffer.from( + const key = Bytes.from("2B7E151628AED2A6ABF7158809CF4F3C", "hex"); + const plaintext = Bytes.from( "6BC1BEE22E409F96E93D7E117393172AAE2D8A57", "hex", ); - const expected = Buffer.from("7D85449EA6EA19C823A7BF78837DFADE", "hex"); + const expected = Bytes.from("7D85449EA6EA19C823A7BF78837DFADE", "hex"); t.deepEqual(computeCMAC(plaintext, key), expected); }); test("computeCMAC() -> should work correctly (part 4)", (t) => { // Test vector taken from https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/AES_CMAC.pdf - const key = Buffer.from("2B7E151628AED2A6ABF7158809CF4F3C", "hex"); - const plaintext = Buffer.from( + const key = Bytes.from("2B7E151628AED2A6ABF7158809CF4F3C", "hex"); + const plaintext = Bytes.from( "6BC1BEE22E409F96E93D7E117393172AAE2D8A571E03AC9C9EB76FAC45AF8E5130C81C46A35CE411E5FBC1191A0A52EFF69F2445DF4F9B17AD2B417BE66C3710", "hex", ); - const expected = Buffer.from("51F0BEBF7E3B9D92FC49741779363CFE", "hex"); + const expected = Bytes.from("51F0BEBF7E3B9D92FC49741779363CFE", "hex"); t.deepEqual(computeCMAC(plaintext, key), expected); }); diff --git a/packages/core/src/security/crypto.ts b/packages/core/src/security/crypto.ts index d03bb98852ba..cb5638f8309b 100644 --- a/packages/core/src/security/crypto.ts +++ b/packages/core/src/security/crypto.ts @@ -1,3 +1,4 @@ +import { Bytes } from "@zwave-js/shared/safe"; import * as crypto from "node:crypto"; import { leftShift1, xor, zeroPad } from "./bufferUtils"; @@ -5,15 +6,15 @@ function encrypt( algorithm: string, blockSize: number, trimToInputLength: boolean, - input: Buffer, - key: Buffer, - iv: Buffer, -): Buffer { + input: Uint8Array, + key: Uint8Array, + iv: Uint8Array, +): Uint8Array { const cipher = crypto.createCipheriv(algorithm, key, iv); cipher.setAutoPadding(false); const { output: plaintext, paddingLength } = zeroPad(input, blockSize); - const ret = Buffer.concat([cipher.update(plaintext), cipher.final()]); + const ret = Bytes.concat([cipher.update(plaintext), cipher.final()]); if (trimToInputLength && paddingLength > 0) { return ret.subarray(0, -paddingLength); @@ -26,15 +27,15 @@ function decrypt( algorithm: string, blockSize: number, trimToInputLength: boolean, - input: Buffer, - key: Buffer, - iv: Buffer, -): Buffer { + input: Uint8Array, + key: Uint8Array, + iv: Uint8Array, +): Uint8Array { const cipher = crypto.createDecipheriv(algorithm, key, iv); cipher.setAutoPadding(false); const { output: ciphertext, paddingLength } = zeroPad(input, blockSize); - const ret = Buffer.concat([cipher.update(ciphertext), cipher.final()]); + const ret = Bytes.concat([cipher.update(ciphertext), cipher.final()]); if (trimToInputLength && paddingLength > 0) { return ret.subarray(0, -paddingLength); @@ -44,8 +45,11 @@ function decrypt( } /** Encrypts a payload using AES-128-ECB (as described in SDS10865) */ -export function encryptAES128ECB(plaintext: Buffer, key: Buffer): Buffer { - return encrypt("aes-128-ecb", 16, false, plaintext, key, Buffer.from([])); +export function encryptAES128ECB( + plaintext: Uint8Array, + key: Uint8Array, +): Uint8Array { + return encrypt("aes-128-ecb", 16, false, plaintext, key, new Uint8Array()); } /** Encrypts a payload using AES-OFB (as described in SDS10865) */ @@ -66,34 +70,34 @@ export const decryptAES128OFB = decrypt.bind( /** Computes a message authentication code for Security S0 (as described in SDS10865) */ export function computeMAC( - authData: Buffer, - key: Buffer, - iv: Buffer = Buffer.alloc(16, 0), -): Buffer { + authData: Uint8Array, + key: Uint8Array, + iv: Uint8Array = new Uint8Array(16).fill(0), +): Uint8Array { const ciphertext = encrypt("aes-128-cbc", 16, false, authData, key, iv); // The MAC is the first 8 bytes of the last 16 byte block return ciphertext.subarray(-16, -8); } /** Decodes a DER-encoded x25519 key (PKCS#8 or SPKI) */ -export function decodeX25519KeyDER(key: Buffer): Buffer { +export function decodeX25519KeyDER(key: Uint8Array): Uint8Array { // We could parse this with asn1js but that doesn't seem necessary for now return key.subarray(-32); } /** Encodes an x25519 key from a raw buffer with DER/PKCS#8 */ -export function encodeX25519KeyDERPKCS8(key: Buffer): Buffer { +export function encodeX25519KeyDERPKCS8(key: Uint8Array): Uint8Array { // We could encode this with asn1js but that doesn't seem necessary for now - return Buffer.concat([ - Buffer.from("302e020100300506032b656e04220420", "hex"), + return Bytes.concat([ + Bytes.from("302e020100300506032b656e04220420", "hex"), key, ]); } /** Encodes an x25519 key from a raw buffer with DER/SPKI */ -export function encodeX25519KeyDERSPKI(key: Buffer): Buffer { +export function encodeX25519KeyDERSPKI(key: Uint8Array): Uint8Array { // We could encode this with asn1js but that doesn't seem necessary for now - return Buffer.concat([Buffer.from("302a300506032b656e032100", "hex"), key]); + return Bytes.concat([Bytes.from("302a300506032b656e032100", "hex"), key]); } export interface KeyPair { @@ -106,7 +110,7 @@ export function generateECDHKeyPair(): KeyPair { return crypto.generateKeyPairSync("x25519"); } -export function keyPairFromRawECDHPrivateKey(privateKey: Buffer): KeyPair { +export function keyPairFromRawECDHPrivateKey(privateKey: Uint8Array): KeyPair { const privateKeyObject = importRawECDHPrivateKey(privateKey); const publicKeyObject = crypto.createPublicKey(privateKeyObject); return { @@ -116,7 +120,9 @@ export function keyPairFromRawECDHPrivateKey(privateKey: Buffer): KeyPair { } /** Takes an ECDH public KeyObject and returns the raw key as a buffer */ -export function extractRawECDHPublicKey(publicKey: crypto.KeyObject): Buffer { +export function extractRawECDHPublicKey( + publicKey: crypto.KeyObject, +): Uint8Array { return decodeX25519KeyDER( publicKey.export({ type: "spki", @@ -126,16 +132,21 @@ export function extractRawECDHPublicKey(publicKey: crypto.KeyObject): Buffer { } /** Converts a raw public key to an ECDH KeyObject */ -export function importRawECDHPublicKey(publicKey: Buffer): crypto.KeyObject { +export function importRawECDHPublicKey( + publicKey: Uint8Array, +): crypto.KeyObject { return crypto.createPublicKey({ - key: encodeX25519KeyDERSPKI(publicKey), + // eslint-disable-next-line no-restricted-globals -- crypto API requires Buffer instances + key: Buffer.from(encodeX25519KeyDERSPKI(publicKey).buffer), format: "der", type: "spki", }); } /** Takes an ECDH private KeyObject and returns the raw key as a buffer */ -export function extractRawECDHPrivateKey(privateKey: crypto.KeyObject): Buffer { +export function extractRawECDHPrivateKey( + privateKey: crypto.KeyObject, +): Uint8Array { return decodeX25519KeyDER( privateKey.export({ type: "pkcs8", @@ -145,9 +156,12 @@ export function extractRawECDHPrivateKey(privateKey: crypto.KeyObject): Buffer { } /** Converts a raw private key to an ECDH KeyObject */ -export function importRawECDHPrivateKey(privateKey: Buffer): crypto.KeyObject { +export function importRawECDHPrivateKey( + privateKey: Uint8Array, +): crypto.KeyObject { return crypto.createPrivateKey({ - key: encodeX25519KeyDERPKCS8(privateKey), + // eslint-disable-next-line no-restricted-globals -- crypto API requires Buffer instances + key: Buffer.from(encodeX25519KeyDERPKCS8(privateKey).buffer), format: "der", type: "pkcs8", }); @@ -165,10 +179,12 @@ export function importRawECDHPrivateKey(privateKey: Buffer): crypto.KeyObject { // public.result.valueBlock.value[1].valueBlock.valueHex, // ); -const Z128 = Buffer.alloc(16, 0); -const R128 = Buffer.from("00000000000000000000000000000087", "hex"); +const Z128 = new Uint8Array(16).fill(0); +const R128 = Bytes.from("00000000000000000000000000000087", "hex"); -function generateAES128CMACSubkeys(key: Buffer): [k1: Buffer, k2: Buffer] { +function generateAES128CMACSubkeys( + key: Uint8Array, +): [k1: Uint8Array, k2: Uint8Array] { // NIST SP 800-38B, chapter 6.1 const L = encryptAES128ECB(Z128, key); const k1 = !(L[0] & 0x80) ? leftShift1(L) : xor(leftShift1(L), R128); @@ -177,7 +193,7 @@ function generateAES128CMACSubkeys(key: Buffer): [k1: Buffer, k2: Buffer] { } /** Computes a message authentication code for Security S2 (as described in SDS13783) */ -export function computeCMAC(message: Buffer, key: Buffer): Buffer { +export function computeCMAC(message: Uint8Array, key: Uint8Array): Uint8Array { const blockSize = 16; const numBlocks = Math.ceil(message.length / blockSize); let lastBlock = message.subarray((numBlocks - 1) * blockSize); @@ -185,7 +201,7 @@ export function computeCMAC(message: Buffer, key: Buffer): Buffer { && message.length % blockSize === 0; if (!lastBlockIsComplete) { lastBlock = zeroPad( - Buffer.concat([lastBlock, Buffer.from([0x80])]), + Bytes.concat([lastBlock, Bytes.from([0x80])]), blockSize, ).output; } @@ -204,111 +220,114 @@ export function computeCMAC(message: Buffer, key: Buffer): Buffer { return ret.subarray(0, blockSize); } -const constantPRK = Buffer.alloc(16, 0x33); +const constantPRK = new Uint8Array(16).fill(0x33); /** Computes the Pseudo Random Key (PRK) used to derive auth, encryption and nonce keys */ export function computePRK( - ecdhSharedSecret: Buffer, - pubKeyA: Buffer, - pubKeyB: Buffer, -): Buffer { - const message = Buffer.concat([ecdhSharedSecret, pubKeyA, pubKeyB]); + ecdhSharedSecret: Uint8Array, + pubKeyA: Uint8Array, + pubKeyB: Uint8Array, +): Uint8Array { + const message = Bytes.concat([ecdhSharedSecret, pubKeyA, pubKeyB]); return computeCMAC(message, constantPRK); } -const constantTE = Buffer.alloc(15, 0x88); +const constantTE = new Uint8Array(15).fill(0x88); /** Derives the temporary auth, encryption and nonce keys from the PRK */ -export function deriveTempKeys(PRK: Buffer): { - tempKeyCCM: Buffer; - tempPersonalizationString: Buffer; +export function deriveTempKeys(PRK: Uint8Array): { + tempKeyCCM: Uint8Array; + tempPersonalizationString: Uint8Array; } { const T1 = computeCMAC( - Buffer.concat([constantTE, Buffer.from([0x01])]), + Bytes.concat([constantTE, [0x01]]), PRK, ); const T2 = computeCMAC( - Buffer.concat([T1, constantTE, Buffer.from([0x02])]), + Bytes.concat([T1, constantTE, [0x02]]), PRK, ); const T3 = computeCMAC( - Buffer.concat([T2, constantTE, Buffer.from([0x03])]), + Bytes.concat([T2, constantTE, [0x03]]), PRK, ); return { tempKeyCCM: T1, - tempPersonalizationString: Buffer.concat([T2, T3]), + tempPersonalizationString: Bytes.concat([T2, T3]), }; } -const constantNK = Buffer.alloc(15, 0x55); +const constantNK = new Uint8Array(15).fill(0x55); /** Derives the CCM, MPAN keys and the personalization string from the permanent network key (PNK) */ -export function deriveNetworkKeys(PNK: Buffer): { - keyCCM: Buffer; - keyMPAN: Buffer; - personalizationString: Buffer; +export function deriveNetworkKeys(PNK: Uint8Array): { + keyCCM: Uint8Array; + keyMPAN: Uint8Array; + personalizationString: Uint8Array; } { const T1 = computeCMAC( - Buffer.concat([constantNK, Buffer.from([0x01])]), + Bytes.concat([constantNK, [0x01]]), PNK, ); const T2 = computeCMAC( - Buffer.concat([T1, constantNK, Buffer.from([0x02])]), + Bytes.concat([T1, constantNK, [0x02]]), PNK, ); const T3 = computeCMAC( - Buffer.concat([T2, constantNK, Buffer.from([0x03])]), + Bytes.concat([T2, constantNK, [0x03]]), PNK, ); const T4 = computeCMAC( - Buffer.concat([T3, constantNK, Buffer.from([0x04])]), + Bytes.concat([T3, constantNK, [0x04]]), PNK, ); return { keyCCM: T1, keyMPAN: T4, - personalizationString: Buffer.concat([T2, T3]), + personalizationString: Bytes.concat([T2, T3]), }; } -const constantNonce = Buffer.alloc(16, 0x26); +const constantNonce = new Uint8Array(16).fill(0x26); /** Computes the Pseudo Random Key (PRK) used to derive the mixed entropy input (MEI) for nonce generation */ -export function computeNoncePRK(senderEI: Buffer, receiverEI: Buffer): Buffer { - const message = Buffer.concat([senderEI, receiverEI]); +export function computeNoncePRK( + senderEI: Uint8Array, + receiverEI: Uint8Array, +): Uint8Array { + const message = Bytes.concat([senderEI, receiverEI]); return computeCMAC(message, constantNonce); } -const constantEI = Buffer.alloc(15, 0x88); +const constantEI = new Uint8Array(15).fill(0x88); /** Derives the MEI from the nonce PRK */ -export function deriveMEI(noncePRK: Buffer): Buffer { +export function deriveMEI(noncePRK: Uint8Array): Uint8Array { const T1 = computeCMAC( - Buffer.concat([ + Bytes.concat([ constantEI, - Buffer.from([0x00]), + [0x00], constantEI, - Buffer.from([0x01]), + [0x01], ]), noncePRK, ); const T2 = computeCMAC( - Buffer.concat([T1, constantEI, Buffer.from([0x02])]), + Bytes.concat([T1, constantEI, [0x02]]), noncePRK, ); - return Buffer.concat([T1, T2]); + return Bytes.concat([T1, T2]); } export const SECURITY_S2_AUTH_TAG_LENGTH = 8; export function encryptAES128CCM( - key: Buffer, - iv: Buffer, - plaintext: Buffer, - additionalData: Buffer, + key: Uint8Array, + iv: Uint8Array, + plaintext: Uint8Array, + additionalData: Uint8Array, authTagLength: number, -): { ciphertext: Buffer; authTag: Buffer } { +): { ciphertext: Uint8Array; authTag: Uint8Array } { // prepare encryption const algorithm = `aes-128-ccm`; const cipher = crypto.createCipheriv(algorithm, key, iv, { authTagLength }); @@ -323,12 +342,12 @@ export function encryptAES128CCM( } export function decryptAES128CCM( - key: Buffer, - iv: Buffer, - ciphertext: Buffer, - additionalData: Buffer, - authTag: Buffer, -): { plaintext: Buffer; authOK: boolean } { + key: Uint8Array, + iv: Uint8Array, + ciphertext: Uint8Array, + additionalData: Uint8Array, + authTag: Uint8Array, +): { plaintext: Uint8Array; authOK: boolean } { // prepare decryption const algorithm = `aes-128-ccm`; const decipher = crypto.createDecipheriv(algorithm, key, iv, { diff --git a/packages/core/src/security/ctr_drbg.test.ts b/packages/core/src/security/ctr_drbg.test.ts index b0a865ddb0d6..dfb5ec243b40 100644 --- a/packages/core/src/security/ctr_drbg.test.ts +++ b/packages/core/src/security/ctr_drbg.test.ts @@ -1,3 +1,4 @@ +import { hexToUint8Array } from "@zwave-js/shared/safe"; import test from "ava"; import * as fs from "node:fs"; import * as path from "node:path"; @@ -25,7 +26,7 @@ function getVectors(alg: string) { for (let j = 1; j < items.length; j++) { const key = items[j].split(" = ")[0]; - const value = Buffer.from(items[j].split(" = ")[1], "hex"); + const value = hexToUint8Array(items[j].split(" = ")[1]); if (vector[key]) vector[key] = [vector[key], value]; else vector[key] = value; @@ -64,12 +65,12 @@ for (const df of [false, true]) { ); drbg.generate( - vector.ReturnedBits.length, + vector.ReturnedBits.byteLength, vector.AdditionalInput[0], ); const result = drbg.generate( - vector.ReturnedBits.length, + vector.ReturnedBits.byteLength, vector.AdditionalInput[1], ); diff --git a/packages/core/src/security/ctr_drbg.ts b/packages/core/src/security/ctr_drbg.ts index df8fbbca6e1f..29eddcc8242c 100644 --- a/packages/core/src/security/ctr_drbg.ts +++ b/packages/core/src/security/ctr_drbg.ts @@ -15,6 +15,7 @@ * https://github.com/netroby/jdk9-dev/blob/master/jdk/src/java.base/share/classes/sun/security/provider/CtrDrbg.java */ +import { Bytes } from "@zwave-js/shared/safe"; import { increment } from "./bufferUtils"; import { encryptAES128ECB } from "./crypto"; @@ -25,15 +26,15 @@ export class CtrDRBG { constructor( bits: 128, derivation: boolean, - entropy?: Buffer, - nonce?: Buffer, - pers?: Buffer, + entropy?: Uint8Array, + nonce?: Uint8Array, + pers?: Uint8Array, ) { - this.ctr = Buffer.alloc(16, 0); + this.ctr = new Uint8Array(16).fill(0); this.keySize = bits >>> 3; this.blkSize = 16; this.entSize = this.keySize + this.blkSize; - this.slab = Buffer.alloc(this.entSize); + this.slab = new Uint8Array(this.entSize); this.K = this.slab.subarray(0, this.keySize); this.V = this.slab.subarray(this.keySize); this.derivation = derivation; @@ -44,23 +45,23 @@ export class CtrDRBG { } /** The internal counter */ - private ctr: Buffer; + private ctr: Uint8Array; private readonly keySize: number; private readonly blkSize: number; private readonly entSize: number; - private slab: Buffer; - private K: Buffer; - private V: Buffer; + private slab: Uint8Array; + private K: Uint8Array; + private V: Uint8Array; private readonly derivation: boolean; // private rounds: number; private initialized: boolean; init( - entropy: Buffer, - nonce: Buffer = Buffer.alloc(0), - pers: Buffer = Buffer.alloc(0), + entropy: Uint8Array, + nonce: Uint8Array = new Uint8Array(), + pers: Uint8Array = new Uint8Array(), ): this { - let seed: Buffer; + let seed: Uint8Array; if (this.derivation) { seed = this.derive(entropy, nonce, pers); @@ -73,16 +74,15 @@ export class CtrDRBG { throw new Error("Personalization string is too long."); } - seed = Buffer.alloc(this.entSize, 0); - - entropy.copy(seed, 0); - nonce.copy(seed, entropy.length); + seed = new Uint8Array(this.entSize).fill(0); + seed.set(entropy, 0); + seed.set(nonce, entropy.length); for (let i = 0; i < pers.length; i++) seed[i] ^= pers[i]; } this.slab.fill(0); - this.V.copy(this.ctr, 0); + this.ctr.set(this.V, 0); this.update(seed); this.initialized = true; // this.rounds = 1; @@ -90,11 +90,11 @@ export class CtrDRBG { return this; } - reseed(entropy: Buffer, add: Buffer = Buffer.alloc(0)): this { + reseed(entropy: Uint8Array, add: Uint8Array = new Uint8Array()): this { // if (this.rounds === 0) if (!this.initialized) throw new Error("DRBG not initialized."); - let seed: Buffer; + let seed: Uint8Array; if (this.derivation) { seed = this.derive(entropy, add); @@ -103,8 +103,8 @@ export class CtrDRBG { throw new Error("Additional data is too long."); } - seed = Buffer.alloc(this.entSize, 0x00); - entropy.copy(seed, 0); + seed = new Uint8Array(this.entSize).fill(0x00); + seed.set(entropy, 0); for (let i = 0; i < add.length; i++) seed[i] ^= add[i]; } @@ -114,12 +114,12 @@ export class CtrDRBG { return this; } - private next(): Buffer { + private next(): Uint8Array { increment(this.ctr); return encryptAES128ECB(this.ctr, this.K); } - generate(len: number, add?: Buffer): Buffer { + generate(len: number, add?: Uint8Array): Uint8Array { // if (this.rounds === 0) if (!this.initialized) throw new Error("DRBG not initialized."); @@ -137,11 +137,11 @@ export class CtrDRBG { } const blocks = Math.ceil(len / this.blkSize); - const out = Buffer.alloc(blocks * this.blkSize); + const out = new Uint8Array(blocks * this.blkSize); for (let i = 0; i < blocks; i++) { const ciphertext = this.next(); - ciphertext.copy(out, i * this.blkSize); + out.set(ciphertext, i * this.blkSize); } this.update(add); @@ -155,27 +155,27 @@ export class CtrDRBG { * Helpers */ - update(seed: Buffer = Buffer.alloc(0)): this { + update(seed: Uint8Array = new Uint8Array()): this { if (seed.length > this.entSize) throw new Error("Seed is too long."); - const newSlab = Buffer.alloc(this.slab.length, 0); + const newSlab = new Uint8Array(this.slab.length).fill(0); // this.slab.fill(0); for (let i = 0; i < this.entSize; i += this.blkSize) { - this.next().copy(newSlab, i); + newSlab.set(this.next(), i); // ciphertext.copy(this.slab, i); } // for (let i = 0; i < seed.length; i++) this.slab[i] ^= seed[i]; for (let i = 0; i < seed.length; i++) newSlab[i] ^= seed[i]; - newSlab.copy(this.slab, 0); - this.V.copy(this.ctr, 0); + this.slab.set(newSlab, 0); + this.ctr.set(this.V, 0); return this; } - serialize(...input: Buffer[]): Buffer { + serialize(...input: Uint8Array[]): Uint8Array { const N = this.entSize; let L = 0; @@ -187,36 +187,41 @@ export class CtrDRBG { if (size % this.blkSize) size += this.blkSize - (size % this.blkSize); // S = IV || (L || N || input || 0x80 || 0x00...) - const S = Buffer.alloc(size, 0x00); + const S = new Uint8Array(size).fill(0x00); + const view = Bytes.view(S); let pos = this.blkSize; - S.writeUInt32BE(L, pos); + view.writeUInt32BE(L, pos); pos += 4; - S.writeUInt32BE(N, pos); + view.writeUInt32BE(N, pos); pos += 4; - for (const item of input) pos += item.copy(S, pos); + for (const item of input) { + S.set(item, pos); + pos += item.length; + } S[pos++] = 0x80; return S; } - derive(...input: Buffer[]): Buffer { + derive(...input: Uint8Array[]): Uint8Array { const S = this.serialize(...input); + const view = Bytes.view(S); const N = S.length / this.blkSize; - const K = Buffer.alloc(this.keySize); + const K = new Uint8Array(this.keySize); const blocks = Math.ceil(this.entSize / this.blkSize); - const slab = Buffer.alloc(blocks * this.blkSize); - const out = Buffer.alloc(blocks * this.blkSize); - const chain = Buffer.alloc(this.blkSize); + const slab = new Uint8Array(blocks * this.blkSize); + const out = new Uint8Array(blocks * this.blkSize); + const chain = new Uint8Array(this.blkSize); for (let i = 0; i < K.length; i++) K[i] = i; for (let i = 0; i < blocks; i++) { chain.fill(0); - S.writeUInt32BE(i, 0); + view.writeUInt32BE(i, 0); // chain = BCC(K, IV || S) for (let j = 0; j < N; j++) { @@ -225,11 +230,10 @@ export class CtrDRBG { } // encrypt in-place - encryptAES128ECB(chain, K).copy(chain, 0); + chain.set(encryptAES128ECB(chain, K), 0); // ctx.encrypt(chain, 0, chain, 0); } - - chain.copy(slab, i * this.blkSize); + slab.set(chain, i * this.blkSize); } const k = slab.subarray(0, this.keySize); @@ -237,9 +241,9 @@ export class CtrDRBG { for (let i = 0; i < blocks; i++) { // encrypt in-place - encryptAES128ECB(x, k).copy(x, 0); + x.set(encryptAES128ECB(x, k), 0); // ctx.encrypt(x, 0, x, 0); - x.copy(out, i * this.blkSize); + out.set(x, i * this.blkSize); } return out.subarray(0, this.entSize); diff --git a/packages/core/src/util/_Types.ts b/packages/core/src/util/_Types.ts index 894dbac0b8f1..e58f97c45785 100644 --- a/packages/core/src/util/_Types.ts +++ b/packages/core/src/util/_Types.ts @@ -8,6 +8,6 @@ export type FirmwareFileFormat = | "bin"; export interface Firmware { - data: Buffer; + data: Uint8Array; firmwareTarget?: number; } diff --git a/packages/core/src/util/crc.test.ts b/packages/core/src/util/crc.test.ts index 9455b39b8def..a2d57c4b1734 100644 --- a/packages/core/src/util/crc.test.ts +++ b/packages/core/src/util/crc.test.ts @@ -1,29 +1,30 @@ +import { Bytes } from "@zwave-js/shared/safe"; import test from "ava"; import { CRC16_CCITT } from "./crc"; // Test cases based on http://srecord.sourceforge.net/crc16-ccitt.html test("CRC16_CCITT() works correctly -> input: (empty)", (t) => { - t.is(CRC16_CCITT(Buffer.from([])), 0x1d0f); + t.is(CRC16_CCITT(new Bytes()), 0x1d0f); }); test("CRC16_CCITT() works correctly -> input: A", (t) => { - t.is(CRC16_CCITT(Buffer.from("A", "ascii")), 0x9479); + t.is(CRC16_CCITT(Bytes.from("A", "ascii")), 0x9479); }); test("CRC16_CCITT() works correctly -> input: 123456789", (t) => { - t.is(CRC16_CCITT(Buffer.from("123456789", "ascii")), 0xe5cc); + t.is(CRC16_CCITT(Bytes.from("123456789", "ascii")), 0xe5cc); }); test("CRC16_CCITT() works correctly -> input: A x 256", (t) => { - t.is(CRC16_CCITT(Buffer.alloc(256, "A", "ascii")), 0xe938); + t.is(CRC16_CCITT(new Uint8Array(256).fill("A".charCodeAt(0))), 0xe938); }); test("CRC16_CCITT() works correctly -> chained with start values", (t) => { const input = "123456789"; let crc: number | undefined; for (let i = 0; i < input.length; i++) { - crc = CRC16_CCITT(Buffer.from(input[i], "ascii"), crc); + crc = CRC16_CCITT(Bytes.from(input[i], "ascii"), crc); } t.is(crc, 0xe5cc); }); diff --git a/packages/core/src/util/crc.ts b/packages/core/src/util/crc.ts index a21fbe5b112d..88eff17e5622 100644 --- a/packages/core/src/util/crc.ts +++ b/packages/core/src/util/crc.ts @@ -1,5 +1,8 @@ // Implementation based on SDS13782 -export function CRC16_CCITT(data: Buffer, startValue: number = 0x1d0f): number { +export function CRC16_CCITT( + data: Uint8Array, + startValue: number = 0x1d0f, +): number { let crc = startValue; const poly = 0x1021; diff --git a/packages/core/src/util/firmware.ts b/packages/core/src/util/firmware.ts index 390f7e0f6cab..9e2d8fdda7a6 100644 --- a/packages/core/src/util/firmware.ts +++ b/packages/core/src/util/firmware.ts @@ -1,4 +1,5 @@ -import { getErrorMessage } from "@zwave-js/shared"; +import { getErrorMessage, isUint8Array } from "@zwave-js/shared"; +import { Bytes } from "@zwave-js/shared/safe"; import * as crypto from "node:crypto"; import MemoryMap from "nrf-intel-hex"; import { ZWaveError, ZWaveErrorCodes } from "../error/ZWaveError"; @@ -7,11 +8,11 @@ import { CRC16_CCITT } from "./crc"; const firmwareIndicators = { // All aeotec updater exes contain this text - aeotec: Buffer.from("Zensys.ZWave", "utf8"), + aeotec: Bytes.from("Zensys.ZWave", "utf8"), // This seems to be the standard beginning of a gecko bootloader firmware gecko: 0xeb17a603, // Encrypted HEC firmware files - hec: Buffer.from("HSENC2", "ascii"), + hec: Bytes.from("HSENC2", "ascii"), }; /** @@ -22,27 +23,28 @@ const firmwareIndicators = { */ export function guessFirmwareFileFormat( filename: string, - rawData: Buffer, + rawData: Uint8Array, ): FirmwareFileFormat { filename = filename.toLowerCase(); + const rawBuffer = Bytes.view(rawData); if (filename.endsWith(".bin")) { return "bin"; } else if ( (filename.endsWith(".exe") || filename.endsWith(".ex_")) - && rawData.includes(firmwareIndicators.aeotec) + && rawBuffer.includes(firmwareIndicators.aeotec) ) { return "aeotec"; } else if (/\.(hex|ota|otz)$/.test(filename)) { return filename.slice(-3) as FirmwareFileFormat; } else if ( filename.endsWith(".gbl") - && rawData.readUInt32BE(0) === firmwareIndicators.gecko + && rawBuffer.readUInt32BE(0) === firmwareIndicators.gecko ) { return "gecko"; } else if ( filename.endsWith(".hec") - && rawData + && rawBuffer .subarray(0, firmwareIndicators.hec.length) .equals(firmwareIndicators.hec) ) { @@ -66,7 +68,7 @@ export function guessFirmwareFileFormat( * The returned firmware data and target can be used to start a firmware update process with `node.beginFirmwareUpdate` */ export function extractFirmware( - rawData: Buffer, + rawData: Uint8Array, format: FirmwareFileFormat, ): Firmware { switch (format) { @@ -106,13 +108,14 @@ export function extractFirmware( } } -function extractFirmwareRAW(data: Buffer): Firmware { +function extractFirmwareRAW(data: Uint8Array): Firmware { return { data }; } -function extractFirmwareAeotec(data: Buffer): Firmware { +function extractFirmwareAeotec(data: Uint8Array): Firmware { + const buffer = Bytes.view(data); // Check if this is an EXE file - if (data.readUInt16BE(0) !== 0x4d5a) { + if (buffer.readUInt16BE(0) !== 0x4d5a) { throw new ZWaveError( "This does not appear to be a valid Aeotec updater (not an executable)!", ZWaveErrorCodes.Argument_Invalid, @@ -120,22 +123,22 @@ function extractFirmwareAeotec(data: Buffer): Firmware { } // The exe file contains the firmware data and filename at the end - const firmwareStart = data.readUInt32BE(data.length - 8); - const firmwareLength = data.readUInt32BE(data.length - 4); + const firmwareStart = buffer.readUInt32BE(buffer.length - 8); + const firmwareLength = buffer.readUInt32BE(buffer.length - 4); let numControlBytes = 8; // Some exe files also contain a 2-byte checksum. The method "ImageCalcCrc16" is used to compute the checksum - if (data.includes(Buffer.from("ImageCalcCrc16", "ascii"))) { + if (buffer.includes(Bytes.from("ImageCalcCrc16", "ascii"))) { numControlBytes += 2; } // Some files don't have such a strict alignment - in that case fall back to ignoring the non-aligned control bytes switch (true) { case firmwareStart + firmwareLength - === data.length - 256 - numControlBytes: + === buffer.length - 256 - numControlBytes: // all good break; - case firmwareStart + firmwareLength === data.length - 256 - 8: + case firmwareStart + firmwareLength === buffer.length - 256 - 8: numControlBytes = 8; break; default: @@ -145,20 +148,20 @@ function extractFirmwareAeotec(data: Buffer): Firmware { ); } - const firmwareData = data.subarray( + const firmwareData = buffer.subarray( firmwareStart, firmwareStart + firmwareLength, ); - const firmwareNameBytes = data - .subarray(data.length - 256 - numControlBytes) + const firmwareNameBytes = buffer + .subarray(buffer.length - 256 - numControlBytes) .subarray(0, 256); // Some exe files contain a CRC-16 checksum, extract that too and check it if (numControlBytes === 10) { - const checksum = data.readUInt16BE(data.length - 10); + const checksum = buffer.readUInt16BE(buffer.length - 10); const actualChecksum = CRC16_CCITT( - Buffer.concat([firmwareData, firmwareNameBytes]), + Bytes.concat([firmwareData, firmwareNameBytes]), 0xfe95, ); if (checksum !== actualChecksum) { @@ -205,18 +208,18 @@ function extractFirmwareAeotec(data: Buffer): Firmware { return ret; } -function extractFirmwareHEX(dataHEX: Buffer | string): Firmware { +function extractFirmwareHEX(dataHEX: Uint8Array | string): Firmware { try { - if (Buffer.isBuffer(dataHEX)) { - dataHEX = dataHEX.toString("ascii"); + if (isUint8Array(dataHEX)) { + dataHEX = Bytes.view(dataHEX).toString("ascii"); } const memMap: Map = MemoryMap.fromHex(dataHEX); // A memory map can be sparse - we'll have to fill the gaps with 0xFF - let data: Buffer = Buffer.from([]); + let data: Bytes = new Bytes(); for (const [offset, chunk] of memMap.entries()) { - data = Buffer.concat([ + data = Bytes.concat([ data, - Buffer.alloc(offset - data.length, 0xff), + Bytes.alloc(offset - data.length, 0xff), chunk, ]); } @@ -233,21 +236,21 @@ function extractFirmwareHEX(dataHEX: Buffer | string): Firmware { } } -function extractFirmwareHEC(data: Buffer): Firmware { +function extractFirmwareHEC(data: Uint8Array): Firmware { const key = "d7a68def0f4a1241940f6cb8017121d15f0e2682e258c9f7553e706e834923b7"; const iv = "0e6519297530583708612a2823663844"; const decipher = crypto.createDecipheriv( "aes-256-cbc", - Buffer.from(key, "hex"), - Buffer.from(iv, "hex"), + Bytes.from(key, "hex"), + Bytes.from(iv, "hex"), ); - const ciphertext = Buffer.from( - data.subarray(6).toString("ascii"), + const ciphertext = Bytes.from( + Bytes.view(data.subarray(6)).toString("ascii"), "base64", ); - const plaintext = Buffer.concat([ + const plaintext = Bytes.concat([ decipher.update(ciphertext), decipher.final(), ]) diff --git a/packages/core/src/values/Cache.ts b/packages/core/src/values/Cache.ts index 3cea1cd36ed0..9a61123b00e2 100644 --- a/packages/core/src/values/Cache.ts +++ b/packages/core/src/values/Cache.ts @@ -1,4 +1,9 @@ -import type { JSONObject } from "@zwave-js/shared"; +import { + type JSONObject, + hexToUint8Array, + isUint8Array, + uint8ArrayToHex, +} from "@zwave-js/shared/safe"; import { composeObject } from "alcalzone-shared/objects"; import { isArray, isObject } from "alcalzone-shared/typeguards"; import { Duration } from "./Duration"; @@ -48,10 +53,10 @@ export function serializeCacheValue(value: unknown): SerializedValue { : valueAsJSON), [SPECIAL_TYPE_KEY]: "duration", }; - } else if (Buffer.isBuffer(value)) { + } else if (isUint8Array(value)) { return { [SPECIAL_TYPE_KEY]: "buffer", - data: value.toString("hex"), + data: uint8ArrayToHex(value), }; } else if ( typeof value === "number" @@ -86,7 +91,7 @@ export function deserializeCacheValue(value: SerializedValue): unknown { } else if (specialType === "duration") { return new Duration(value.value ?? 1, value.unit); } else if (specialType === "buffer") { - return Buffer.from(value.data, "hex"); + return hexToUint8Array(value.data); } } return value; diff --git a/packages/core/src/values/Primitive.test.ts b/packages/core/src/values/Primitive.test.ts index 5e0a6c04daa6..eb4bde0fe6b2 100644 --- a/packages/core/src/values/Primitive.test.ts +++ b/packages/core/src/values/Primitive.test.ts @@ -1,3 +1,4 @@ +import { Bytes } from "@zwave-js/shared"; import test from "ava"; import { ZWaveErrorCodes } from "../error/ZWaveError"; import { assertZWaveError } from "../test/assertZWaveError"; @@ -78,10 +79,10 @@ test("parseMaybeNumber() -> should return undefined otherwise", (t) => { test("parseFloatWithScale() -> should correctly extract the scale", (t) => { const tests = [ - { payload: Buffer.from([0b00000001, 0]), expected: 0b00 }, - { payload: Buffer.from([0b00001001, 0]), expected: 0b01 }, - { payload: Buffer.from([0b00010001, 0]), expected: 0b10 }, - { payload: Buffer.from([0b00011001, 0]), expected: 0b11 }, + { payload: Uint8Array.from([0b00000001, 0]), expected: 0b00 }, + { payload: Uint8Array.from([0b00001001, 0]), expected: 0b01 }, + { payload: Uint8Array.from([0b00010001, 0]), expected: 0b10 }, + { payload: Uint8Array.from([0b00011001, 0]), expected: 0b11 }, ]; for (const { payload, expected } of tests) { t.is(parseFloatWithScale(payload).scale, expected); @@ -91,12 +92,12 @@ test("parseFloatWithScale() -> should correctly extract the scale", (t) => { test("parseFloatWithScale() -> should correctly extract the value", (t) => { const tests = [ { - payload: Buffer.from([0b00100001, 15]), + payload: Uint8Array.from([0b00100001, 15]), expected: 1.5, numDigits: 1, }, { - payload: Buffer.from([0b11001100, 0x81, 0x23, 0x45, 0x67]), + payload: Uint8Array.from([0b11001100, 0x81, 0x23, 0x45, 0x67]), expected: -2128.394905, numDigits: 6, }, @@ -113,22 +114,22 @@ test("encodeFloatWithScale() -> should correctly encode the scale", (t) => { { scale: 0b00, value: 0, - expected: Buffer.from([0b00000001, 0]), + expected: Bytes.from([0b00000001, 0]), }, { scale: 0b01, value: 0, - expected: Buffer.from([0b00001001, 0]), + expected: Bytes.from([0b00001001, 0]), }, { scale: 0b10, value: 0, - expected: Buffer.from([0b00010001, 0]), + expected: Bytes.from([0b00010001, 0]), }, { scale: 0b11, value: 0, - expected: Buffer.from([0b00011001, 0]), + expected: Bytes.from([0b00011001, 0]), }, ]; for (const { scale, value, expected } of tests) { @@ -155,12 +156,12 @@ test("encodeFloatWithScale() -> should correctly detect the necessary precision { value: 1.5, scale: 0b00, - expected: Buffer.from([0b00100001, 15]), + expected: Bytes.from([0b00100001, 15]), }, { value: -2128.394905, scale: 0b01, - expected: Buffer.from([0b11001100, 0x81, 0x23, 0x45, 0x67]), + expected: Bytes.from([0b11001100, 0x81, 0x23, 0x45, 0x67]), }, ]; for (const { scale, value, expected } of tests) { @@ -174,20 +175,20 @@ test("encodeFloatWithScale() -> should use the specified override options", (t) scale: 0b00, value: 0, override: { size: 2 }, // Force 2 bytes for the value - expected: Buffer.from([0b000_00_010, 0, 0]), + expected: Bytes.from([0b000_00_010, 0, 0]), }, { scale: 0b01, value: 100, override: { precision: 2 }, // Force 2 decimal digits // resulting in 2 bytes size - expected: Buffer.from([0b010_01_010, 0x27, 0x10]), + expected: Bytes.from([0b010_01_010, 0x27, 0x10]), }, { scale: 0b10, value: 100, override: { precision: 1, size: 3 }, - expected: Buffer.from([0b001_10_011, 0, 0x03, 0xe8]), + expected: Bytes.from([0b001_10_011, 0, 0x03, 0xe8]), }, ]; for (const { scale, value, override, expected } of tests) { @@ -201,7 +202,7 @@ test("encodeFloatWithScale() -> should fall back to sane options when the overri scale: 0b10, value: 100, override: { precision: 1, size: 1 }, // invalid, this requires a size of at least 2 - expected: Buffer.from([0b001_10_010, 0x03, 0xe8]), + expected: Bytes.from([0b001_10_010, 0x03, 0xe8]), }, ]; for (const { scale, value, override, expected } of tests) { @@ -219,8 +220,8 @@ test("encodeFloatWithScale() -> should throw when the value cannot be represente }); test("parseBitMask() -> should correctly convert a bit mask into a numeric array", (t) => { const tests = [ - { mask: Buffer.from([0b10111001]), expected: [1, 4, 5, 6, 8] }, - { mask: Buffer.from([0b11, 0b110]), expected: [1, 2, 10, 11] }, + { mask: Uint8Array.from([0b10111001]), expected: [1, 4, 5, 6, 8] }, + { mask: Uint8Array.from([0b11, 0b110]), expected: [1, 2, 10, 11] }, ]; for (const { mask, expected } of tests) { t.deepEqual(parseBitMask(mask), expected); @@ -230,22 +231,22 @@ test("parseBitMask() -> should correctly convert a bit mask into a numeric array test("parseBitMask() -> should take the 2nd parameter into account when calculating the resulting values", (t) => { const tests = [ { - mask: Buffer.from([0b10111001]), + mask: Uint8Array.from([0b10111001]), startValue: 0, expected: [0, 3, 4, 5, 7], }, { - mask: Buffer.from([0b10111001]), + mask: Uint8Array.from([0b10111001]), startValue: 1, expected: [1, 4, 5, 6, 8], }, { - mask: Buffer.from([0b10111001]), + mask: Uint8Array.from([0b10111001]), startValue: 2, expected: [2, 5, 6, 7, 9], }, { - mask: Buffer.from([0b11, 0b110]), + mask: Uint8Array.from([0b11, 0b110]), startValue: 3, expected: [3, 4, 12, 13], }, @@ -260,12 +261,12 @@ test("encodeBitMask() -> should correctly convert a numeric array into a bit mas { values: [1, 4, 5, 6, 8], max: 8, - expected: Buffer.from([0b10111001]), + expected: Bytes.from([0b10111001]), }, { values: [1, 2, 10, 11], max: 16, - expected: Buffer.from([0b11, 0b110]), + expected: Bytes.from([0b11, 0b110]), }, ]; for (const { values, max, expected } of tests) { @@ -289,13 +290,13 @@ test("encodeBitMask() -> should respect the startValue too", (t) => { values: [2, 4, 8], max: 11, startValue: 2, - expected: Buffer.from([0b01000101, 0]), + expected: Bytes.from([0b01000101, 0]), }, { values: [0, 2, 10, 11], max: 19, startValue: 0, - expected: Buffer.from([0b101, 0b1100, 0]), + expected: Bytes.from([0b101, 0b1100, 0]), }, ]; for (const { values, max, startValue, expected } of tests) { diff --git a/packages/core/src/values/Primitive.ts b/packages/core/src/values/Primitive.ts index 49e389783fb5..e951bb8b772b 100644 --- a/packages/core/src/values/Primitive.ts +++ b/packages/core/src/values/Primitive.ts @@ -1,3 +1,4 @@ +import { Bytes } from "@zwave-js/shared/safe"; import { MAX_NODES_LR, NUM_LR_NODES_PER_SEGMENT, @@ -77,7 +78,7 @@ export function maybeUnknownToString( * Parses a floating point value with a scale from a buffer. */ export function parseFloatWithScale( - payload: Buffer, + payload: Uint8Array, allowEmpty?: false, ): { value: number; @@ -90,7 +91,7 @@ export function parseFloatWithScale( * @param allowEmpty Whether empty floats (precision = scale = size = 0 no value) are accepted */ export function parseFloatWithScale( - payload: Buffer, + payload: Uint8Array, allowEmpty: true, ): { value?: number; @@ -103,7 +104,7 @@ export function parseFloatWithScale( * @param allowEmpty Whether empty floats (precision = scale = size = 0 no value) are accepted */ export function parseFloatWithScale( - payload: Buffer, + payload: Uint8Array, allowEmpty: boolean = false, ): { value?: number; @@ -111,15 +112,16 @@ export function parseFloatWithScale( bytesRead: number; } { validatePayload(payload.length >= 1); - const precision = (payload[0] & 0b111_00_000) >>> 5; - const scale = (payload[0] & 0b000_11_000) >>> 3; - const size = payload[0] & 0b111; + const buffer = Bytes.view(payload); + const precision = (buffer[0] & 0b111_00_000) >>> 5; + const scale = (buffer[0] & 0b000_11_000) >>> 3; + const size = buffer[0] & 0b111; if (allowEmpty && size === 0) { validatePayload(precision === 0, scale === 0); return { bytesRead: 1 }; } else { - validatePayload(size >= 1, size <= 4, payload.length >= 1 + size); - const value = payload.readIntBE(1, size) / Math.pow(10, precision); + validatePayload(size >= 1, size <= 4, buffer.length >= 1 + size); + const value = buffer.readIntBE(1, size) / Math.pow(10, precision); return { value, scale, bytesRead: 1 + size }; } } @@ -219,7 +221,7 @@ export function encodeFloatWithScale( size?: number; precision?: number; } = {}, -): Buffer { +): Bytes { const precision = override.precision ?? Math.min(getPrecision(value), 7); value = Math.round(value * Math.pow(10, precision)); let size: number | undefined = getMinIntegerSize(value, true); @@ -231,7 +233,7 @@ export function encodeFloatWithScale( } else if (override.size != undefined && override.size > size) { size = override.size; } - const ret = Buffer.allocUnsafe(1 + size); + const ret = new Bytes(1 + size); ret[0] = ((precision & 0b111) << 5) | ((scale & 0b11) << 3) | (size & 0b111); @@ -241,7 +243,7 @@ export function encodeFloatWithScale( /** Parses a bit mask into a numeric array */ export function parseBitMask( - mask: Buffer, + mask: Uint8Array | ArrayLike, startValue: number = 1, numBits: number = mask.length * 8, ): number[] { @@ -261,11 +263,11 @@ export function encodeBitMask( values: readonly number[], maxValue: number = Math.max(...values), startValue: number = 1, -): Buffer { - if (!Number.isFinite(maxValue)) return Buffer.from([0]); +): Bytes { + if (!Number.isFinite(maxValue)) return Bytes.from([0]); const numBytes = Math.ceil((maxValue - startValue + 1) / 8); - const ret = Buffer.alloc(numBytes, 0); + const ret = new Bytes(numBytes).fill(0); for (let val = startValue; val <= maxValue; val++) { if (!values.includes(val)) continue; const byteNum = (val - startValue) >>> 3; // id / 8 @@ -275,25 +277,25 @@ export function encodeBitMask( return ret; } -export function parseNodeBitMask(mask: Buffer): number[] { +export function parseNodeBitMask(mask: Uint8Array): number[] { return parseBitMask(mask.subarray(0, NUM_NODEMASK_BYTES)); } export function parseLongRangeNodeBitMask( - mask: Buffer, + mask: Uint8Array | ArrayLike, startValue: number, ): number[] { return parseBitMask(mask, startValue); } -export function encodeNodeBitMask(nodeIDs: readonly number[]): Buffer { +export function encodeNodeBitMask(nodeIDs: readonly number[]): Bytes { return encodeBitMask(nodeIDs, MAX_NODES_LR); } export function encodeLongRangeNodeBitMask( nodeIDs: readonly number[], startValue: number, -): Buffer { +): Bytes { return encodeBitMask( nodeIDs, startValue + NUM_LR_NODES_PER_SEGMENT - 1, diff --git a/packages/flash/src/cli.ts b/packages/flash/src/cli.ts index 83b81a21f174..51caecbd7e7a 100644 --- a/packages/flash/src/cli.ts +++ b/packages/flash/src/cli.ts @@ -21,7 +21,7 @@ if (!port || !filename) { const verbose = !!argv.verbose; -let firmware: Buffer; +let firmware: Uint8Array; const driver = new Driver(port, { logConfig: verbose @@ -99,7 +99,7 @@ async function flash() { } async function main() { - let rawFile: Buffer; + let rawFile: Uint8Array; try { const fullPath = path.isAbsolute(filename) ? filename diff --git a/packages/host/src/FileSystem.ts b/packages/host/src/FileSystem.ts index f2bd47d862e2..6bb2ccea5ac3 100644 --- a/packages/host/src/FileSystem.ts +++ b/packages/host/src/FileSystem.ts @@ -3,7 +3,7 @@ export interface FileSystem { ensureDir(path: string): Promise; writeFile( file: string, - data: string | Buffer, + data: string | Uint8Array, options?: | { encoding: BufferEncoding; diff --git a/packages/maintenance/src/refactorBuffer.ts b/packages/maintenance/src/refactorBuffer.ts new file mode 100644 index 000000000000..7f6603a0cb3d --- /dev/null +++ b/packages/maintenance/src/refactorBuffer.ts @@ -0,0 +1,57 @@ +import fs from "node:fs/promises"; +import { Project, SyntaxKind } from "ts-morph"; + +async function main() { + const project = new Project({ + tsConfigFilePath: "packages/zwave-js/tsconfig.json", + }); + + const sourceFiles = project.getSourceFiles().filter((file) => + file.getFilePath().includes("/serialapi/") + ); + for (const file of sourceFiles) { + const hasBytesImport = file.getImportDeclarations().some((i) => + i.getModuleSpecifierValue().startsWith("@zwave-js/shared") + && i.getNamedImports().some((n) => n.getName() === "Bytes") + ); + + const usesBytesImport = file.getDescendantsOfKind(SyntaxKind.Identifier) + .some((i) => i.getText() === "Bytes"); + + if (file.getBaseName().includes("DeleteSUCReturnRouteMessages")) { + debugger; + } + + if (hasBytesImport || !usesBytesImport) { + continue; + } + + const existing = file.getImportDeclaration((decl) => + decl.getModuleSpecifierValue().startsWith("@zwave-js/shared") + ); + if (!existing) { + file.addImportDeclaration({ + moduleSpecifier: "@zwave-js/shared", + namedImports: [{ + name: "Bytes", + }], + }); + } else { + existing.addNamedImport({ + name: "Bytes", + }); + } + + await file.save(); + } +} + +void main().catch(async (e) => { + await fs.writeFile(`${e.filePath}.old`, e.oldText); + await fs.writeFile(`${e.filePath}.new`, e.newText); + console.error(`Error refactoring file ${e.filePath} + old text: ${e.filePath}.old + new text: ${e.filePath}.new`); + + process.exit(1); +}); diff --git a/packages/maintenance/src/refactorZnifferParsing.ts b/packages/maintenance/src/refactorZnifferParsing.ts new file mode 100644 index 000000000000..1a0c28d7a612 --- /dev/null +++ b/packages/maintenance/src/refactorZnifferParsing.ts @@ -0,0 +1,272 @@ +import fs from "node:fs/promises"; +import { + type Node, + Project, + SyntaxKind, + VariableDeclarationKind, +} from "ts-morph"; + +async function main() { + const project = new Project({ + tsConfigFilePath: "packages/zwave-js/tsconfig.json", + }); + // project.addSourceFilesAtPaths("packages/cc/src/cc/**/*CC.ts"); + + const sourceFiles = project.getSourceFiles().filter((file) => + file.getFilePath().endsWith("ZnifferMessages.ts") + ); + for (const file of sourceFiles) { + // const filePath = path.relative(process.cwd(), file.getFilePath()); + + const msgImplementations = file.getDescendantsOfKind( + SyntaxKind.ClassDeclaration, + ).filter((cls) => { + return (cls.getExtends()?.getText() === "ZnifferMessage"); + }); + const ctors = msgImplementations.map((cls) => { + const ctors = cls.getConstructors(); + if (ctors.length !== 1) return; + const ctor = ctors[0]; + + // Make sure we have exactly one parameter + const ctorParams = ctor.getParameters(); + if (ctorParams.length !== 1) return; + const ctorParam = ctorParams[0]; + + // with a union type where one is MessageDeserializationOptions + const types = ctorParam.getDescendantsOfKind( + SyntaxKind.TypeReference, + ).map((t) => t.getText()); + if ( + types.length !== 1 + || types[0] !== "ZnifferMessageOptions" + ) { + return; + } + + // Ensure the constructor contains + // if (gotDeserializationOptions(options)) { + + const ifStatement = ctor.getBody() + ?.getChildrenOfKind(SyntaxKind.IfStatement) + .filter((stmt) => !!stmt.getElseStatement()) + .find((stmt) => { + const expr = stmt.getExpression(); + if (!expr) return false; + return expr.getText() + === "gotDeserializationOptions(options)"; + }); + if (!ifStatement) return; + + return [cls.getName(), ctor, ifStatement] as const; + }).filter((ctor) => ctor != undefined); + + if (!ctors.length) continue; + + for (const [clsName, ctor, ifStatement] of ctors) { + // Update the constructor signature + + ctor.getParameters()[0].setType( + `${clsName}Options & ZnifferMessageBaseOptions`, + ); + + // Replace "this.payload" with "raw.payload" + const methodBody = ctor.getBody()!.asKind(SyntaxKind.Block)!; + methodBody + ?.getDescendantsOfKind(SyntaxKind.PropertyAccessExpression) + .filter((expr) => expr.getText() === "this.payload") + .forEach((expr) => { + expr.replaceWithText("raw.payload"); + }); + + // Replace all other assignments with let declarations + const parseImplBlock = ifStatement + ? ifStatement.getThenStatement().asKind( + SyntaxKind.Block, + )! + : methodBody; + const assignments = parseImplBlock + .getDescendantsOfKind(SyntaxKind.BinaryExpression) + .map((be) => { + if ( + be.getOperatorToken().getKind() + !== SyntaxKind.EqualsToken + ) return; + const left = be.getLeft(); + if (!left.isKind(SyntaxKind.PropertyAccessExpression)) { + return; + } + if ( + left.getExpression().getKind() + !== SyntaxKind.ThisKeyword + ) return; + const stmt = be.getParentIfKind( + SyntaxKind.ExpressionStatement, + ); + if (!stmt) return; + const identifier = left.getName(); + if ( + identifier === "ownNodeId" + || identifier === "_ownNodeId" + ) return; + const value = be.getRight(); + return [stmt, left, identifier, value] as const; + }) + .filter((ass) => ass != undefined); + + const properties = new Map(); + for (const [expr, left, identifier, value] of assignments) { + if (!properties.has(identifier)) { + // Find the correct type to use + let valueType: string | undefined = value.getType() + .getText().replaceAll( + /import\(.*?\)\./g, + "", + ); + const prop = ctor.getParent().getProperty( + identifier, + ); + if (prop) { + valueType = prop.getType().getText().replaceAll( + /import\(.*?\)\./g, + "", + ); + } + valueType = valueType.replace(/^readonly /, ""); + // Avoid trivially inferred types + const typeIsTrivial = valueType === "number" + || valueType === "number[]"; + + if (expr.getParent() === parseImplBlock) { + // Top level, create a variable declaration + const index = expr.getChildIndex(); + parseImplBlock.insertVariableStatement(index + 1, { + declarationKind: VariableDeclarationKind.Let, + declarations: [{ + name: identifier, + type: typeIsTrivial ? undefined : valueType, + initializer: value.getFullText(), + }], + }); + expr.remove(); + } else { + // Not top level, create an assignment instead + left.replaceWithText(identifier); + // Find the position to create the declaration + let cur: Node = expr; + while (cur.getParent() !== parseImplBlock) { + cur = cur.getParent()!; + } + const index = cur.getChildIndex(); + parseImplBlock.insertVariableStatement(index, { + declarationKind: VariableDeclarationKind.Let, + declarations: [{ + name: identifier, + type: typeIsTrivial ? undefined : valueType, + }], + }); + } + + properties.set(identifier, valueType); + } else { + left.replaceWithText(identifier); + } + } + + // Replace all occurences of this.xxx with just xxx + const thisAccesses = parseImplBlock.getDescendantsOfKind( + SyntaxKind.PropertyAccessExpression, + ) + .filter((expr) => + !!expr.getExpressionIfKind(SyntaxKind.ThisKeyword) + ); + for (const expr of thisAccesses) { + expr.replaceWithText(expr.getName()); + } + + // Replace options.ctx with ctx + const optionsDotCtx = parseImplBlock.getDescendantsOfKind( + SyntaxKind.PropertyAccessExpression, + ) + .filter((expr) => expr.getText() === "options.ctx"); + for (const expr of optionsDotCtx) { + expr.replaceWithText("ctx"); + } + + // Add a new parse method after the constructor + const ctorIndex = ctor.getChildIndex(); + const method = ctor.getParent().insertMethod(ctorIndex + 1, { + name: "from", + parameters: [{ + name: "raw", + type: "ZnifferMessageRaw", + }], + isStatic: true, + statements: + // For parse/create constructors, take the then block + ifStatement + ? ifStatement.getThenStatement() + .getChildSyntaxList()! + .getFullText() + // else take the whole constructor without "super()" + : methodBody.getStatementsWithComments().filter((s) => + !s.getText().startsWith("super(") + ).map((s) => s.getText()), + returnType: clsName, + }).toggleModifier("public", true); + + // Instantiate the class at the end of the parse method + method.getFirstDescendantByKind(SyntaxKind.Block)!.addStatements(` +return new this({ + ${[...properties.keys()].join(",\n")} +})`); + + // preserve only the super() call + methodBody.getStatementsWithComments().slice(1).forEach( + (stmt) => { + stmt.remove(); + }, + ); + // And add a best-guess implementation for the constructor + methodBody.addStatements("\n\n// TODO: Check implementation:"); + methodBody.addStatements( + [...properties.keys()].map((id) => { + if (id.startsWith("_")) id = id.slice(1); + return `this.${id} = options.${id};`; + }).join("\n"), + ); + + // Also we probably need to define the options type + const klass = ctor.getParent(); + file.insertInterface(klass.getChildIndex(), { + name: `${clsName}Options`, + isExported: true, + properties: [...properties.keys()].map((id) => { + if (id.startsWith("_")) id = id.slice(1); + return { + name: id, + hasQuestionToken: properties.get(id)?.includes( + "undefined", + ), + type: properties.get(id)?.replace( + "| undefined", + "", + ), + }; + }), + }); + } + + await file.save(); + } +} + +void main().catch(async (e) => { + await fs.writeFile(`${e.filePath}.old`, e.oldText); + await fs.writeFile(`${e.filePath}.new`, e.newText); + console.error(`Error refactoring file ${e.filePath} + old text: ${e.filePath}.old + new text: ${e.filePath}.new`); + + process.exit(1); +}); diff --git a/packages/nvmedit/src/convert.test.ts b/packages/nvmedit/src/convert.test.ts index ad3dfd6b8e93..e621d8b8c27b 100644 --- a/packages/nvmedit/src/convert.test.ts +++ b/packages/nvmedit/src/convert.test.ts @@ -1,5 +1,5 @@ import { cloneDeep } from "@zwave-js/shared/safe"; -import test from "ava"; +import test, { type ExecutionContext } from "ava"; import fs from "fs-extra"; import path from "node:path"; import { jsonToNVM, migrateNVM } from "."; @@ -13,6 +13,14 @@ import { } from "./convert"; import type { NVM500JSON } from "./nvm500/NVMParser"; +function bufferEquals( + t: ExecutionContext, + actual: Uint8Array, + expected: Uint8Array, +) { + t.deepEqual(actual.buffer, expected.buffer); +} + { const suite = "700-series, binary to JSON"; @@ -69,7 +77,7 @@ import type { NVM500JSON } from "./nvm500/NVMParser"; const json = await nvmToJSON(nvmIn); const nvmOut = await jsonToNVM(json, version); - t.deepEqual(nvmOut, nvmIn); + bufferEquals(t, nvmOut, nvmIn); }); } } @@ -125,7 +133,7 @@ import type { NVM500JSON } from "./nvm500/NVMParser"; json.controller.protocolVersion, ); - t.deepEqual(nvmOut, nvmIn); + bufferEquals(t, nvmOut, nvmIn); }); } } @@ -195,5 +203,5 @@ test("700 to 700 migration shortcut", async (t) => { ); const converted = await migrateNVM(nvmSource, nvmTarget); - t.deepEqual(converted, nvmSource); + bufferEquals(t, converted, nvmSource); }); diff --git a/packages/nvmedit/src/convert.ts b/packages/nvmedit/src/convert.ts index d051ae6183a5..e94f7a408662 100644 --- a/packages/nvmedit/src/convert.ts +++ b/packages/nvmedit/src/convert.ts @@ -11,7 +11,13 @@ import { isZWaveError, stripUndefined, } from "@zwave-js/core/safe"; -import { cloneDeep, num2hex, pick } from "@zwave-js/shared/safe"; +import { + Bytes, + buffer2hex, + cloneDeep, + num2hex, + pick, +} from "@zwave-js/shared/safe"; import { isObject } from "alcalzone-shared/typeguards"; import semver from "semver"; import { MAX_PROTOCOL_FILE_FORMAT, SUC_MAX_UPDATES } from "./consts"; @@ -554,7 +560,7 @@ export function nvmObjectsToJSON( const controller: NVMJSONController = { protocolVersion, applicationVersion, - homeId: `0x${controllerInfoFile.homeId.toString("hex")}`, + homeId: buffer2hex(controllerInfoFile.homeId), ...pick(controllerInfoFile, controllerProps), ...pick(applicationTypeFile, [ "isListening", @@ -664,7 +670,7 @@ function nvmJSONControllerToFileOptions( ctrlr: NVMJSONController, ): ControllerInfoFileOptions { const ret = { - homeId: Buffer.from(ctrlr.homeId.replace(/^0x/, ""), "hex"), + homeId: Bytes.from(ctrlr.homeId.replace(/^0x/, ""), "hex"), nodeId: ctrlr.nodeId, lastNodeId: ctrlr.lastNodeId, staticControllerNodeId: ctrlr.staticControllerNodeId, @@ -673,7 +679,7 @@ function nvmJSONControllerToFileOptions( maxNodeId: ctrlr.maxNodeId, reservedId: ctrlr.reservedId, systemState: ctrlr.systemState, - } as ControllerInfoFileOptions; + } as unknown as ControllerInfoFileOptions; if (ctrlr.sucAwarenessPushNeeded != undefined) { // @ts-expect-error We're dealing with a conditional object here // TS doesn't like that. @@ -698,7 +704,7 @@ function nvmJSONControllerToFileOptions( /** Reads an NVM buffer of a 700+ series stick and returns its JSON representation */ export async function nvmToJSON( - buffer: Buffer, + buffer: Uint8Array, debugLogs: boolean = false, ): Promise { const io = new NVMMemoryIO(buffer); @@ -900,7 +906,7 @@ export async function nvmToJSON( const controller: NVMJSONController = { protocolVersion, applicationVersion, - homeId: `0x${controllerInfoFile.homeId.toString("hex")}`, + homeId: buffer2hex(controllerInfoFile.homeId), ...pick(controllerInfoFile, [ "nodeId", "lastNodeId", @@ -942,7 +948,9 @@ export async function nvmToJSON( } : {}), sucUpdateEntries, - applicationData: applicationData?.toString("hex") ?? null, + applicationData: + (applicationData && Bytes.view(applicationData).toString("hex")) + ?? null, applicationName: applicationName ?? null, }; @@ -979,7 +987,7 @@ export async function nvmToJSON( /** Reads an NVM buffer of a 500-series stick and returns its JSON representation */ export async function nvm500ToJSON( - buffer: Buffer, + buffer: Uint8Array, ): Promise> { const io = new NVMMemoryIO(buffer); const nvm = new NVM500(io); @@ -1084,7 +1092,7 @@ export async function nvm500ToJSON( domain: "controller", type: "learnedHomeId", }); - if (learnedHomeId?.equals(Buffer.alloc(4, 0))) { + if (learnedHomeId?.length === 4 && learnedHomeId.every((b) => b === 0)) { learnedHomeId = undefined; } @@ -1172,9 +1180,9 @@ export async function nvm500ToJSON( const controller: NVM500JSONController = { protocolVersion: info.nvmDescriptor.protocolVersion, applicationVersion: info.nvmDescriptor.firmwareVersion, - ownHomeId: `0x${ownHomeId.toString("hex")}`, + ownHomeId: buffer2hex(ownHomeId), learnedHomeId: learnedHomeId - ? `0x${learnedHomeId.toString("hex")}` + ? buffer2hex(learnedHomeId) : null, nodeId: ownNodeId, lastNodeId, @@ -1195,7 +1203,9 @@ export async function nvm500ToJSON( }, preferredRepeaters, commandClasses, - applicationData: applicationData?.toString("hex") ?? null, + applicationData: + (applicationData && Bytes.view(applicationData).toString("hex")) + ?? null, }; return { @@ -1209,7 +1219,7 @@ export async function nvm500ToJSON( export async function jsonToNVM( json: NVMJSON, targetSDKVersion: string, -): Promise { +): Promise { const parsedVersion = semver.parse(targetSDKVersion); if (!parsedVersion) { throw new ZWaveError( @@ -1223,7 +1233,7 @@ export async function jsonToNVM( const nvmSize = sharedFileSystem ? ZWAVE_SHARED_NVM_SIZE : (ZWAVE_APPLICATION_NVM_SIZE + ZWAVE_PROTOCOL_NVM_SIZE); - const ret = Buffer.allocUnsafe(nvmSize); + const ret = new Uint8Array(nvmSize); const io = new NVMMemoryIO(ret); const nvm3 = new NVM3(io); await nvm3.erase(json.meta); @@ -1365,7 +1375,7 @@ export async function jsonToNVM( if (target.controller.applicationData) { await adapter.set( { domain: "controller", type: "applicationData" }, - Buffer.from(target.controller.applicationData, "hex"), + Bytes.from(target.controller.applicationData, "hex"), ); } @@ -1505,7 +1515,7 @@ export async function jsonToNVM( export async function jsonToNVM500( json: Required, protocolVersion: string, -): Promise { +): Promise { // Try to find a matching implementation const impl = nvm500Impls.find( (p) => @@ -1523,7 +1533,7 @@ export async function jsonToNVM500( const { layout, nvmSize } = resolveLayout(impl.layout); // Erase the NVM and set some basic information - const ret = Buffer.allocUnsafe(nvmSize); + const ret = new Uint8Array(nvmSize); const io = new NVMMemoryIO(ret); const nvm = new NVM500(io); await nvm.erase({ @@ -1550,12 +1560,12 @@ export async function jsonToNVM500( await adapter.set( { domain: "controller", type: "homeId" }, - Buffer.from(c.ownHomeId.replace(/^0x/, ""), "hex"), + Bytes.from(c.ownHomeId.replace(/^0x/, ""), "hex"), ); await adapter.set( { domain: "controller", type: "learnedHomeId" }, c.learnedHomeId - ? Buffer.from(c.learnedHomeId.replace(/^0x/, ""), "hex") + ? Bytes.from(c.learnedHomeId.replace(/^0x/, ""), "hex") : undefined, ); @@ -1639,7 +1649,7 @@ export async function jsonToNVM500( if (c.applicationData) { await adapter.set( { domain: "controller", type: "applicationData" }, - Buffer.from(c.applicationData, "hex"), + Bytes.from(c.applicationData, "hex"), ); } @@ -1730,7 +1740,7 @@ export function json500To700( let applicationData: string | null = null; if (source.controller.applicationData) { - let raw = Buffer.from(source.controller.applicationData, "hex"); + let raw = Bytes.from(source.controller.applicationData, "hex"); // Find actual start and end of application data, ignoring zeroes let start = 0; while (start < raw.length && raw[start] === 0) { @@ -1899,9 +1909,9 @@ export function json700To500(json: NVMJSON): NVM500JSON { /** Converts the given source NVM into a format that is compatible with the given target NVM */ export async function migrateNVM( - sourceNVM: Buffer, - targetNVM: Buffer, -): Promise { + sourceNVM: Uint8Array, + targetNVM: Uint8Array, +): Promise { let source: ParsedNVM; let target: ParsedNVM; let sourceProtocolFileFormat: number | undefined; diff --git a/packages/nvmedit/src/lib/NVM3.ts b/packages/nvmedit/src/lib/NVM3.ts index bb675ab15029..721f2e1ea98c 100644 --- a/packages/nvmedit/src/lib/NVM3.ts +++ b/packages/nvmedit/src/lib/NVM3.ts @@ -1,5 +1,5 @@ import { ZWaveError, ZWaveErrorCodes } from "@zwave-js/core"; -import { getEnumMemberName, num2hex } from "@zwave-js/shared"; +import { Bytes, getEnumMemberName, num2hex } from "@zwave-js/shared"; import { type NVM, NVMAccess, type NVMIO } from "./common/definitions"; import { nvmReadBuffer, nvmReadUInt32LE, nvmWriteBuffer } from "./common/utils"; import { @@ -86,7 +86,7 @@ export interface NVM3Meta { export type NVM3EraseOptions = Partial; -export class NVM3 implements NVM { +export class NVM3 implements NVM { public constructor(io: NVMIO) { this._io = io; } @@ -286,7 +286,7 @@ export class NVM3 implements NVM { return section.objectLocations.has(fileId); } - public readObjectData(object: NVM3ObjectHeader): Promise { + public readObjectData(object: NVM3ObjectHeader): Promise { return nvmReadBuffer( this._io, object.offset + object.headerSize, @@ -294,7 +294,7 @@ export class NVM3 implements NVM { ); } - public async get(fileId: number): Promise { + public async get(fileId: number): Promise { this._info ??= await this.init(); // Determine which ring buffer to read in @@ -305,7 +305,7 @@ export class NVM3 implements NVM { // TODO: There should be no need for scanning, since we know the object locations after init(). // Start scanning backwards through the pages ring buffer, starting with the current page - let parts: Buffer[] | undefined; + let parts: Uint8Array[] | undefined; let complete = false; let objType: ObjectType | undefined; const resetFragments = () => { @@ -400,7 +400,7 @@ export class NVM3 implements NVM { if (!parts?.length || !complete || objType == undefined) return; - return Buffer.concat(parts); + return Bytes.concat(parts); } private async writeObjects(objects: NVM3Object[]): Promise { @@ -449,8 +449,8 @@ export class NVM3 implements NVM { page.objects = []; const pageHeaderBuffer = serializePageHeader(page); - const pageBuffer = Buffer.alloc(page.pageSize, 0xff); - pageHeaderBuffer.copy(pageBuffer, 0); + const pageBuffer = new Uint8Array(page.pageSize).fill(0xff); + pageBuffer.set(pageHeaderBuffer, 0); await nvmWriteBuffer(this._io, page.offset, pageBuffer); } @@ -522,7 +522,7 @@ export class NVM3 implements NVM { } } - public async set(property: number, value: Buffer): Promise { + public async set(property: number, value: Uint8Array): Promise { if (!this._info) await this.init(); await this.ensureWritable(); @@ -539,7 +539,7 @@ export class NVM3 implements NVM { /** Writes multiple values to the NVM at once. `null` / `undefined` cause the value to be deleted */ public async setMany( - values: [number, Buffer | null | undefined][], + values: [number, Uint8Array | null | undefined][], ): Promise { if (!this._info) await this.init(); await this.ensureWritable(); @@ -547,7 +547,7 @@ export class NVM3 implements NVM { // Group objects by their NVM section const objectsBySection = new Map< number, /* offset */ - [number, Buffer | null | undefined][] + [number, Uint8Array | null | undefined][] >(); for (const [key, value] of values) { const sectionOffset = @@ -642,7 +642,7 @@ export class NVM3 implements NVM { const numPages = this._io.size / pageSize; for (let i = 0; i < numPages; i++) { const offset = i * pageSize; - const pageBuffer = Buffer.alloc(pageSize, 0xff); + const pageBuffer = new Uint8Array(pageSize).fill(0xff); const pageHeader: NVM3PageHeader = { offset, version: 0x01, @@ -654,7 +654,7 @@ export class NVM3 implements NVM { status: PageStatus.OK, writeSize, }; - serializePageHeader(pageHeader).copy(pageBuffer, 0); + pageBuffer.set(serializePageHeader(pageHeader), 0); await nvmWriteBuffer(this._io, offset, pageBuffer); if (sharedFileSystem || offset < ZWAVE_APPLICATION_NVM_SIZE) { @@ -708,7 +708,9 @@ async function readPageHeader( ); } - const buffer = (await io.read(offset, NVM3_PAGE_HEADER_SIZE)).buffer; + const buffer = Bytes.view( + (await io.read(offset, NVM3_PAGE_HEADER_SIZE)).buffer, + ); const { version, eraseCount } = tryGetVersionAndEraseCount(buffer); @@ -771,10 +773,11 @@ async function readPageHeader( } function tryGetVersionAndEraseCount( - header: Buffer, + header: Uint8Array, ): { version: number; eraseCount: number } { - const version = header.readUInt16LE(0); - const magic = header.readUInt16LE(2); + const buffer = Bytes.view(header); + const version = buffer.readUInt16LE(0); + const magic = buffer.readUInt16LE(2); if (magic !== NVM3_PAGE_MAGIC) { throw new ZWaveError( "Not a valid NVM3 page!", @@ -789,12 +792,12 @@ function tryGetVersionAndEraseCount( } // The erase counter is saved twice, once normally, once inverted - let eraseCount = header.readUInt32LE(4); + let eraseCount = buffer.readUInt32LE(4); const eraseCountCode = eraseCount >>> NVM3_PAGE_COUNTER_SIZE; eraseCount &= NVM3_PAGE_COUNTER_MASK; validateBergerCode(eraseCount, eraseCountCode, NVM3_PAGE_COUNTER_SIZE); - let eraseCountInv = header.readUInt32LE(8); + let eraseCountInv = buffer.readUInt32LE(8); const eraseCountInvCode = eraseCountInv >>> NVM3_PAGE_COUNTER_SIZE; eraseCountInv &= NVM3_PAGE_COUNTER_MASK; validateBergerCode( diff --git a/packages/nvmedit/src/lib/NVM500.ts b/packages/nvmedit/src/lib/NVM500.ts index 23b6905f6371..3c49169b36e7 100644 --- a/packages/nvmedit/src/lib/NVM500.ts +++ b/packages/nvmedit/src/lib/NVM500.ts @@ -5,6 +5,7 @@ import { encodeBitMask, parseBitMask, } from "@zwave-js/core"; +import { Bytes } from "@zwave-js/shared/safe"; import { type NVM, NVMAccess, type NVMIO } from "./common/definitions"; import { type Route, encodeRoute, parseRoute } from "./common/routeCache"; import { @@ -166,7 +167,9 @@ export class NVM500 implements NVM { } else if (entry.type === NVMEntryType.NVMModuleDescriptor) { const entryData = await this.readRawEntry(resolvedEntry); // NVMModuleDescriptor is always a single entry - const descriptor = parseNVMModuleDescriptor(entryData[0]); + const descriptor = parseNVMModuleDescriptor( + entryData[0], + ); if (descriptor.size !== moduleSize) { throw new ZWaveError( "NVM module descriptor size does not match module size!", @@ -251,7 +254,7 @@ export class NVM500 implements NVM { private async readSingleRawEntry( entry: ResolvedNVMEntry, index: number, - ): Promise { + ): Promise { if (index >= entry.count) { throw new ZWaveError( `Index out of range. Tried to read entry ${index} of ${entry.count}.`, @@ -267,8 +270,8 @@ export class NVM500 implements NVM { private async readRawEntry( entry: ResolvedNVMEntry, - ): Promise { - const ret: Buffer[] = []; + ): Promise { + const ret: Uint8Array[] = []; const nvmData = await nvmReadBuffer( this._io, entry.offset, @@ -282,7 +285,7 @@ export class NVM500 implements NVM { return ret; } - private parseEntry(type: NVMEntryType, data: Buffer): NVMData { + private parseEntry(type: NVMEntryType, data: Bytes): NVMData { switch (type) { case NVMEntryType.Byte: return data.readUInt8(0); @@ -322,16 +325,18 @@ export class NVM500 implements NVM { private async readEntry( entry: ResolvedNVMEntry, ): Promise { - const data: Buffer[] = await this.readRawEntry(entry); - return data.map((buffer) => this.parseEntry(entry.type, buffer)); + const data: Uint8Array[] = await this.readRawEntry(entry); + return data.map((buffer) => + this.parseEntry(entry.type, Bytes.view(buffer)) + ); } private async readSingleEntry( entry: ResolvedNVMEntry, index: number, ): Promise { - const data: Buffer = await this.readSingleRawEntry(entry, index); - return this.parseEntry(entry.type, data); + const data = await this.readSingleRawEntry(entry, index); + return this.parseEntry(entry.type, Bytes.view(data)); } public async get(property: NVMEntryName): Promise { @@ -361,34 +366,31 @@ export class NVM500 implements NVM { type: NVMEntryType, data: NVMData, entrySize?: number, - ): Buffer { + ): Bytes { const size = entrySize ?? NVMEntrySizes[type]; switch (type) { case NVMEntryType.Byte: - return Buffer.from([data as number]); + return Bytes.from([data as number]); case NVMEntryType.Word: case NVMEntryType.NVMModuleSize: { - const ret = Buffer.allocUnsafe(2); + const ret = new Bytes(2); ret.writeUInt16BE(data as number, 0); return ret; } case NVMEntryType.DWord: { - const ret = Buffer.allocUnsafe(4); + const ret = new Bytes(4); ret.writeUInt32BE(data as number, 0); return ret; } case NVMEntryType.NodeInfo: return data ? encodeNVM500NodeInfo(data as NVM500NodeInfo) - : Buffer.alloc(size, 0); + : new Bytes(size).fill(0); case NVMEntryType.NodeMask: { - const ret = Buffer.alloc(size, 0); + const ret = new Bytes(size).fill(0); if (data) { - encodeBitMask(data as number[], MAX_NODES, 1).copy( - ret, - 0, - ); + ret.set(encodeBitMask(data as number[], MAX_NODES, 1), 0); } return ret; } @@ -403,14 +405,14 @@ export class NVM500 implements NVM { case NVMEntryType.NVMDescriptor: return encodeNVMDescriptor(data as NVMDescriptor); case NVMEntryType.Buffer: - return data as Buffer; + return data as Bytes; } } private async writeSingleRawEntry( entry: ResolvedNVMEntry, index: number, - data: Buffer, + data: Uint8Array, ): Promise { if (index >= entry.count) { throw new ZWaveError( @@ -427,12 +429,12 @@ export class NVM500 implements NVM { private async writeRawEntry( entry: ResolvedNVMEntry, - data: Buffer[], + data: Uint8Array[], ): Promise { await nvmWriteBuffer( this._io, entry.offset, - Buffer.concat(data), + Bytes.concat(data), ); } @@ -498,7 +500,7 @@ export class NVM500 implements NVM { data.push(value); break; case NVMEntryType.Buffer: - data.push(Buffer.alloc(size, value)); + data.push(new Uint8Array(size).fill(value)); break; case NVMEntryType.NodeMask: data.push(new Array(size).fill(value)); @@ -528,7 +530,11 @@ export class NVM500 implements NVM { options: NVM500EraseOptions, ): Promise { // Blank NVM with 0xff - await nvmWriteBuffer(this._io, 0, Buffer.alloc(options.nvmSize, 0xff)); + await nvmWriteBuffer( + this._io, + 0, + new Uint8Array(options.nvmSize).fill(0xff), + ); // Compute module sizes const layoutEntries = Array.from(options.layout.values()); diff --git a/packages/nvmedit/src/lib/common/definitions.ts b/packages/nvmedit/src/lib/common/definitions.ts index ee104b5a1f0d..6a0e13e17451 100644 --- a/packages/nvmedit/src/lib/common/definitions.ts +++ b/packages/nvmedit/src/lib/common/definitions.ts @@ -46,7 +46,7 @@ export interface NVMIO { read( offset: number, length: number, - ): Promise<{ buffer: Buffer; endOfFile: boolean }>; + ): Promise<{ buffer: Uint8Array; endOfFile: boolean }>; /** * Writes a chunk of data with the given length from the NVM. @@ -54,7 +54,7 @@ export interface NVMIO { */ write( offset: number, - data: Buffer, + data: Uint8Array, ): Promise<{ bytesWritten: number; endOfFile: boolean }>; /** Closes the NVM */ @@ -115,7 +115,7 @@ export type ControllerNVMPropertyTypes = Expand< protocolVersion: string; protocolFileFormat: number; applicationVersion: string; - applicationData: Buffer; + applicationData: Uint8Array; preferredRepeaters?: number[]; sucUpdateEntries: SUCUpdateEntry[]; appRouteLock: number[]; @@ -133,7 +133,7 @@ export type ControllerNVMPropertyTypes = Expand< }> // 500 series only & Partial<{ - learnedHomeId: Buffer; + learnedHomeId: Uint8Array; commandClasses: CommandClasses[]; systemState: number; watchdogStarted: number; diff --git a/packages/nvmedit/src/lib/common/routeCache.ts b/packages/nvmedit/src/lib/common/routeCache.ts index 2e78f1c62bbd..93ebb80d7e7b 100644 --- a/packages/nvmedit/src/lib/common/routeCache.ts +++ b/packages/nvmedit/src/lib/common/routeCache.ts @@ -4,14 +4,13 @@ import { RouteProtocolDataRate, protocolDataRateMask, } from "@zwave-js/core/safe"; +import { Bytes } from "@zwave-js/shared/safe"; const ROUTE_SIZE = MAX_REPEATERS + 1; export const ROUTECACHE_SIZE = 2 * ROUTE_SIZE; export const EMPTY_ROUTECACHE_FILL = 0xff; -export const emptyRouteCache = Buffer.alloc( - ROUTECACHE_SIZE, - EMPTY_ROUTECACHE_FILL, -); +export const emptyRouteCache = new Uint8Array(ROUTECACHE_SIZE) + .fill(EMPTY_ROUTECACHE_FILL); enum Beaming { "1000ms" = 0x40, @@ -30,7 +29,7 @@ export interface RouteCache { nlwr: Route; } -export function parseRoute(buffer: Buffer, offset: number): Route { +export function parseRoute(buffer: Uint8Array, offset: number): Route { const routeConf = buffer[offset + MAX_REPEATERS]; const ret: Route = { beaming: (Beaming[routeConf & 0x60] ?? false) as FLiRS, @@ -43,8 +42,8 @@ export function parseRoute(buffer: Buffer, offset: number): Route { return ret; } -export function encodeRoute(route: Route | undefined): Buffer { - const ret = Buffer.alloc(ROUTE_SIZE, 0); +export function encodeRoute(route: Route | undefined): Bytes { + const ret = new Bytes(ROUTE_SIZE).fill(0); if (route) { if (route.repeaterNodeIDs) { for ( diff --git a/packages/nvmedit/src/lib/common/sucUpdateEntry.ts b/packages/nvmedit/src/lib/common/sucUpdateEntry.ts index f9ad86cce429..cdfc8a2fa7e9 100644 --- a/packages/nvmedit/src/lib/common/sucUpdateEntry.ts +++ b/packages/nvmedit/src/lib/common/sucUpdateEntry.ts @@ -5,6 +5,7 @@ import { encodeCCList, parseCCList, } from "@zwave-js/core/safe"; +import { Bytes } from "@zwave-js/shared"; import { SUC_UPDATE_ENTRY_SIZE, SUC_UPDATE_NODEPARM_MAX } from "../../consts"; export interface SUCUpdateEntry { @@ -15,7 +16,7 @@ export interface SUCUpdateEntry { } export function parseSUCUpdateEntry( - buffer: Buffer, + buffer: Uint8Array, offset: number, ): SUCUpdateEntry | undefined { const slice = buffer.subarray(offset, offset + SUC_UPDATE_ENTRY_SIZE); @@ -37,8 +38,8 @@ export function parseSUCUpdateEntry( export function encodeSUCUpdateEntry( entry: SUCUpdateEntry | undefined, -): Buffer { - const ret = Buffer.alloc(SUC_UPDATE_ENTRY_SIZE, 0); +): Bytes { + const ret = new Bytes(SUC_UPDATE_ENTRY_SIZE).fill(0); if (entry) { ret[0] = entry.nodeId; ret[1] = entry.changeType; @@ -49,7 +50,7 @@ export function encodeSUCUpdateEntry( ZWaveErrorCodes.Argument_Invalid, ); } - ccList.copy(ret, 2); + ret.set(ccList, 2); } return ret; } diff --git a/packages/nvmedit/src/lib/common/utils.ts b/packages/nvmedit/src/lib/common/utils.ts index c1ecbf171cfa..cdf877a694eb 100644 --- a/packages/nvmedit/src/lib/common/utils.ts +++ b/packages/nvmedit/src/lib/common/utils.ts @@ -1,3 +1,4 @@ +import { Bytes } from "@zwave-js/shared/safe"; import type { NVMIO } from "./definitions"; export async function nvmReadUInt32LE( @@ -5,7 +6,8 @@ export async function nvmReadUInt32LE( position: number, ): Promise { const { buffer } = await io.read(position, 4); - return buffer.readUInt32LE(0); + const bytes = Bytes.view(buffer); + return bytes.readUInt32LE(0); } export async function nvmReadUInt16LE( @@ -13,7 +15,8 @@ export async function nvmReadUInt16LE( position: number, ): Promise { const { buffer } = await io.read(position, 2); - return buffer.readUInt16LE(0); + const bytes = Bytes.view(buffer); + return bytes.readUInt16LE(0); } export async function nvmReadUInt32BE( @@ -21,7 +24,8 @@ export async function nvmReadUInt32BE( position: number, ): Promise { const { buffer } = await io.read(position, 4); - return buffer.readUInt32BE(0); + const bytes = Bytes.view(buffer); + return bytes.readUInt32BE(0); } export async function nvmReadUInt16BE( @@ -29,7 +33,8 @@ export async function nvmReadUInt16BE( position: number, ): Promise { const { buffer } = await io.read(position, 2); - return buffer.readUInt16BE(0); + const bytes = Bytes.view(buffer); + return bytes.readUInt16BE(0); } export async function nvmReadUInt8( @@ -37,13 +42,14 @@ export async function nvmReadUInt8( position: number, ): Promise { const { buffer } = await io.read(position, 1); - return buffer.readUInt8(0); + const bytes = Bytes.view(buffer); + return bytes.readUInt8(0); } export async function nvmWriteBuffer( io: NVMIO, position: number, - buffer: Buffer, + buffer: Uint8Array, ): Promise { const chunkSize = await io.determineChunkSize(); let offset = 0; @@ -58,8 +64,8 @@ export async function nvmReadBuffer( io: NVMIO, position: number, length: number, -): Promise { - const ret = Buffer.allocUnsafe(length); +): Promise { + const ret = new Uint8Array(length); const chunkSize = await io.determineChunkSize(); let offset = 0; while (offset < length) { @@ -67,7 +73,7 @@ export async function nvmReadBuffer( position + offset, Math.min(chunkSize, length - offset), ); - buffer.copy(ret, offset); + ret.set(buffer, offset); offset += buffer.length; if (endOfFile) break; } diff --git a/packages/nvmedit/src/lib/io/BufferedNVMReader.ts b/packages/nvmedit/src/lib/io/BufferedNVMReader.ts index 84fa990773ad..7b41faebf423 100644 --- a/packages/nvmedit/src/lib/io/BufferedNVMReader.ts +++ b/packages/nvmedit/src/lib/io/BufferedNVMReader.ts @@ -1,8 +1,9 @@ +import { Bytes } from "@zwave-js/shared/safe"; import { type NVMAccess, type NVMIO } from "../common/definitions"; interface BufferedChunk { offset: number; - data: Buffer; + data: Uint8Array; } export class BufferedNVMReader implements NVMIO { @@ -32,7 +33,7 @@ export class BufferedNVMReader implements NVMIO { private async readBuffered( alignedOffset: number, chunkSize: number, - ): Promise { + ): Promise { let buffered = this._buffer.find((chunk) => chunk.offset === alignedOffset ); @@ -50,7 +51,7 @@ export class BufferedNVMReader implements NVMIO { async read( offset: number, length: number, - ): Promise<{ buffer: Buffer; endOfFile: boolean }> { + ): Promise<{ buffer: Uint8Array; endOfFile: boolean }> { // Limit the read size to the chunk size. This ensures we have to deal with maximum 2 chunks or read requests const chunkSize = await this.determineChunkSize(); length = Math.min(length, chunkSize); @@ -61,12 +62,12 @@ export class BufferedNVMReader implements NVMIO { - (offset + length) % chunkSize; // Read one or two chunks, depending on how many are needed - const chunks: Buffer[] = []; + const chunks: Uint8Array[] = []; chunks.push(await this.readBuffered(firstChunkStart, chunkSize)); if (secondChunkStart > firstChunkStart) { chunks.push(await this.readBuffered(secondChunkStart, chunkSize)); } - const alignedBuffer = Buffer.concat(chunks); + const alignedBuffer = Bytes.concat(chunks); // Then slice out the section we need const endOfFile = offset + length >= this.size; @@ -83,7 +84,7 @@ export class BufferedNVMReader implements NVMIO { async write( offset: number, - data: Buffer, + data: Uint8Array, ): Promise<{ bytesWritten: number; endOfFile: boolean }> { const ret = await this._inner.write(offset, data); diff --git a/packages/nvmedit/src/lib/io/NVMFileIO.ts b/packages/nvmedit/src/lib/io/NVMFileIO.ts index 8c683b351c64..2accfd2a3951 100644 --- a/packages/nvmedit/src/lib/io/NVMFileIO.ts +++ b/packages/nvmedit/src/lib/io/NVMFileIO.ts @@ -58,7 +58,7 @@ export class NVMFileIO implements NVMIO { async read( offset: number, length: number, - ): Promise<{ buffer: Buffer; endOfFile: boolean }> { + ): Promise<{ buffer: Uint8Array; endOfFile: boolean }> { if (this._handle == undefined) { throw new ZWaveError( "The NVM file is not open", @@ -66,7 +66,7 @@ export class NVMFileIO implements NVMIO { ); } const readResult = await this._handle.read({ - buffer: Buffer.alloc(length), + buffer: new Uint8Array(length), position: offset, }); @@ -79,7 +79,7 @@ export class NVMFileIO implements NVMIO { async write( offset: number, - data: Buffer, + data: Uint8Array, ): Promise<{ bytesWritten: number; endOfFile: boolean }> { if (this._handle == undefined) { throw new ZWaveError( diff --git a/packages/nvmedit/src/lib/io/NVMMemoryIO.ts b/packages/nvmedit/src/lib/io/NVMMemoryIO.ts index 01f34a88ada0..7596a7a18e8d 100644 --- a/packages/nvmedit/src/lib/io/NVMMemoryIO.ts +++ b/packages/nvmedit/src/lib/io/NVMMemoryIO.ts @@ -3,11 +3,11 @@ import { NVMAccess, type NVMIO } from "../common/definitions"; /** An im-memory implementation of NVMIO */ export class NVMMemoryIO implements NVMIO { - public constructor(buffer: Buffer) { + public constructor(buffer: Uint8Array) { this._buffer = buffer; } - private _buffer: Buffer; + private _buffer: Uint8Array; open(_access: NVMAccess.Read | NVMAccess.Write): Promise { // Nothing to do @@ -30,7 +30,7 @@ export class NVMMemoryIO implements NVMIO { read( offset: number, length: number, - ): Promise<{ buffer: Buffer; endOfFile: boolean }> { + ): Promise<{ buffer: Uint8Array; endOfFile: boolean }> { return Promise.resolve({ buffer: this._buffer.subarray(offset, offset + length), endOfFile: offset + length >= this._buffer.length, @@ -39,7 +39,7 @@ export class NVMMemoryIO implements NVMIO { write( offset: number, - data: Buffer, + data: Uint8Array, ): Promise<{ bytesWritten: number; endOfFile: boolean }> { if (offset + data.length > this.size) { throw new ZWaveError( @@ -48,7 +48,7 @@ export class NVMMemoryIO implements NVMIO { ); } - data.copy(this._buffer, offset, 0, data.length); + this._buffer.set(data, offset); return Promise.resolve({ bytesWritten: data.length, endOfFile: offset + data.length >= this._buffer.length, diff --git a/packages/nvmedit/src/lib/nvm3/adapter.ts b/packages/nvmedit/src/lib/nvm3/adapter.ts index b2145b878d44..531c88e96c57 100644 --- a/packages/nvmedit/src/lib/nvm3/adapter.ts +++ b/packages/nvmedit/src/lib/nvm3/adapter.ts @@ -90,7 +90,7 @@ export class NVM3Adapter implements NVMAdapter { } | undefined; /** A list of pending changes that haven't been written to the NVM yet. `null` indicates a deleted entry. */ - private _pendingChanges: Map = new Map(); + private _pendingChanges: Map = new Map(); private getFileVersion(fileId: number): string { if ( @@ -174,7 +174,7 @@ export class NVM3Adapter implements NVMAdapter { if (!skipInit && !this._initialized) await this.init(); // Prefer pending changes over the actual NVM, so changes can be composed - let data: Buffer | null | undefined; + let data: Uint8Array | null | undefined; if (this._pendingChanges.has(fileId)) { data = this._pendingChanges.get(fileId); } else { diff --git a/packages/nvmedit/src/lib/nvm3/files/ApplicationCCsFile.ts b/packages/nvmedit/src/lib/nvm3/files/ApplicationCCsFile.ts index fc8cb797c5c0..c2cbfa05225f 100644 --- a/packages/nvmedit/src/lib/nvm3/files/ApplicationCCsFile.ts +++ b/packages/nvmedit/src/lib/nvm3/files/ApplicationCCsFile.ts @@ -1,4 +1,5 @@ import { CommandClasses } from "@zwave-js/core/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import type { NVM3Object } from "../object"; import { NVMFile, @@ -55,8 +56,8 @@ export class ApplicationCCsFile extends NVMFile { public includedSecurelyInsecureCCs: CommandClasses[]; public includedSecurelySecureCCs: CommandClasses[]; - public serialize(): NVM3Object & { data: Buffer } { - this.payload = Buffer.alloc((1 + MAX_CCs) * 3); + public serialize(): NVM3Object & { data: Bytes } { + this.payload = new Bytes((1 + MAX_CCs) * 3); let offset = 0; for ( const array of [ diff --git a/packages/nvmedit/src/lib/nvm3/files/ApplicationDataFile.ts b/packages/nvmedit/src/lib/nvm3/files/ApplicationDataFile.ts index 031d0c240779..e16d6fc8b74b 100644 --- a/packages/nvmedit/src/lib/nvm3/files/ApplicationDataFile.ts +++ b/packages/nvmedit/src/lib/nvm3/files/ApplicationDataFile.ts @@ -1,3 +1,4 @@ +import { type Bytes } from "@zwave-js/shared/safe"; import { NVMFile, type NVMFileCreationOptions, @@ -8,7 +9,7 @@ import { } from "./NVMFile"; export interface ApplicationDataFileOptions extends NVMFileCreationOptions { - applicationData: Buffer; + applicationData: Bytes; } export const ApplicationDataFileID = 200; @@ -26,10 +27,10 @@ export class ApplicationDataFile extends NVMFile { } // Just binary data - public get applicationData(): Buffer { + public get applicationData(): Bytes { return this.payload; } - public set applicationData(value: Buffer) { + public set applicationData(value: Bytes) { this.payload = value; } } diff --git a/packages/nvmedit/src/lib/nvm3/files/ApplicationNameFile.ts b/packages/nvmedit/src/lib/nvm3/files/ApplicationNameFile.ts index db5ba967aa8d..69074a7a4711 100644 --- a/packages/nvmedit/src/lib/nvm3/files/ApplicationNameFile.ts +++ b/packages/nvmedit/src/lib/nvm3/files/ApplicationNameFile.ts @@ -1,4 +1,4 @@ -import { cpp2js } from "@zwave-js/shared"; +import { Bytes, cpp2js } from "@zwave-js/shared"; import { type NVM3Object } from "../object"; import { NVMFile, @@ -31,11 +31,11 @@ export class ApplicationNameFile extends NVMFile { public name: string; - public serialize(): NVM3Object & { data: Buffer } { + public serialize(): NVM3Object & { data: Bytes } { // Return a zero-terminated string with a fixed length of 30 bytes - const nameAsString = Buffer.from(this.name, "utf8"); - this.payload = Buffer.alloc(30, 0); - nameAsString.subarray(0, this.payload.length - 1).copy(this.payload); + const nameAsString = Bytes.from(this.name, "utf8"); + this.payload = new Bytes(30).fill(0); + this.payload.set(nameAsString.subarray(0, this.payload.length), 0); return super.serialize(); } } diff --git a/packages/nvmedit/src/lib/nvm3/files/ApplicationRFConfigFile.ts b/packages/nvmedit/src/lib/nvm3/files/ApplicationRFConfigFile.ts index 9a096d3639cb..a2f1b286a881 100644 --- a/packages/nvmedit/src/lib/nvm3/files/ApplicationRFConfigFile.ts +++ b/packages/nvmedit/src/lib/nvm3/files/ApplicationRFConfigFile.ts @@ -4,7 +4,11 @@ import { ZWaveError, ZWaveErrorCodes, } from "@zwave-js/core/safe"; -import { type AllOrNone, getEnumMemberName } from "@zwave-js/shared/safe"; +import { + type AllOrNone, + Bytes, + getEnumMemberName, +} from "@zwave-js/shared/safe"; import semver from "semver"; import type { NVM3Object } from "../object"; import { @@ -43,8 +47,8 @@ export class ApplicationRFConfigFile extends NVMFile { if (gotDeserializationOptions(options)) { if (this.payload.length === 3 || this.payload.length === 6) { this.rfRegion = this.payload[0]; - this.txPower = this.payload.readIntLE(1, 1) / 10; - this.measured0dBm = this.payload.readIntLE(2, 1) / 10; + this.txPower = this.payload.readInt8(1) / 10; + this.measured0dBm = this.payload.readInt8(2) / 10; if (this.payload.length === 6) { this.enablePTI = this.payload[3]; this.maxTXPower = this.payload.readInt16LE(4) / 10; @@ -85,12 +89,11 @@ export class ApplicationRFConfigFile extends NVMFile { public maxTXPower?: number; public nodeIdType?: NodeIDType; - public serialize(): NVM3Object & { data: Buffer } { + public serialize(): NVM3Object & { data: Bytes } { if (semver.lt(this.fileVersion, "7.18.1")) { - this.payload = Buffer.alloc( + this.payload = new Bytes( semver.gte(this.fileVersion, "7.15.3") ? 6 : 3, - 0, - ); + ).fill(0); this.payload[0] = this.rfRegion; this.payload.writeIntLE(this.txPower * 10, 1, 1); this.payload.writeIntLE(this.measured0dBm * 10, 2, 1); @@ -99,14 +102,14 @@ export class ApplicationRFConfigFile extends NVMFile { this.payload.writeInt16LE((this.maxTXPower ?? 0) * 10, 4); } } else if (semver.lt(this.fileVersion, "7.21.0")) { - this.payload = Buffer.alloc(8, 0); + this.payload = new Bytes(8).fill(0); this.payload[0] = this.rfRegion; this.payload.writeInt16LE(this.txPower * 10, 1); this.payload.writeInt16LE(this.measured0dBm * 10, 3); this.payload[5] = this.enablePTI ?? 0; this.payload.writeInt16LE((this.maxTXPower ?? 0) * 10, 6); } else { - this.payload = Buffer.alloc(9, 0); + this.payload = new Bytes(9).fill(0); this.payload[0] = this.rfRegion; this.payload.writeInt16LE(this.txPower * 10, 1); this.payload.writeInt16LE(this.measured0dBm * 10, 3); diff --git a/packages/nvmedit/src/lib/nvm3/files/ApplicationTypeFile.ts b/packages/nvmedit/src/lib/nvm3/files/ApplicationTypeFile.ts index 112a79cedcb6..e756cb626b34 100644 --- a/packages/nvmedit/src/lib/nvm3/files/ApplicationTypeFile.ts +++ b/packages/nvmedit/src/lib/nvm3/files/ApplicationTypeFile.ts @@ -1,3 +1,4 @@ +import { Bytes } from "@zwave-js/shared/safe"; import type { NVM3Object } from "../object"; import { NVMFile, @@ -42,8 +43,8 @@ export class ApplicationTypeFile extends NVMFile { public genericDeviceClass: number; public specificDeviceClass: number; - public serialize(): NVM3Object & { data: Buffer } { - this.payload = Buffer.from([ + public serialize(): NVM3Object & { data: Bytes } { + this.payload = Bytes.from([ (this.isListening ? 0b1 : 0) | (this.optionalFunctionality ? 0b10 : 0), this.genericDeviceClass, diff --git a/packages/nvmedit/src/lib/nvm3/files/ControllerInfoFile.ts b/packages/nvmedit/src/lib/nvm3/files/ControllerInfoFile.ts index c1a4bfd0b102..63cb8ff743bb 100644 --- a/packages/nvmedit/src/lib/nvm3/files/ControllerInfoFile.ts +++ b/packages/nvmedit/src/lib/nvm3/files/ControllerInfoFile.ts @@ -3,7 +3,7 @@ import { ZWaveErrorCodes, stripUndefined, } from "@zwave-js/core/safe"; -import { buffer2hex } from "@zwave-js/shared"; +import { Bytes, buffer2hex } from "@zwave-js/shared"; import type { NVM3Object } from "../object"; import { NVMFile, @@ -17,7 +17,7 @@ import { export type ControllerInfoFileOptions = & NVMFileCreationOptions & { - homeId: Buffer; + homeId: Uint8Array; nodeId: number; lastNodeId: number; staticControllerNodeId: number; @@ -82,7 +82,7 @@ export class ControllerInfoFile extends NVMFile { ); } } else { - this.homeId = options.homeId; + this.homeId = Bytes.view(options.homeId); this.nodeId = options.nodeId; this.lastNodeId = options.lastNodeId; this.staticControllerNodeId = options.staticControllerNodeId; @@ -104,7 +104,7 @@ export class ControllerInfoFile extends NVMFile { } } - public homeId: Buffer; + public homeId: Uint8Array; public nodeId: number; public lastNodeId: number; public staticControllerNodeId: number; @@ -120,10 +120,10 @@ export class ControllerInfoFile extends NVMFile { public primaryLongRangeChannelId?: number; public dcdcConfig?: number; - public serialize(): NVM3Object & { data: Buffer } { + public serialize(): NVM3Object & { data: Bytes } { if (this.lastNodeIdLR != undefined) { - this.payload = Buffer.allocUnsafe(22); - this.homeId.copy(this.payload, 0); + this.payload = new Bytes(22); + this.payload.set(this.homeId, 0); this.payload.writeUInt16LE(this.nodeId, 4); this.payload.writeUInt16LE(this.staticControllerNodeId, 6); this.payload.writeUInt16LE(this.lastNodeIdLR, 8); @@ -139,9 +139,9 @@ export class ControllerInfoFile extends NVMFile { this.payload[21] = this.dcdcConfig!; } else { // V0 - this.payload = Buffer.concat([ + this.payload = Bytes.concat([ this.homeId, - Buffer.from([ + Bytes.from([ this.nodeId, this.lastNodeId, this.staticControllerNodeId, diff --git a/packages/nvmedit/src/lib/nvm3/files/NVMFile.ts b/packages/nvmedit/src/lib/nvm3/files/NVMFile.ts index b2881c4c5e19..0102fcdd00a9 100644 --- a/packages/nvmedit/src/lib/nvm3/files/NVMFile.ts +++ b/packages/nvmedit/src/lib/nvm3/files/NVMFile.ts @@ -1,6 +1,11 @@ import { createSimpleReflectionDecorator } from "@zwave-js/core"; import { ZWaveError, ZWaveErrorCodes } from "@zwave-js/core/safe"; -import { type TypedClassDecorator, num2hex } from "@zwave-js/shared"; +import { + Bytes, + type TypedClassDecorator, + isUint8Array, + num2hex, +} from "@zwave-js/shared"; import { FragmentType, NVM3_MAX_OBJ_SIZE_SMALL, ObjectType } from "../consts"; import type { NVM3Object } from "../object"; @@ -10,13 +15,13 @@ export interface NVMFileBaseOptions { } export interface NVMFileDeserializationOptions extends NVMFileBaseOptions { fileId: number; - data: Buffer; + data: Bytes; } export function gotDeserializationOptions( options: NVMFileOptions, ): options is NVMFileDeserializationOptions { - return "data" in options && Buffer.isBuffer(options.data); + return "data" in options && isUint8Array(options.data); } // eslint-disable-next-line @typescript-eslint/no-empty-object-type @@ -38,11 +43,11 @@ export class NVMFile { if (typeof fileId === "number") { this.fileId = fileId; } - this.payload = Buffer.allocUnsafe(0); + this.payload = new Bytes(); } } - protected payload: Buffer; + protected payload: Bytes; public fileId: number = 0; public fileVersion: string; @@ -51,7 +56,7 @@ export class NVMFile { */ public static from( fileId: number, - data: Buffer, + data: Uint8Array, fileVersion: string, ): NVMFile { const Constructor = getNVMFileConstructor(fileId)!; @@ -65,7 +70,7 @@ export class NVMFile { /** * Serializes this NVMFile into an NVM Object */ - public serialize(): NVM3Object & { data: Buffer } { + public serialize(): NVM3Object & { data: Bytes } { if (!this.fileId) { throw new Error("The NVM file ID must be set before serializing"); } diff --git a/packages/nvmedit/src/lib/nvm3/files/NodeInfoFiles.ts b/packages/nvmedit/src/lib/nvm3/files/NodeInfoFiles.ts index a49d39698eb5..5252eab97ea2 100644 --- a/packages/nvmedit/src/lib/nvm3/files/NodeInfoFiles.ts +++ b/packages/nvmedit/src/lib/nvm3/files/NodeInfoFiles.ts @@ -11,7 +11,7 @@ import { parseBitMask, parseNodeProtocolInfo, } from "@zwave-js/core/safe"; -import { pick } from "@zwave-js/shared/safe"; +import { Bytes, pick } from "@zwave-js/shared/safe"; import type { NVM3Object } from "../object"; import { NVMFile, @@ -27,7 +27,7 @@ export const LR_NODEINFOS_PER_FILE_V5 = 50; const NODEINFO_SIZE = 1 + 5 + NUM_NODEMASK_BYTES; const LR_NODEINFO_SIZE = 3; const EMPTY_NODEINFO_FILL = 0xff; -const emptyNodeInfo = Buffer.alloc(NODEINFO_SIZE, EMPTY_NODEINFO_FILL); +const emptyNodeInfo = new Uint8Array(NODEINFO_SIZE).fill(EMPTY_NODEINFO_FILL); export interface NodeInfo extends Omit @@ -41,7 +41,7 @@ export interface NodeInfo function parseNodeInfo( nodeId: number, - buffer: Buffer, + buffer: Uint8Array, offset: number, ): NodeInfo { const { hasSpecificDeviceClass, ...protocolInfo } = parseNodeProtocolInfo( @@ -66,8 +66,8 @@ function parseNodeInfo( }; } -function encodeNodeInfo(nodeInfo: NodeInfo): Buffer { - const ret = Buffer.alloc(NODEINFO_SIZE); +function encodeNodeInfo(nodeInfo: NodeInfo): Bytes { + const ret = new Bytes(NODEINFO_SIZE); const hasSpecificDeviceClass = nodeInfo.specificDeviceClass != null; const protocolInfo: NodeProtocolInfo = { @@ -84,11 +84,11 @@ function encodeNodeInfo(nodeInfo: NodeInfo): Buffer { ]), hasSpecificDeviceClass, }; - encodeNodeProtocolInfo(protocolInfo).copy(ret, 0); + ret.set(encodeNodeProtocolInfo(protocolInfo), 0); ret[3] = nodeInfo.genericDeviceClass; if (hasSpecificDeviceClass) ret[4] = nodeInfo.specificDeviceClass!; - encodeBitMask(nodeInfo.neighbors, MAX_NODES).copy(ret, 5); + ret.set(encodeBitMask(nodeInfo.neighbors, MAX_NODES), 5); ret[5 + NUM_NODEMASK_BYTES] = nodeInfo.sucUpdateIndex; return ret; @@ -104,7 +104,7 @@ export interface LRNodeInfo function parseLRNodeInfo( nodeId: number, - buffer: Buffer, + buffer: Uint8Array, offset: number, ): LRNodeInfo { // The node info in LR NVM files is packed: @@ -162,8 +162,8 @@ function parseLRNodeInfo( }; } -function encodeLRNodeInfo(nodeInfo: LRNodeInfo): Buffer { - const ret = Buffer.alloc(LR_NODEINFO_SIZE); +function encodeLRNodeInfo(nodeInfo: LRNodeInfo): Uint8Array { + const ret = new Bytes(LR_NODEINFO_SIZE); let capability = 0; if (nodeInfo.isRouting) capability |= 0b0000_0001; @@ -215,7 +215,7 @@ export class NodeInfoFileV0 extends NVMFile { public nodeInfo: NodeInfo; - public serialize(): NVM3Object & { data: Buffer } { + public serialize(): NVM3Object & { data: Bytes } { this.fileId = nodeIdToNodeInfoFileIDV0(this.nodeInfo.nodeId); this.payload = encodeNodeInfo(this.nodeInfo); return super.serialize(); @@ -280,14 +280,13 @@ export class NodeInfoFileV1 extends NVMFile { public nodeInfos: NodeInfo[]; - public serialize(): NVM3Object & { data: Buffer } { + public serialize(): NVM3Object & { data: Bytes } { // The infos must be sorted by node ID this.nodeInfos.sort((a, b) => a.nodeId - b.nodeId); const minNodeId = this.nodeInfos[0].nodeId; this.fileId = nodeIdToNodeInfoFileIDV1(minNodeId); - this.payload = Buffer.alloc( - NODEINFO_SIZE * NODEINFOS_PER_FILE_V1, + this.payload = new Bytes(NODEINFO_SIZE * NODEINFOS_PER_FILE_V1).fill( EMPTY_NODEINFO_FILL, ); @@ -298,7 +297,7 @@ export class NodeInfoFileV1 extends NVMFile { for (const nodeInfo of this.nodeInfos) { const offset = (nodeInfo.nodeId - minFileNodeId) * NODEINFO_SIZE; - encodeNodeInfo(nodeInfo).copy(this.payload, offset); + this.payload.set(encodeNodeInfo(nodeInfo), offset); } return super.serialize(); @@ -366,16 +365,14 @@ export class LRNodeInfoFileV5 extends NVMFile { public nodeInfos: LRNodeInfo[]; - public serialize(): NVM3Object & { data: Buffer } { + public serialize(): NVM3Object & { data: Bytes } { // The infos must be sorted by node ID this.nodeInfos.sort((a, b) => a.nodeId - b.nodeId); const minNodeId = this.nodeInfos[0].nodeId; this.fileId = nodeIdToLRNodeInfoFileIDV5(minNodeId); - this.payload = Buffer.alloc( - LR_NODEINFO_SIZE * LR_NODEINFOS_PER_FILE_V5, - EMPTY_NODEINFO_FILL, - ); + this.payload = new Bytes(LR_NODEINFO_SIZE * LR_NODEINFOS_PER_FILE_V5) + .fill(EMPTY_NODEINFO_FILL); const minFileNodeId = Math.floor((minNodeId - 256) / LR_NODEINFOS_PER_FILE_V5) @@ -384,7 +381,7 @@ export class LRNodeInfoFileV5 extends NVMFile { for (const nodeInfo of this.nodeInfos) { const offset = (nodeInfo.nodeId - minFileNodeId) * LR_NODEINFO_SIZE; - encodeLRNodeInfo(nodeInfo).copy(this.payload, offset); + this.payload.set(encodeLRNodeInfo(nodeInfo), offset); } return super.serialize(); diff --git a/packages/nvmedit/src/lib/nvm3/files/ProtocolNodeMaskFiles.ts b/packages/nvmedit/src/lib/nvm3/files/ProtocolNodeMaskFiles.ts index a9e0b2eee6d0..d0bf00d64986 100644 --- a/packages/nvmedit/src/lib/nvm3/files/ProtocolNodeMaskFiles.ts +++ b/packages/nvmedit/src/lib/nvm3/files/ProtocolNodeMaskFiles.ts @@ -1,4 +1,5 @@ import { NODE_ID_MAX, encodeBitMask, parseBitMask } from "@zwave-js/core/safe"; +import { type Bytes } from "@zwave-js/shared/safe"; import type { NVM3Object } from "../object"; import { NVMFile, @@ -33,7 +34,7 @@ export class ProtocolNodeMaskFile extends NVMFile { this.nodeIdSet = new Set(value); } - public serialize(): NVM3Object & { data: Buffer } { + public serialize(): NVM3Object & { data: Bytes } { this.payload = encodeBitMask([...this.nodeIdSet], NODE_ID_MAX); return super.serialize(); } @@ -125,7 +126,7 @@ export class ProtocolLRNodeListFile extends NVMFile { this.nodeIdSet = new Set(value); } - public serialize(): NVM3Object & { data: Buffer } { + public serialize(): NVM3Object & { data: Bytes } { // There are only 128 bytes for the bitmask, so the LR node IDs only go up to 1279 this.payload = encodeBitMask([...this.nodeIdSet], 1279, 256); return super.serialize(); diff --git a/packages/nvmedit/src/lib/nvm3/files/RouteCacheFiles.ts b/packages/nvmedit/src/lib/nvm3/files/RouteCacheFiles.ts index cb3963258365..a54e42d48d1f 100644 --- a/packages/nvmedit/src/lib/nvm3/files/RouteCacheFiles.ts +++ b/packages/nvmedit/src/lib/nvm3/files/RouteCacheFiles.ts @@ -1,4 +1,5 @@ import { MAX_NODES, MAX_REPEATERS } from "@zwave-js/core/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { EMPTY_ROUTECACHE_FILL, ROUTECACHE_SIZE, @@ -50,9 +51,9 @@ export class RouteCacheFileV0 extends NVMFile { public routeCache: RouteCache; - public serialize(): NVM3Object & { data: Buffer } { + public serialize(): NVM3Object & { data: Bytes } { this.fileId = nodeIdToRouteCacheFileIDV0(this.routeCache.nodeId); - this.payload = Buffer.concat([ + this.payload = Bytes.concat([ encodeRoute(this.routeCache.lwr), encodeRoute(this.routeCache.nlwr), ]); @@ -120,16 +121,14 @@ export class RouteCacheFileV1 extends NVMFile { public routeCaches: RouteCache[]; - public serialize(): NVM3Object & { data: Buffer } { + public serialize(): NVM3Object & { data: Bytes } { // The route infos must be sorted by node ID this.routeCaches.sort((a, b) => a.nodeId - b.nodeId); const minNodeId = this.routeCaches[0].nodeId; this.fileId = nodeIdToRouteCacheFileIDV1(minNodeId); - this.payload = Buffer.alloc( - ROUTECACHES_PER_FILE_V1 * ROUTECACHE_SIZE, - EMPTY_ROUTECACHE_FILL, - ); + this.payload = new Bytes(ROUTECACHES_PER_FILE_V1 * ROUTECACHE_SIZE) + .fill(EMPTY_ROUTECACHE_FILL); const minFileNodeId = Math.floor((minNodeId - 1) / ROUTECACHES_PER_FILE_V1) @@ -139,10 +138,11 @@ export class RouteCacheFileV1 extends NVMFile { for (const routeCache of this.routeCaches) { const offset = (routeCache.nodeId - minFileNodeId) * ROUTECACHE_SIZE; - Buffer.concat([ + const routes = Bytes.concat([ encodeRoute(routeCache.lwr), encodeRoute(routeCache.nlwr), - ]).copy(this.payload, offset); + ]); + this.payload.set(routes, offset); } return super.serialize(); diff --git a/packages/nvmedit/src/lib/nvm3/files/SUCUpdateEntriesFile.ts b/packages/nvmedit/src/lib/nvm3/files/SUCUpdateEntriesFile.ts index 07a44d7c86b6..ecb9203b02a0 100644 --- a/packages/nvmedit/src/lib/nvm3/files/SUCUpdateEntriesFile.ts +++ b/packages/nvmedit/src/lib/nvm3/files/SUCUpdateEntriesFile.ts @@ -1,3 +1,4 @@ +import { Bytes } from "@zwave-js/shared/safe"; import { SUC_MAX_UPDATES, SUC_UPDATE_ENTRY_SIZE } from "../../../consts"; import { type SUCUpdateEntry, @@ -43,12 +44,14 @@ export class SUCUpdateEntriesFileV0 extends NVMFile { public updateEntries: SUCUpdateEntry[]; - public serialize(): NVM3Object & { data: Buffer } { - this.payload = Buffer.alloc(SUC_MAX_UPDATES * SUC_UPDATE_ENTRY_SIZE, 0); + public serialize(): NVM3Object & { data: Bytes } { + this.payload = new Bytes(SUC_MAX_UPDATES * SUC_UPDATE_ENTRY_SIZE).fill( + 0, + ); for (let i = 0; i < this.updateEntries.length; i++) { const offset = i * SUC_UPDATE_ENTRY_SIZE; const entry = this.updateEntries[i]; - encodeSUCUpdateEntry(entry).copy(this.payload, offset); + this.payload.set(encodeSUCUpdateEntry(entry), offset); } return super.serialize(); } @@ -100,15 +103,14 @@ export class SUCUpdateEntriesFileV5 extends NVMFile { public updateEntries: SUCUpdateEntry[]; - public serialize(): NVM3Object & { data: Buffer } { - this.payload = Buffer.alloc( + public serialize(): NVM3Object & { data: Bytes } { + this.payload = new Bytes( SUC_UPDATES_PER_FILE_V5 * SUC_UPDATE_ENTRY_SIZE, - 0xff, - ); + ).fill(0xff); for (let i = 0; i < this.updateEntries.length; i++) { const offset = i * SUC_UPDATE_ENTRY_SIZE; const entry = this.updateEntries[i]; - encodeSUCUpdateEntry(entry).copy(this.payload, offset); + this.payload.set(encodeSUCUpdateEntry(entry), offset); } return super.serialize(); } diff --git a/packages/nvmedit/src/lib/nvm3/files/VersionFiles.ts b/packages/nvmedit/src/lib/nvm3/files/VersionFiles.ts index 7a8d12adbb60..f409f7deeef5 100644 --- a/packages/nvmedit/src/lib/nvm3/files/VersionFiles.ts +++ b/packages/nvmedit/src/lib/nvm3/files/VersionFiles.ts @@ -1,3 +1,4 @@ +import { Bytes } from "@zwave-js/shared"; import type { NVM3Object } from "../object"; import { NVMFile, @@ -38,8 +39,8 @@ export class VersionFile extends NVMFile { public minor: number; public patch: number; - public serialize(): NVM3Object & { data: Buffer } { - this.payload = Buffer.from([ + public serialize(): NVM3Object & { data: Bytes } { + this.payload = Bytes.from([ this.patch, this.minor, this.major, diff --git a/packages/nvmedit/src/lib/nvm3/object.ts b/packages/nvmedit/src/lib/nvm3/object.ts index 3eea81b06364..4bf0b7fa3416 100644 --- a/packages/nvmedit/src/lib/nvm3/object.ts +++ b/packages/nvmedit/src/lib/nvm3/object.ts @@ -1,3 +1,4 @@ +import { Bytes } from "@zwave-js/shared"; import { FragmentType, NVM3_CODE_LARGE_SHIFT, @@ -33,17 +34,17 @@ export interface NVM3Object { type: ObjectType; fragmentType: FragmentType; key: number; - data?: Buffer; + data?: Uint8Array; } -export function serializeObject(obj: NVM3Object): Buffer { +export function serializeObject(obj: NVM3Object): Uint8Array { const isLarge = obj.type === ObjectType.DataLarge || obj.type === ObjectType.CounterLarge; const headerSize = isLarge ? NVM3_OBJ_HEADER_SIZE_LARGE : NVM3_OBJ_HEADER_SIZE_SMALL; const dataLength = obj.data?.length ?? 0; - const ret = Buffer.allocUnsafe(dataLength + headerSize); + const ret = new Bytes(dataLength + headerSize); // Write header if (isLarge) { @@ -77,7 +78,7 @@ export function serializeObject(obj: NVM3Object): Buffer { // Write data if (obj.data) { - obj.data.copy(ret, headerSize); + ret.set(obj.data, headerSize); } return ret; } diff --git a/packages/nvmedit/src/lib/nvm3/page.ts b/packages/nvmedit/src/lib/nvm3/page.ts index 21880a02521b..513b8da25d55 100644 --- a/packages/nvmedit/src/lib/nvm3/page.ts +++ b/packages/nvmedit/src/lib/nvm3/page.ts @@ -1,3 +1,4 @@ +import { Bytes } from "@zwave-js/shared"; import { NVM3_MIN_PAGE_SIZE, NVM3_PAGE_COUNTER_MASK, @@ -38,8 +39,8 @@ export function pageSizeFromBits(bits: number): number { export function serializePageHeader( header: Omit, -): Buffer { - const ret = Buffer.alloc(NVM3_PAGE_HEADER_SIZE); +): Uint8Array { + const ret = new Bytes(NVM3_PAGE_HEADER_SIZE); ret.writeUInt16LE(header.version, 0); ret.writeUInt16LE(NVM3_PAGE_MAGIC, 2); diff --git a/packages/nvmedit/src/lib/nvm3/utils.ts b/packages/nvmedit/src/lib/nvm3/utils.ts index 5a32fd0b3164..bf2087893fa0 100644 --- a/packages/nvmedit/src/lib/nvm3/utils.ts +++ b/packages/nvmedit/src/lib/nvm3/utils.ts @@ -1,5 +1,5 @@ import { ZWaveError, ZWaveErrorCodes } from "@zwave-js/core/safe"; -import { num2hex } from "@zwave-js/shared"; +import { buffer2hex, num2hex } from "@zwave-js/shared"; import { type NVM3 } from "../NVM3"; import { FragmentType, ObjectType, PageStatus } from "./consts"; import { NVMFile } from "./files/NVMFile"; @@ -113,7 +113,7 @@ function dumpObject( if (obj.data) { console.log( `${prefix} data: ${ - obj.data.toString("hex") + buffer2hex(obj.data) } (${obj.data.length} bytes)`, ); } diff --git a/packages/nvmedit/src/lib/nvm500/EntryParsers.ts b/packages/nvmedit/src/lib/nvm500/EntryParsers.ts index 52f02accb428..909b4da48198 100644 --- a/packages/nvmedit/src/lib/nvm500/EntryParsers.ts +++ b/packages/nvmedit/src/lib/nvm500/EntryParsers.ts @@ -3,6 +3,7 @@ import { encodeNodeProtocolInfo, parseNodeProtocolInfo, } from "@zwave-js/core/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { padStart } from "alcalzone-shared/strings"; import type { NVMModuleType } from "./shared"; @@ -16,9 +17,10 @@ export interface NVMDescriptor { } export function parseNVMDescriptor( - buffer: Buffer, + data: Uint8Array, offset: number = 0, ): NVMDescriptor { + const buffer = Bytes.view(data); return { manufacturerID: buffer.readUInt16BE(offset), firmwareID: buffer.readUInt16BE(offset + 2), @@ -36,8 +38,8 @@ export function parseNVMDescriptor( }; } -export function encodeNVMDescriptor(descriptor: NVMDescriptor): Buffer { - const ret = Buffer.allocUnsafe(12); +export function encodeNVMDescriptor(descriptor: NVMDescriptor): Bytes { + const ret = new Bytes(12); ret.writeUInt16BE(descriptor.manufacturerID, 0); ret.writeUInt16BE(descriptor.firmwareID, 2); ret.writeUInt16BE(descriptor.productType, 4); @@ -62,9 +64,10 @@ export interface NVMModuleDescriptor { } export function parseNVMModuleDescriptor( - buffer: Buffer, + data: Uint8Array, offset: number = 0, ): NVMModuleDescriptor { + const buffer = Bytes.view(data); return { size: buffer.readUInt16BE(offset), type: buffer[offset + 2], @@ -74,8 +77,8 @@ export function parseNVMModuleDescriptor( export function encodeNVMModuleDescriptor( descriptior: NVMModuleDescriptor, -): Buffer { - const ret = Buffer.allocUnsafe(5); +): Bytes { + const ret = new Bytes(5); ret.writeUInt16BE(descriptior.size, 0); ret[2] = descriptior.type; const versionParts = descriptior.version.split(".").map((i) => parseInt(i)); @@ -92,7 +95,7 @@ export interface NVM500NodeInfo } export function parseNVM500NodeInfo( - buffer: Buffer, + buffer: Uint8Array, offset: number, ): NVM500NodeInfo { const { hasSpecificDeviceClass, ...protocolInfo } = parseNodeProtocolInfo( @@ -110,13 +113,13 @@ export function parseNVM500NodeInfo( }; } -export function encodeNVM500NodeInfo(nodeInfo: NVM500NodeInfo): Buffer { - return Buffer.concat([ +export function encodeNVM500NodeInfo(nodeInfo: NVM500NodeInfo): Bytes { + return Bytes.concat([ encodeNodeProtocolInfo({ ...nodeInfo, hasSpecificDeviceClass: !!nodeInfo.specificDeviceClass, }), - Buffer.from([ + Bytes.from([ nodeInfo.genericDeviceClass, nodeInfo.specificDeviceClass ?? 0, ]), diff --git a/packages/nvmedit/src/lib/nvm500/adapter.ts b/packages/nvmedit/src/lib/nvm500/adapter.ts index 900a4109ae45..12fba9ee195d 100644 --- a/packages/nvmedit/src/lib/nvm500/adapter.ts +++ b/packages/nvmedit/src/lib/nvm500/adapter.ts @@ -3,6 +3,7 @@ import { ZWaveError, ZWaveErrorCodes, } from "@zwave-js/core"; +import { Bytes } from "@zwave-js/shared"; import { assertNever } from "alcalzone-shared/helpers"; import { SUC_MAX_UPDATES } from "../../consts"; import { type NVM500, type NVM500Info } from "../NVM500"; @@ -97,7 +98,9 @@ export class NVM500Adapter implements NVMAdapter { return 500; case "applicationData": - return this.getOnly("EEOFFSET_HOST_OFFSET_START_far"); + return this.getOnly( + "EEOFFSET_HOST_OFFSET_START_far", + ); case "applicationName": // Not supported in 500 series @@ -109,7 +112,7 @@ export class NVM500Adapter implements NVMAdapter { "EX_NVM_HOME_ID_far", ); if (homeId == undefined) return; - const ret = Buffer.alloc(4, 0); + const ret = new Bytes(4).fill(0); // FIXME: BE? LE? ret.writeUInt32BE(homeId, 0); return ret; @@ -119,7 +122,7 @@ export class NVM500Adapter implements NVMAdapter { // 500 series stores the home ID as a number const homeId = await this.getOnly("NVM_HOMEID_far"); if (homeId == undefined) return; - const ret = Buffer.alloc(4, 0); + const ret = new Bytes(4).fill(0); // FIXME: BE? LE? ret.writeUInt32BE(homeId, 0); return ret; @@ -357,7 +360,7 @@ export class NVM500Adapter implements NVMAdapter { case "applicationData": return this.setOnly( "EEOFFSET_HOST_OFFSET_START_far", - value ?? Buffer.alloc(NVM_SERIALAPI_HOST_SIZE, 0xff), + value ?? new Bytes(NVM_SERIALAPI_HOST_SIZE).fill(0xff), ); case "applicationName": diff --git a/packages/nvmedit/src/lib/nvm500/shared.ts b/packages/nvmedit/src/lib/nvm500/shared.ts index cbea9b150c86..64c307237c8a 100644 --- a/packages/nvmedit/src/lib/nvm500/shared.ts +++ b/packages/nvmedit/src/lib/nvm500/shared.ts @@ -96,7 +96,7 @@ export interface NVMEntry { export type ResolvedNVMEntry = Required; export type NVMData = - | Buffer + | Uint8Array | number | NVMDescriptor | number[] diff --git a/packages/serial/src/log/Logger.ts b/packages/serial/src/log/Logger.ts index ccc4b6918646..cfffa8087d99 100644 --- a/packages/serial/src/log/Logger.ts +++ b/packages/serial/src/log/Logger.ts @@ -54,7 +54,7 @@ export class SerialLogger extends ZWaveLoggerBase { /** * Logs receipt of unexpected data while waiting for an ACK, NAK, CAN, or data frame */ - public discarded(data: Buffer): void { + public discarded(data: Uint8Array): void { if (this.isVisible()) { const direction: DataDirection = "inbound"; this.logger.log({ @@ -94,11 +94,11 @@ export class SerialLogger extends ZWaveLoggerBase { * @param direction The direction the data was sent * @param data The data that was transmitted or received */ - public data(direction: DataDirection, data: Buffer): void { + public data(direction: DataDirection, data: Uint8Array): void { if (this.isVisible()) { this.logger.log({ level: SERIAL_LOGLEVEL, - message: `0x${data.toString("hex")}`, + message: buffer2hex(data), secondaryTags: `(${data.length} bytes)`, direction: getDirectionPrefix(direction), context: { diff --git a/packages/serial/src/message/Message.test.ts b/packages/serial/src/message/Message.test.ts index 7d565a56b882..8bea7d0ef2e4 100644 --- a/packages/serial/src/message/Message.test.ts +++ b/packages/serial/src/message/Message.test.ts @@ -1,5 +1,6 @@ import { ZWaveErrorCodes, assertZWaveError } from "@zwave-js/core"; import { createTestingHost } from "@zwave-js/host"; +import { Bytes } from "@zwave-js/shared"; import test from "ava"; import { FunctionType, MessageType } from "./Constants"; import { Message, messageTypes } from "./Message"; @@ -7,7 +8,7 @@ import { Message, messageTypes } from "./Message"; test("should deserialize and serialize correctly", (t) => { // actual messages from OZW const okayMessages = [ - Buffer.from([ + Bytes.from([ 0x01, 0x09, 0x00, @@ -20,9 +21,9 @@ test("should deserialize and serialize correctly", (t) => { 0x0b, 0xca, ]), - Buffer.from([0x01, 0x05, 0x00, 0x47, 0x04, 0x20, 0x99]), - Buffer.from([0x01, 0x06, 0x00, 0x46, 0x0c, 0x0d, 0x32, 0x8c]), - Buffer.from([ + Bytes.from([0x01, 0x05, 0x00, 0x47, 0x04, 0x20, 0x99]), + Bytes.from([0x01, 0x06, 0x00, 0x46, 0x0c, 0x0d, 0x32, 0x8c]), + Bytes.from([ 0x01, 0x0a, 0x00, @@ -45,7 +46,7 @@ test("should deserialize and serialize correctly", (t) => { test("should serialize correctly when the payload is null", (t) => { // synthetic message - const expected = Buffer.from([0x01, 0x03, 0x00, 0xff, 0x03]); + const expected = Bytes.from([0x01, 0x03, 0x00, 0xff, 0x03]); const message = new Message({ type: MessageType.Request, functionType: 0xff as any, @@ -55,34 +56,34 @@ test("should serialize correctly when the payload is null", (t) => { test("should throw the correct error when parsing a faulty message", (t) => { // fake messages to produce certain errors - const brokenMessages: [Buffer, string, ZWaveErrorCodes][] = [ + const brokenMessages: [Bytes, string, ZWaveErrorCodes][] = [ // too short (<5 bytes) [ - Buffer.from([0x01, 0x02, 0x00, 0x00]), + Bytes.from([0x01, 0x02, 0x00, 0x00]), "truncated", ZWaveErrorCodes.PacketFormat_Truncated, ], // no SOF [ - Buffer.from([0x00, 0x03, 0x00, 0x00, 0x00]), + Bytes.from([0x00, 0x03, 0x00, 0x00, 0x00]), "start with SOF", ZWaveErrorCodes.PacketFormat_Invalid, ], // too short for the provided data length [ - Buffer.from([0x01, 0x04, 0x00, 0x00, 0x00]), + Bytes.from([0x01, 0x04, 0x00, 0x00, 0x00]), "truncated", ZWaveErrorCodes.PacketFormat_Truncated, ], // invalid checksum [ - Buffer.from([0x01, 0x03, 0x00, 0x00, 0x00]), + Bytes.from([0x01, 0x03, 0x00, 0x00, 0x00]), "checksum", ZWaveErrorCodes.PacketFormat_Checksum, ], // invalid checksum (once more with a real packet) [ - Buffer.from([0x01, 0x05, 0x00, 0x47, 0x04, 0x20, 0x98]), + Bytes.from([0x01, 0x05, 0x00, 0x47, 0x04, 0x20, 0x98]), "checksum", ZWaveErrorCodes.PacketFormat_Checksum, ], @@ -113,7 +114,7 @@ test("toJSON() should return a semi-readable JSON representation", (t) => { const msg2 = new Message({ type: MessageType.Request, functionType: FunctionType.GetControllerVersion, - payload: Buffer.from("aabbcc", "hex"), + payload: Bytes.from("aabbcc", "hex"), }); const json2 = { name: msg2.constructor.name, @@ -137,7 +138,7 @@ test("toJSON() should return a semi-readable JSON representation", (t) => { type: MessageType.Request, functionType: FunctionType.GetControllerVersion, expectedResponse: FunctionType.GetControllerVersion, - payload: Buffer.from("aabbcc", "hex"), + payload: Bytes.from("aabbcc", "hex"), }); const json4 = { name: msg4.constructor.name, @@ -154,7 +155,7 @@ test("toJSON() should return a semi-readable JSON representation", (t) => { }); test("Parsing a buffer with an unknown function type returns an unspecified `Message` instance", (t) => { - const unknown = Buffer.from([0x01, 0x03, 0x00, 0x00, 0xfc]); + const unknown = Bytes.from([0x01, 0x03, 0x00, 0x00, 0xfc]); t.is( Message.parse(unknown, {} as any).constructor, Message, diff --git a/packages/serial/src/message/Message.ts b/packages/serial/src/message/Message.ts index 5e17c08701c5..6d68bdebec7b 100644 --- a/packages/serial/src/message/Message.ts +++ b/packages/serial/src/message/Message.ts @@ -18,7 +18,11 @@ import type { GetSupportedCCVersion, HostIDs, } from "@zwave-js/host"; -import type { JSONObject, TypedClassDecorator } from "@zwave-js/shared/safe"; +import { + Bytes, + type JSONObject, + type TypedClassDecorator, +} from "@zwave-js/shared/safe"; import { num2hex, staticExtends } from "@zwave-js/shared/safe"; import { FunctionType, MessageType } from "./Constants"; import { MessageHeaders } from "./MessageHeaders"; @@ -50,7 +54,7 @@ export interface MessageOptions extends MessageBaseOptions { functionType?: FunctionType; expectedResponse?: FunctionType | typeof Message | ResponsePredicate; expectedCallback?: FunctionType | typeof Message | ResponsePredicate; - payload?: Buffer; + payload?: Bytes; } export interface MessageEncodingContext @@ -87,7 +91,7 @@ export function hasNodeId(msg: T): msg is T & HasNodeId { } /** Returns the number of bytes the first message in the buffer occupies */ -function getMessageLength(data: Buffer): number { +function getMessageLength(data: Uint8Array): number { const remainingLength = data[1]; return remainingLength + 2; } @@ -96,10 +100,10 @@ export class MessageRaw { public constructor( public readonly type: MessageType, public readonly functionType: FunctionType, - public readonly payload: Buffer, + public readonly payload: Bytes, ) {} - public static parse(data: Buffer): MessageRaw { + public static parse(data: Uint8Array): MessageRaw { // SOF, length, type, commandId and checksum must be present if (!data.length || data.length < 5) { throw new ZWaveError( @@ -136,12 +140,12 @@ export class MessageRaw { const type: MessageType = data[2]; const functionType: FunctionType = data[3]; const payloadLength = messageLength - 5; - const payload = data.subarray(4, 4 + payloadLength); + const payload = Bytes.view(data.subarray(4, 4 + payloadLength)); return new MessageRaw(type, functionType, payload); } - public withPayload(payload: Buffer): MessageRaw { + public withPayload(payload: Bytes): MessageRaw { return new MessageRaw(this.type, this.functionType, payload); } } @@ -161,7 +165,7 @@ export class Message { // Fall back to decorated response/callback types if none is given expectedResponse = getExpectedResponse(this), expectedCallback = getExpectedCallback(this), - payload = Buffer.allocUnsafe(0), + payload = new Bytes(), callbackId, } = options; @@ -187,7 +191,7 @@ export class Message { } public static parse( - data: Buffer, + data: Uint8Array, ctx: MessageParsingContext, ): Message { const raw = MessageRaw.parse(data); @@ -223,7 +227,7 @@ export class Message { | typeof Message | ResponsePredicate | undefined; - public payload: Buffer; // TODO: Length limit 255 + public payload: Bytes; // TODO: Length limit 255 /** Used to map requests to callbacks */ public callbackId: number | undefined; @@ -265,15 +269,15 @@ export class Message { /** Serializes this message into a Buffer */ // eslint-disable-next-line @typescript-eslint/no-unused-vars - public serialize(ctx: MessageEncodingContext): Buffer { - const ret = Buffer.allocUnsafe(this.payload.length + 5); + public serialize(ctx: MessageEncodingContext): Bytes { + const ret = new Bytes(this.payload.length + 5); ret[0] = MessageHeaders.SOF; // length of the following data, including the checksum ret[1] = this.payload.length + 3; // write the remaining data ret[2] = this.type; ret[3] = this.functionType; - this.payload.copy(ret, 4); + ret.set(this.payload, 4); // followed by the checksum ret[ret.length - 1] = computeChecksum(ret); return ret; @@ -446,7 +450,7 @@ export class Message { } /** Computes the checksum for a serialized message as defined in the Z-Wave specs */ -function computeChecksum(message: Buffer): number { +function computeChecksum(message: Uint8Array): number { let ret = 0xff; // exclude SOF and checksum byte from the computation for (let i = 1; i < message.length - 1; i++) { diff --git a/packages/serial/src/message/ZnifferMessages.ts b/packages/serial/src/message/ZnifferMessages.ts index de4cc9ed5b49..7546d9933086 100644 --- a/packages/serial/src/message/ZnifferMessages.ts +++ b/packages/serial/src/message/ZnifferMessages.ts @@ -8,51 +8,100 @@ import { getZWaveChipType, validatePayload, } from "@zwave-js/core"; -import { getEnumMemberName } from "@zwave-js/shared"; +import { Bytes, getEnumMemberName } from "@zwave-js/shared"; import { ZnifferFrameType, ZnifferFunctionType, ZnifferMessageType, } from "./Constants"; -export type ZnifferMessageConstructor = new ( - options: ZnifferMessageOptions, -) => T; - -export type DeserializingZnifferMessageConstructor = - new ( - options: ZnifferMessageDeserializationOptions, - ) => T; - -export interface ZnifferMessageDeserializationOptions { - data: Buffer; -} - -/** - * Tests whether the given message constructor options contain a buffer for deserialization - */ -function gotDeserializationOptions( - options: Record | undefined, -): options is ZnifferMessageDeserializationOptions { - return options != undefined && Buffer.isBuffer(options.data); -} +export type ZnifferMessageConstructor = + & typeof ZnifferMessage + & { + new ( + options: ZnifferMessageOptions, + ): T; + }; // eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface ZnifferMessageBaseOptions { // Intentionally empty } -export interface ZnifferMessageCreationOptions - extends ZnifferMessageBaseOptions -{ - messageType: ZnifferMessageType; +export interface ZnifferMessageOptions extends ZnifferMessageBaseOptions { + type: ZnifferMessageType; functionType?: ZnifferFunctionType; - payload?: Buffer; + payload?: Bytes; } -export type ZnifferMessageOptions = - | ZnifferMessageCreationOptions - | ZnifferMessageDeserializationOptions; +export class ZnifferMessageRaw { + public constructor( + public readonly type: ZnifferMessageType, + public readonly functionType: ZnifferFunctionType | undefined, + public readonly payload: Bytes, + ) {} + + public static parse(data: Uint8Array): ZnifferMessageRaw { + // Assume that we're dealing with a complete frame + const type = data[0]; + if (type === ZnifferMessageType.Command) { + const functionType = data[1]; + const length = data[2]; + const payload = Bytes.view(data.subarray(3, 3 + length)); + + return new ZnifferMessageRaw(type, functionType, payload); + } else if (type === ZnifferMessageType.Data) { + // The ZnifferParser takes care of segmenting frames, so here we + // only cut off the type byte from the payload + const payload = Bytes.view(data.subarray(1)); + return new ZnifferMessageRaw(type, undefined, payload); + } else { + throw new ZWaveError( + `Invalid Zniffer message type ${type as any}`, + ZWaveErrorCodes.PacketFormat_InvalidPayload, + ); + } + } + + public withPayload(payload: Bytes): ZnifferMessageRaw { + return new ZnifferMessageRaw(this.type, this.functionType, payload); + } +} + +/** + * Retrieves the correct constructor for the next message in the given Buffer. + * It is assumed that the buffer has been checked beforehand + */ +function getZnifferMessageConstructor( + raw: ZnifferMessageRaw, +): ZnifferMessageConstructor { + // We hardcode the list of constructors here, since the Zniffer protocol has + // a very limited list of messages + if (raw.type === ZnifferMessageType.Command) { + switch (raw.functionType) { + case ZnifferFunctionType.GetVersion: + return ZnifferGetVersionResponse as any; + case ZnifferFunctionType.SetFrequency: + return ZnifferSetFrequencyResponse; + case ZnifferFunctionType.GetFrequencies: + return ZnifferGetFrequenciesResponse as any; + case ZnifferFunctionType.Start: + return ZnifferStartResponse; + case ZnifferFunctionType.Stop: + return ZnifferStopResponse; + case ZnifferFunctionType.SetBaudRate: + return ZnifferSetBaudRateResponse; + case ZnifferFunctionType.GetFrequencyInfo: + return ZnifferGetFrequencyInfoResponse as any; + default: + return ZnifferMessage; + } + } else if (raw.type === ZnifferMessageType.Data) { + return ZnifferDataMessage as any; + } else { + return ZnifferMessage; + } +} /** * Represents a Zniffer message for communication with the serial interface @@ -61,43 +110,37 @@ export class ZnifferMessage { public constructor( options: ZnifferMessageOptions, ) { - // decide which implementation we follow - if (gotDeserializationOptions(options)) { - // #1: deserialize from payload - const payload = options.data; - - // Assume that we're dealing with a complete frame - this.type = payload[0]; - if (this.type === ZnifferMessageType.Command) { - this.functionType = payload[1]; - const length = payload[2]; - this.payload = payload.subarray(3, 3 + length); - } else if (this.type === ZnifferMessageType.Data) { - // The ZnifferParser takes care of segmenting frames, so here we - // only cut off the type byte from the payload - this.payload = payload.subarray(1); - } else { - throw new ZWaveError( - `Invalid Zniffer message type ${this.type as any}`, - ZWaveErrorCodes.PacketFormat_InvalidPayload, - ); - } - } else { - this.type = options.messageType; - this.functionType = options.functionType; - this.payload = options.payload || Buffer.allocUnsafe(0); - } + this.type = options.type; + this.functionType = options.functionType; + this.payload = options.payload || new Bytes(); + } + + public static parse( + data: Uint8Array, + ): ZnifferMessage { + const raw = ZnifferMessageRaw.parse(data); + const Constructor = getZnifferMessageConstructor(raw); + return Constructor.from(raw); + } + + /** Creates an instance of the message that is serialized in the given buffer */ + public static from(raw: ZnifferMessageRaw): ZnifferMessage { + return new this({ + type: raw.type, + functionType: raw.functionType, + payload: raw.payload, + }); } public type: ZnifferMessageType; public functionType?: ZnifferFunctionType; - public payload: Buffer; // TODO: Length limit 255 + public payload: Bytes; /** Serializes this message into a Buffer */ - public serialize(): Buffer { + public serialize(): Bytes { if (this.type === ZnifferMessageType.Command) { - return Buffer.concat([ - Buffer.from([ + return Bytes.concat([ + Bytes.from([ this.type, this.functionType!, this.payload.length, @@ -105,10 +148,16 @@ export class ZnifferMessage { this.payload, ]); } else if (this.type === ZnifferMessageType.Data) { - const ret = Buffer.allocUnsafe(this.payload.length + 1); - ret[0] = this.type; - this.payload.copy(ret, 1); - this.payload[9] = this.payload.length - 10; + const ret = Bytes.concat([ + [this.type], + this.payload, + ]); + ret[9] = this.payload.length - 10; + // FIXME: Is this correct? It used to be + // const ret = new Bytes(this.payload.length + 1); + // ret[0] = this.type; + // this.payload.copy(ret, 1); + // this.payload[9] = this.payload.length - 10; return ret; } else { throw new ZWaveError( @@ -117,55 +166,9 @@ export class ZnifferMessage { ); } } - - /** - * Retrieves the correct constructor for the next message in the given Buffer. - * It is assumed that the buffer has been checked beforehand - */ - public static getConstructor( - data: Buffer, - ): ZnifferMessageConstructor { - const type = data[0]; - // We hardcode the list of constructors here, since the Zniffer protocol has - // a very limited list of messages - if (type === ZnifferMessageType.Command) { - const functionType = data[1]; - switch (functionType) { - case ZnifferFunctionType.GetVersion: - return ZnifferGetVersionResponse; - case ZnifferFunctionType.SetFrequency: - return ZnifferSetFrequencyResponse; - case ZnifferFunctionType.GetFrequencies: - return ZnifferGetFrequenciesResponse; - case ZnifferFunctionType.Start: - return ZnifferStartResponse; - case ZnifferFunctionType.Stop: - return ZnifferStopResponse; - case ZnifferFunctionType.SetBaudRate: - return ZnifferSetBaudRateResponse; - case ZnifferFunctionType.GetFrequencyInfo: - return ZnifferGetFrequencyInfoResponse; - default: - return ZnifferMessage; - } - } else if (type === ZnifferMessageType.Data) { - return ZnifferDataMessage; - } else { - return ZnifferMessage; - } - } - - /** Creates an instance of the message that is serialized in the given buffer */ - public static from( - options: ZnifferMessageDeserializationOptions, - ): ZnifferMessage { - const Constructor = ZnifferMessage.getConstructor(options.data); - const ret = new Constructor(options); - return ret; - } } -function computeChecksumXOR(buffer: Buffer): number { +function computeChecksumXOR(buffer: Uint8Array): number { let ret = 0xff; for (let i = 0; i < buffer.length; i++) { ret ^= buffer[i]; @@ -182,81 +185,112 @@ export interface ZnifferFrameInfo { rssi?: RSSI; } +export interface ZnifferDataMessageOptions { + frameType: ZnifferFrameType; + channel: number; + protocolDataRate: ZnifferProtocolDataRate; + region: number; + rssiRaw: number; + payload: Bytes; + checksumOK: boolean; +} + export class ZnifferDataMessage extends ZnifferMessage implements ZnifferFrameInfo { - public constructor(options: ZnifferMessageOptions) { - super(options); - - if (gotDeserializationOptions(options)) { - this.frameType = this.payload[0]; - // bytes 1-2 are 0 - this.channel = this.payload[3] >>> 5; - this.protocolDataRate = this.payload[3] & 0b11111; - const checksumLength = - this.protocolDataRate >= ZnifferProtocolDataRate.ZWave_100k - ? 2 - : 1; - this.region = this.payload[4]; - this.rssiRaw = this.payload[5]; - - if (this.frameType === ZnifferFrameType.Data) { - validatePayload.withReason( - `ZnifferDataMessage[6] = ${this.payload[6]}`, - )(this.payload[6] === 0x21); - validatePayload.withReason( - `ZnifferDataMessage[7] = ${this.payload[7]}`, - )(this.payload[7] === 0x03); - // Length is already validated, so we just skip the length byte - - const mpduOffset = 9; - const checksum = this.payload.readUIntBE( - this.payload.length - checksumLength, - checksumLength, - ); + public constructor( + options: ZnifferDataMessageOptions & ZnifferMessageBaseOptions, + ) { + super({ + type: ZnifferMessageType.Data, + payload: options.payload, + }); - // Compute checksum over the entire MPDU - const expectedChecksum = checksumLength === 1 - ? computeChecksumXOR( - this.payload.subarray(mpduOffset, -checksumLength), - ) - : CRC16_CCITT( - this.payload.subarray(mpduOffset, -checksumLength), - ); - - this.checksumOK = checksum === expectedChecksum; - this.payload = this.payload.subarray( - mpduOffset, - -checksumLength, - ); - } else if ( - this.frameType === ZnifferFrameType.BeamStart - ) { - validatePayload.withReason( - `ZnifferDataMessage[6] = ${this.payload[6]}`, - )(this.payload[6] === 0x55); - - // There is no checksum - this.checksumOK = true; - this.payload = this.payload.subarray(6); - } else if (this.frameType === ZnifferFrameType.BeamStop) { - // This always seems to contain the same 2 bytes - // There is no checksum - this.checksumOK = true; - this.payload = Buffer.alloc(0); - } else { - validatePayload.fail( - `Unsupported frame type ${ - getEnumMemberName(ZnifferFrameType, this.frameType) - }`, + this.frameType = options.frameType; + this.channel = options.channel; + this.protocolDataRate = options.protocolDataRate; + this.region = options.region; + this.rssiRaw = options.rssiRaw; + this.checksumOK = options.checksumOK; + } + + public static from(raw: ZnifferMessageRaw): ZnifferDataMessage { + const frameType: ZnifferFrameType = raw.payload[0]; + + // bytes 1-2 are 0 + const channel = raw.payload[3] >>> 5; + const protocolDataRate: ZnifferProtocolDataRate = raw.payload[3] + & 0b11111; + const checksumLength = + protocolDataRate >= ZnifferProtocolDataRate.ZWave_100k + ? 2 + : 1; + const region = raw.payload[4]; + const rssiRaw = raw.payload[5]; + let checksumOK: boolean; + let payload: Bytes; + + if (frameType === ZnifferFrameType.Data) { + validatePayload.withReason( + `ZnifferDataMessage[6] = ${raw.payload[6]}`, + )(raw.payload[6] === 0x21); + validatePayload.withReason( + `ZnifferDataMessage[7] = ${raw.payload[7]}`, + )(raw.payload[7] === 0x03); + // Length is already validated, so we just skip the length byte + + const mpduOffset = 9; + const checksum = raw.payload.readUIntBE( + raw.payload.length - checksumLength, + checksumLength, + ); + + // Compute checksum over the entire MPDU + const expectedChecksum = checksumLength === 1 + ? computeChecksumXOR( + raw.payload.subarray(mpduOffset, -checksumLength), + ) + : CRC16_CCITT( + raw.payload.subarray(mpduOffset, -checksumLength), ); - } + + checksumOK = checksum === expectedChecksum; + payload = raw.payload.subarray( + mpduOffset, + -checksumLength, + ); + } else if ( + frameType === ZnifferFrameType.BeamStart + ) { + validatePayload.withReason( + `ZnifferDataMessage[6] = ${raw.payload[6]}`, + )(raw.payload[6] === 0x55); + + // There is no checksum + checksumOK = true; + payload = raw.payload.subarray(6); + } else if (frameType === ZnifferFrameType.BeamStop) { + // This always seems to contain the same 2 bytes + // There is no checksum + checksumOK = true; + payload = new Bytes(); } else { - throw new ZWaveError( - `Sending ${this.constructor.name} is not supported!`, - ZWaveErrorCodes.Driver_NotSupported, + validatePayload.fail( + `Unsupported frame type ${ + getEnumMemberName(ZnifferFrameType, frameType) + }`, ); } + + return new this({ + frameType, + channel, + protocolDataRate, + region, + rssiRaw, + payload, + checksumOK, + }); } public readonly frameType: ZnifferFrameType; @@ -271,26 +305,45 @@ export class ZnifferDataMessage extends ZnifferMessage export class ZnifferGetVersionRequest extends ZnifferMessage { public constructor() { super({ - messageType: ZnifferMessageType.Command, + type: ZnifferMessageType.Command, functionType: ZnifferFunctionType.GetVersion, }); } } +export interface ZnifferGetVersionResponseOptions { + chipType: string | UnknownZWaveChipType; + majorVersion: number; + minorVersion: number; +} + export class ZnifferGetVersionResponse extends ZnifferMessage { - public constructor(options: ZnifferMessageOptions) { - super(options); + public constructor( + options: ZnifferGetVersionResponseOptions & ZnifferMessageBaseOptions, + ) { + super({ + type: ZnifferMessageType.Command, + functionType: ZnifferFunctionType.GetVersion, + }); - if (gotDeserializationOptions(options)) { - this.chipType = getZWaveChipType(this.payload[0], this.payload[1]); - this.majorVersion = this.payload[2]; - this.minorVersion = this.payload[3]; - } else { - throw new ZWaveError( - `Sending ${this.constructor.name} is not supported!`, - ZWaveErrorCodes.Driver_NotSupported, - ); - } + this.chipType = options.chipType; + this.majorVersion = options.majorVersion; + this.minorVersion = options.minorVersion; + } + + public static from(raw: ZnifferMessageRaw): ZnifferGetVersionResponse { + const chipType: string | UnknownZWaveChipType = getZWaveChipType( + raw.payload[0], + raw.payload[1], + ); + const majorVersion = raw.payload[2]; + const minorVersion = raw.payload[3]; + + return new this({ + chipType, + majorVersion, + minorVersion, + }); } public readonly chipType: string | UnknownZWaveChipType; @@ -305,7 +358,7 @@ export interface ZnifferSetFrequencyRequestOptions { export class ZnifferSetFrequencyRequest extends ZnifferMessage { public constructor(options: ZnifferSetFrequencyRequestOptions) { super({ - messageType: ZnifferMessageType.Command, + type: ZnifferMessageType.Command, functionType: ZnifferFunctionType.SetFrequency, }); @@ -314,8 +367,8 @@ export class ZnifferSetFrequencyRequest extends ZnifferMessage { public frequency: number; - public serialize(): Buffer { - this.payload = Buffer.from([this.frequency]); + public serialize(): Bytes { + this.payload = Bytes.from([this.frequency]); return super.serialize(); } } @@ -326,26 +379,42 @@ export class ZnifferSetFrequencyResponse extends ZnifferMessage { export class ZnifferGetFrequenciesRequest extends ZnifferMessage { public constructor() { super({ - messageType: ZnifferMessageType.Command, + type: ZnifferMessageType.Command, functionType: ZnifferFunctionType.GetFrequencies, }); } } + +export interface ZnifferGetFrequenciesResponseOptions { + currentFrequency: number; + supportedFrequencies: number[]; +} + export class ZnifferGetFrequenciesResponse extends ZnifferMessage { - public constructor(options: ZnifferMessageOptions) { - super(options); - - if (gotDeserializationOptions(options)) { - this.currentFrequency = this.payload[0]; - this.supportedFrequencies = [ - ...this.payload.subarray(1), - ]; - } else { - throw new ZWaveError( - `Sending ${this.constructor.name} is not supported!`, - ZWaveErrorCodes.Driver_NotSupported, - ); - } + public constructor( + options: + & ZnifferGetFrequenciesResponseOptions + & ZnifferMessageBaseOptions, + ) { + super({ + type: ZnifferMessageType.Command, + functionType: ZnifferFunctionType.GetFrequencies, + }); + + this.currentFrequency = options.currentFrequency; + this.supportedFrequencies = options.supportedFrequencies; + } + + public static from(raw: ZnifferMessageRaw): ZnifferGetFrequenciesResponse { + const currentFrequency = raw.payload[0]; + const supportedFrequencies = [ + ...raw.payload.subarray(1), + ]; + + return new this({ + currentFrequency, + supportedFrequencies, + }); } public readonly currentFrequency: number; @@ -355,7 +424,7 @@ export class ZnifferGetFrequenciesResponse extends ZnifferMessage { export class ZnifferStartRequest extends ZnifferMessage { public constructor() { super({ - messageType: ZnifferMessageType.Command, + type: ZnifferMessageType.Command, functionType: ZnifferFunctionType.Start, }); } @@ -367,7 +436,7 @@ export class ZnifferStartResponse extends ZnifferMessage { export class ZnifferStopRequest extends ZnifferMessage { public constructor() { super({ - messageType: ZnifferMessageType.Command, + type: ZnifferMessageType.Command, functionType: ZnifferFunctionType.Stop, }); } @@ -384,7 +453,7 @@ export interface ZnifferSetBaudRateRequestOptions { export class ZnifferSetBaudRateRequest extends ZnifferMessage { public constructor(options: ZnifferSetBaudRateRequestOptions) { super({ - messageType: ZnifferMessageType.Command, + type: ZnifferMessageType.Command, functionType: ZnifferFunctionType.SetBaudRate, }); @@ -393,8 +462,8 @@ export class ZnifferSetBaudRateRequest extends ZnifferMessage { public baudrate: 0; - public serialize(): Buffer { - this.payload = Buffer.from([this.baudrate]); + public serialize(): Bytes { + this.payload = Bytes.from([this.baudrate]); return super.serialize(); } } @@ -409,7 +478,7 @@ export interface ZnifferGetFrequencyInfoRequestOptions { export class ZnifferGetFrequencyInfoRequest extends ZnifferMessage { public constructor(options: ZnifferGetFrequencyInfoRequestOptions) { super({ - messageType: ZnifferMessageType.Command, + type: ZnifferMessageType.Command, functionType: ZnifferFunctionType.GetFrequencyInfo, }); @@ -418,28 +487,48 @@ export class ZnifferGetFrequencyInfoRequest extends ZnifferMessage { public frequency: number; - public serialize(): Buffer { - this.payload = Buffer.from([this.frequency]); + public serialize(): Bytes { + this.payload = Bytes.from([this.frequency]); return super.serialize(); } } +export interface ZnifferGetFrequencyInfoResponseOptions { + frequency: number; + numChannels: number; + frequencyName: string; +} + export class ZnifferGetFrequencyInfoResponse extends ZnifferMessage { - public constructor(options: ZnifferMessageOptions) { - super(options); - - if (gotDeserializationOptions(options)) { - this.frequency = this.payload[0]; - this.numChannels = this.payload[1]; - this.frequencyName = this.payload - .subarray(2) - .toString("ascii"); - } else { - throw new ZWaveError( - `Sending ${this.constructor.name} is not supported!`, - ZWaveErrorCodes.Driver_NotSupported, - ); - } + public constructor( + options: + & ZnifferGetFrequencyInfoResponseOptions + & ZnifferMessageBaseOptions, + ) { + super({ + type: ZnifferMessageType.Command, + functionType: ZnifferFunctionType.GetFrequencyInfo, + }); + + this.frequency = options.frequency; + this.numChannels = options.numChannels; + this.frequencyName = options.frequencyName; + } + + public static from( + raw: ZnifferMessageRaw, + ): ZnifferGetFrequencyInfoResponse { + const frequency = raw.payload[0]; + const numChannels = raw.payload[1]; + const frequencyName: string = raw.payload + .subarray(2) + .toString("ascii"); + + return new this({ + frequency, + numChannels, + frequencyName, + }); } public readonly frequency: number; diff --git a/packages/serial/src/mock/MockSerialPort.ts b/packages/serial/src/mock/MockSerialPort.ts index f9ede7a3dd66..4d10ceec8271 100644 --- a/packages/serial/src/mock/MockSerialPort.ts +++ b/packages/serial/src/mock/MockSerialPort.ts @@ -19,7 +19,7 @@ const instances = new Map(); class MockBinding extends PassThrough {} interface MockSerialPortEventCallbacks extends ZWaveSerialPortEventCallbacks { - write: (data: Buffer) => void; + write: (data: Uint8Array) => void; } type MockSerialPortEvents = Extract; @@ -82,7 +82,7 @@ export class MockSerialPort extends ZWaveSerialPort { } public readonly closeStub = sinon.stub().resolves(); - public receiveData(data: Buffer): void { + public receiveData(data: Uint8Array): void { this.serial.emit("data", data); } @@ -90,15 +90,15 @@ export class MockSerialPort extends ZWaveSerialPort { this.emit("error", err); } - public writeAsync(data: Buffer): Promise { + public writeAsync(data: Uint8Array): Promise { this._lastWrite = data; this.emit("write", data); return this.writeStub(data); } public readonly writeStub = sinon.stub(); - private _lastWrite: string | number[] | Buffer | undefined; - public get lastWrite(): string | number[] | Buffer | undefined { + private _lastWrite: string | number[] | Uint8Array | undefined; + public get lastWrite(): string | number[] | Uint8Array | undefined { return this._lastWrite; } } @@ -112,7 +112,7 @@ export async function createAndOpenMockedZWaveSerialPort( SerialPortMockBinding.reset(); SerialPortMockBinding.createPort(path, { record: true, - readyData: Buffer.from([]), + readyData: new Uint8Array(), }); const port = new ZWaveSerialPort( diff --git a/packages/serial/src/mock/SerialPortBindingMock.ts b/packages/serial/src/mock/SerialPortBindingMock.ts index 9c8a7d55f0f3..b1ea3ead679f 100644 --- a/packages/serial/src/mock/SerialPortBindingMock.ts +++ b/packages/serial/src/mock/SerialPortBindingMock.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-restricted-globals -- The serialport typings require a Node.js Buffer */ /* eslint-disable @typescript-eslint/require-await */ // Clone of https://github.com/serialport/binding-mock with support for emitting events on the written side @@ -10,15 +11,15 @@ import type { SetOptions, UpdateOptions, } from "@serialport/bindings-interface"; -import { TypedEventEmitter } from "@zwave-js/shared"; +import { Bytes, TypedEventEmitter, isUint8Array } from "@zwave-js/shared"; export interface MockPortInternal { - data: Buffer; + data: Uint8Array; // echo: boolean; // record: boolean; info: PortInfo; maxReadSize: number; - readyData?: Buffer; + readyData?: Uint8Array; openOpt?: OpenOptions; instance?: MockPortBinding; } @@ -26,7 +27,7 @@ export interface MockPortInternal { export interface CreatePortOptions { echo?: boolean; record?: boolean; - readyData?: Buffer; + readyData?: Uint8Array; maxReadSize?: number; manufacturer?: string; vendorId?: string; @@ -78,7 +79,7 @@ export const MockBinding: MockBindingInterface = { }; ports[path] = { - data: Buffer.alloc(0), + data: new Uint8Array(), // echo: optWithDefaults.echo, // record: optWithDefaults.record, readyData: optWithDefaults.readyData, @@ -150,7 +151,7 @@ export const MockBinding: MockBindingInterface = { }; interface MockPortBindingEvents { - write: (data: Buffer) => void; + write: (data: Uint8Array) => void; close: () => void; } @@ -163,8 +164,8 @@ export class MockPortBinding extends TypedEventEmitter readonly openOptions: Required; readonly port: MockPortInternal; private pendingRead: null | ((err: null | Error) => void); - lastWrite: null | Buffer; - recording: Buffer; + lastWrite: null | Uint8Array; + recording: Uint8Array; writeOperation: null | Promise; isOpen: boolean; serialNumber?: string; @@ -177,7 +178,7 @@ export class MockPortBinding extends TypedEventEmitter this.pendingRead = null; this.isOpen = true; this.lastWrite = null; - this.recording = Buffer.alloc(0); + this.recording = new Uint8Array(); this.writeOperation = null; // in flight promise or null this.serialNumber = port.info.serialNumber; @@ -192,12 +193,12 @@ export class MockPortBinding extends TypedEventEmitter } // Emit data on a mock port - emitData(data: Buffer | string): void { + emitData(data: Uint8Array | string): void { if (!this.isOpen || !this.port) { throw new Error("Port must be open to pretend to receive data"); } - const bufferData = Buffer.isBuffer(data) ? data : Buffer.from(data); - this.port.data = Buffer.concat([this.port.data, bufferData]); + const bufferData = isUint8Array(data) ? data : Bytes.from(data); + this.port.data = Bytes.concat([this.port.data, bufferData]); if (this.pendingRead) { process.nextTick(this.pendingRead); this.pendingRead = null; @@ -216,7 +217,7 @@ export class MockPortBinding extends TypedEventEmitter port.openOpt = undefined; // reset data on close - port.data = Buffer.alloc(0); + port.data = new Uint8Array(); this.serialNumber = undefined; this.isOpen = false; if (this.pendingRead) { @@ -268,11 +269,20 @@ export class MockPortBinding extends TypedEventEmitter } if (this.port.data.length <= 0) { return new Promise((resolve, reject) => { - this.pendingRead = (err) => { + this.pendingRead = async (err) => { if (err) { return reject(err); } - this.read(buffer, offset, length).then(resolve, reject); + try { + const readResult = await this.read( + buffer, + offset, + length, + ); + resolve(readResult); + } catch (e) { + reject(e as Error); + } }; }); } @@ -282,14 +292,14 @@ export class MockPortBinding extends TypedEventEmitter : this.port.maxReadSize; const data = this.port.data.subarray(0, lengthToRead); - const bytesRead = data.copy(buffer, offset); + buffer.set(data, offset); this.port.data = this.port.data.subarray(lengthToRead); - return { bytesRead, buffer }; + return { bytesRead: data.length, buffer }; } - async write(buffer: Buffer): Promise { - if (!Buffer.isBuffer(buffer)) { - throw new TypeError("\"buffer\" is not a Buffer"); + async write(buffer: Uint8Array): Promise { + if (!isUint8Array(buffer)) { + throw new TypeError("\"buffer\" is not a Uint8Array"); } if (!this.isOpen || !this.port) { @@ -307,7 +317,7 @@ export class MockPortBinding extends TypedEventEmitter return; // throw new Error("Write canceled"); } - const data = (this.lastWrite = Buffer.from(buffer)); // copy + const data = (this.lastWrite = Bytes.from(buffer)); // copy this.emit("write", data); // if (this.port.record) { @@ -383,7 +393,7 @@ export class MockPortBinding extends TypedEventEmitter throw new Error("Port is not open"); } await resolveNextTick(); - this.port.data = Buffer.alloc(0); + this.port.data = new Uint8Array(); } async drain(): Promise { diff --git a/packages/serial/src/parsers/BootloaderParsers.ts b/packages/serial/src/parsers/BootloaderParsers.ts index 0b7c5c0b47ba..76c806d0ba88 100644 --- a/packages/serial/src/parsers/BootloaderParsers.ts +++ b/packages/serial/src/parsers/BootloaderParsers.ts @@ -90,7 +90,7 @@ export class BootloaderScreenParser extends Transform { const charCode = this.receiveBuffer.charCodeAt(0); if (!isFlowControl(charCode)) break; - this.logger?.data("inbound", Buffer.from([charCode])); + this.logger?.data("inbound", Uint8Array.from([charCode])); this.push(charCode); this.receiveBuffer = this.receiveBuffer.slice(1); } diff --git a/packages/serial/src/parsers/SerialAPIParser.ts b/packages/serial/src/parsers/SerialAPIParser.ts index 79653c6eda60..da3decdecd6e 100644 --- a/packages/serial/src/parsers/SerialAPIParser.ts +++ b/packages/serial/src/parsers/SerialAPIParser.ts @@ -1,4 +1,4 @@ -import { num2hex } from "@zwave-js/shared"; +import { Bytes, num2hex } from "@zwave-js/shared"; import { Transform, type TransformCallback } from "node:stream"; import type { SerialLogger } from "../log/Logger"; import { MessageHeaders } from "../message/MessageHeaders"; @@ -6,12 +6,12 @@ import { MessageHeaders } from "../message/MessageHeaders"; /** * Checks if there's enough data in the buffer to deserialize a complete message */ -function containsCompleteMessage(data?: Buffer): boolean { +function containsCompleteMessage(data?: Uint8Array): boolean { return !!data && data.length >= 5 && data.length >= getMessageLength(data); } /** Given a buffer that starts with SOF, this method returns the number of bytes the first message occupies in the buffer */ -function getMessageLength(data: Buffer): number { +function getMessageLength(data: Uint8Array): number { const remainingLength = data[1]; return remainingLength + 2; } @@ -19,13 +19,13 @@ function getMessageLength(data: Buffer): number { export class SerialAPIParser extends Transform { constructor( private logger?: SerialLogger, - private onDiscarded?: (data: Buffer) => void, + private onDiscarded?: (data: Uint8Array) => void, ) { // We read byte streams but emit messages super({ readableObjectMode: true }); } - private receiveBuffer = Buffer.allocUnsafe(0); + private receiveBuffer = new Bytes(); // Allow ignoring the high nibble of an ACK once to work around an issue in the 700 series firmware public ignoreAckHighNibble: boolean = false; @@ -35,7 +35,7 @@ export class SerialAPIParser extends Transform { encoding: string, callback: TransformCallback, ): void { - this.receiveBuffer = Buffer.concat([this.receiveBuffer, chunk]); + this.receiveBuffer = Bytes.concat([this.receiveBuffer, chunk]); while (this.receiveBuffer.length > 0) { if (this.receiveBuffer[0] !== MessageHeaders.SOF) { @@ -101,7 +101,7 @@ export class SerialAPIParser extends Transform { } } // Continue with the next valid byte - this.receiveBuffer = skipBytes(this.receiveBuffer, skip); + this.receiveBuffer = this.receiveBuffer.subarray(skip); continue; } @@ -113,7 +113,7 @@ export class SerialAPIParser extends Transform { const msgLength = getMessageLength(this.receiveBuffer); // emit it and slice the read bytes from the buffer const msg = this.receiveBuffer.subarray(0, msgLength); - this.receiveBuffer = skipBytes(this.receiveBuffer, msgLength); + this.receiveBuffer = this.receiveBuffer.subarray(msgLength); this.logger?.data("inbound", msg); this.push(msg); @@ -122,8 +122,3 @@ export class SerialAPIParser extends Transform { callback(); } } - -/** Skips the first n bytes of a buffer and returns the rest */ -export function skipBytes(buf: Buffer, n: number): Buffer { - return Buffer.from(buf.subarray(n)); -} diff --git a/packages/serial/src/parsers/ZnifferParser.ts b/packages/serial/src/parsers/ZnifferParser.ts index 5300bbaf88ad..c501ac0ac3a9 100644 --- a/packages/serial/src/parsers/ZnifferParser.ts +++ b/packages/serial/src/parsers/ZnifferParser.ts @@ -1,10 +1,11 @@ +import { Bytes } from "@zwave-js/shared"; import { Transform, type TransformCallback } from "node:stream"; import type { SerialLogger } from "../log/Logger"; import { ZnifferFrameType } from "../message/Constants"; import { ZnifferMessageHeaders } from "../message/MessageHeaders"; /** Given a buffer that starts with SOF, this method returns the number of bytes the first message occupies in the buffer */ -function getMessageLength(data: Buffer): number | undefined { +function getMessageLength(data: Uint8Array): number | undefined { if (!data || data.length === 0) return; if (data[0] === ZnifferMessageHeaders.SOCF) { // Control frame: SOF, CMD, remaining length @@ -30,13 +31,13 @@ function getMessageLength(data: Buffer): number | undefined { export class ZnifferParser extends Transform { constructor( private logger?: SerialLogger, - private onDiscarded?: (data: Buffer) => void, + private onDiscarded?: (data: Uint8Array) => void, ) { // We read byte streams but emit messages super({ readableObjectMode: true }); } - private receiveBuffer = Buffer.allocUnsafe(0); + private receiveBuffer = new Bytes(); // Allow ignoring the high nibble of an ACK once to work around an issue in the 700 series firmware public ignoreAckHighNibble: boolean = false; @@ -46,7 +47,7 @@ export class ZnifferParser extends Transform { encoding: string, callback: TransformCallback, ): void { - this.receiveBuffer = Buffer.concat([this.receiveBuffer, chunk]); + this.receiveBuffer = Bytes.concat([this.receiveBuffer, chunk]); while (this.receiveBuffer.length > 0) { // Scan ahead until the next valid byte and log the invalid bytes, if any @@ -65,7 +66,7 @@ export class ZnifferParser extends Transform { this.onDiscarded?.(discarded); // Continue with the next valid byte - this.receiveBuffer = skipBytes(this.receiveBuffer, skip); + this.receiveBuffer = this.receiveBuffer.subarray(skip); continue; } @@ -80,7 +81,7 @@ export class ZnifferParser extends Transform { // We have at least one complete message. // emit it and slice the read bytes from the buffer const msg = this.receiveBuffer.subarray(0, msgLength); - this.receiveBuffer = skipBytes(this.receiveBuffer, msgLength); + this.receiveBuffer = this.receiveBuffer.subarray(msgLength); this.logger?.data("inbound", msg); this.push(msg); @@ -89,8 +90,3 @@ export class ZnifferParser extends Transform { callback(); } } - -/** Skips the first n bytes of a buffer and returns the rest */ -export function skipBytes(buf: Buffer, n: number): Buffer { - return Buffer.from(buf.subarray(n)); -} diff --git a/packages/serial/src/serialapi/application/ApplicationCommandRequest.ts b/packages/serial/src/serialapi/application/ApplicationCommandRequest.ts index bcdf0f7fecbb..2e8dda8176d5 100644 --- a/packages/serial/src/serialapi/application/ApplicationCommandRequest.ts +++ b/packages/serial/src/serialapi/application/ApplicationCommandRequest.ts @@ -21,6 +21,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; +import { Bytes } from "@zwave-js/shared/safe"; import { type MessageWithCC } from "../utils"; export enum ApplicationCommandStatusFlags { @@ -43,7 +44,7 @@ export type ApplicationCommandRequestOptions = | { command: CommandClass } | { nodeId: number; - serializedCC: Buffer; + serializedCC: Uint8Array; } ) & { @@ -153,8 +154,8 @@ export class ApplicationCommandRequest extends Message return this._nodeId ?? super.getNodeId(); } - public serializedCC: Buffer | undefined; - public serializeCC(ctx: CCEncodingContext): Buffer { + public serializedCC: Uint8Array | undefined; + public serializeCC(ctx: CCEncodingContext): Uint8Array { if (!this.serializedCC) { if (!this.command) { throw new ZWaveError( @@ -167,7 +168,7 @@ export class ApplicationCommandRequest extends Message return this.serializedCC; } - public serialize(ctx: MessageEncodingContext): Buffer { + public serialize(ctx: MessageEncodingContext): Bytes { const statusByte = (this.frameType === "broadcast" ? ApplicationCommandStatusFlags.TypeBroad : this.frameType === "multicast" @@ -180,10 +181,10 @@ export class ApplicationCommandRequest extends Message this.getNodeId() ?? ctx.ownNodeId, ctx.nodeIdType, ); - this.payload = Buffer.concat([ - Buffer.from([statusByte]), + this.payload = Bytes.concat([ + [statusByte], nodeId, - Buffer.from([serializedCC.length]), + [serializedCC.length], serializedCC, ]); diff --git a/packages/serial/src/serialapi/application/ApplicationUpdateRequest.ts b/packages/serial/src/serialapi/application/ApplicationUpdateRequest.ts index 1f9b5b32d87b..466c9ae9f5f4 100644 --- a/packages/serial/src/serialapi/application/ApplicationUpdateRequest.ts +++ b/packages/serial/src/serialapi/application/ApplicationUpdateRequest.ts @@ -23,7 +23,7 @@ import { type SuccessIndicator, messageTypes, } from "@zwave-js/serial"; -import { buffer2hex, getEnumMemberName } from "@zwave-js/shared"; +import { Bytes, buffer2hex, getEnumMemberName } from "@zwave-js/shared"; export enum ApplicationUpdateTypes { SmartStart_NodeInfo_Received = 0x86, // An included smart start node has been powered up @@ -91,9 +91,9 @@ export class ApplicationUpdateRequest extends Message { public readonly updateType: ApplicationUpdateTypes; - public serialize(ctx: MessageEncodingContext): Buffer { - this.payload = Buffer.concat([ - Buffer.from([this.updateType]), + public serialize(ctx: MessageEncodingContext): Bytes { + this.payload = Bytes.concat([ + Bytes.from([this.updateType]), this.payload, ]); return super.serialize(ctx); @@ -135,7 +135,7 @@ export class ApplicationUpdateRequestWithNodeInfo public nodeId: number; public nodeInformation: NodeUpdatePayload; - public serialize(ctx: MessageEncodingContext): Buffer { + public serialize(ctx: MessageEncodingContext): Bytes { this.payload = encodeNodeUpdatePayload( this.nodeInformation, ctx.nodeIdType, @@ -198,7 +198,7 @@ export class ApplicationUpdateRequestNodeRemoved export interface ApplicationUpdateRequestSmartStartHomeIDReceivedBaseOptions { remoteNodeId: number; - nwiHomeId: Buffer; + nwiHomeId: Uint8Array; basicDeviceClass: BasicDeviceClass; genericDeviceClass: number; specificDeviceClass: number; @@ -237,7 +237,7 @@ class ApplicationUpdateRequestSmartStartHomeIDReceivedBase offset += nodeIdBytes; // next byte is rxStatus offset++; - const nwiHomeId: Buffer = raw.payload.subarray(offset, offset + 4); + const nwiHomeId = raw.payload.subarray(offset, offset + 4); offset += 4; const ccLength = raw.payload[offset++]; const basicDeviceClass: BasicDeviceClass = raw.payload[offset++]; @@ -258,7 +258,7 @@ class ApplicationUpdateRequestSmartStartHomeIDReceivedBase } public readonly remoteNodeId: number; - public readonly nwiHomeId: Buffer; + public readonly nwiHomeId: Uint8Array; public readonly basicDeviceClass: BasicDeviceClass; public readonly genericDeviceClass: number; diff --git a/packages/serial/src/serialapi/application/BridgeApplicationCommandRequest.test.ts b/packages/serial/src/serialapi/application/BridgeApplicationCommandRequest.test.ts index 87ace9ef00d6..5da452cbf1c6 100644 --- a/packages/serial/src/serialapi/application/BridgeApplicationCommandRequest.test.ts +++ b/packages/serial/src/serialapi/application/BridgeApplicationCommandRequest.test.ts @@ -1,12 +1,13 @@ // import "@zwave-js/cc"; import { Message } from "@zwave-js/serial"; +import { Bytes } from "@zwave-js/shared/safe"; import test from "ava"; test("BridgeApplicationCommandRequest can be parsed without RSSI", async (t) => { // Repro for https://github.com/zwave-js/node-zwave-js/issues/4335 t.notThrows(() => Message.parse( - Buffer.from( + Bytes.from( "011200a80001020a320221340000000000000069", "hex", ), diff --git a/packages/serial/src/serialapi/application/BridgeApplicationCommandRequest.ts b/packages/serial/src/serialapi/application/BridgeApplicationCommandRequest.ts index 3b81f34a557c..5929319ded0a 100644 --- a/packages/serial/src/serialapi/application/BridgeApplicationCommandRequest.ts +++ b/packages/serial/src/serialapi/application/BridgeApplicationCommandRequest.ts @@ -32,7 +32,7 @@ export type BridgeApplicationCommandRequestOptions = | { command: CommandClass } | { nodeId: number; - serializedCC: Buffer; + serializedCC: Uint8Array; } ) & { @@ -163,7 +163,7 @@ export class BridgeApplicationCommandRequest extends Message public readonly ownNodeId: number; - public readonly serializedCC: Buffer | undefined; + public readonly serializedCC: Uint8Array | undefined; // This needs to be writable or unwrapping MultiChannelCCs crashes public command: CommandClass | undefined; diff --git a/packages/serial/src/serialapi/application/SerialAPIStartedRequest.ts b/packages/serial/src/serialapi/application/SerialAPIStartedRequest.ts index a432ffd8fdd3..70fe2b8f6b87 100644 --- a/packages/serial/src/serialapi/application/SerialAPIStartedRequest.ts +++ b/packages/serial/src/serialapi/application/SerialAPIStartedRequest.ts @@ -15,7 +15,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; -import { getEnumMemberName, num2hex } from "@zwave-js/shared"; +import { Bytes, getEnumMemberName, num2hex } from "@zwave-js/shared"; export enum SerialAPIWakeUpReason { /** The Z-Wave API Module has been woken up by reset or external interrupt. */ @@ -118,18 +118,18 @@ export class SerialAPIStartedRequest extends Message { public controlledCCs: CommandClasses[]; public supportsLongRange: boolean = false; - public serialize(ctx: MessageEncodingContext): Buffer { + public serialize(ctx: MessageEncodingContext): Bytes { const ccList = encodeCCList(this.supportedCCs, this.controlledCCs); const numCCBytes = ccList.length; - this.payload = Buffer.allocUnsafe(6 + numCCBytes + 1); + this.payload = new Bytes(6 + numCCBytes + 1); this.payload[0] = this.wakeUpReason; this.payload[1] = this.watchdogEnabled ? 0b1 : 0; this.payload[2] = this.isListening ? 0b10_000_000 : 0; this.payload[3] = this.genericDeviceClass; this.payload[4] = this.specificDeviceClass; this.payload[5] = numCCBytes; - ccList.copy(this.payload, 6); + this.payload.set(ccList, 6); this.payload[6 + numCCBytes] = this.supportsLongRange ? 0b1 : 0; return super.serialize(ctx); diff --git a/packages/serial/src/serialapi/capability/GetControllerCapabilitiesMessages.ts b/packages/serial/src/serialapi/capability/GetControllerCapabilitiesMessages.ts index 7e0fdbe10880..f22b5eed8c46 100644 --- a/packages/serial/src/serialapi/capability/GetControllerCapabilitiesMessages.ts +++ b/packages/serial/src/serialapi/capability/GetControllerCapabilitiesMessages.ts @@ -11,6 +11,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; +import { Bytes } from "@zwave-js/shared/safe"; @messageTypes(MessageType.Request, FunctionType.GetControllerCapabilities) @expectedResponse(FunctionType.GetControllerCapabilities) @@ -83,8 +84,8 @@ export class GetControllerCapabilitiesResponse extends Message { public isStaticUpdateController: boolean; public noNodesIncluded: boolean; - public serialize(ctx: MessageEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: MessageEncodingContext): Bytes { + this.payload = Bytes.from([ (this.isSecondary ? ControllerCapabilityFlags.Secondary : 0) | (this.isUsingHomeIdFromOtherNetwork ? ControllerCapabilityFlags.OnOtherNetwork diff --git a/packages/serial/src/serialapi/capability/GetControllerVersionMessages.ts b/packages/serial/src/serialapi/capability/GetControllerVersionMessages.ts index ec5a00dfd39b..2209eab7580e 100644 --- a/packages/serial/src/serialapi/capability/GetControllerVersionMessages.ts +++ b/packages/serial/src/serialapi/capability/GetControllerVersionMessages.ts @@ -11,7 +11,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; -import { cpp2js } from "@zwave-js/shared"; +import { Bytes, cpp2js } from "@zwave-js/shared"; @messageTypes(MessageType.Request, FunctionType.GetControllerVersion) @expectedResponse(FunctionType.GetControllerVersion) @@ -52,10 +52,10 @@ export class GetControllerVersionResponse extends Message { public controllerType: ZWaveLibraryTypes; public libraryVersion: string; - public serialize(ctx: MessageEncodingContext): Buffer { - this.payload = Buffer.concat([ - Buffer.from(`${this.libraryVersion}\0`, "ascii"), - Buffer.from([this.controllerType]), + public serialize(ctx: MessageEncodingContext): Bytes { + this.payload = Bytes.concat([ + Bytes.from(`${this.libraryVersion}\0`, "ascii"), + Bytes.from([this.controllerType]), ]); return super.serialize(ctx); diff --git a/packages/serial/src/serialapi/capability/GetLongRangeNodesMessages.ts b/packages/serial/src/serialapi/capability/GetLongRangeNodesMessages.ts index 5424ecfb865b..64b3f5c75d5a 100644 --- a/packages/serial/src/serialapi/capability/GetLongRangeNodesMessages.ts +++ b/packages/serial/src/serialapi/capability/GetLongRangeNodesMessages.ts @@ -17,6 +17,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; +import { Bytes } from "@zwave-js/shared/safe"; function getFirstNodeId(segmentNumber: number): number { return 256 + NUM_LR_NODES_PER_SEGMENT * segmentNumber; @@ -51,8 +52,8 @@ export class GetLongRangeNodesRequest extends Message { public segmentNumber: number; - public serialize(ctx: MessageEncodingContext): Buffer { - this.payload = Buffer.from([this.segmentNumber]); + public serialize(ctx: MessageEncodingContext): Bytes { + this.payload = Bytes.from([this.segmentNumber]); return super.serialize(ctx); } } @@ -110,8 +111,8 @@ export class GetLongRangeNodesResponse extends Message { public segmentNumber: number; public nodeIds: readonly number[]; - public serialize(ctx: MessageEncodingContext): Buffer { - this.payload = Buffer.allocUnsafe( + public serialize(ctx: MessageEncodingContext): Bytes { + this.payload = new Bytes( 3 + NUM_LR_NODEMASK_SEGMENT_BYTES, ); @@ -123,7 +124,7 @@ export class GetLongRangeNodesResponse extends Message { this.nodeIds, getFirstNodeId(this.segmentNumber), ); - nodeBitMask.copy(this.payload, 3); + this.payload.set(nodeBitMask, 3); return super.serialize(ctx); } diff --git a/packages/serial/src/serialapi/capability/GetSerialApiCapabilitiesMessages.ts b/packages/serial/src/serialapi/capability/GetSerialApiCapabilitiesMessages.ts index 45f749ec072c..ea20f55e29b3 100644 --- a/packages/serial/src/serialapi/capability/GetSerialApiCapabilitiesMessages.ts +++ b/packages/serial/src/serialapi/capability/GetSerialApiCapabilitiesMessages.ts @@ -11,6 +11,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; +import { Bytes } from "@zwave-js/shared/safe"; const NUM_FUNCTIONS = 256; const NUM_FUNCTION_BYTES = NUM_FUNCTIONS / 8; @@ -76,8 +77,8 @@ export class GetSerialApiCapabilitiesResponse extends Message { public productId: number; public supportedFunctionTypes: FunctionType[]; - public serialize(ctx: MessageEncodingContext): Buffer { - this.payload = Buffer.allocUnsafe(8 + NUM_FUNCTION_BYTES); + public serialize(ctx: MessageEncodingContext): Bytes { + this.payload = new Bytes(8 + NUM_FUNCTION_BYTES); const firmwareBytes = this.firmwareVersion .split(".", 2) @@ -93,7 +94,7 @@ export class GetSerialApiCapabilitiesResponse extends Message { this.supportedFunctionTypes, NUM_FUNCTIONS, ); - functionBitMask.copy(this.payload, 8); + this.payload.set(functionBitMask, 8); return super.serialize(ctx); } diff --git a/packages/serial/src/serialapi/capability/GetSerialApiInitDataMessages.ts b/packages/serial/src/serialapi/capability/GetSerialApiInitDataMessages.ts index da55f570d9f7..6fcaf8a55fd9 100644 --- a/packages/serial/src/serialapi/capability/GetSerialApiInitDataMessages.ts +++ b/packages/serial/src/serialapi/capability/GetSerialApiInitDataMessages.ts @@ -25,6 +25,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; +import { Bytes } from "@zwave-js/shared/safe"; @messageTypes(MessageType.Request, FunctionType.GetSerialApiInitData) @expectedResponse(FunctionType.GetSerialApiInitData) @@ -131,7 +132,7 @@ export class GetSerialApiInitDataResponse extends Message { public zwaveChipType?: string | UnknownZWaveChipType; - public serialize(ctx: MessageEncodingContext): Buffer { + public serialize(ctx: MessageEncodingContext): Bytes { let chipType: UnknownZWaveChipType | undefined; if (typeof this.zwaveChipType === "string") { chipType = getChipTypeAndVersion(this.zwaveChipType); @@ -139,7 +140,7 @@ export class GetSerialApiInitDataResponse extends Message { chipType = this.zwaveChipType; } - this.payload = Buffer.allocUnsafe( + this.payload = new Bytes( 3 + NUM_NODEMASK_BYTES + (chipType ? 2 : 0), ); @@ -159,7 +160,7 @@ export class GetSerialApiInitDataResponse extends Message { this.payload[2] = NUM_NODEMASK_BYTES; const nodeBitMask = encodeBitMask(this.nodeIds, MAX_NODES); - nodeBitMask.copy(this.payload, 3); + this.payload.set(nodeBitMask, 3); if (chipType) { this.payload[3 + NUM_NODEMASK_BYTES] = chipType.type; diff --git a/packages/serial/src/serialapi/capability/HardResetRequest.ts b/packages/serial/src/serialapi/capability/HardResetRequest.ts index f83825ffb9c7..66f7b3ec17af 100644 --- a/packages/serial/src/serialapi/capability/HardResetRequest.ts +++ b/packages/serial/src/serialapi/capability/HardResetRequest.ts @@ -12,6 +12,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; +import { Bytes } from "@zwave-js/shared/safe"; @messageTypes(MessageType.Request, FunctionType.HardReset) @priority(MessagePriority.Controller) @@ -30,9 +31,9 @@ export class HardResetRequestBase extends Message { @expectedCallback(FunctionType.HardReset) export class HardResetRequest extends HardResetRequestBase { - public serialize(ctx: MessageEncodingContext): Buffer { + public serialize(ctx: MessageEncodingContext): Bytes { this.assertCallbackId(); - this.payload = Buffer.from([this.callbackId]); + this.payload = Bytes.from([this.callbackId]); return super.serialize(ctx); } diff --git a/packages/serial/src/serialapi/capability/LongRangeChannelMessages.ts b/packages/serial/src/serialapi/capability/LongRangeChannelMessages.ts index cedd32833176..d9f338f904dc 100644 --- a/packages/serial/src/serialapi/capability/LongRangeChannelMessages.ts +++ b/packages/serial/src/serialapi/capability/LongRangeChannelMessages.ts @@ -18,7 +18,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; -import { getEnumMemberName } from "@zwave-js/shared"; +import { Bytes, getEnumMemberName } from "@zwave-js/shared"; @messageTypes(MessageType.Request, FunctionType.GetLongRangeChannel) @expectedResponse(FunctionType.GetLongRangeChannel) @@ -109,8 +109,8 @@ export class SetLongRangeChannelRequest extends Message { public channel: LongRangeChannel; - public serialize(ctx: MessageEncodingContext): Buffer { - this.payload = Buffer.from([this.channel]); + public serialize(ctx: MessageEncodingContext): Bytes { + this.payload = Bytes.from([this.channel]); return super.serialize(ctx); } diff --git a/packages/serial/src/serialapi/capability/SerialAPISetupMessages.test.ts b/packages/serial/src/serialapi/capability/SerialAPISetupMessages.test.ts index 87f181df0d87..9a246765b065 100644 --- a/packages/serial/src/serialapi/capability/SerialAPISetupMessages.test.ts +++ b/packages/serial/src/serialapi/capability/SerialAPISetupMessages.test.ts @@ -1,9 +1,10 @@ import { Message } from "@zwave-js/serial"; +import { Bytes } from "@zwave-js/shared/safe"; import test from "ava"; import { SerialAPISetup_GetSupportedCommandsResponse } from "./SerialAPISetupMessages"; test("GetSupportedCommandsResponse with extended bitmask parses correctly (pre-7.19.1 encoding)", (t) => { - const data = Buffer.from( + const data = Bytes.from( "0116010b01fe160103000100000001000000000000000109", "hex", ); @@ -23,7 +24,7 @@ test("GetSupportedCommandsResponse with extended bitmask parses correctly (pre-7 }); test("GetSupportedCommandsResponse with extended bitmask parses correctly (post-7.19.1 encoding)", (t) => { - const data = Buffer.from( + const data = Bytes.from( "0116010b01ff8b8001800000008000000000000000800097", "hex", ); diff --git a/packages/serial/src/serialapi/capability/SerialAPISetupMessages.ts b/packages/serial/src/serialapi/capability/SerialAPISetupMessages.ts index 9dcc5d212ae5..983d00b46249 100644 --- a/packages/serial/src/serialapi/capability/SerialAPISetupMessages.ts +++ b/packages/serial/src/serialapi/capability/SerialAPISetupMessages.ts @@ -27,7 +27,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; -import { getEnumMemberName } from "@zwave-js/shared"; +import { Bytes, getEnumMemberName } from "@zwave-js/shared"; export enum SerialAPISetupCommand { Unsupported = 0x00, @@ -123,9 +123,9 @@ export class SerialAPISetupRequest extends Message { public command: SerialAPISetupCommand; - public serialize(ctx: MessageEncodingContext): Buffer { - this.payload = Buffer.concat([ - Buffer.from([this.command]), + public serialize(ctx: MessageEncodingContext): Bytes { + this.payload = Bytes.concat([ + Bytes.from([this.command]), this.payload, ]); @@ -352,8 +352,8 @@ export class SerialAPISetup_SetTXStatusReportRequest public enabled: boolean; - public serialize(ctx: MessageEncodingContext): Buffer { - this.payload = Buffer.from([this.enabled ? 0xff : 0x00]); + public serialize(ctx: MessageEncodingContext): Bytes { + this.payload = Bytes.from([this.enabled ? 0xff : 0x00]); return super.serialize(ctx); } @@ -440,8 +440,8 @@ export class SerialAPISetup_SetNodeIDTypeRequest extends SerialAPISetupRequest { public nodeIdType: NodeIDType; - public serialize(ctx: MessageEncodingContext): Buffer { - this.payload = Buffer.from([this.nodeIdType]); + public serialize(ctx: MessageEncodingContext): Bytes { + this.payload = Bytes.from([this.nodeIdType]); return super.serialize(ctx); } @@ -568,8 +568,8 @@ export class SerialAPISetup_SetRFRegionRequest extends SerialAPISetupRequest { public region: RFRegion; - public serialize(ctx: MessageEncodingContext): Buffer { - this.payload = Buffer.from([this.region]); + public serialize(ctx: MessageEncodingContext): Bytes { + this.payload = Bytes.from([this.region]); return super.serialize(ctx); } @@ -727,8 +727,8 @@ export class SerialAPISetup_SetPowerlevelRequest extends SerialAPISetupRequest { public powerlevel: number; public measured0dBm: number; - public serialize(ctx: MessageEncodingContext): Buffer { - this.payload = Buffer.allocUnsafe(2); + public serialize(ctx: MessageEncodingContext): Bytes { + this.payload = new Bytes(2); // The values are in 0.1 dBm this.payload.writeInt8(Math.round(this.powerlevel * 10), 0); this.payload.writeInt8(Math.round(this.measured0dBm * 10), 1); @@ -898,8 +898,8 @@ export class SerialAPISetup_SetPowerlevel16BitRequest public powerlevel: number; public measured0dBm: number; - public serialize(ctx: MessageEncodingContext): Buffer { - this.payload = Buffer.allocUnsafe(4); + public serialize(ctx: MessageEncodingContext): Bytes { + this.payload = new Bytes(4); // The values are in 0.1 dBm this.payload.writeInt16BE(Math.round(this.powerlevel * 10), 0); this.payload.writeInt16BE(Math.round(this.measured0dBm * 10), 2); @@ -1058,8 +1058,8 @@ export class SerialAPISetup_SetLongRangeMaximumTxPowerRequest /** The maximum LR TX power in dBm */ public limit: number; - public serialize(ctx: MessageEncodingContext): Buffer { - this.payload = Buffer.allocUnsafe(2); + public serialize(ctx: MessageEncodingContext): Bytes { + this.payload = new Bytes(2); // The values are in 0.1 dBm, signed this.payload.writeInt16BE(Math.round(this.limit * 10), 0); @@ -1285,8 +1285,8 @@ export class SerialAPISetup_GetRegionInfoRequest extends SerialAPISetupRequest { public region: RFRegion; - public serialize(ctx: MessageEncodingContext): Buffer { - this.payload = Buffer.from([this.region]); + public serialize(ctx: MessageEncodingContext): Bytes { + this.payload = Bytes.from([this.region]); return super.serialize(ctx); } diff --git a/packages/serial/src/serialapi/capability/SetApplicationNodeInformationRequest.ts b/packages/serial/src/serialapi/capability/SetApplicationNodeInformationRequest.ts index 2fc8f9c42bba..618cf30b3bb3 100644 --- a/packages/serial/src/serialapi/capability/SetApplicationNodeInformationRequest.ts +++ b/packages/serial/src/serialapi/capability/SetApplicationNodeInformationRequest.ts @@ -14,7 +14,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; -import { num2hex } from "@zwave-js/shared"; +import { Bytes, num2hex } from "@zwave-js/shared"; export interface SetApplicationNodeInformationRequestOptions extends MessageBaseOptions @@ -46,10 +46,10 @@ export class SetApplicationNodeInformationRequest extends Message { public supportedCCs: CommandClasses[]; public controlledCCs: CommandClasses[]; - public serialize(ctx: MessageEncodingContext): Buffer { + public serialize(ctx: MessageEncodingContext): Bytes { const ccList = encodeCCList(this.supportedCCs, this.controlledCCs); const ccListLength = Math.min(ccList.length, 35); - this.payload = Buffer.from([ + this.payload = Bytes.from([ this.isListening ? 0x01 : 0, // APPLICATION_NODEINFO_LISTENING this.genericDeviceClass, this.specificDeviceClass, diff --git a/packages/serial/src/serialapi/capability/SetLongRangeShadowNodeIDsRequest.ts b/packages/serial/src/serialapi/capability/SetLongRangeShadowNodeIDsRequest.ts index 54e14ce968ac..44cc804f05df 100644 --- a/packages/serial/src/serialapi/capability/SetLongRangeShadowNodeIDsRequest.ts +++ b/packages/serial/src/serialapi/capability/SetLongRangeShadowNodeIDsRequest.ts @@ -10,6 +10,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; +import { type Bytes } from "@zwave-js/shared/safe"; export interface LongRangeShadowNodeIDsRequestOptions { shadowNodeIds: number[]; @@ -46,8 +47,7 @@ export class SetLongRangeShadowNodeIDsRequest extends Message { public shadowNodeIds: number[]; - public serialize(ctx: MessageEncodingContext): Buffer { - this.payload = Buffer.allocUnsafe(1); + public serialize(ctx: MessageEncodingContext): Bytes { this.payload = encodeBitMask( this.shadowNodeIds, LONG_RANGE_SHADOW_NODE_IDS_START diff --git a/packages/serial/src/serialapi/memory/GetControllerIdMessages.ts b/packages/serial/src/serialapi/memory/GetControllerIdMessages.ts index a6a6968bfc17..a66e8a43ae15 100644 --- a/packages/serial/src/serialapi/memory/GetControllerIdMessages.ts +++ b/packages/serial/src/serialapi/memory/GetControllerIdMessages.ts @@ -12,7 +12,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; -import { num2hex } from "@zwave-js/shared"; +import { Bytes, num2hex } from "@zwave-js/shared"; @messageTypes(MessageType.Request, FunctionType.GetControllerId) @expectedResponse(FunctionType.GetControllerId) @@ -55,12 +55,12 @@ export class GetControllerIdResponse extends Message { public homeId: number; public ownNodeId: number; - public serialize(ctx: MessageEncodingContext): Buffer { + public serialize(ctx: MessageEncodingContext): Bytes { const nodeId = encodeNodeID(this.ownNodeId, ctx.nodeIdType); - const homeId = Buffer.allocUnsafe(4); + const homeId = new Bytes(4); homeId.writeUInt32BE(this.homeId, 0); - this.payload = Buffer.concat([homeId, nodeId]); + this.payload = Bytes.concat([homeId, nodeId]); return super.serialize(ctx); } diff --git a/packages/serial/src/serialapi/misc/SetRFReceiveModeMessages.ts b/packages/serial/src/serialapi/misc/SetRFReceiveModeMessages.ts index a33f499a6f8b..9dc73f51742e 100644 --- a/packages/serial/src/serialapi/misc/SetRFReceiveModeMessages.ts +++ b/packages/serial/src/serialapi/misc/SetRFReceiveModeMessages.ts @@ -19,6 +19,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; +import { Bytes } from "@zwave-js/shared/safe"; export interface SetRFReceiveModeRequestOptions { /** Whether the stick should receive (true) or not (false) */ @@ -50,8 +51,8 @@ export class SetRFReceiveModeRequest extends Message { public enabled: boolean; - public serialize(ctx: MessageEncodingContext): Buffer { - this.payload = Buffer.from([this.enabled ? 0x01 : 0x00]); + public serialize(ctx: MessageEncodingContext): Bytes { + this.payload = Bytes.from([this.enabled ? 0x01 : 0x00]); return super.serialize(ctx); } diff --git a/packages/serial/src/serialapi/misc/SetSerialApiTimeoutsMessages.ts b/packages/serial/src/serialapi/misc/SetSerialApiTimeoutsMessages.ts index 5130568affdb..ec95f3391318 100644 --- a/packages/serial/src/serialapi/misc/SetSerialApiTimeoutsMessages.ts +++ b/packages/serial/src/serialapi/misc/SetSerialApiTimeoutsMessages.ts @@ -11,6 +11,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; +import { Bytes } from "@zwave-js/shared/safe"; export interface SetSerialApiTimeoutsRequestOptions { ackTimeout: number; @@ -32,8 +33,8 @@ export class SetSerialApiTimeoutsRequest extends Message { public ackTimeout: number; public byteTimeout: number; - public serialize(ctx: MessageEncodingContext): Buffer { - this.payload = Buffer.from([ + public serialize(ctx: MessageEncodingContext): Bytes { + this.payload = Bytes.from([ Math.round(this.ackTimeout / 10), Math.round(this.byteTimeout / 10), ]); diff --git a/packages/serial/src/serialapi/network-mgmt/AddNodeToNetworkRequest.ts b/packages/serial/src/serialapi/network-mgmt/AddNodeToNetworkRequest.ts index 2d38b3998f6d..45225274aac1 100644 --- a/packages/serial/src/serialapi/network-mgmt/AddNodeToNetworkRequest.ts +++ b/packages/serial/src/serialapi/network-mgmt/AddNodeToNetworkRequest.ts @@ -30,7 +30,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; -import { buffer2hex, getEnumMemberName } from "@zwave-js/shared"; +import { Bytes, buffer2hex, getEnumMemberName } from "@zwave-js/shared"; export enum AddNodeType { Any = 1, @@ -67,8 +67,8 @@ export interface AddNodeToNetworkRequestOptions { } export interface AddNodeDSKToNetworkRequestOptions { - nwiHomeId: Buffer; - authHomeId: Buffer; + nwiHomeId: Uint8Array; + authHomeId: Uint8Array; highPower?: boolean; networkWide?: boolean; protocol?: Protocols; @@ -171,13 +171,13 @@ export class AddNodeToNetworkRequest extends AddNodeToNetworkRequestBase { /** Whether to include network wide */ public networkWide: boolean = false; - public serialize(ctx: MessageEncodingContext): Buffer { + public serialize(ctx: MessageEncodingContext): Bytes { this.assertCallbackId(); let data: number = this.addNodeType || AddNodeType.Any; if (this.highPower) data |= AddNodeFlags.HighPower; if (this.networkWide) data |= AddNodeFlags.NetworkWide; - this.payload = Buffer.from([data, this.callbackId]); + this.payload = Bytes.from([data, this.callbackId]); return super.serialize(ctx); } @@ -208,13 +208,13 @@ export class AddNodeToNetworkRequest extends AddNodeToNetworkRequestBase { } export class EnableSmartStartListenRequest extends AddNodeToNetworkRequestBase { - public serialize(ctx: MessageEncodingContext): Buffer { + public serialize(ctx: MessageEncodingContext): Bytes { const control: number = AddNodeType.SmartStartListen | AddNodeFlags.NetworkWide; // The Serial API does not send a callback, so disable waiting for one this.callbackId = 0; - this.payload = Buffer.from([control, this.callbackId]); + this.payload = Bytes.from([control, this.callbackId]); return super.serialize(ctx); } @@ -242,8 +242,8 @@ export class AddNodeDSKToNetworkRequest extends AddNodeToNetworkRequestBase { } /** The home IDs of node to add */ - public nwiHomeId: Buffer; - public authHomeId: Buffer; + public nwiHomeId: Uint8Array; + public authHomeId: Uint8Array; /** Whether to use high power */ public highPower: boolean; /** Whether to include network wide */ @@ -251,7 +251,7 @@ export class AddNodeDSKToNetworkRequest extends AddNodeToNetworkRequestBase { /** Whether to include as long-range or not */ public protocol: Protocols; - public serialize(ctx: MessageEncodingContext): Buffer { + public serialize(ctx: MessageEncodingContext): Bytes { this.assertCallbackId(); let control: number = AddNodeType.SmartStartDSK; if (this.highPower) control |= AddNodeFlags.HighPower; @@ -260,8 +260,8 @@ export class AddNodeDSKToNetworkRequest extends AddNodeToNetworkRequestBase { control |= AddNodeFlags.ProtocolLongRange; } - this.payload = Buffer.concat([ - Buffer.from([control, this.callbackId]), + this.payload = Bytes.concat([ + Bytes.from([control, this.callbackId]), this.nwiHomeId, this.authHomeId, ]); @@ -378,11 +378,11 @@ export class AddNodeToNetworkRequestStatusReport public readonly status: AddNodeStatus; public readonly statusContext: AddNodeStatusContext | undefined; - public serialize(ctx: MessageEncodingContext): Buffer { + public serialize(ctx: MessageEncodingContext): Bytes { this.assertCallbackId(); - this.payload = Buffer.from([this.callbackId, this.status]); + this.payload = Bytes.from([this.callbackId, this.status]); if (this.statusContext?.basicDeviceClass != undefined) { - this.payload = Buffer.concat([ + this.payload = Bytes.concat([ this.payload, encodeNodeUpdatePayload( this.statusContext as NodeUpdatePayload, diff --git a/packages/serial/src/serialapi/network-mgmt/AssignPriorityReturnRouteMessages.ts b/packages/serial/src/serialapi/network-mgmt/AssignPriorityReturnRouteMessages.ts index 3cf86d202682..b7117d0a3b27 100644 --- a/packages/serial/src/serialapi/network-mgmt/AssignPriorityReturnRouteMessages.ts +++ b/packages/serial/src/serialapi/network-mgmt/AssignPriorityReturnRouteMessages.ts @@ -24,7 +24,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; -import { getEnumMemberName } from "@zwave-js/shared"; +import { Bytes, getEnumMemberName } from "@zwave-js/shared"; @messageTypes(MessageType.Request, FunctionType.AssignPriorityReturnRoute) @priority(MessagePriority.Normal) @@ -99,17 +99,17 @@ export class AssignPriorityReturnRouteRequest public repeaters: number[]; public routeSpeed: ZWaveDataRate; - public serialize(ctx: MessageEncodingContext): Buffer { + public serialize(ctx: MessageEncodingContext): Bytes { this.assertCallbackId(); const nodeId = encodeNodeID(this.nodeId, ctx.nodeIdType); const destinationNodeId = encodeNodeID( this.destinationNodeId, ctx.nodeIdType, ); - this.payload = Buffer.concat([ + this.payload = Bytes.concat([ nodeId, destinationNodeId, - Buffer.from([ + Bytes.from([ this.repeaters[0] ?? 0, this.repeaters[1] ?? 0, this.repeaters[2] ?? 0, diff --git a/packages/serial/src/serialapi/network-mgmt/AssignPrioritySUCReturnRouteMessages.ts b/packages/serial/src/serialapi/network-mgmt/AssignPrioritySUCReturnRouteMessages.ts index 80c1907b8a1e..21fbdcf9534d 100644 --- a/packages/serial/src/serialapi/network-mgmt/AssignPrioritySUCReturnRouteMessages.ts +++ b/packages/serial/src/serialapi/network-mgmt/AssignPrioritySUCReturnRouteMessages.ts @@ -24,7 +24,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; -import { getEnumMemberName } from "@zwave-js/shared"; +import { Bytes, getEnumMemberName } from "@zwave-js/shared"; @messageTypes(MessageType.Request, FunctionType.AssignPrioritySUCReturnRoute) @priority(MessagePriority.Normal) @@ -92,12 +92,12 @@ export class AssignPrioritySUCReturnRouteRequest public repeaters: number[]; public routeSpeed: ZWaveDataRate; - public serialize(ctx: MessageEncodingContext): Buffer { + public serialize(ctx: MessageEncodingContext): Bytes { this.assertCallbackId(); const nodeId = encodeNodeID(this.nodeId, ctx.nodeIdType); - this.payload = Buffer.concat([ + this.payload = Bytes.concat([ nodeId, - Buffer.from([ + Bytes.from([ this.repeaters[0] ?? 0, this.repeaters[1] ?? 0, this.repeaters[2] ?? 0, diff --git a/packages/serial/src/serialapi/network-mgmt/AssignReturnRouteMessages.ts b/packages/serial/src/serialapi/network-mgmt/AssignReturnRouteMessages.ts index 2c2e9d484b23..5ee3cbe22dc0 100644 --- a/packages/serial/src/serialapi/network-mgmt/AssignReturnRouteMessages.ts +++ b/packages/serial/src/serialapi/network-mgmt/AssignReturnRouteMessages.ts @@ -23,7 +23,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; -import { getEnumMemberName } from "@zwave-js/shared"; +import { Bytes, getEnumMemberName } from "@zwave-js/shared"; @messageTypes(MessageType.Request, FunctionType.AssignReturnRoute) @priority(MessagePriority.Normal) @@ -77,7 +77,7 @@ export class AssignReturnRouteRequest extends AssignReturnRouteRequestBase { public nodeId: number; public destinationNodeId: number; - public serialize(ctx: MessageEncodingContext): Buffer { + public serialize(ctx: MessageEncodingContext): Bytes { this.assertCallbackId(); const nodeId = encodeNodeID(this.nodeId, ctx.nodeIdType); const destinationNodeId = encodeNodeID( @@ -85,10 +85,10 @@ export class AssignReturnRouteRequest extends AssignReturnRouteRequestBase { ctx.nodeIdType, ); - this.payload = Buffer.concat([ + this.payload = Bytes.concat([ nodeId, destinationNodeId, - Buffer.from([this.callbackId]), + Bytes.from([this.callbackId]), ]); return super.serialize(ctx); diff --git a/packages/serial/src/serialapi/network-mgmt/AssignSUCReturnRouteMessages.ts b/packages/serial/src/serialapi/network-mgmt/AssignSUCReturnRouteMessages.ts index b85a0edec06c..7c0596fe41a5 100644 --- a/packages/serial/src/serialapi/network-mgmt/AssignSUCReturnRouteMessages.ts +++ b/packages/serial/src/serialapi/network-mgmt/AssignSUCReturnRouteMessages.ts @@ -19,7 +19,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; -import { getEnumMemberName } from "@zwave-js/shared"; +import { Bytes, getEnumMemberName } from "@zwave-js/shared"; @messageTypes(MessageType.Request, FunctionType.AssignSUCReturnRoute) @priority(MessagePriority.Normal) @@ -82,10 +82,10 @@ export class AssignSUCReturnRouteRequest public nodeId: number; public readonly disableCallbackFunctionTypeCheck?: boolean; - public serialize(ctx: MessageEncodingContext): Buffer { + public serialize(ctx: MessageEncodingContext): Bytes { this.assertCallbackId(); const nodeId = encodeNodeID(this.nodeId, ctx.nodeIdType); - this.payload = Buffer.concat([nodeId, Buffer.from([this.callbackId])]); + this.payload = Bytes.concat([nodeId, Bytes.from([this.callbackId])]); return super.serialize(ctx); } @@ -123,8 +123,8 @@ export class AssignSUCReturnRouteResponse extends Message public wasExecuted: boolean; - public serialize(ctx: MessageEncodingContext): Buffer { - this.payload = Buffer.from([this.wasExecuted ? 0x01 : 0]); + public serialize(ctx: MessageEncodingContext): Bytes { + this.payload = Bytes.from([this.wasExecuted ? 0x01 : 0]); return super.serialize(ctx); } @@ -177,9 +177,9 @@ export class AssignSUCReturnRouteRequestTransmitReport public transmitStatus: TransmitStatus; - public serialize(ctx: MessageEncodingContext): Buffer { + public serialize(ctx: MessageEncodingContext): Bytes { this.assertCallbackId(); - this.payload = Buffer.from([this.callbackId, this.transmitStatus]); + this.payload = Bytes.from([this.callbackId, this.transmitStatus]); return super.serialize(ctx); } diff --git a/packages/serial/src/serialapi/network-mgmt/DeleteReturnRouteMessages.ts b/packages/serial/src/serialapi/network-mgmt/DeleteReturnRouteMessages.ts index cf1eb46ecbe9..6f87b772bb2c 100644 --- a/packages/serial/src/serialapi/network-mgmt/DeleteReturnRouteMessages.ts +++ b/packages/serial/src/serialapi/network-mgmt/DeleteReturnRouteMessages.ts @@ -23,7 +23,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; -import { getEnumMemberName } from "@zwave-js/shared"; +import { Bytes, getEnumMemberName } from "@zwave-js/shared"; @messageTypes(MessageType.Request, FunctionType.DeleteReturnRoute) @priority(MessagePriority.Normal) @@ -68,10 +68,10 @@ export class DeleteReturnRouteRequest extends DeleteReturnRouteRequestBase { public nodeId: number; - public serialize(ctx: MessageEncodingContext): Buffer { + public serialize(ctx: MessageEncodingContext): Bytes { this.assertCallbackId(); const nodeId = encodeNodeID(this.nodeId, ctx.nodeIdType); - this.payload = Buffer.concat([nodeId, Buffer.from([this.callbackId])]); + this.payload = Bytes.concat([nodeId, Bytes.from([this.callbackId])]); return super.serialize(ctx); } diff --git a/packages/serial/src/serialapi/network-mgmt/DeleteSUCReturnRouteMessages.ts b/packages/serial/src/serialapi/network-mgmt/DeleteSUCReturnRouteMessages.ts index be76caf364cc..a183f1b5e6dc 100644 --- a/packages/serial/src/serialapi/network-mgmt/DeleteSUCReturnRouteMessages.ts +++ b/packages/serial/src/serialapi/network-mgmt/DeleteSUCReturnRouteMessages.ts @@ -21,7 +21,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; -import { getEnumMemberName } from "@zwave-js/shared"; +import { Bytes, getEnumMemberName } from "@zwave-js/shared"; @messageTypes(MessageType.Request, FunctionType.DeleteSUCReturnRoute) @priority(MessagePriority.Normal) @@ -84,10 +84,10 @@ export class DeleteSUCReturnRouteRequest public nodeId: number; public readonly disableCallbackFunctionTypeCheck?: boolean; - public serialize(ctx: MessageEncodingContext): Buffer { + public serialize(ctx: MessageEncodingContext): Bytes { this.assertCallbackId(); const nodeId = encodeNodeID(this.nodeId, ctx.nodeIdType); - this.payload = Buffer.concat([nodeId, Buffer.from([this.callbackId])]); + this.payload = Bytes.concat([nodeId, Bytes.from([this.callbackId])]); return super.serialize(ctx); } @@ -125,8 +125,8 @@ export class DeleteSUCReturnRouteResponse extends Message public readonly wasExecuted: boolean; - public serialize(ctx: MessageEncodingContext): Buffer { - this.payload = Buffer.from([this.wasExecuted ? 0x01 : 0]); + public serialize(ctx: MessageEncodingContext): Bytes { + this.payload = Bytes.from([this.wasExecuted ? 0x01 : 0]); return super.serialize(ctx); } @@ -179,9 +179,9 @@ export class DeleteSUCReturnRouteRequestTransmitReport public readonly transmitStatus: TransmitStatus; - public serialize(ctx: MessageEncodingContext): Buffer { + public serialize(ctx: MessageEncodingContext): Bytes { this.assertCallbackId(); - this.payload = Buffer.from([this.callbackId, this.transmitStatus]); + this.payload = Bytes.from([this.callbackId, this.transmitStatus]); return super.serialize(ctx); } diff --git a/packages/serial/src/serialapi/network-mgmt/GetNodeProtocolInfoMessages._test.ts b/packages/serial/src/serialapi/network-mgmt/GetNodeProtocolInfoMessages._test.ts index 2b42dd2df2df..10483ac8b4a0 100644 --- a/packages/serial/src/serialapi/network-mgmt/GetNodeProtocolInfoMessages._test.ts +++ b/packages/serial/src/serialapi/network-mgmt/GetNodeProtocolInfoMessages._test.ts @@ -101,7 +101,7 @@ export default foo; // }); // // TODO: Pick up an actual message from OZW to test this -// // const rawBuf = Buffer.from("011001155a2d5761766520342e3035000197", "hex"); +// // const rawBuf = Bytes.from("011001155a2d5761766520342e3035000197", "hex"); // // const parsed = new GetNodeProtocolInfo(undefined); // // parsed.deserialize(rawBuf); diff --git a/packages/serial/src/serialapi/network-mgmt/GetNodeProtocolInfoMessages.ts b/packages/serial/src/serialapi/network-mgmt/GetNodeProtocolInfoMessages.ts index 82280bbad8f9..f65e023edb95 100644 --- a/packages/serial/src/serialapi/network-mgmt/GetNodeProtocolInfoMessages.ts +++ b/packages/serial/src/serialapi/network-mgmt/GetNodeProtocolInfoMessages.ts @@ -24,6 +24,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; +import { Bytes } from "@zwave-js/shared"; import { isObject } from "alcalzone-shared/typeguards"; export interface GetNodeProtocolInfoRequestOptions { @@ -57,7 +58,7 @@ export class GetNodeProtocolInfoRequest extends Message { // but this is a message to the controller public requestedNodeId: number; - public serialize(ctx: MessageEncodingContext): Buffer { + public serialize(ctx: MessageEncodingContext): Bytes { this.payload = encodeNodeID(this.requestedNodeId, ctx.nodeIdType); return super.serialize(ctx); } @@ -166,7 +167,7 @@ export class GetNodeProtocolInfoResponse extends Message { public genericDeviceClass: number; public specificDeviceClass: number; - public serialize(ctx: MessageEncodingContext): Buffer { + public serialize(ctx: MessageEncodingContext): Bytes { const protocolInfo = encodeNodeProtocolInfo({ isListening: this.isListening, isFrequentListening: this.isFrequentListening, @@ -179,9 +180,9 @@ export class GetNodeProtocolInfoResponse extends Message { supportsBeaming: this.supportsBeaming, hasSpecificDeviceClass: this.specificDeviceClass !== 0, }); - this.payload = Buffer.concat([ + this.payload = Bytes.concat([ protocolInfo, - Buffer.from([ + Bytes.from([ this.basicDeviceClass, this.genericDeviceClass, this.specificDeviceClass, diff --git a/packages/serial/src/serialapi/network-mgmt/GetPriorityRouteMessages.ts b/packages/serial/src/serialapi/network-mgmt/GetPriorityRouteMessages.ts index 22705b08cefe..5d356d343d70 100644 --- a/packages/serial/src/serialapi/network-mgmt/GetPriorityRouteMessages.ts +++ b/packages/serial/src/serialapi/network-mgmt/GetPriorityRouteMessages.ts @@ -22,7 +22,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; -import { getEnumMemberName } from "@zwave-js/shared"; +import { type Bytes, getEnumMemberName } from "@zwave-js/shared"; export interface GetPriorityRouteRequestOptions { destinationNodeId: number; @@ -53,7 +53,7 @@ export class GetPriorityRouteRequest extends Message { public destinationNodeId: number; - public serialize(ctx: MessageEncodingContext): Buffer { + public serialize(ctx: MessageEncodingContext): Bytes { this.payload = encodeNodeID( this.destinationNodeId, ctx.nodeIdType, diff --git a/packages/serial/src/serialapi/network-mgmt/GetRoutingInfoMessages.ts b/packages/serial/src/serialapi/network-mgmt/GetRoutingInfoMessages.ts index e2b8b98ea046..e16209718f85 100644 --- a/packages/serial/src/serialapi/network-mgmt/GetRoutingInfoMessages.ts +++ b/packages/serial/src/serialapi/network-mgmt/GetRoutingInfoMessages.ts @@ -17,6 +17,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; +import { Bytes } from "@zwave-js/shared"; export interface GetRoutingInfoRequestOptions { nodeId: number; @@ -41,13 +42,13 @@ export class GetRoutingInfoRequest extends Message { public removeNonRepeaters: boolean; public removeBadLinks: boolean; - public serialize(ctx: MessageEncodingContext): Buffer { + public serialize(ctx: MessageEncodingContext): Bytes { const nodeId = encodeNodeID(this.sourceNodeId, ctx.nodeIdType); const optionsByte = (this.removeBadLinks ? 0b1000_0000 : 0) | (this.removeNonRepeaters ? 0b0100_0000 : 0); - this.payload = Buffer.concat([ + this.payload = Bytes.concat([ nodeId, - Buffer.from([ + Bytes.from([ optionsByte, 0, // callbackId - this must be 0 as per the docs ]), diff --git a/packages/serial/src/serialapi/network-mgmt/GetSUCNodeIdMessages.ts b/packages/serial/src/serialapi/network-mgmt/GetSUCNodeIdMessages.ts index 5726b1097d6b..789864cb0cc4 100644 --- a/packages/serial/src/serialapi/network-mgmt/GetSUCNodeIdMessages.ts +++ b/packages/serial/src/serialapi/network-mgmt/GetSUCNodeIdMessages.ts @@ -11,6 +11,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; +import { type Bytes } from "@zwave-js/shared"; @messageTypes(MessageType.Request, FunctionType.GetSUCNodeId) @expectedResponse(FunctionType.GetSUCNodeId) @@ -49,7 +50,7 @@ export class GetSUCNodeIdResponse extends Message { /** The node id of the SUC or 0 if none is present */ public sucNodeId: number; - public serialize(ctx: MessageEncodingContext): Buffer { + public serialize(ctx: MessageEncodingContext): Bytes { this.payload = encodeNodeID(this.sucNodeId, ctx.nodeIdType); return super.serialize(ctx); } diff --git a/packages/serial/src/serialapi/network-mgmt/IsFailedNodeMessages.ts b/packages/serial/src/serialapi/network-mgmt/IsFailedNodeMessages.ts index f7e262908e93..e66f4c014788 100644 --- a/packages/serial/src/serialapi/network-mgmt/IsFailedNodeMessages.ts +++ b/packages/serial/src/serialapi/network-mgmt/IsFailedNodeMessages.ts @@ -11,6 +11,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; +import { type Bytes } from "@zwave-js/shared"; export interface IsFailedNodeRequestOptions { // This must not be called nodeId or rejectAllTransactions may reject the request @@ -31,7 +32,7 @@ export class IsFailedNodeRequest extends Message { // This must not be called nodeId or rejectAllTransactions may reject the request public failedNodeId: number; - public serialize(ctx: MessageEncodingContext): Buffer { + public serialize(ctx: MessageEncodingContext): Bytes { this.payload = encodeNodeID(this.failedNodeId, ctx.nodeIdType); return super.serialize(ctx); } diff --git a/packages/serial/src/serialapi/network-mgmt/RemoveFailedNodeMessages.ts b/packages/serial/src/serialapi/network-mgmt/RemoveFailedNodeMessages.ts index 3fd45fa50097..3c8bf01ff549 100644 --- a/packages/serial/src/serialapi/network-mgmt/RemoveFailedNodeMessages.ts +++ b/packages/serial/src/serialapi/network-mgmt/RemoveFailedNodeMessages.ts @@ -16,6 +16,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; +import { Bytes } from "@zwave-js/shared"; export enum RemoveFailedNodeStartFlags { OK = 0, @@ -77,10 +78,10 @@ export class RemoveFailedNodeRequest extends RemoveFailedNodeRequestBase { /** The node that should be removed */ public failedNodeId: number; - public serialize(ctx: MessageEncodingContext): Buffer { + public serialize(ctx: MessageEncodingContext): Bytes { this.assertCallbackId(); const nodeId = encodeNodeID(this.failedNodeId, ctx.nodeIdType); - this.payload = Buffer.concat([nodeId, Buffer.from([this.callbackId])]); + this.payload = Bytes.concat([nodeId, Bytes.from([this.callbackId])]); return super.serialize(ctx); } } diff --git a/packages/serial/src/serialapi/network-mgmt/RemoveNodeFromNetworkRequest.ts b/packages/serial/src/serialapi/network-mgmt/RemoveNodeFromNetworkRequest.ts index 5bed8a59a5b6..7638e085fef7 100644 --- a/packages/serial/src/serialapi/network-mgmt/RemoveNodeFromNetworkRequest.ts +++ b/packages/serial/src/serialapi/network-mgmt/RemoveNodeFromNetworkRequest.ts @@ -20,6 +20,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; +import { Bytes } from "@zwave-js/shared"; export enum RemoveNodeType { Any = 1, @@ -131,13 +132,13 @@ export class RemoveNodeFromNetworkRequest /** Whether to exclude network wide */ public networkWide: boolean = false; - public serialize(ctx: MessageEncodingContext): Buffer { + public serialize(ctx: MessageEncodingContext): Bytes { this.assertCallbackId(); let data: number = this.removeNodeType || RemoveNodeType.Any; if (this.highPower) data |= RemoveNodeFlags.HighPower; if (this.networkWide) data |= RemoveNodeFlags.NetworkWide; - this.payload = Buffer.from([data, this.callbackId]); + this.payload = Bytes.from([data, this.callbackId]); return super.serialize(ctx); } @@ -221,11 +222,11 @@ export class RemoveNodeFromNetworkRequestStatusReport return this.status !== RemoveNodeStatus.Failed; } - public serialize(ctx: MessageEncodingContext): Buffer { + public serialize(ctx: MessageEncodingContext): Bytes { this.assertCallbackId(); - this.payload = Buffer.from([this.callbackId, this.status]); + this.payload = Bytes.from([this.callbackId, this.status]); if (this.statusContext?.nodeId != undefined) { - this.payload = Buffer.concat([ + this.payload = Bytes.concat([ this.payload, encodeNodeID(this.statusContext.nodeId, ctx.nodeIdType), ]); diff --git a/packages/serial/src/serialapi/network-mgmt/ReplaceFailedNodeRequest.ts b/packages/serial/src/serialapi/network-mgmt/ReplaceFailedNodeRequest.ts index cb7e4ad9932e..3cb3927e50ce 100644 --- a/packages/serial/src/serialapi/network-mgmt/ReplaceFailedNodeRequest.ts +++ b/packages/serial/src/serialapi/network-mgmt/ReplaceFailedNodeRequest.ts @@ -15,6 +15,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; +import { Bytes } from "@zwave-js/shared"; export enum ReplaceFailedNodeStartFlags { OK = 0, @@ -76,10 +77,10 @@ export class ReplaceFailedNodeRequest extends ReplaceFailedNodeRequestBase { /** The node that should be removed */ public failedNodeId: number; - public serialize(ctx: MessageEncodingContext): Buffer { + public serialize(ctx: MessageEncodingContext): Bytes { this.assertCallbackId(); const nodeId = encodeNodeID(this.failedNodeId, ctx.nodeIdType); - this.payload = Buffer.concat([nodeId, Buffer.from([this.callbackId])]); + this.payload = Bytes.concat([nodeId, Bytes.from([this.callbackId])]); return super.serialize(ctx); } } diff --git a/packages/serial/src/serialapi/network-mgmt/RequestNodeInfoMessages.ts b/packages/serial/src/serialapi/network-mgmt/RequestNodeInfoMessages.ts index a4a26b4b4b3f..03eb51eb8c8f 100644 --- a/packages/serial/src/serialapi/network-mgmt/RequestNodeInfoMessages.ts +++ b/packages/serial/src/serialapi/network-mgmt/RequestNodeInfoMessages.ts @@ -18,6 +18,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; +import { Bytes } from "@zwave-js/shared"; import { ApplicationUpdateRequestNodeInfoReceived, ApplicationUpdateRequestNodeInfoRequestFailed, @@ -55,8 +56,8 @@ export class RequestNodeInfoResponse extends Message return this.wasSent; } - public serialize(ctx: MessageEncodingContext): Buffer { - this.payload = Buffer.from([this.wasSent ? 0x01 : 0]); + public serialize(ctx: MessageEncodingContext): Bytes { + this.payload = Bytes.from([this.wasSent ? 0x01 : 0]); return super.serialize(ctx); } @@ -117,7 +118,7 @@ export class RequestNodeInfoRequest extends Message { return false; } - public serialize(ctx: MessageEncodingContext): Buffer { + public serialize(ctx: MessageEncodingContext): Bytes { this.payload = encodeNodeID(this.nodeId, ctx.nodeIdType); return super.serialize(ctx); } diff --git a/packages/serial/src/serialapi/network-mgmt/RequestNodeNeighborUpdateMessages.ts b/packages/serial/src/serialapi/network-mgmt/RequestNodeNeighborUpdateMessages.ts index cf980afa4cfd..d111425dbec3 100644 --- a/packages/serial/src/serialapi/network-mgmt/RequestNodeNeighborUpdateMessages.ts +++ b/packages/serial/src/serialapi/network-mgmt/RequestNodeNeighborUpdateMessages.ts @@ -17,7 +17,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; -import { getEnumMemberName } from "@zwave-js/shared"; +import { Bytes, getEnumMemberName } from "@zwave-js/shared"; export enum NodeNeighborUpdateStatus { UpdateStarted = 0x21, @@ -61,10 +61,10 @@ export class RequestNodeNeighborUpdateRequest public nodeId: number; public discoveryTimeout: number; - public serialize(ctx: MessageEncodingContext): Buffer { + public serialize(ctx: MessageEncodingContext): Bytes { this.assertCallbackId(); const nodeId = encodeNodeID(this.nodeId, ctx.nodeIdType); - this.payload = Buffer.concat([nodeId, Buffer.from([this.callbackId])]); + this.payload = Bytes.concat([nodeId, Bytes.from([this.callbackId])]); return super.serialize(ctx); } diff --git a/packages/serial/src/serialapi/network-mgmt/SetLearnModeMessages.ts b/packages/serial/src/serialapi/network-mgmt/SetLearnModeMessages.ts index 1a311f601558..0b5d2ba2df2e 100644 --- a/packages/serial/src/serialapi/network-mgmt/SetLearnModeMessages.ts +++ b/packages/serial/src/serialapi/network-mgmt/SetLearnModeMessages.ts @@ -19,7 +19,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; -import { buffer2hex, getEnumMemberName } from "@zwave-js/shared"; +import { Bytes, buffer2hex, getEnumMemberName } from "@zwave-js/shared"; const LEARN_MODE_EMPTY_NODE_ID = 0xef; // who knows why... @@ -87,9 +87,9 @@ export class SetLearnModeRequest extends SetLearnModeRequestBase { public intent: LearnModeIntent; - public serialize(ctx: MessageEncodingContext): Buffer { + public serialize(ctx: MessageEncodingContext): Bytes { this.assertCallbackId(); - this.payload = Buffer.from([ + this.payload = Bytes.from([ this.intent, this.callbackId, ]); @@ -151,7 +151,7 @@ export class SetLearnModeResponse extends Message implements SuccessIndicator { export interface SetLearnModeCallbackOptions { status: LearnModeStatus; assignedNodeId: number; - statusMessage?: Buffer; + statusMessage?: Uint8Array; } export class SetLearnModeCallback extends SetLearnModeRequestBase @@ -176,7 +176,7 @@ export class SetLearnModeCallback extends SetLearnModeRequestBase const callbackId = raw.payload[0]; const status: LearnModeStatus = raw.payload[1]; const assignedNodeId = raw.payload[2]; - let statusMessage: Buffer | undefined; + let statusMessage: Uint8Array | undefined; if (raw.payload.length > 3) { const msgLength = raw.payload[3]; statusMessage = raw.payload.subarray(4, 4 + msgLength); @@ -192,7 +192,7 @@ export class SetLearnModeCallback extends SetLearnModeRequestBase public readonly status: LearnModeStatus; public readonly assignedNodeId: number; - public readonly statusMessage?: Buffer; + public readonly statusMessage?: Uint8Array; isOK(): boolean { return this.status !== LearnModeStatus.Failed; diff --git a/packages/serial/src/serialapi/network-mgmt/SetPriorityRouteMessages.ts b/packages/serial/src/serialapi/network-mgmt/SetPriorityRouteMessages.ts index de19e54e8398..f76bd5865679 100644 --- a/packages/serial/src/serialapi/network-mgmt/SetPriorityRouteMessages.ts +++ b/packages/serial/src/serialapi/network-mgmt/SetPriorityRouteMessages.ts @@ -23,7 +23,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; -import { type AllOrNone, getEnumMemberName } from "@zwave-js/shared"; +import { type AllOrNone, Bytes, getEnumMemberName } from "@zwave-js/shared"; export type SetPriorityRouteRequestOptions = & { @@ -81,7 +81,7 @@ export class SetPriorityRouteRequest extends Message { public repeaters: number[] | undefined; public routeSpeed: ZWaveDataRate | undefined; - public serialize(ctx: MessageEncodingContext): Buffer { + public serialize(ctx: MessageEncodingContext): Bytes { const nodeId = encodeNodeID( this.destinationNodeId, ctx.nodeIdType, @@ -91,9 +91,9 @@ export class SetPriorityRouteRequest extends Message { this.payload = nodeId; } else { // Set the priority route - this.payload = Buffer.concat([ + this.payload = Bytes.concat([ nodeId, - Buffer.from([ + Bytes.from([ this.repeaters[0] ?? 0, this.repeaters[1] ?? 0, this.repeaters[2] ?? 0, diff --git a/packages/serial/src/serialapi/network-mgmt/SetSUCNodeIDMessages.ts b/packages/serial/src/serialapi/network-mgmt/SetSUCNodeIDMessages.ts index b8c92b518054..5d304867fb75 100644 --- a/packages/serial/src/serialapi/network-mgmt/SetSUCNodeIDMessages.ts +++ b/packages/serial/src/serialapi/network-mgmt/SetSUCNodeIDMessages.ts @@ -23,6 +23,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; +import { Bytes } from "@zwave-js/shared"; export enum SetSUCNodeIdStatus { Succeeded = 0x05, @@ -86,12 +87,12 @@ export class SetSUCNodeIdRequest extends SetSUCNodeIdRequestBase { private _ownNodeId: number; - public serialize(ctx: MessageEncodingContext): Buffer { + public serialize(ctx: MessageEncodingContext): Bytes { this.assertCallbackId(); const nodeId = encodeNodeID(this.sucNodeId, ctx.nodeIdType); - this.payload = Buffer.concat([ + this.payload = Bytes.concat([ nodeId, - Buffer.from([ + Bytes.from([ this.enableSUC ? 0x01 : 0x00, this.transmitOptions, this.enableSIS ? 0x01 : 0x00, diff --git a/packages/serial/src/serialapi/nvm/ExtNVMReadLongBufferMessages.ts b/packages/serial/src/serialapi/nvm/ExtNVMReadLongBufferMessages.ts index 6c6a7c382592..375d16b6e330 100644 --- a/packages/serial/src/serialapi/nvm/ExtNVMReadLongBufferMessages.ts +++ b/packages/serial/src/serialapi/nvm/ExtNVMReadLongBufferMessages.ts @@ -16,7 +16,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; -import { num2hex } from "@zwave-js/shared"; +import { Bytes, num2hex } from "@zwave-js/shared"; export interface ExtNVMReadLongBufferRequestOptions { offset: number; @@ -63,8 +63,8 @@ export class ExtNVMReadLongBufferRequest extends Message { public offset: number; public length: number; - public serialize(ctx: MessageEncodingContext): Buffer { - this.payload = Buffer.allocUnsafe(5); + public serialize(ctx: MessageEncodingContext): Bytes { + this.payload = new Bytes(5); this.payload.writeUIntBE(this.offset, 0, 3); this.payload.writeUInt16BE(this.length, 3); return super.serialize(ctx); @@ -82,7 +82,7 @@ export class ExtNVMReadLongBufferRequest extends Message { } export interface ExtNVMReadLongBufferResponseOptions { - buffer: Buffer; + buffer: Uint8Array; } @messageTypes(MessageType.Response, FunctionType.ExtNVMReadLongBuffer) @@ -105,7 +105,7 @@ export class ExtNVMReadLongBufferResponse extends Message { }); } - public readonly buffer: Buffer; + public readonly buffer: Uint8Array; public toLogEntry(): MessageOrCCLogEntry { return { diff --git a/packages/serial/src/serialapi/nvm/ExtNVMReadLongByteMessages.ts b/packages/serial/src/serialapi/nvm/ExtNVMReadLongByteMessages.ts index e4f731952e2b..d8d16d3a3a90 100644 --- a/packages/serial/src/serialapi/nvm/ExtNVMReadLongByteMessages.ts +++ b/packages/serial/src/serialapi/nvm/ExtNVMReadLongByteMessages.ts @@ -16,7 +16,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; -import { num2hex } from "@zwave-js/shared"; +import { Bytes, num2hex } from "@zwave-js/shared"; export interface ExtNVMReadLongByteRequestOptions { offset: number; @@ -53,8 +53,8 @@ export class ExtNVMReadLongByteRequest extends Message { public offset: number; - public serialize(ctx: MessageEncodingContext): Buffer { - this.payload = Buffer.allocUnsafe(3); + public serialize(ctx: MessageEncodingContext): Bytes { + this.payload = new Bytes(3); this.payload.writeUIntBE(this.offset, 0, 3); return super.serialize(ctx); } diff --git a/packages/serial/src/serialapi/nvm/ExtNVMWriteLongBufferMessages.ts b/packages/serial/src/serialapi/nvm/ExtNVMWriteLongBufferMessages.ts index 16ec55c2fc32..167739b0a20c 100644 --- a/packages/serial/src/serialapi/nvm/ExtNVMWriteLongBufferMessages.ts +++ b/packages/serial/src/serialapi/nvm/ExtNVMWriteLongBufferMessages.ts @@ -16,11 +16,11 @@ import { messageTypes, priority, } from "@zwave-js/serial"; -import { num2hex } from "@zwave-js/shared"; +import { Bytes, num2hex } from "@zwave-js/shared"; export interface ExtNVMWriteLongBufferRequestOptions { offset: number; - buffer: Buffer; + buffer: Uint8Array; } @messageTypes(MessageType.Request, FunctionType.ExtNVMWriteLongBuffer) @@ -60,13 +60,13 @@ export class ExtNVMWriteLongBufferRequest extends Message { } public offset: number; - public buffer: Buffer; + public buffer: Uint8Array; - public serialize(ctx: MessageEncodingContext): Buffer { - this.payload = Buffer.allocUnsafe(5 + this.buffer.length); + public serialize(ctx: MessageEncodingContext): Bytes { + this.payload = new Bytes(5 + this.buffer.length); this.payload.writeUIntBE(this.offset, 0, 3); this.payload.writeUInt16BE(this.buffer.length, 3); - this.buffer.copy(this.payload, 5); + this.payload.set(this.buffer, 5); return super.serialize(ctx); } diff --git a/packages/serial/src/serialapi/nvm/ExtNVMWriteLongByteMessages.ts b/packages/serial/src/serialapi/nvm/ExtNVMWriteLongByteMessages.ts index f37424913f10..db28c3bbe6f9 100644 --- a/packages/serial/src/serialapi/nvm/ExtNVMWriteLongByteMessages.ts +++ b/packages/serial/src/serialapi/nvm/ExtNVMWriteLongByteMessages.ts @@ -16,7 +16,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; -import { num2hex } from "@zwave-js/shared"; +import { Bytes, num2hex } from "@zwave-js/shared"; export interface ExtNVMWriteLongByteRequestOptions { offset: number; @@ -62,8 +62,8 @@ export class ExtNVMWriteLongByteRequest extends Message { public offset: number; public byte: number; - public serialize(ctx: MessageEncodingContext): Buffer { - this.payload = Buffer.allocUnsafe(4); + public serialize(ctx: MessageEncodingContext): Bytes { + this.payload = new Bytes(4); this.payload.writeUIntBE(this.offset, 0, 3); this.payload[3] = this.byte; return super.serialize(ctx); diff --git a/packages/serial/src/serialapi/nvm/ExtendedNVMOperationsMessages.ts b/packages/serial/src/serialapi/nvm/ExtendedNVMOperationsMessages.ts index 3b7ef415d22f..61118f750255 100644 --- a/packages/serial/src/serialapi/nvm/ExtendedNVMOperationsMessages.ts +++ b/packages/serial/src/serialapi/nvm/ExtendedNVMOperationsMessages.ts @@ -21,7 +21,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; -import { getEnumMemberName, num2hex } from "@zwave-js/shared"; +import { Bytes, getEnumMemberName, num2hex } from "@zwave-js/shared"; export enum ExtendedNVMOperationsCommand { Open = 0x00, @@ -46,9 +46,9 @@ export class ExtendedNVMOperationsRequest extends Message { // This must be set in subclasses public command!: ExtendedNVMOperationsCommand; - public serialize(ctx: MessageEncodingContext): Buffer { - this.payload = Buffer.concat([ - Buffer.from([this.command]), + public serialize(ctx: MessageEncodingContext): Bytes { + this.payload = Bytes.concat([ + Bytes.from([this.command]), this.payload, ]); @@ -139,8 +139,8 @@ export class ExtendedNVMOperationsReadRequest public length: number; public offset: number; - public serialize(ctx: MessageEncodingContext): Buffer { - this.payload = Buffer.allocUnsafe(5); + public serialize(ctx: MessageEncodingContext): Bytes { + this.payload = new Bytes(5); this.payload[0] = this.length; this.payload.writeUInt32BE(this.offset, 1); @@ -164,7 +164,7 @@ export class ExtendedNVMOperationsReadRequest export interface ExtendedNVMOperationsWriteRequestOptions { offset: number; - buffer: Buffer; + buffer: Uint8Array; } export class ExtendedNVMOperationsWriteRequest @@ -206,13 +206,13 @@ export class ExtendedNVMOperationsWriteRequest } public offset: number; - public buffer: Buffer; + public buffer: Uint8Array; - public serialize(ctx: MessageEncodingContext): Buffer { - this.payload = Buffer.allocUnsafe(1 + 4 + this.buffer.length); + public serialize(ctx: MessageEncodingContext): Bytes { + this.payload = new Bytes(1 + 4 + this.buffer.length); this.payload[0] = this.buffer.length; this.payload.writeUInt32BE(this.offset, 1); - this.buffer.copy(this.payload, 5); + this.payload.set(this.buffer, 5); return super.serialize(ctx); } @@ -235,7 +235,7 @@ export class ExtendedNVMOperationsWriteRequest export interface ExtendedNVMOperationsResponseOptions { status: ExtendedNVMOperationStatus; offsetOrSize: number; - bufferOrBitmask: Buffer; + bufferOrBitmask: Uint8Array; } @messageTypes(MessageType.Response, FunctionType.ExtendedNVMOperations) @@ -271,14 +271,14 @@ export class ExtendedNVMOperationsResponse extends Message // - Read command: the read NVM data // - Write/Close command: nothing // - Open command: bit mask of supported sub-commands - let bufferOrBitmask: Buffer; + let bufferOrBitmask: Uint8Array; if (dataLength > 0 && raw.payload.length >= offset + dataLength) { bufferOrBitmask = raw.payload.subarray( offset, offset + dataLength, ); } else { - bufferOrBitmask = Buffer.from([]); + bufferOrBitmask = new Uint8Array(); } return new this({ @@ -297,7 +297,7 @@ export class ExtendedNVMOperationsResponse extends Message public readonly status: ExtendedNVMOperationStatus; public readonly offsetOrSize: number; - public readonly bufferOrBitmask: Buffer; + public readonly bufferOrBitmask: Uint8Array; public toLogEntry(): MessageOrCCLogEntry { const message: MessageRecord = { diff --git a/packages/serial/src/serialapi/nvm/FirmwareUpdateNVMMessages.ts b/packages/serial/src/serialapi/nvm/FirmwareUpdateNVMMessages.ts index ffc021cd4393..67b22860e0f0 100644 --- a/packages/serial/src/serialapi/nvm/FirmwareUpdateNVMMessages.ts +++ b/packages/serial/src/serialapi/nvm/FirmwareUpdateNVMMessages.ts @@ -20,7 +20,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; -import { getEnumMemberName, num2hex } from "@zwave-js/shared"; +import { Bytes, getEnumMemberName, num2hex } from "@zwave-js/shared"; export enum FirmwareUpdateNVMCommand { Init = 0x00, @@ -105,9 +105,9 @@ export class FirmwareUpdateNVMRequest extends Message { public command: FirmwareUpdateNVMCommand; - public serialize(ctx: MessageEncodingContext): Buffer { - this.payload = Buffer.concat([ - Buffer.from([this.command]), + public serialize(ctx: MessageEncodingContext): Bytes { + this.payload = Bytes.concat([ + Bytes.from([this.command]), this.payload, ]); @@ -255,8 +255,8 @@ export class FirmwareUpdateNVM_SetNewImageRequest public newImage: boolean; - public serialize(ctx: MessageEncodingContext): Buffer { - this.payload = Buffer.from([this.newImage ? 1 : 0]); + public serialize(ctx: MessageEncodingContext): Bytes { + this.payload = Bytes.from([this.newImage ? 1 : 0]); return super.serialize(ctx); } @@ -406,8 +406,8 @@ export class FirmwareUpdateNVM_UpdateCRC16Request return 30000; } - public serialize(ctx: MessageEncodingContext): Buffer { - this.payload = Buffer.allocUnsafe(7); + public serialize(ctx: MessageEncodingContext): Bytes { + this.payload = new Bytes(7); this.payload.writeUIntBE(this.offset, 0, 3); this.payload.writeUInt16BE(this.blockLength, 3); this.payload.writeUInt16BE(this.crcSeed, 5); @@ -524,7 +524,7 @@ export class FirmwareUpdateNVM_IsValidCRC16Response export interface FirmwareUpdateNVM_WriteRequestOptions { offset: number; - buffer: Buffer; + buffer: Uint8Array; } @subCommandRequest(FirmwareUpdateNVMCommand.Write) @@ -553,12 +553,13 @@ export class FirmwareUpdateNVM_WriteRequest extends FirmwareUpdateNVMRequest { } public offset: number; - public buffer: Buffer; + public buffer: Uint8Array; - public serialize(ctx: MessageEncodingContext): Buffer { - this.payload = Buffer.concat([Buffer.allocUnsafe(5), this.buffer]); + public serialize(ctx: MessageEncodingContext): Bytes { + this.payload = new Bytes(5 + this.buffer.length); this.payload.writeUIntBE(this.offset, 0, 3); this.payload.writeUInt16BE(this.buffer.length, 3); + this.payload.set(this.buffer, 5); return super.serialize(ctx); } diff --git a/packages/serial/src/serialapi/nvm/NVMOperationsMessages.ts b/packages/serial/src/serialapi/nvm/NVMOperationsMessages.ts index 0f9908669b09..463214537306 100644 --- a/packages/serial/src/serialapi/nvm/NVMOperationsMessages.ts +++ b/packages/serial/src/serialapi/nvm/NVMOperationsMessages.ts @@ -19,7 +19,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; -import { getEnumMemberName, num2hex } from "@zwave-js/shared"; +import { Bytes, getEnumMemberName, num2hex } from "@zwave-js/shared"; export enum NVMOperationsCommand { Open = 0x00, @@ -43,9 +43,9 @@ export class NVMOperationsRequest extends Message { // This must be set in subclasses public command!: NVMOperationsCommand; - public serialize(ctx: MessageEncodingContext): Buffer { - this.payload = Buffer.concat([ - Buffer.from([this.command]), + public serialize(ctx: MessageEncodingContext): Bytes { + this.payload = Bytes.concat([ + Bytes.from([this.command]), this.payload, ]); @@ -127,8 +127,8 @@ export class NVMOperationsReadRequest extends NVMOperationsRequest { public length: number; public offset: number; - public serialize(ctx: MessageEncodingContext): Buffer { - this.payload = Buffer.allocUnsafe(3); + public serialize(ctx: MessageEncodingContext): Bytes { + this.payload = new Bytes(3); this.payload[0] = this.length; this.payload.writeUInt16BE(this.offset, 1); @@ -152,7 +152,7 @@ export class NVMOperationsReadRequest extends NVMOperationsRequest { export interface NVMOperationsWriteRequestOptions { offset: number; - buffer: Buffer; + buffer: Uint8Array; } export class NVMOperationsWriteRequest extends NVMOperationsRequest { @@ -192,13 +192,13 @@ export class NVMOperationsWriteRequest extends NVMOperationsRequest { } public offset: number; - public buffer: Buffer; + public buffer: Uint8Array; - public serialize(ctx: MessageEncodingContext): Buffer { - this.payload = Buffer.allocUnsafe(3 + this.buffer.length); + public serialize(ctx: MessageEncodingContext): Bytes { + this.payload = new Bytes(3 + this.buffer.length); this.payload[0] = this.buffer.length; this.payload.writeUInt16BE(this.offset, 1); - this.buffer.copy(this.payload, 3); + this.payload.set(this.buffer, 3); return super.serialize(ctx); } @@ -221,7 +221,7 @@ export class NVMOperationsWriteRequest extends NVMOperationsRequest { export interface NVMOperationsResponseOptions { status: NVMOperationStatus; offsetOrSize: number; - buffer: Buffer; + buffer: Uint8Array; } @messageTypes(MessageType.Response, FunctionType.NVMOperations) @@ -252,11 +252,11 @@ export class NVMOperationsResponse extends Message implements SuccessIndicator { const dataLength = raw.payload[1]; // The response to the write command contains the offset and written data length, but no data - let buffer: Buffer; + let buffer: Uint8Array; if (dataLength > 0 && raw.payload.length >= 4 + dataLength) { buffer = raw.payload.subarray(4, 4 + dataLength); } else { - buffer = Buffer.from([]); + buffer = new Uint8Array(); } return new this({ @@ -275,7 +275,7 @@ export class NVMOperationsResponse extends Message implements SuccessIndicator { public readonly status: NVMOperationStatus; public readonly offsetOrSize: number; - public readonly buffer: Buffer; + public readonly buffer: Uint8Array; public toLogEntry(): MessageOrCCLogEntry { const message: MessageRecord = { diff --git a/packages/serial/src/serialapi/transport/SendDataBridgeMessages.ts b/packages/serial/src/serialapi/transport/SendDataBridgeMessages.ts index 7d3c78a68a60..71bf919bffb8 100644 --- a/packages/serial/src/serialapi/transport/SendDataBridgeMessages.ts +++ b/packages/serial/src/serialapi/transport/SendDataBridgeMessages.ts @@ -31,7 +31,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; -import { getEnumMemberName, num2hex } from "@zwave-js/shared"; +import { Bytes, getEnumMemberName, num2hex } from "@zwave-js/shared"; import { clamp } from "alcalzone-shared/math"; import { ApplicationCommandRequest } from "../application/ApplicationCommandRequest"; import { BridgeApplicationCommandRequest } from "../application/BridgeApplicationCommandRequest"; @@ -61,7 +61,7 @@ export type SendDataBridgeRequestOptions< | { command: CCType } | { nodeId: number; - serializedCC: Buffer; + serializedCC: Uint8Array; } ) & { @@ -129,8 +129,8 @@ export class SendDataBridgeRequest return this.command?.nodeId ?? this._nodeId; } - public serializedCC: Buffer | undefined; - public serializeCC(ctx: CCEncodingContext): Buffer { + public serializedCC: Uint8Array | undefined; + public serializeCC(ctx: CCEncodingContext): Uint8Array { if (!this.serializedCC) { if (!this.command) { throw new ZWaveError( @@ -149,7 +149,7 @@ export class SendDataBridgeRequest this.callbackId = undefined; } - public serialize(ctx: MessageEncodingContext): Buffer { + public serialize(ctx: MessageEncodingContext): Bytes { this.assertCallbackId(); const sourceNodeId = encodeNodeID( this.sourceNodeId, @@ -161,12 +161,12 @@ export class SendDataBridgeRequest ); const serializedCC = this.serializeCC(ctx); - this.payload = Buffer.concat([ + this.payload = Bytes.concat([ sourceNodeId, destinationNodeId, - Buffer.from([serializedCC.length]), + Bytes.from([serializedCC.length]), serializedCC, - Buffer.from([this.transmitOptions, 0, 0, 0, 0, this.callbackId]), + Bytes.from([this.transmitOptions, 0, 0, 0, 0, this.callbackId]), ]); return super.serialize(ctx); @@ -336,7 +336,7 @@ export type SendDataMulticastBridgeRequestOptions< | { command: CCType } | { nodeIds: MulticastDestination; - serializedCC: Buffer; + serializedCC: Uint8Array; } ) & { @@ -415,8 +415,8 @@ export class SendDataMulticastBridgeRequest< return undefined; } - public serializedCC: Buffer | undefined; - public serializeCC(ctx: CCEncodingContext): Buffer { + public serializedCC: Uint8Array | undefined; + public serializeCC(ctx: CCEncodingContext): Uint8Array { if (!this.serializedCC) { if (!this.command) { throw new ZWaveError( @@ -435,7 +435,7 @@ export class SendDataMulticastBridgeRequest< this.callbackId = undefined; } - public serialize(ctx: MessageEncodingContext): Buffer { + public serialize(ctx: MessageEncodingContext): Bytes { this.assertCallbackId(); const serializedCC = this.serializeCC(ctx); const sourceNodeId = encodeNodeID( @@ -445,15 +445,15 @@ export class SendDataMulticastBridgeRequest< const destinationNodeIDs = (this.command?.nodeId ?? this.nodeIds) .map((id) => encodeNodeID(id, ctx.nodeIdType)); - this.payload = Buffer.concat([ + this.payload = Bytes.concat([ sourceNodeId, // # of target nodes, not # of bytes - Buffer.from([destinationNodeIDs.length]), + Bytes.from([destinationNodeIDs.length]), ...destinationNodeIDs, - Buffer.from([serializedCC.length]), + Bytes.from([serializedCC.length]), // payload serializedCC, - Buffer.from([this.transmitOptions, this.callbackId]), + Bytes.from([this.transmitOptions, this.callbackId]), ]); return super.serialize(ctx); diff --git a/packages/serial/src/serialapi/transport/SendDataMessages.ts b/packages/serial/src/serialapi/transport/SendDataMessages.ts index 2c5c34439d20..ec90059df1f1 100644 --- a/packages/serial/src/serialapi/transport/SendDataMessages.ts +++ b/packages/serial/src/serialapi/transport/SendDataMessages.ts @@ -31,7 +31,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; -import { getEnumMemberName, num2hex } from "@zwave-js/shared"; +import { Bytes, getEnumMemberName, num2hex } from "@zwave-js/shared"; import { clamp } from "alcalzone-shared/math"; import { ApplicationCommandRequest } from "../application/ApplicationCommandRequest"; import { BridgeApplicationCommandRequest } from "../application/BridgeApplicationCommandRequest"; @@ -67,7 +67,7 @@ export type SendDataRequestOptions< | { command: CCType } | { nodeId: number; - serializedCC: Buffer; + serializedCC: Uint8Array; } ) & { @@ -159,8 +159,8 @@ export class SendDataRequest return this.command?.nodeId ?? this._nodeId; } - public serializedCC: Buffer | undefined; - public serializeCC(ctx: CCEncodingContext): Buffer { + public serializedCC: Uint8Array | undefined; + public serializeCC(ctx: CCEncodingContext): Uint8Array { if (!this.serializedCC) { if (!this.command) { throw new ZWaveError( @@ -179,18 +179,18 @@ export class SendDataRequest this.callbackId = undefined; } - public serialize(ctx: MessageEncodingContext): Buffer { + public serialize(ctx: MessageEncodingContext): Bytes { this.assertCallbackId(); const nodeId = encodeNodeID( this.command?.nodeId ?? this._nodeId, ctx.nodeIdType, ); const serializedCC = this.serializeCC(ctx); - this.payload = Buffer.concat([ + this.payload = Bytes.concat([ nodeId, - Buffer.from([serializedCC.length]), + Bytes.from([serializedCC.length]), serializedCC, - Buffer.from([this.transmitOptions, this.callbackId]), + Bytes.from([this.transmitOptions, this.callbackId]), ]); return super.serialize(ctx); @@ -271,14 +271,14 @@ export class SendDataRequestTransmitReport extends SendDataRequestBase public transmitStatus: TransmitStatus; public txReport: TXReport | undefined; - public serialize(ctx: MessageEncodingContext): Buffer { + public serialize(ctx: MessageEncodingContext): Bytes { this.assertCallbackId(); - this.payload = Buffer.from([ + this.payload = Bytes.from([ this.callbackId, this.transmitStatus, ]); if (this.txReport) { - this.payload = Buffer.concat([ + this.payload = Bytes.concat([ this.payload, encodeTXReport(this.txReport), ]); @@ -335,8 +335,8 @@ export class SendDataResponse extends Message implements SuccessIndicator { public wasSent: boolean; - public serialize(ctx: MessageEncodingContext): Buffer { - this.payload = Buffer.from([this.wasSent ? 1 : 0]); + public serialize(ctx: MessageEncodingContext): Bytes { + this.payload = Bytes.from([this.wasSent ? 1 : 0]); return super.serialize(ctx); } @@ -372,7 +372,7 @@ export type SendDataMulticastRequestOptions = | { command: CCType } | { nodeIds: MulticastDestination; - serializedCC: Buffer; + serializedCC: Uint8Array; } ) & { @@ -479,8 +479,8 @@ export class SendDataMulticastRequest< return undefined; } - public serializedCC: Buffer | undefined; - public serializeCC(ctx: CCEncodingContext): Buffer { + public serializedCC: Uint8Array | undefined; + public serializeCC(ctx: CCEncodingContext): Uint8Array { if (!this.serializedCC) { if (!this.command) { throw new ZWaveError( @@ -499,19 +499,19 @@ export class SendDataMulticastRequest< this.callbackId = undefined; } - public serialize(ctx: MessageEncodingContext): Buffer { + public serialize(ctx: MessageEncodingContext): Bytes { this.assertCallbackId(); const serializedCC = this.serializeCC(ctx); const destinationNodeIDs = (this.command?.nodeId ?? this.nodeIds) .map((id) => encodeNodeID(id, ctx.nodeIdType)); - this.payload = Buffer.concat([ + this.payload = Bytes.concat([ // # of target nodes, not # of bytes - Buffer.from([destinationNodeIDs.length]), + Bytes.from([destinationNodeIDs.length]), ...destinationNodeIDs, - Buffer.from([serializedCC.length]), + Bytes.from([serializedCC.length]), // payload serializedCC, - Buffer.from([this.transmitOptions, this.callbackId]), + Bytes.from([this.transmitOptions, this.callbackId]), ]); return super.serialize(ctx); @@ -565,9 +565,9 @@ export class SendDataMulticastRequestTransmitReport public transmitStatus: TransmitStatus; - public serialize(ctx: MessageEncodingContext): Buffer { + public serialize(ctx: MessageEncodingContext): Bytes { this.assertCallbackId(); - this.payload = Buffer.from([this.callbackId, this.transmitStatus]); + this.payload = Bytes.from([this.callbackId, this.transmitStatus]); return super.serialize(ctx); } @@ -617,8 +617,8 @@ export class SendDataMulticastResponse extends Message public wasSent: boolean; - public serialize(ctx: MessageEncodingContext): Buffer { - this.payload = Buffer.from([this.wasSent ? 1 : 0]); + public serialize(ctx: MessageEncodingContext): Bytes { + this.payload = Bytes.from([this.wasSent ? 1 : 0]); return super.serialize(ctx); } diff --git a/packages/serial/src/serialapi/transport/SendDataShared.ts b/packages/serial/src/serialapi/transport/SendDataShared.ts index d227e42cf985..eb63dba41ab6 100644 --- a/packages/serial/src/serialapi/transport/SendDataShared.ts +++ b/packages/serial/src/serialapi/transport/SendDataShared.ts @@ -11,6 +11,7 @@ import { rssiToString, stripUndefined, } from "@zwave-js/core/safe"; +import { Bytes } from "@zwave-js/shared/safe"; import { AssignPriorityReturnRouteRequestTransmitReport } from "../network-mgmt/AssignPriorityReturnRouteMessages"; import { AssignPrioritySUCReturnRouteRequestTransmitReport } from "../network-mgmt/AssignPrioritySUCReturnRouteMessages"; import { AssignReturnRouteRequestTransmitReport } from "../network-mgmt/AssignReturnRouteMessages"; @@ -54,8 +55,8 @@ export type TransmitReport = // const RSSI_RESERVED_START = 11; -export function parseRSSI(payload: Buffer, offset: number = 0): RSSI { - const ret = payload.readInt8(offset); +export function parseRSSI(payload: Uint8Array, offset: number = 0): RSSI { + const ret = Bytes.view(payload).readInt8(offset); // Filter out reserved values // TODO: Figure out for which controllers this is relevant // if ( @@ -68,16 +69,19 @@ export function parseRSSI(payload: Buffer, offset: number = 0): RSSI { } export function tryParseRSSI( - payload: Buffer, + payload: Uint8Array, offset: number = 0, ): RSSI | undefined { if (payload.length <= offset) return; return parseRSSI(payload, offset); } -function parseTXPower(payload: Buffer, offset: number = 0): number | undefined { +function parseTXPower( + payload: Uint8Array, + offset: number = 0, +): number | undefined { if (payload.length <= offset) return; - const ret = payload.readInt8(offset); + const ret = Bytes.view(payload).readInt8(offset); if (ret >= -127 && ret <= 126) return ret; } @@ -87,42 +91,43 @@ function parseTXPower(payload: Buffer, offset: number = 0): number | undefined { */ export function parseTXReport( includeACK: boolean, - payload: Buffer, + payload: Uint8Array, ): TXReport | undefined { - if (payload.length < 17) return; - const numRepeaters = payload[2]; + const buffer = Bytes.view(payload); + if (buffer.length < 17) return; + const numRepeaters = buffer[2]; const ret: TXReport = { - txTicks: payload.readUInt16BE(0), - ackRSSI: includeACK ? parseRSSI(payload, 3) : undefined, + txTicks: buffer.readUInt16BE(0), + ackRSSI: includeACK ? parseRSSI(buffer, 3) : undefined, ackRepeaterRSSI: includeACK ? [ - parseRSSI(payload, 4), - parseRSSI(payload, 5), - parseRSSI(payload, 6), - parseRSSI(payload, 7), + parseRSSI(buffer, 4), + parseRSSI(buffer, 5), + parseRSSI(buffer, 6), + parseRSSI(buffer, 7), ] : undefined, - ackChannelNo: includeACK ? payload[8] : undefined, - txChannelNo: payload[9], - routeSchemeState: payload[10], - repeaterNodeIds: [payload[11], payload[12], payload[13], payload[14]], - beam1000ms: !!(payload[15] & 0b0100_0000), - beam250ms: !!(payload[15] & 0b0010_0000), - routeSpeed: payload[15] & 0b0000_0111, - routingAttempts: payload[16], + ackChannelNo: includeACK ? buffer[8] : undefined, + txChannelNo: buffer[9], + routeSchemeState: buffer[10], + repeaterNodeIds: [buffer[11], buffer[12], buffer[13], buffer[14]], + beam1000ms: !!(buffer[15] & 0b0100_0000), + beam250ms: !!(buffer[15] & 0b0010_0000), + routeSpeed: buffer[15] & 0b0000_0111, + routingAttempts: buffer[16], // These might be missing: - failedRouteLastFunctionalNodeId: payload[17], - failedRouteFirstNonFunctionalNodeId: payload[18], - txPower: parseTXPower(payload, 19), - measuredNoiseFloor: tryParseRSSI(payload, 20), + failedRouteLastFunctionalNodeId: buffer[17], + failedRouteFirstNonFunctionalNodeId: buffer[18], + txPower: parseTXPower(buffer, 19), + measuredNoiseFloor: tryParseRSSI(buffer, 20), destinationAckTxPower: includeACK - ? parseTXPower(payload, 21) + ? parseTXPower(buffer, 21) : undefined, destinationAckMeasuredRSSI: includeACK - ? tryParseRSSI(payload, 22) + ? tryParseRSSI(buffer, 22) : undefined, destinationAckMeasuredNoiseFloor: includeACK - ? tryParseRSSI(payload, 23) + ? tryParseRSSI(buffer, 23) : undefined, }; // Remove unused repeaters from arrays @@ -167,8 +172,8 @@ export function serializableTXReportToTXReport( }; } -export function encodeTXReport(report: SerializableTXReport): Buffer { - const ret = Buffer.alloc(24, 0); +export function encodeTXReport(report: SerializableTXReport): Uint8Array { + const ret = new Bytes(24).fill(0); ret.writeUInt16BE(report.txTicks, 0); ret[2] = report.repeaterNodeIds?.length ?? 0; ret.writeInt8(report.ackRSSI ?? RssiError.NotAvailable, 3); diff --git a/packages/serial/src/serialapi/transport/SendTestFrameMessages.ts b/packages/serial/src/serialapi/transport/SendTestFrameMessages.ts index 9a64f56ca3e4..fc46449cfc9e 100644 --- a/packages/serial/src/serialapi/transport/SendTestFrameMessages.ts +++ b/packages/serial/src/serialapi/transport/SendTestFrameMessages.ts @@ -21,7 +21,7 @@ import { messageTypes, priority, } from "@zwave-js/serial"; -import { getEnumMemberName } from "@zwave-js/shared"; +import { Bytes, getEnumMemberName } from "@zwave-js/shared"; @messageTypes(MessageType.Request, FunctionType.SendTestFrame) @priority(MessagePriority.Normal) @@ -78,12 +78,12 @@ export class SendTestFrameRequest extends SendTestFrameRequestBase { public testNodeId: number; public powerlevel: Powerlevel; - public serialize(ctx: MessageEncodingContext): Buffer { + public serialize(ctx: MessageEncodingContext): Bytes { this.assertCallbackId(); const nodeId = encodeNodeID(this.testNodeId, ctx.nodeIdType); - this.payload = Buffer.concat([ + this.payload = Bytes.concat([ nodeId, - Buffer.from([ + Bytes.from([ this.powerlevel, this.callbackId, ]), diff --git a/packages/serial/src/serialapi/utils.ts b/packages/serial/src/serialapi/utils.ts index 42e157f97377..61f7be63ba43 100644 --- a/packages/serial/src/serialapi/utils.ts +++ b/packages/serial/src/serialapi/utils.ts @@ -1,5 +1,6 @@ import { CommandClass } from "@zwave-js/cc"; import { type Message } from "@zwave-js/serial"; +import { isUint8Array } from "@zwave-js/shared"; import { ApplicationCommandRequest } from "./application/ApplicationCommandRequest"; import { BridgeApplicationCommandRequest } from "./application/BridgeApplicationCommandRequest"; import { type SendDataMessage, isSendData } from "./transport/SendDataShared"; @@ -16,7 +17,7 @@ export function isCommandRequest( } export interface MessageWithCC { - serializedCC: Buffer | undefined; + serializedCC: Uint8Array | undefined; command: CommandClass | undefined; } @@ -30,7 +31,7 @@ export function isMessageWithCC( } export interface ContainsSerializedCC { - serializedCC: Buffer; + serializedCC: Uint8Array; } export function containsSerializedCC( @@ -38,7 +39,7 @@ export function containsSerializedCC( ): container is T & ContainsSerializedCC { return !!container && "serializedCC" in container - && Buffer.isBuffer(container.serializedCC); + && isUint8Array(container.serializedCC); } export interface ContainsCC { diff --git a/packages/serial/src/serialport/ZWaveSerialPort.test.ts b/packages/serial/src/serialport/ZWaveSerialPort.test.ts index f79f82fe968a..1956062822e0 100644 --- a/packages/serial/src/serialport/ZWaveSerialPort.test.ts +++ b/packages/serial/src/serialport/ZWaveSerialPort.test.ts @@ -1,3 +1,4 @@ +import { Bytes } from "@zwave-js/shared/safe"; import ava, { type TestFn } from "ava"; import { PassThrough } from "node:stream"; import { setTimeout as wait } from "node:timers/promises"; @@ -17,7 +18,7 @@ const test = ava as TestFn; async function waitForData(port: { once: (event: "data", callback: (data: any) => void) => any; }): Promise< - MessageHeaders.ACK | MessageHeaders.NAK | MessageHeaders.CAN | Buffer + MessageHeaders.ACK | MessageHeaders.NAK | MessageHeaders.CAN | Uint8Array > { return new Promise((resolve) => { port.once("data", resolve); @@ -48,8 +49,8 @@ test("isOpen returns false after closing", async (t) => { test("passes written data through unchanged", async (t) => { const { port, binding } = t.context; const buffers = [ - Buffer.from([1, 2, 3]), - Buffer.from("abcdef1234567890", "hex"), + Bytes.from([1, 2, 3]), + Bytes.from("abcdef1234567890", "hex"), ]; for (const buffer of buffers) { await port.writeAsync(buffer); @@ -61,21 +62,21 @@ test("write rejects if the port is not open", async (t) => { const { port } = t.context; await port.close(); await t.throwsAsync(() => - port.writeAsync(Buffer.from([MessageHeaders.ACK])) + port.writeAsync(Bytes.from([MessageHeaders.ACK])) ); }); test("emit an event for each single-byte message that was read", async (t) => { const { port, binding } = t.context; - binding.emitData(Buffer.from([MessageHeaders.ACK])); + binding.emitData(Bytes.from([MessageHeaders.ACK])); let data = await waitForData(port); t.deepEqual(data, MessageHeaders.ACK); - binding.emitData(Buffer.from([MessageHeaders.CAN])); + binding.emitData(Bytes.from([MessageHeaders.CAN])); data = await waitForData(port); t.deepEqual(data, MessageHeaders.CAN); - binding.emitData(Buffer.from([MessageHeaders.NAK])); + binding.emitData(Bytes.from([MessageHeaders.NAK])); data = await waitForData(port); t.deepEqual(data, MessageHeaders.NAK); }); @@ -83,7 +84,7 @@ test("emit an event for each single-byte message that was read", async (t) => { test("emits a series of events when multiple single-byte messages are received", async (t) => { const { port, binding } = t.context; binding.emitData( - Buffer.from([ + Bytes.from([ MessageHeaders.ACK, MessageHeaders.CAN, MessageHeaders.NAK, @@ -106,7 +107,7 @@ test("emits a series of events when multiple single-byte messages are received", test("skips all invalid/unexpected data", async (t) => { const { port, binding } = t.context; binding.emitData( - Buffer.from([ + Bytes.from([ MessageHeaders.ACK, MessageHeaders.CAN, 0xff, @@ -133,7 +134,7 @@ test("skips all invalid/unexpected data", async (t) => { test("skips all invalid/unexpected data (test 2)", async (t) => { const { port, binding } = t.context; binding.emitData( - Buffer.from([ + Bytes.from([ MessageHeaders.ACK, MessageHeaders.CAN, 0xff, @@ -143,7 +144,7 @@ test("skips all invalid/unexpected data (test 2)", async (t) => { ]), ); setTimeout(() => { - binding.emitData(Buffer.from([MessageHeaders.NAK])); + binding.emitData(Bytes.from([MessageHeaders.NAK])); }, 10); let count = 0; @@ -160,7 +161,7 @@ test("skips all invalid/unexpected data (test 2)", async (t) => { }); test("emits a buffer when a message is received", async (t) => { const { port, binding } = t.context; - const data = Buffer.from([ + const data = Bytes.from([ MessageHeaders.SOF, 0x05, // remaining length 0xff, @@ -177,7 +178,7 @@ test("emits a buffer when a message is received", async (t) => { test("may be consumed with an async iterator", async (t) => { const { port, binding } = t.context; - const data = Buffer.from([ + const data = Bytes.from([ MessageHeaders.ACK, MessageHeaders.CAN, 0xff, @@ -204,7 +205,7 @@ test("can be piped into", async (t) => { passThrough.pipe(port); return new Promise((resolve) => { - const data = Buffer.from([1, 2, 3, 4, 5]); + const data = Bytes.from([1, 2, 3, 4, 5]); passThrough.write(data, (err) => { t.falsy(err); // I see no better way of forcing the write to bubble through the streams @@ -221,6 +222,7 @@ test("can be piped to a reader", async (t) => { const stream = new PassThrough(); port.pipe(stream); + // eslint-disable-next-line no-restricted-globals -- Serialport uses Node.js Buffers const expected = Buffer.from([0x01, 0x03, 0xff, 0xff, 0xff]); binding.emitData(expected); @@ -237,7 +239,7 @@ test("can be unpiped again", async (t) => { port.pipe(stream); port.unpipe(); - const expected = Buffer.from([0x01, 0x03, 0xff, 0xff, 0xff]); + const expected = Bytes.from([0x01, 0x03, 0xff, 0xff, 0xff]); binding.emitData(expected); await wait(1); diff --git a/packages/serial/src/serialport/ZWaveSerialPortBase.ts b/packages/serial/src/serialport/ZWaveSerialPortBase.ts index 2c880173e0f3..a3884abd8c79 100644 --- a/packages/serial/src/serialport/ZWaveSerialPortBase.ts +++ b/packages/serial/src/serialport/ZWaveSerialPortBase.ts @@ -1,5 +1,5 @@ import type { ZWaveLogContainer } from "@zwave-js/core"; -import { Mixin } from "@zwave-js/shared"; +import { Bytes, Mixin } from "@zwave-js/shared"; import { EventEmitter } from "node:events"; import { PassThrough, type Readable, type Writable } from "node:stream"; import { SerialLogger } from "../log/Logger"; @@ -17,7 +17,7 @@ export type ZWaveSerialChunk = | MessageHeaders.ACK | MessageHeaders.NAK | MessageHeaders.CAN - | Buffer; + | Uint8Array; export enum ZWaveSerialMode { SerialAPI, @@ -27,7 +27,7 @@ export enum ZWaveSerialMode { export interface ZWaveSerialPortEventCallbacks { error: (e: Error) => void; data: (data: ZWaveSerialChunk) => void; - discardedData: (data: Buffer) => void; + discardedData: (data: Uint8Array) => void; bootloaderData: (data: BootloaderChunk) => void; } @@ -156,9 +156,10 @@ export class ZWaveSerialPortBase extends PassThrough { // Check the incoming messages and route them to the correct parser this.serial.on("data", (data) => { if (this.mode == undefined) { + const buffer = Bytes.view(data as Uint8Array); // If we haven't figured out the startup mode yet, // inspect the chunk to see if it contains the bootloader preamble - const str = (data as Buffer).toString("ascii") + const str = buffer.toString("ascii") // like .trim(), but including null bytes .replaceAll(/^[\s\0]+|[\s\0]+$/g, ""); @@ -166,12 +167,12 @@ export class ZWaveSerialPortBase extends PassThrough { // We're sure we're in bootloader mode this.mode = ZWaveSerialMode.Bootloader; } else if ( - (data as Buffer).every((b) => + buffer.every((b) => b === 0x00 || b === 0x0a || b === 0x0d || (b >= 0x20 && b <= 0x7e) - ) && (data as Buffer).some((b) => b >= 0x20 && b <= 0x7e) + ) && buffer.some((b) => b >= 0x20 && b <= 0x7e) ) { // Only printable line breaks, null bytes and at least one printable ASCII character // --> We're pretty sure we're in bootloader mode @@ -234,7 +235,7 @@ export class ZWaveSerialPortBase extends PassThrough { return this._isOpen; } - public async writeAsync(data: Buffer): Promise { + public async writeAsync(data: Uint8Array): Promise { if (!this.isOpen) { throw new Error("The serial port is not open!"); } diff --git a/packages/serial/src/zniffer/ZnifferSerialPortBase.ts b/packages/serial/src/zniffer/ZnifferSerialPortBase.ts index c389ccb97da7..946135ec8a68 100644 --- a/packages/serial/src/zniffer/ZnifferSerialPortBase.ts +++ b/packages/serial/src/zniffer/ZnifferSerialPortBase.ts @@ -8,8 +8,8 @@ import { type ZWaveSerialPortImplementation } from "../serialport/ZWaveSerialPor export interface ZnifferSerialPortEventCallbacks { error: (e: Error) => void; - data: (data: Buffer) => void; - discardedData: (data: Buffer) => void; + data: (data: Uint8Array) => void; + discardedData: (data: Uint8Array) => void; } export type ZnifferSerialPortEvents = Extract< @@ -66,7 +66,7 @@ export class ZnifferSerialPortBase extends PassThrough { // Allow strongly-typed async iteration declare public [Symbol.asyncIterator]: () => AsyncIterableIterator< - Buffer + Uint8Array >; constructor( @@ -161,7 +161,7 @@ export class ZnifferSerialPortBase extends PassThrough { return this._isOpen; } - public async writeAsync(data: Buffer): Promise { + public async writeAsync(data: Uint8Array): Promise { if (!this.isOpen) { throw new Error("The serial port is not open!"); } diff --git a/packages/shared/src/Bytes.test.ts b/packages/shared/src/Bytes.test.ts new file mode 100644 index 000000000000..f1af7f9e3ba2 --- /dev/null +++ b/packages/shared/src/Bytes.test.ts @@ -0,0 +1,114 @@ +import test from "ava"; +import { Bytes } from "./Bytes"; +import { uint8ArrayToHex } from "./uint8array-extras"; + +test("writeUIntLE works as expected", (t) => { + const b = new Bytes(10); + b.writeUIntLE(0xde, 0, 1); + b.writeUIntLE(0xdead, 1, 2); + b.writeUIntLE(0xdeadbe, 3, 3); + b.writeUIntLE(0xdeadbeef, 6, 4); + t.is(uint8ArrayToHex(b), "deaddebeaddeefbeadde"); +}); + +test("writeUIntBE works as expected", (t) => { + const b = new Bytes(10); + b.writeUIntBE(0xde, 0, 1); + b.writeUIntBE(0xdead, 1, 2); + b.writeUIntBE(0xdeadbe, 3, 3); + b.writeUIntBE(0xdeadbeef, 6, 4); + t.is(uint8ArrayToHex(b), "dedeaddeadbedeadbeef"); +}); + +test("readUIntLE works as expected", (t) => { + const b = Bytes.from("deaddebeaddeefbeadde", "hex"); + const v1 = b.readUIntLE(0, 1); + const v2 = b.readUIntLE(1, 2); + const v3 = b.readUIntLE(3, 3); + const v4 = b.readUIntLE(6, 4); + t.is(v1, 0xde); + t.is(v2, 0xdead); + t.is(v3, 0xdeadbe); + t.is(v4, 0xdeadbeef); +}); + +test("readUIntBE works as expected", (t) => { + const b = Bytes.from("dedeaddeadbedeadbeef", "hex"); + const v1 = b.readUIntBE(0, 1); + const v2 = b.readUIntBE(1, 2); + const v3 = b.readUIntBE(3, 3); + const v4 = b.readUIntBE(6, 4); + t.is(v1, 0xde); + t.is(v2, 0xdead); + t.is(v3, 0xdeadbe); + t.is(v4, 0xdeadbeef); +}); + +test("writeIntLE works as expected", (t) => { + const b = new Bytes(10); + b.writeIntLE(-127, 0, 1); + b.writeIntLE(-31870, 1, 2); + b.writeIntLE(-7961212, 3, 3); + b.writeIntLE(-1870034809, 6, 4); + t.is(uint8ArrayToHex(b), "81828384858687888990"); +}); + +test("writeIntBE works as expected", (t) => { + const b = new Bytes(10); + b.writeIntBE(-127, 0, 1); + b.writeIntBE(-32125, 1, 2); + b.writeIntBE(-8092282, 3, 3); + b.writeIntBE(-2021095024, 6, 4); + t.is(uint8ArrayToHex(b), "81828384858687888990"); +}); + +test("readIntLE works as expected", (t) => { + const b = Bytes.from("81828384858687888990", "hex"); + const v1 = b.readIntLE(0, 1); + const v2 = b.readIntLE(1, 2); + const v3 = b.readIntLE(3, 3); + const v4 = b.readIntLE(6, 4); + t.is(v1, -127); + t.is(v2, -31870); + t.is(v3, -7961212); + t.is(v4, -1870034809); +}); + +test("readIntBE works as expected", (t) => { + const b = Bytes.from("81828384858687888990", "hex"); + const v1 = b.readIntBE(0, 1); + const v2 = b.readIntBE(1, 2); + const v3 = b.readIntBE(3, 3); + const v4 = b.readIntBE(6, 4); + t.is(v1, -127); + t.is(v2, -32125); + t.is(v3, -8092282); + t.is(v4, -2021095024); +}); + +test("Buffer.concat works as expected", (t) => { + // No total length + const b1 = Bytes.from([1, 2, 3]); + const b2 = Bytes.from([4, 5, 6]); + const b3 = Bytes.from([7, 8, 9]); + const b4 = Bytes.concat([b1, b2, b3]); + t.is(uint8ArrayToHex(b4), "010203040506070809"); + + // Higher total length + const b5 = Bytes.concat([b1, b2, b3], 12); + t.is(uint8ArrayToHex(b5), "010203040506070809000000"); + + // Shorter total length + const b6 = Bytes.concat([b1, b2, b3], 8); + t.is(uint8ArrayToHex(b6), "0102030405060708"); +}); + +test("subarray works multiple times in a row", (t) => { + const b = Bytes.from([1, 2, 3, 4, 5, 6, 7, 8, 9]); + const b1 = b.subarray(1, 4); // 2, 3, 4 + const b2 = b1.subarray(1, 2); // 3 + t.is(uint8ArrayToHex(b1), "020304"); + t.is(uint8ArrayToHex(b2), "03"); + + t.is(b2.readInt8(0), 3); +}); diff --git a/packages/shared/src/Bytes.ts b/packages/shared/src/Bytes.ts new file mode 100644 index 000000000000..805ca6abd11a --- /dev/null +++ b/packages/shared/src/Bytes.ts @@ -0,0 +1,1253 @@ +/** A portable version of Node.js's Buffer that is more compatible with the native Uint8Array */ + +import { + type TypedArray, + areUint8ArraysEqual, + concatUint8Arrays, + hexToUint8Array, + includes, + isUint8ArrayOrArrayBuffer, + stringToUint8Array, + uint8ArrayToBase64, + uint8ArrayToHex, + uint8ArrayToString, +} from "./uint8array-extras"; + +/** An almost drop-in replacement for the Node.js Buffer class that's compatible with the native Uint8Array */ +// See https://sindresorhus.com/blog/goodbye-nodejs-buffer for background +export class Bytes extends Uint8Array { + /** Returns `true` if both `buf` and `other` have exactly the same bytes,`false` otherwise. Equivalent to `buf.compare(otherBuffer) === 0`. */ + public equals(other: Uint8Array): boolean { + return areUint8ArraysEqual(this, other); + } + + /** + Convert a value to a `Buffer` without copying its data. + + This can be useful for converting a Node.js `Buffer` to a portable `Buffer` instance. The Node.js `Buffer` is already an `Uint8Array` subclass, but [it alters some behavior](https://sindresorhus.com/blog/goodbye-nodejs-buffer), so it can be useful to cast it to a pure `Uint8Array` or portable `Buffer` before returning it. + + Tip: If you want a copy, just call `.slice()` on the return value. + */ + public static view( + value: TypedArray | ArrayBuffer | DataView, + ): Bytes { + if (value instanceof ArrayBuffer) { + return new this(value); + } + + if (ArrayBuffer.isView(value)) { + return new this(value.buffer, value.byteOffset, value.byteLength); + } + + throw new TypeError(`Unsupported value, got \`${typeof value}\`.`); + } + + public static from( + data: Uint8Array | ArrayBuffer | ArrayLike | Iterable, + ): Bytes; + + /** + * Creates a new Buffer containing the given JavaScript string {str}. + * If provided, the {encoding} parameter identifies the character encoding. + * If not provided, {encoding} defaults to 'utf8'. + */ + public static from(data: string, encoding?: BufferEncoding): Bytes; + + public static from( + arrayLike: Iterable, + mapfn?: (v: number, k: number) => number, + thisArg?: any, + ): Bytes; + + public static from( + data: + | string + | Uint8Array + | ArrayBuffer + | ArrayLike + | Iterable, + encodingOrMapfn?: BufferEncoding | ((v: number, k: number) => number), + thisArg?: any, + ): Bytes { + if (typeof data === "string") { + const encoding = encodingOrMapfn as BufferEncoding | undefined; + switch (encoding) { + case "ascii": + case "utf-8": + case "utf8": + case undefined: + return Bytes.view(stringToUint8Array(data)); + case "hex": + return Bytes.view(hexToUint8Array(data)); + } + + throw new Error(`Unsupported encoding: ${encoding}`); + } else if (isUint8ArrayOrArrayBuffer(data)) { + // .from copies! + return new Bytes(data); + } else if ("length" in data) { + return Bytes.view(super.from(data)); + } else { + return Bytes.view( + super.from(data, encodingOrMapfn as any, thisArg), + ); + } + } + + /** + * Allocates a new `Buffer` of `size` bytes. If `fill` is `undefined`, the`Buffer` will be zero-filled. + * + * ```js + * import { Buffer } from 'node:buffer'; + * + * const buf = Buffer.alloc(5); + * + * console.log(buf); + * // Prints: + * ``` + * + * If `size` is larger than {@link constants.MAX_LENGTH} or smaller than 0, `ERR_OUT_OF_RANGE` is thrown. + * + * If `fill` is specified, the allocated `Buffer` will be initialized by calling `buf.fill(fill)`. + * + * A `TypeError` will be thrown if `size` is not a number. + * @since v5.10.0 + * @param size The desired length of the new `Buffer`. + * @param [fill=0] A value to pre-fill the new `Buffer` with. + * @param [encoding='utf8'] If `fill` is a string, this is its encoding. + */ + public static alloc(size: number, fill?: number): Bytes { + const ret = new Bytes(size); + if (fill !== undefined) { + ret.fill(fill); + } + return ret; + } + + public toString(encoding: BufferEncoding = "utf8"): string { + switch (encoding) { + case "hex": + return uint8ArrayToHex(this); + case "base64": + return uint8ArrayToBase64(this); + case "base64url": + return uint8ArrayToBase64(this, { urlSafe: true }); + default: + return uint8ArrayToString(this, encoding); + } + } + + public subarray(start?: number, end?: number): Bytes { + return Bytes.view(super.subarray(start, end)); + } + + /** + * Equivalent to `buf.indexOf() !== -1`. + * + * @since v5.3.0 + * @param value What to search for. + * @param [byteOffset=0] Where to begin searching in `buf`. If negative, then offset is calculated from the end of `buf`. + * @param [encoding='utf8'] If `value` is a string, this is its encoding. + * @return `true` if `value` was found in `buf`, `false` otherwise. + */ + includes( + value: number | Bytes, + byteOffset: number = 0, + ): boolean { + if (typeof value === "number") { + return super.includes(value, byteOffset); + } else if (byteOffset) { + return includes(this.subarray(byteOffset), value); + } else { + return includes(this, value); + } + } + + // /** + // * Returns `true` if `obj` is a `Buffer`, `false` otherwise. + // * + // * ```js + // * import { Buffer } from 'node:buffer'; + // * + // * Buffer.isBuffer(Buffer.alloc(10)); // true + // * Buffer.isBuffer(Buffer.from('foo')); // true + // * Buffer.isBuffer('a string'); // false + // * Buffer.isBuffer([]); // false + // * Buffer.isBuffer(new Uint8Array(1024)); // false + // * ``` + // * @since v0.1.101 + // */ + // public static isBuffer(obj: any): obj is Buffer { + // return obj && obj instanceof Buffer; + // } + + /** + * Returns a new `Buffer` which is the result of concatenating all the `Buffer` instances in the `list` together. + * + * If the list has no items, or if the `totalLength` is 0, then a new zero-length `Buffer` is returned. + * + * If `totalLength` is not provided, it is calculated from the `Buffer` instances + * in `list` by adding their lengths. + * + * If `totalLength` is provided, it is coerced to an unsigned integer. If the + * combined length of the `Buffer`s in `list` exceeds `totalLength`, the result is + * truncated to `totalLength`. + * + * ```js + * import { Buffer } from 'node:buffer'; + * + * // Create a single `Buffer` from a list of three `Buffer` instances. + * + * const buf1 = Buffer.alloc(10); + * const buf2 = Buffer.alloc(14); + * const buf3 = Buffer.alloc(18); + * const totalLength = buf1.length + buf2.length + buf3.length; + * + * console.log(totalLength); + * // Prints: 42 + * + * const bufA = Buffer.concat([buf1, buf2, buf3], totalLength); + * + * console.log(bufA); + * // Prints: + * console.log(bufA.length); + * // Prints: 42 + * ``` + * + * `Buffer.concat()` may also use the internal `Buffer` pool like `new Buffer()` does. + * @since v0.7.11 + * @param list List of `Buffer` or {@link Uint8Array} instances to concatenate. + * @param totalLength Total length of the `Buffer` instances in `list` when concatenated. + */ + public static concat( + list: readonly (Uint8Array | ArrayLike)[], + totalLength?: number, + ): Bytes { + return Bytes.view(concatUint8Arrays(list, totalLength)); + } + + private getDataView(): DataView { + return new DataView(this.buffer, this.byteOffset, this.byteLength); + } + + /** + * Writes `value` to `buf` at the specified `offset` as big-endian. + * + * `value` is interpreted and written as a two's complement signed integer. + * + * ```js + * import { Buffer } from 'node:buffer'; + * + * const buf = new Buffer(8); + * + * buf.writeBigInt64BE(0x0102030405060708n, 0); + * + * console.log(buf); + * // Prints: + * ``` + * @since v12.0.0, v10.20.0 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy: `0 <= offset <= buf.length - 8`. + * @return `offset` plus the number of bytes written. + */ + writeBigInt64BE(value: bigint, offset: number = 0): number { + const view = this.getDataView(); + view.setBigInt64(offset, value, false); + return offset + 8; + } + + /** + * Writes `value` to `buf` at the specified `offset` as little-endian. + * + * `value` is interpreted and written as a two's complement signed integer. + * + * ```js + * import { Buffer } from 'node:buffer'; + * + * const buf = new Buffer(8); + * + * buf.writeBigInt64LE(0x0102030405060708n, 0); + * + * console.log(buf); + * // Prints: + * ``` + * @since v12.0.0, v10.20.0 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy: `0 <= offset <= buf.length - 8`. + * @return `offset` plus the number of bytes written. + */ + writeBigInt64LE(value: bigint, offset: number = 0): number { + const view = this.getDataView(); + view.setBigInt64(offset, value, true); + return offset + 8; + } + + /** + * Writes `value` to `buf` at the specified `offset` as big-endian. + * + * ```js + * import { Buffer } from 'node:buffer'; + * + * const buf = new Buffer(8); + * + * buf.writeBigUInt64BE(0xdecafafecacefaden, 0); + * + * console.log(buf); + * // Prints: + * ``` + * @since v12.0.0, v10.20.0 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy: `0 <= offset <= buf.length - 8`. + * @return `offset` plus the number of bytes written. + */ + writeBigUInt64BE(value: bigint, offset: number = 0): number { + const view = this.getDataView(); + view.setBigUint64(offset, value, false); + return offset + 8; + } + + /** + * Writes `value` to `buf` at the specified `offset` as little-endian + * + * ```js + * import { Buffer } from 'node:buffer'; + * + * const buf = new Buffer(8); + * + * buf.writeBigUInt64LE(0xdecafafecacefaden, 0); + * + * console.log(buf); + * // Prints: + * ``` + * + * @since v12.0.0, v10.20.0 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy: `0 <= offset <= buf.length - 8`. + * @return `offset` plus the number of bytes written. + */ + writeBigUInt64LE(value: bigint, offset: number = 0): number { + const view = this.getDataView(); + view.setBigUint64(offset, value, true); + return offset + 8; + } + + /** + * Writes `byteLength` bytes of `value` to `buf` at the specified `offset`as little-endian. Supports up to 48 bits of accuracy. Behavior is undefined + * when `value` is anything other than an unsigned integer. + * + * ```js + * import { Buffer } from 'node:buffer'; + * + * const buf = new Buffer(6); + * + * buf.writeUIntLE(0x1234567890ab, 0, 6); + * + * console.log(buf); + * // Prints: + * ``` + * @since v0.5.5 + * @param value Number to be written to `buf`. + * @param offset Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - byteLength`. + * @param byteLength Number of bytes to write. Must satisfy `0 < byteLength <= 6`. + * @return `offset` plus the number of bytes written. + */ + writeUIntLE(value: number, offset: number, byteLength: number): number { + switch (byteLength) { + case 1: + return this.writeUInt8(value, offset); + case 2: + return this.writeUInt16LE(value, offset); + case 3: { + let ret = this.writeUInt16LE(value & 0xffff, offset); + ret = this.writeUInt8(value >>> 16, ret); + return ret; + } + case 4: + return this.writeUInt32LE(value, offset); + + // Numbers > 32 bit need to be converted to BigInt for the bitwise operations to work + case 5: { + const big = BigInt(value); + const low = Number(big & 0xffffffffn); + const high = Number(big >> 32n); + let ret = this.writeUInt32LE(low, offset); + ret = this.writeUInt8(high, ret); + return ret; + } + case 6: { + const big = BigInt(value); + const low = Number(big & 0xffffffffn); + const high = Number(big >> 32n); + let ret = this.writeUInt32LE(low, offset); + ret = this.writeUInt16LE(high, ret); + return ret; + } + default: + throw new RangeError( + `The value of "byteLength" is out of range. It must be >= 1 and <= 6. Received ${byteLength}`, + ); + } + } + /** + * Writes `byteLength` bytes of `value` to `buf` at the specified `offset`as big-endian. Supports up to 48 bits of accuracy. Behavior is undefined + * when `value` is anything other than an unsigned integer. + * + * ```js + * import { Buffer } from 'node:buffer'; + * + * const buf = new Buffer(6); + * + * buf.writeUIntBE(0x1234567890ab, 0, 6); + * + * console.log(buf); + * // Prints: + * ``` + * @since v0.5.5 + * @param value Number to be written to `buf`. + * @param offset Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - byteLength`. + * @param byteLength Number of bytes to write. Must satisfy `0 < byteLength <= 6`. + * @return `offset` plus the number of bytes written. + */ + writeUIntBE(value: number, offset: number, byteLength: number): number { + switch (byteLength) { + case 1: + return this.writeUInt8(value, offset); + case 2: + return this.writeUInt16BE(value, offset); + case 3: { + let ret = this.writeUInt16BE(value >> 8, offset); + ret = this.writeUInt8(value & 0xff, ret); + return ret; + } + case 4: + return this.writeUInt32BE(value, offset); + + // Numbers > 32 bit need to be converted to BigInt for the bitwise operations to work + case 5: { + const big = BigInt(value); + const high = Number(big >> 8n); + const low = Number(big & 0xffn); + let ret = this.writeUInt32BE(high, offset); + ret = this.writeUInt8(low, ret); + return ret; + } + case 6: { + const big = BigInt(value); + const high = Number(big >> 16n); + const low = Number(big & 0xffffn); + let ret = this.writeUInt32BE(high, offset); + ret = this.writeUInt16BE(low, ret); + return ret; + } + default: + throw new RangeError( + `The value of "byteLength" is out of range. It must be >= 1 and <= 6. Received ${byteLength}`, + ); + } + } + /** + * Writes `byteLength` bytes of `value` to `buf` at the specified `offset`as little-endian. Supports up to 48 bits of accuracy. Behavior is undefined + * when `value` is anything other than a signed integer. + * + * ```js + * import { Buffer } from 'node:buffer'; + * + * const buf = new Buffer(6); + * + * buf.writeIntLE(0x1234567890ab, 0, 6); + * + * console.log(buf); + * // Prints: + * ``` + * @since v0.11.15 + * @param value Number to be written to `buf`. + * @param offset Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - byteLength`. + * @param byteLength Number of bytes to write. Must satisfy `0 < byteLength <= 6`. + * @return `offset` plus the number of bytes written. + */ + writeIntLE(value: number, offset: number, byteLength: number): number { + switch (byteLength) { + case 1: + return this.writeInt8(value, offset); + case 2: + return this.writeInt16LE(value, offset); + case 3: { + let ret = this.writeInt16LE(value & 0xffff, offset); + ret = this.writeInt8(value >> 16, ret); + return ret; + } + case 4: + return this.writeInt32LE(value, offset); + + case 5: + case 6: + throw new RangeError( + `writeIntLE is currently not implemented for byteLength ${byteLength}`, + ); + default: + throw new RangeError( + `The value of "byteLength" is out of range. It must be >= 1 and <= 6. Received ${byteLength}`, + ); + } + } + /** + * Writes `byteLength` bytes of `value` to `buf` at the specified `offset`as big-endian. Supports up to 48 bits of accuracy. Behavior is undefined when`value` is anything other than a + * signed integer. + * + * ```js + * import { Buffer } from 'node:buffer'; + * + * const buf = new Buffer(6); + * + * buf.writeIntBE(0x1234567890ab, 0, 6); + * + * console.log(buf); + * // Prints: + * ``` + * @since v0.11.15 + * @param value Number to be written to `buf`. + * @param offset Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - byteLength`. + * @param byteLength Number of bytes to write. Must satisfy `0 < byteLength <= 6`. + * @return `offset` plus the number of bytes written. + */ + writeIntBE(value: number, offset: number, byteLength: number): number { + switch (byteLength) { + case 1: + return this.writeInt8(value, offset); + case 2: + return this.writeInt16BE(value, offset); + case 3: { + let ret = this.writeInt8(value >> 16, offset); + ret = this.writeInt16BE(value & 0xffff, ret); + return ret; + } + case 4: + return this.writeInt32BE(value, offset); + + case 5: + case 6: + throw new RangeError( + `writeIntBE is currently not implemented for byteLength ${byteLength}`, + ); + default: + throw new RangeError( + `The value of "byteLength" is out of range. It must be >= 1 and <= 6. Received ${byteLength}`, + ); + } + } + + /** + * Writes `value` to `buf` at the specified `offset`. `value` must be a + * valid unsigned 8-bit integer. Behavior is undefined when `value` is anything + * other than an unsigned 8-bit integer. + * + * ```js + * import { Buffer } from 'node:buffer'; + * + * const buf = new Buffer(4); + * + * buf.writeUInt8(0x3, 0); + * buf.writeUInt8(0x4, 1); + * buf.writeUInt8(0x23, 2); + * buf.writeUInt8(0x42, 3); + * + * console.log(buf); + * // Prints: + * ``` + * @since v0.5.0 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 1`. + * @return `offset` plus the number of bytes written. + */ + writeUInt8(value: number, offset: number = 0): number { + const view = this.getDataView(); + view.setUint8(offset, value); + return offset + 1; + } + /** + * Writes `value` to `buf` at the specified `offset` as little-endian. The `value`must be a valid unsigned 16-bit integer. Behavior is undefined when `value` is + * anything other than an unsigned 16-bit integer. + * + * ```js + * import { Buffer } from 'node:buffer'; + * + * const buf = new Buffer(4); + * + * buf.writeUInt16LE(0xdead, 0); + * buf.writeUInt16LE(0xbeef, 2); + * + * console.log(buf); + * // Prints: + * ``` + * @since v0.5.5 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 2`. + * @return `offset` plus the number of bytes written. + */ + writeUInt16LE(value: number, offset: number = 0): number { + const view = this.getDataView(); + view.setUint16(offset, value, true); + return offset + 2; + } + /** + * Writes `value` to `buf` at the specified `offset` as big-endian. The `value`must be a valid unsigned 16-bit integer. Behavior is undefined when `value`is anything other than an + * unsigned 16-bit integer. + * + * ```js + * import { Buffer } from 'node:buffer'; + * + * const buf = new Buffer(4); + * + * buf.writeUInt16BE(0xdead, 0); + * buf.writeUInt16BE(0xbeef, 2); + * + * console.log(buf); + * // Prints: + * ``` + * @since v0.5.5 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 2`. + * @return `offset` plus the number of bytes written. + */ + writeUInt16BE(value: number, offset: number = 0): number { + const view = this.getDataView(); + view.setUint16(offset, value, false); + return offset + 2; + } + /** + * Writes `value` to `buf` at the specified `offset` as little-endian. The `value`must be a valid unsigned 32-bit integer. Behavior is undefined when `value` is + * anything other than an unsigned 32-bit integer. + * + * ```js + * import { Buffer } from 'node:buffer'; + * + * const buf = new Buffer(4); + * + * buf.writeUInt32LE(0xfeedface, 0); + * + * console.log(buf); + * // Prints: + * ``` + * @since v0.5.5 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 4`. + * @return `offset` plus the number of bytes written. + */ + writeUInt32LE(value: number, offset: number = 0): number { + const view = this.getDataView(); + view.setUint32(offset, value, true); + return offset + 4; + } + /** + * Writes `value` to `buf` at the specified `offset` as big-endian. The `value`must be a valid unsigned 32-bit integer. Behavior is undefined when `value`is anything other than an + * unsigned 32-bit integer. + * + * ```js + * import { Buffer } from 'node:buffer'; + * + * const buf = new Buffer(4); + * + * buf.writeUInt32BE(0xfeedface, 0); + * + * console.log(buf); + * // Prints: + * ``` + * @since v0.5.5 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 4`. + * @return `offset` plus the number of bytes written. + */ + writeUInt32BE(value: number, offset: number = 0): number { + const view = this.getDataView(); + view.setUint32(offset, value, false); + return offset + 4; + } + /** + * Writes `value` to `buf` at the specified `offset`. `value` must be a valid + * signed 8-bit integer. Behavior is undefined when `value` is anything other than + * a signed 8-bit integer. + * + * `value` is interpreted and written as a two's complement signed integer. + * + * ```js + * import { Buffer } from 'node:buffer'; + * + * const buf = new Buffer(2); + * + * buf.writeInt8(2, 0); + * buf.writeInt8(-2, 1); + * + * console.log(buf); + * // Prints: + * ``` + * @since v0.5.0 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 1`. + * @return `offset` plus the number of bytes written. + */ + writeInt8(value: number, offset: number = 0): number { + const view = this.getDataView(); + view.setInt8(offset, value); + return offset + 1; + } + /** + * Writes `value` to `buf` at the specified `offset` as little-endian. The `value`must be a valid signed 16-bit integer. Behavior is undefined when `value` is + * anything other than a signed 16-bit integer. + * + * The `value` is interpreted and written as a two's complement signed integer. + * + * ```js + * import { Buffer } from 'node:buffer'; + * + * const buf = new Buffer(2); + * + * buf.writeInt16LE(0x0304, 0); + * + * console.log(buf); + * // Prints: + * ``` + * @since v0.5.5 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 2`. + * @return `offset` plus the number of bytes written. + */ + writeInt16LE(value: number, offset: number = 0): number { + const view = this.getDataView(); + view.setInt16(offset, value, true); + return offset + 2; + } + /** + * Writes `value` to `buf` at the specified `offset` as big-endian. The `value`must be a valid signed 16-bit integer. Behavior is undefined when `value` is + * anything other than a signed 16-bit integer. + * + * The `value` is interpreted and written as a two's complement signed integer. + * + * ```js + * import { Buffer } from 'node:buffer'; + * + * const buf = new Buffer(2); + * + * buf.writeInt16BE(0x0102, 0); + * + * console.log(buf); + * // Prints: + * ``` + * @since v0.5.5 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 2`. + * @return `offset` plus the number of bytes written. + */ + writeInt16BE(value: number, offset: number = 0): number { + const view = this.getDataView(); + view.setInt16(offset, value, false); + return offset + 2; + } + /** + * Writes `value` to `buf` at the specified `offset` as little-endian. The `value`must be a valid signed 32-bit integer. Behavior is undefined when `value` is + * anything other than a signed 32-bit integer. + * + * The `value` is interpreted and written as a two's complement signed integer. + * + * ```js + * import { Buffer } from 'node:buffer'; + * + * const buf = new Buffer(4); + * + * buf.writeInt32LE(0x05060708, 0); + * + * console.log(buf); + * // Prints: + * ``` + * @since v0.5.5 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 4`. + * @return `offset` plus the number of bytes written. + */ + writeInt32LE(value: number, offset: number = 0): number { + const view = this.getDataView(); + view.setInt32(offset, value, true); + return offset + 4; + } + /** + * Writes `value` to `buf` at the specified `offset` as big-endian. The `value`must be a valid signed 32-bit integer. Behavior is undefined when `value` is + * anything other than a signed 32-bit integer. + * + * The `value` is interpreted and written as a two's complement signed integer. + * + * ```js + * import { Buffer } from 'node:buffer'; + * + * const buf = new Buffer(4); + * + * buf.writeInt32BE(0x01020304, 0); + * + * console.log(buf); + * // Prints: + * ``` + * @since v0.5.5 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 4`. + * @return `offset` plus the number of bytes written. + */ + writeInt32BE(value: number, offset: number = 0): number { + const view = this.getDataView(); + view.setInt32(offset, value, false); + return offset + 4; + } + + /** + * Reads an unsigned, big-endian 64-bit integer from `buf` at the specified`offset`. + * + * ```js + * import { Buffer } from 'node:buffer'; + * + * const buf = Buffer.from([0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff]); + * + * console.log(buf.readBigUInt64BE(0)); + * // Prints: 4294967295n + * ``` + * @since v12.0.0, v10.20.0 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy: `0 <= offset <= buf.length - 8`. + */ + readBigUInt64BE(offset: number = 0): bigint { + const view = this.getDataView(); + return view.getBigUint64(offset, false); + } + /** + * Reads an unsigned, little-endian 64-bit integer from `buf` at the specified`offset`. + * + * ```js + * import { Buffer } from 'node:buffer'; + * + * const buf = Buffer.from([0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff]); + * + * console.log(buf.readBigUInt64LE(0)); + * // Prints: 18446744069414584320n + * ``` + * @since v12.0.0, v10.20.0 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy: `0 <= offset <= buf.length - 8`. + */ + readBigUInt64LE(offset: number = 0): bigint { + const view = this.getDataView(); + return view.getBigUint64(offset, true); + } + /** + * Reads a signed, big-endian 64-bit integer from `buf` at the specified `offset`. + * + * Integers read from a `Buffer` are interpreted as two's complement signed + * values. + * @since v12.0.0, v10.20.0 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy: `0 <= offset <= buf.length - 8`. + */ + readBigInt64BE(offset: number = 0): bigint { + const view = this.getDataView(); + return view.getBigInt64(offset, false); + } + /** + * Reads a signed, little-endian 64-bit integer from `buf` at the specified`offset`. + * + * Integers read from a `Buffer` are interpreted as two's complement signed + * values. + * @since v12.0.0, v10.20.0 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy: `0 <= offset <= buf.length - 8`. + */ + readBigInt64LE(offset: number = 0): bigint { + const view = this.getDataView(); + return view.getBigInt64(offset, true); + } + /** + * Reads `byteLength` number of bytes from `buf` at the specified `offset` and interprets the result as an unsigned, little-endian integer supporting + * up to 48 bits of accuracy. + * + * ```js + * import { Buffer } from 'node:buffer'; + * + * const buf = Buffer.from([0x12, 0x34, 0x56, 0x78, 0x90, 0xab]); + * + * console.log(buf.readUIntLE(0, 6).toString(16)); + * // Prints: ab9078563412 + * ``` + * @since v0.11.15 + * @param offset Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - byteLength`. + * @param byteLength Number of bytes to read. Must satisfy `0 < byteLength <= 6`. + */ + readUIntLE(offset: number, byteLength: number): number { + switch (byteLength) { + case 1: + return this.readUInt8(offset); + case 2: + return this.readUInt16LE(offset); + case 3: { + let ret = this.readUInt16LE(offset); + ret |= this.readUInt8(offset + 2) << 16; + return ret; + } + case 4: + return this.readUInt32LE(offset); + + // Numbers > 32 bit need to be converted to BigInt for the bitwise operations to work + case 5: { + let ret = BigInt(this.readUInt32LE(offset)); + ret |= BigInt(this.readUInt8(offset + 4)) << 32n; + return Number(ret); + } + case 6: { + let ret = BigInt(this.readUInt32LE(offset)); + ret |= BigInt(this.readUInt16LE(offset + 4)) << 32n; + return Number(ret); + } + default: + throw new RangeError( + `The value of "byteLength" is out of range. It must be >= 1 and <= 6. Received ${byteLength}`, + ); + } + } + /** + * Reads `byteLength` number of bytes from `buf` at the specified `offset` and interprets the result as an unsigned big-endian integer supporting + * up to 48 bits of accuracy. + * + * ```js + * import { Buffer } from 'node:buffer'; + * + * const buf = Buffer.from([0x12, 0x34, 0x56, 0x78, 0x90, 0xab]); + * + * console.log(buf.readUIntBE(0, 6).toString(16)); + * // Prints: 1234567890ab + * console.log(buf.readUIntBE(1, 6).toString(16)); + * // Throws ERR_OUT_OF_RANGE. + * ``` + * @since v0.11.15 + * @param offset Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - byteLength`. + * @param byteLength Number of bytes to read. Must satisfy `0 < byteLength <= 6`. + */ + readUIntBE(offset: number, byteLength: number): number { + switch (byteLength) { + case 1: + return this.readUInt8(offset); + case 2: + return this.readUInt16BE(offset); + case 3: { + let ret = this.readUInt8(offset) << 16; + ret |= this.readUInt16BE(offset + 1); + return ret; + } + case 4: + return this.readUInt32BE(offset); + + // Numbers > 32 bit need to be converted to BigInt for the bitwise operations to work + case 5: { + let ret = BigInt(this.readUInt32BE(offset)) << 32n; + ret |= BigInt(this.readUInt8(offset + 4)); + return Number(ret); + } + case 6: { + let ret = BigInt(this.readUInt32BE(offset)) << 32n; + ret |= BigInt(this.readUInt16BE(offset + 4)); + return Number(ret); + } + default: + throw new RangeError( + `The value of "byteLength" is out of range. It must be >= 1 and <= 6. Received ${byteLength}`, + ); + } + } + /** + * Reads `byteLength` number of bytes from `buf` at the specified `offset` and interprets the result as a little-endian, two's complement signed value + * supporting up to 48 bits of accuracy. + * + * ```js + * import { Buffer } from 'node:buffer'; + * + * const buf = Buffer.from([0x12, 0x34, 0x56, 0x78, 0x90, 0xab]); + * + * console.log(buf.readIntLE(0, 6).toString(16)); + * // Prints: -546f87a9cbee + * ``` + * @since v0.11.15 + * @param offset Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - byteLength`. + * @param byteLength Number of bytes to read. Must satisfy `0 < byteLength <= 6`. + */ + readIntLE(offset: number, byteLength: number): number { + switch (byteLength) { + case 1: + return this.readInt8(offset); + case 2: + return this.readInt16LE(offset); + case 3: { + // Read the lower 2 bytes as unsigned, since the upper byte is signed + let ret = this.readUInt16LE(offset); + ret |= this.readInt8(offset + 2) << 16; + return ret; + } + case 4: + return this.readInt32LE(offset); + + case 5: + case 6: + throw new RangeError( + `readIntLE is currently not implemented for byteLength ${byteLength}`, + ); + default: + throw new RangeError( + `The value of "byteLength" is out of range. It must be >= 1 and <= 6. Received ${byteLength}`, + ); + } + } + /** + * Reads `byteLength` number of bytes from `buf` at the specified `offset` and interprets the result as a big-endian, two's complement signed value + * supporting up to 48 bits of accuracy. + * + * ```js + * import { Buffer } from 'node:buffer'; + * + * const buf = Buffer.from([0x12, 0x34, 0x56, 0x78, 0x90, 0xab]); + * + * console.log(buf.readIntBE(0, 6).toString(16)); + * // Prints: 1234567890ab + * console.log(buf.readIntBE(1, 6).toString(16)); + * // Throws ERR_OUT_OF_RANGE. + * console.log(buf.readIntBE(1, 0).toString(16)); + * // Throws ERR_OUT_OF_RANGE. + * ``` + * @since v0.11.15 + * @param offset Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - byteLength`. + * @param byteLength Number of bytes to read. Must satisfy `0 < byteLength <= 6`. + */ + readIntBE(offset: number, byteLength: number): number { + switch (byteLength) { + case 1: + return this.readInt8(offset); + case 2: + return this.readInt16BE(offset); + case 3: { + // The upper byte is signed + let ret = this.readInt8(offset) << 16; + // read the rest as unsigned + ret |= this.readUInt16BE(offset + 1); + return ret; + } + case 4: + return this.readInt32BE(offset); + + case 5: + case 6: + throw new RangeError( + `readIntBE is currently not implemented for byteLength ${byteLength}`, + ); + default: + throw new RangeError( + `The value of "byteLength" is out of range. It must be >= 1 and <= 6. Received ${byteLength}`, + ); + } + } + /** + * Reads an unsigned 8-bit integer from `buf` at the specified `offset`. + * + * ```js + * import { Buffer } from 'node:buffer'; + * + * const buf = Buffer.from([1, -2]); + * + * console.log(buf.readUInt8(0)); + * // Prints: 1 + * console.log(buf.readUInt8(1)); + * // Prints: 254 + * console.log(buf.readUInt8(2)); + * // Throws ERR_OUT_OF_RANGE. + * ``` + * @since v0.5.0 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 1`. + */ + readUInt8(offset: number = 0): number { + const view = this.getDataView(); + return view.getUint8(offset); + } + /** + * Reads an unsigned, little-endian 16-bit integer from `buf` at the specified`offset`. + * + * ```js + * import { Buffer } from 'node:buffer'; + * + * const buf = Buffer.from([0x12, 0x34, 0x56]); + * + * console.log(buf.readUInt16LE(0).toString(16)); + * // Prints: 3412 + * console.log(buf.readUInt16LE(1).toString(16)); + * // Prints: 5634 + * console.log(buf.readUInt16LE(2).toString(16)); + * // Throws ERR_OUT_OF_RANGE. + * ``` + * @since v0.5.5 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 2`. + */ + readUInt16LE(offset: number = 0): number { + const view = this.getDataView(); + return view.getUint16(offset, true); + } + /** + * Reads an unsigned, big-endian 16-bit integer from `buf` at the specified`offset`. + * + * ```js + * import { Buffer } from 'node:buffer'; + * + * const buf = Buffer.from([0x12, 0x34, 0x56]); + * + * console.log(buf.readUInt16BE(0).toString(16)); + * // Prints: 1234 + * console.log(buf.readUInt16BE(1).toString(16)); + * // Prints: 3456 + * ``` + * @since v0.5.5 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 2`. + */ + readUInt16BE(offset: number = 0): number { + const view = this.getDataView(); + return view.getUint16(offset, false); + } + /** + * Reads an unsigned, little-endian 32-bit integer from `buf` at the specified`offset`. + * + * ```js + * import { Buffer } from 'node:buffer'; + * + * const buf = Buffer.from([0x12, 0x34, 0x56, 0x78]); + * + * console.log(buf.readUInt32LE(0).toString(16)); + * // Prints: 78563412 + * console.log(buf.readUInt32LE(1).toString(16)); + * // Throws ERR_OUT_OF_RANGE. + * ``` + * @since v0.5.5 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 4`. + */ + readUInt32LE(offset: number = 0): number { + const view = this.getDataView(); + return view.getUint32(offset, true); + } + /** + * Reads an unsigned, big-endian 32-bit integer from `buf` at the specified`offset`. + * + * ```js + * import { Buffer } from 'node:buffer'; + * + * const buf = Buffer.from([0x12, 0x34, 0x56, 0x78]); + * + * console.log(buf.readUInt32BE(0).toString(16)); + * // Prints: 12345678 + * ``` + * @since v0.5.5 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 4`. + */ + readUInt32BE(offset: number = 0): number { + const view = this.getDataView(); + return view.getUint32(offset, false); + } + /** + * Reads a signed 8-bit integer from `buf` at the specified `offset`. + * + * Integers read from a `Buffer` are interpreted as two's complement signed values. + * + * ```js + * import { Buffer } from 'node:buffer'; + * + * const buf = Buffer.from([-1, 5]); + * + * console.log(buf.readInt8(0)); + * // Prints: -1 + * console.log(buf.readInt8(1)); + * // Prints: 5 + * console.log(buf.readInt8(2)); + * // Throws ERR_OUT_OF_RANGE. + * ``` + * @since v0.5.0 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 1`. + */ + readInt8(offset: number = 0): number { + const view = this.getDataView(); + return view.getInt8(offset); + } + /** + * Reads a signed, little-endian 16-bit integer from `buf` at the specified`offset`. + * + * Integers read from a `Buffer` are interpreted as two's complement signed values. + * + * ```js + * import { Buffer } from 'node:buffer'; + * + * const buf = Buffer.from([0, 5]); + * + * console.log(buf.readInt16LE(0)); + * // Prints: 1280 + * console.log(buf.readInt16LE(1)); + * // Throws ERR_OUT_OF_RANGE. + * ``` + * @since v0.5.5 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 2`. + */ + readInt16LE(offset: number = 0): number { + const view = this.getDataView(); + return view.getInt16(offset, true); + } + /** + * Reads a signed, big-endian 16-bit integer from `buf` at the specified `offset`. + * + * Integers read from a `Buffer` are interpreted as two's complement signed values. + * + * ```js + * import { Buffer } from 'node:buffer'; + * + * const buf = Buffer.from([0, 5]); + * + * console.log(buf.readInt16BE(0)); + * // Prints: 5 + * ``` + * @since v0.5.5 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 2`. + */ + readInt16BE(offset: number = 0): number { + const view = this.getDataView(); + return view.getInt16(offset, false); + } + /** + * Reads a signed, little-endian 32-bit integer from `buf` at the specified`offset`. + * + * Integers read from a `Buffer` are interpreted as two's complement signed values. + * + * ```js + * import { Buffer } from 'node:buffer'; + * + * const buf = Buffer.from([0, 0, 0, 5]); + * + * console.log(buf.readInt32LE(0)); + * // Prints: 83886080 + * console.log(buf.readInt32LE(1)); + * // Throws ERR_OUT_OF_RANGE. + * ``` + * @since v0.5.5 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 4`. + */ + readInt32LE(offset: number = 0): number { + const view = this.getDataView(); + return view.getInt32(offset, true); + } + /** + * Reads a signed, big-endian 32-bit integer from `buf` at the specified `offset`. + * + * Integers read from a `Buffer` are interpreted as two's complement signed values. + * + * ```js + * import { Buffer } from 'node:buffer'; + * + * const buf = Buffer.from([0, 0, 0, 5]); + * + * console.log(buf.readInt32BE(0)); + * // Prints: 5 + * ``` + * @since v0.5.5 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 4`. + */ + readInt32BE(offset: number = 0): number { + const view = this.getDataView(); + return view.getInt32(offset, false); + } +} diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index e9fc00d7f3f1..ca7c63d06644 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/consistent-type-exports */ export * from "./AsyncQueue"; +export { Bytes } from "./Bytes"; export * from "./EventEmitter"; export { ObjectKeyMap } from "./ObjectKeyMap"; export type { ReadonlyObjectKeyMap } from "./ObjectKeyMap"; @@ -11,5 +12,13 @@ export * from "./fs"; export * from "./inheritance"; export * from "./strings"; export * from "./types"; +export { + areUint8ArraysEqual, + assertUint8Array, + hexToUint8Array, + isUint8Array, + uint8ArrayToHex, + uint8ArrayToString, +} from "./uint8array-extras"; export * from "./utils"; export * from "./wrappingCounter"; diff --git a/packages/shared/src/index_safe.ts b/packages/shared/src/index_safe.ts index adc6b2d3d7c0..8a0cdcb25174 100644 --- a/packages/shared/src/index_safe.ts +++ b/packages/shared/src/index_safe.ts @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/consistent-type-exports */ /* @forbiddenImports external */ +export { Bytes } from "./Bytes"; export { ObjectKeyMap } from "./ObjectKeyMap"; export type { ReadonlyObjectKeyMap } from "./ObjectKeyMap"; export * from "./ThrowingMap"; @@ -9,5 +10,13 @@ export * from "./errors"; export * from "./inheritance"; export * from "./strings"; export * from "./types"; +export { + areUint8ArraysEqual, + assertUint8Array, + hexToUint8Array, + isUint8Array, + uint8ArrayToHex, + uint8ArrayToString, +} from "./uint8array-extras"; export * from "./utils"; export * from "./wrappingCounter"; diff --git a/packages/shared/src/strings.test.ts b/packages/shared/src/strings.test.ts index 7c5d4003a1a0..0ce6dc29dc2b 100644 --- a/packages/shared/src/strings.test.ts +++ b/packages/shared/src/strings.test.ts @@ -1,6 +1,13 @@ import test from "ava"; -import { cpp2js, isPrintableASCIIWithWhitespace, num2hex } from "./strings"; +import { Bytes } from "./Bytes"; +import { + cpp2js, + isPrintableASCIIWithWhitespace, + num2hex, + stringToUint8ArrayUTF16BE, + uint8ArrayToStringUTF16BE, +} from "./strings"; test("cpp2js() -> should truncate null-terminated strings", (t) => { const testCases = [ @@ -52,3 +59,17 @@ test("isPrintableASCIIWithWhitespace() -> should return true for ASCII strings t t.is(isPrintableASCIIWithWhitespace(inp), result); } }); + +test("stringToUint8ArrayUTF16BE / uint8ArrayToStringUTF16BE", (t) => { + const testCases = [ + ["a", [0x00, 0x61]], + ["\u00e4", [0x00, 0xe4]], + ["🐔", [0xd8, 0x3d, 0xdc, 0x14]], + ] as const; + for (const [inp, out] of testCases) { + // One way + t.deepEqual(stringToUint8ArrayUTF16BE(inp), Bytes.from(out)); + // And back + t.is(uint8ArrayToStringUTF16BE(Bytes.from(out)), inp); + } +}); diff --git a/packages/shared/src/strings.ts b/packages/shared/src/strings.ts index dca75f8d42ed..fae3d75d3cf3 100644 --- a/packages/shared/src/strings.ts +++ b/packages/shared/src/strings.ts @@ -1,4 +1,6 @@ import { padStart } from "alcalzone-shared/strings"; +import { Bytes } from "./Bytes"; +import { uint8ArrayToHex } from "./uint8array-extras"; /** Translates a null-terminated (C++) string to JS */ export function cpp2js(str: string): string { @@ -45,9 +47,12 @@ export function stringify(arg: unknown, space: 4 | "\t" = 4): string { * @param buffer The value to be formatted as hexadecimal * @param uppercase Whether uppercase letters should be used */ -export function buffer2hex(buffer: Buffer, uppercase: boolean = false): string { +export function buffer2hex( + buffer: Uint8Array, + uppercase: boolean = false, +): string { if (buffer.length === 0) return "(empty)"; - let ret = buffer.toString("hex"); + let ret = uint8ArrayToHex(buffer); if (uppercase) ret = ret.toUpperCase(); return "0x" + ret; } @@ -85,3 +90,23 @@ export function formatDate(year: number, month: number, day: number): string { ) }-${padStart(day.toString(), 2, "0")}`; } + +export function stringToUint8ArrayUTF16BE(str: string): Uint8Array { + // TextEncoder only supports UTF-8, so we have to do this manually + const ret = new Bytes(str.length * 2); + for (let i = 0; i < str.length; i++) { + const code = str.charCodeAt(i); + ret.writeUInt16BE(code, i * 2); + } + return ret; +} + +export function uint8ArrayToStringUTF16BE(arr: Uint8Array): string { + // TextDecoder only supports UTF-8, so we have to do this manually + let ret = ""; + const view = Bytes.view(arr); + for (let i = 0; i < arr.length; i += 2) { + ret += String.fromCharCode(view.readUInt16BE(i)); + } + return ret; +} diff --git a/packages/shared/src/uint8array-extras.ts b/packages/shared/src/uint8array-extras.ts new file mode 100644 index 000000000000..defcff7fa7e2 --- /dev/null +++ b/packages/shared/src/uint8array-extras.ts @@ -0,0 +1,667 @@ +/// https://github.com/sindresorhus/uint8array-extras/ but as CommonJS + +export type TypedArray = + | Int8Array + | Uint8Array + | Uint8ClampedArray + | Int16Array + | Uint16Array + | Int32Array + | Uint32Array + | Float32Array + | Float64Array + | BigInt64Array + | BigUint64Array; + +const uint8ArrayStringified = "[object Uint8Array]"; +const arrayBufferStringified = "[object ArrayBuffer]"; + +function isType any>( + value: unknown, + typeConstructor: T, + typeStringified: string, +): value is InstanceType { + if (!value) { + return false; + } + + if (value.constructor === typeConstructor) { + return true; + } + + return Object.prototype.toString.call(value) === typeStringified; +} + +/** +Check if the given value is an instance of `Uint8Array`. + +Replacement for [`Buffer.isBuffer()`](https://nodejs.org/api/buffer.html#static-method-bufferisbufferobj). + +@example +``` +import {isUint8Array} from 'uint8array-extras'; + +console.log(isUint8Array(new Uint8Array())); +//=> true + +console.log(isUint8Array(Buffer.from('x'))); +//=> true + +console.log(isUint8Array(new ArrayBuffer(10))); +//=> false +``` +*/ +export function isUint8Array(value: unknown): value is Uint8Array { + return isType(value, Uint8Array, uint8ArrayStringified); +} + +function isArrayBuffer(value: unknown): value is ArrayBuffer { + return isType(value, ArrayBuffer, arrayBufferStringified); +} + +function isArrayLike(value: unknown): value is ArrayLike { + return typeof value === "object" + && value !== null + && "length" in value + && typeof value.length === "number"; +} + +export function isUint8ArrayOrArrayBuffer( + value: unknown, +): value is Uint8Array | ArrayBuffer { + return isUint8Array(value) || isArrayBuffer(value); +} + +export function isUint8ArrayOrArrayLike( + value: unknown, +): value is Uint8Array | ArrayLike { + return isUint8Array(value) || isArrayBuffer(value); +} + +/** +Throw a `TypeError` if the given value is not an instance of `Uint8Array`. + +@example +``` +import {assertUint8Array} from 'uint8array-extras'; + +try { + assertUint8Array(new ArrayBuffer(10)); // Throws a TypeError +} catch (error) { + console.error(error.message); +} +``` +*/ +export function assertUint8Array(value: unknown): asserts value is Uint8Array { + if (!isUint8Array(value)) { + throw new TypeError(`Expected \`Uint8Array\`, got \`${typeof value}\``); + } +} + +export function assertUint8ArrayOrArrayBuffer( + value: unknown, +): asserts value is Uint8Array | ArrayBuffer { + if (!isUint8ArrayOrArrayBuffer(value)) { + throw new TypeError( + `Expected \`Uint8Array\` or \`ArrayBuffer\`, got \`${typeof value}\``, + ); + } +} + +export function assertUint8ArrayOrArrayLike( + value: unknown, +): asserts value is Uint8Array | ArrayLike { + if (!isUint8Array(value) && !isArrayLike(value)) { + throw new TypeError( + `Expected \`Uint8Array\` or a numeric array, got \`${typeof value}\``, + ); + } +} + +/** +Convert a value to a `Uint8Array` without copying its data. + +This can be useful for converting a `Buffer` to a pure `Uint8Array`. `Buffer` is already an `Uint8Array` subclass, but [`Buffer` alters some behavior](https://sindresorhus.com/blog/goodbye-nodejs-buffer), so it can be useful to cast it to a pure `Uint8Array` before returning it. + +Tip: If you want a copy, just call `.slice()` on the return value. +*/ +export function toUint8Array( + value: TypedArray | ArrayBuffer | DataView, +): Uint8Array { + if (value instanceof ArrayBuffer) { + return new Uint8Array(value); + } + + if (ArrayBuffer.isView(value)) { + return new Uint8Array(value.buffer, value.byteOffset, value.byteLength); + } + + throw new TypeError(`Unsupported value, got \`${typeof value}\`.`); +} + +/** +Concatenate the given arrays into a new array. + +If `arrays` is empty, it will return a zero-sized `Uint8Array`. + +If `totalLength` is not specified, it is calculated from summing the lengths of the given arrays. + +Replacement for [`Buffer.concat()`](https://nodejs.org/api/buffer.html#static-method-bufferconcatlist-totallength). + +@example +``` +import {concatUint8Arrays} from 'uint8array-extras'; + +const a = new Uint8Array([1, 2, 3]); +const b = new Uint8Array([4, 5, 6]); + +console.log(concatUint8Arrays([a, b])); +//=> Uint8Array [1, 2, 3, 4, 5, 6] +``` +*/ +export function concatUint8Arrays( + arrays: readonly (Uint8Array | ArrayLike)[], + totalLength?: number, +): Uint8Array { + if (arrays.length === 0) { + return new Uint8Array(0); + } + + totalLength ??= arrays.reduce( + (accumulator, currentValue) => accumulator + currentValue.length, + 0, + ); + + const returnValue = new Uint8Array(totalLength); + + let offset = 0; + for (let array of arrays) { + if (isUint8Array(array)) { + if (offset + array.length > totalLength) { + array = array.subarray(0, totalLength - offset); + } + } else if (isArrayLike(array)) { + if (offset + array.length > totalLength) { + array = Uint8Array.from(array).subarray( + 0, + totalLength - offset, + ); + } + } else { + throw new TypeError( + `Expected \`Uint8Array\` or a numeric array, got \`${typeof array}\``, + ); + } + returnValue.set(array, offset); + offset += array.length; + if (offset >= totalLength) break; + } + + return returnValue; +} + +/** +Check if two arrays are identical by verifying that they contain the same bytes in the same sequence. + +Replacement for [`Buffer#equals()`](https://nodejs.org/api/buffer.html#bufequalsotherbuffer). + +@example +``` +import {areUint8ArraysEqual} from 'uint8array-extras'; + +const a = new Uint8Array([1, 2, 3]); +const b = new Uint8Array([1, 2, 3]); +const c = new Uint8Array([4, 5, 6]); + +console.log(areUint8ArraysEqual(a, b)); +//=> true + +console.log(areUint8ArraysEqual(a, c)); +//=> false +``` +*/ +export function areUint8ArraysEqual(a: Uint8Array, b: Uint8Array): boolean { + assertUint8Array(a); + assertUint8Array(b); + + if (a === b) { + return true; + } + + if (a.length !== b.length) { + return false; + } + + for (let index = 0; index < a.length; index++) { + if (a[index] !== b[index]) { + return false; + } + } + + return true; +} + +/** +Compare two arrays and indicate their relative order or equality. Useful for sorting. + +Replacement for [`Buffer.compare()`](https://nodejs.org/api/buffer.html#static-method-buffercomparebuf1-buf2). + +@example +``` +import {compareUint8Arrays} from 'uint8array-extras'; + +const array1 = new Uint8Array([1, 2, 3]); +const array2 = new Uint8Array([4, 5, 6]); +const array3 = new Uint8Array([7, 8, 9]); + +[array3, array1, array2].sort(compareUint8Arrays); +//=> [[1, 2, 3], [4, 5, 6], [7, 8, 9]] +``` +*/ +export function compareUint8Arrays(a: Uint8Array, b: Uint8Array): 0 | 1 | -1 { + assertUint8Array(a); + assertUint8Array(b); + + const length = Math.min(a.length, b.length); + + for (let index = 0; index < length; index++) { + const diff = a[index] - b[index]; + if (diff !== 0) { + return Math.sign(diff) as 1 | -1; + } + } + + // At this point, all the compared elements are equal. + // The shorter array should come first if the arrays are of different lengths. + return Math.sign(a.length - b.length) as 1 | 0 | -1; +} + +const cachedDecoders: Record = { + utf8: new globalThis.TextDecoder("utf8"), +}; + +/** +Convert a `Uint8Array` to a string. + +@param encoding - The [encoding](https://developer.mozilla.org/en-US/docs/Web/API/Encoding_API/Encodings) to convert from. Default: `'utf8'` + +Replacement for [`Buffer#toString()`](https://nodejs.org/api/buffer.html#buftostringencoding-start-end). For the `encoding` parameter, `latin1` should be used instead of `binary` and `utf-16le` instead of `utf16le`. + +@example +``` +import {uint8ArrayToString} from 'uint8array-extras'; + +const byteArray = new Uint8Array([72, 101, 108, 108, 111]); +console.log(uint8ArrayToString(byteArray)); +//=> 'Hello' + +const zh = new Uint8Array([167, 65, 166, 110]); +console.log(uint8ArrayToString(zh, 'big5')); +//=> '你好' + +const ja = new Uint8Array([130, 177, 130, 241, 130, 201, 130, 191, 130, 205]); +console.log(uint8ArrayToString(ja, 'shift-jis')); +//=> 'こんにちは' +``` +*/ +export function uint8ArrayToString( + array: Uint8Array | ArrayBuffer, + encoding: string = "utf8", +): string { + assertUint8ArrayOrArrayBuffer(array); + cachedDecoders[encoding] ??= new globalThis.TextDecoder(encoding); + return cachedDecoders[encoding].decode(array); +} + +function assertString(value: unknown): asserts value is string { + if (typeof value !== "string") { + throw new TypeError(`Expected \`string\`, got \`${typeof value}\``); + } +} + +const cachedEncoder = new globalThis.TextEncoder(); + +/** +Convert a string to a `Uint8Array` (using UTF-8 encoding). + +Replacement for [`Buffer.from('Hello')`](https://nodejs.org/api/buffer.html#static-method-bufferfromstring-encoding). + +@example +``` +import {stringToUint8Array} from 'uint8array-extras'; + +console.log(stringToUint8Array('Hello')); +//=> Uint8Array [72, 101, 108, 108, 111] +``` +*/ +export function stringToUint8Array(string: string): Uint8Array { + assertString(string); + return cachedEncoder.encode(string); +} + +function base64ToBase64Url(base64: string): string { + return base64.replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, ""); +} + +function base64UrlToBase64(base64url: string): string { + return base64url.replaceAll("-", "+").replaceAll("_", "/"); +} + +// Reference: https://phuoc.ng/collection/this-vs-that/concat-vs-push/ +const MAX_BLOCK_SIZE = 65_535; + +/** +Convert a `Uint8Array` to a Base64-encoded string. + +Specify `{urlSafe: true}` to get a [Base64URL](https://base64.guru/standards/base64url)-encoded string. + +Replacement for [`Buffer#toString('base64')`](https://nodejs.org/api/buffer.html#buftostringencoding-start-end). + +@example +``` +import {uint8ArrayToBase64} from 'uint8array-extras'; + +const byteArray = new Uint8Array([72, 101, 108, 108, 111]); + +console.log(uint8ArrayToBase64(byteArray)); +//=> 'SGVsbG8=' +``` +*/ +export function uint8ArrayToBase64( + array: Uint8Array, + options?: { urlSafe: boolean }, +): string { + assertUint8Array(array); + + const { urlSafe = false } = options ?? {}; + + let base64; + + if (array.length < MAX_BLOCK_SIZE) { + // Required as `btoa` and `atob` don't properly support Unicode: https://developer.mozilla.org/en-US/docs/Glossary/Base64#the_unicode_problem + base64 = globalThis.btoa( + String.fromCodePoint.apply(null, array as any), + ); + } else { + base64 = ""; + for (const value of array) { + base64 += String.fromCodePoint(value); + } + + base64 = globalThis.btoa(base64); + } + + return urlSafe ? base64ToBase64Url(base64) : base64; +} + +/** +Convert a Base64-encoded or [Base64URL](https://base64.guru/standards/base64url)-encoded string to a `Uint8Array`. + +Replacement for [`Buffer.from('SGVsbG8=', 'base64')`](https://nodejs.org/api/buffer.html#static-method-bufferfromstring-encoding). + +@example +``` +import {base64ToUint8Array} from 'uint8array-extras'; + +console.log(base64ToUint8Array('SGVsbG8=')); +//=> Uint8Array [72, 101, 108, 108, 111] +``` +*/ +export function base64ToUint8Array(base64String: string): Uint8Array { + assertString(base64String); + return Uint8Array.from( + globalThis.atob(base64UrlToBase64(base64String)), + (x) => x.codePointAt(0)!, + ); +} + +/** +Encode a string to Base64-encoded string. + +Specify `{urlSafe: true}` to get a [Base64URL](https://base64.guru/standards/base64url)-encoded string. + +Replacement for `Buffer.from('Hello').toString('base64')` and [`btoa()`](https://developer.mozilla.org/en-US/docs/Web/API/btoa). + +@example +``` +import {stringToBase64} from 'uint8array-extras'; + +console.log(stringToBase64('Hello')); +//=> 'SGVsbG8=' +``` +*/ +export function stringToBase64( + string: string, + options?: { urlSafe: boolean }, +): string { + assertString(string); + return uint8ArrayToBase64(stringToUint8Array(string), options); +} + +/** +Decode a Base64-encoded or [Base64URL](https://base64.guru/standards/base64url)-encoded string to a string. + +Replacement for `Buffer.from('SGVsbG8=', 'base64').toString()` and [`atob()`](https://developer.mozilla.org/en-US/docs/Web/API/atob). + +@example +``` +import {base64ToString} from 'uint8array-extras'; + +console.log(base64ToString('SGVsbG8=')); +//=> 'Hello' +``` +*/ +export function base64ToString(base64String: string): string { + assertString(base64String); + return uint8ArrayToString(base64ToUint8Array(base64String)); +} + +const byteToHexLookupTable = Array.from( + { length: 256 }, + (_, index) => index.toString(16).padStart(2, "0"), +); + +/** +Convert a `Uint8Array` to a Hex string. + +Replacement for [`Buffer#toString('hex')`](https://nodejs.org/api/buffer.html#buftostringencoding-start-end). + +@example +``` +import {uint8ArrayToHex} from 'uint8array-extras'; + +const byteArray = new Uint8Array([72, 101, 108, 108, 111]); + +console.log(uint8ArrayToHex(byteArray)); +//=> '48656c6c6f' +``` +*/ +export function uint8ArrayToHex(array: Uint8Array): string { + assertUint8Array(array); + + // Concatenating a string is faster than using an array. + let hexString = ""; + + for (let index = 0; index < array.length; index++) { + hexString += byteToHexLookupTable[array[index]]; + } + + return hexString; +} + +const hexToDecimalLookupTable = { + 0: 0, + 1: 1, + 2: 2, + 3: 3, + 4: 4, + 5: 5, + 6: 6, + 7: 7, + 8: 8, + 9: 9, + a: 10, + b: 11, + c: 12, + d: 13, + e: 14, + f: 15, + A: 10, + B: 11, + C: 12, + D: 13, + E: 14, + F: 15, +}; + +/** +Convert a Hex string to a `Uint8Array`. + +Replacement for [`Buffer.from('48656c6c6f', 'hex')`](https://nodejs.org/api/buffer.html#static-method-bufferfromstring-encoding). + +@example +``` +import {hexToUint8Array} from 'uint8array-extras'; + +console.log(hexToUint8Array('48656c6c6f')); +//=> Uint8Array [72, 101, 108, 108, 111] +``` +*/ +export function hexToUint8Array(hexString: string): Uint8Array { + assertString(hexString); + + if (hexString.length % 2 !== 0) { + throw new Error("Invalid Hex string length."); + } + + const resultLength = hexString.length / 2; + const bytes = new Uint8Array(resultLength); + + for (let index = 0; index < resultLength; index++) { + const highNibble: number | undefined = + (hexToDecimalLookupTable as any)[hexString[index * 2]]; + const lowNibble: number | undefined = + (hexToDecimalLookupTable as any)[hexString[(index * 2) + 1]]; + + if (highNibble === undefined || lowNibble === undefined) { + throw new Error( + `Invalid Hex character encountered at position ${index * 2}`, + ); + } + + bytes[index] = (highNibble << 4) | lowNibble; + } + + return bytes; +} + +/** +Read `DataView#byteLength` number of bytes from the given view, up to 48-bit. + +Replacement for [`Buffer#readUintBE`](https://nodejs.org/api/buffer.html#bufreadintbeoffset-bytelength) + +@example +``` +import {getUintBE} from 'uint8array-extras'; + +const byteArray = new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90, 0xab]); + +console.log(getUintBE(new DataView(byteArray.buffer))); +//=> 20015998341291 +``` +*/ +export function getUintBE(view: DataView): number { + const { byteLength } = view; + + if (byteLength === 6) { + return (view.getUint16(0) * (2 ** 32)) + view.getUint32(2); + } + + if (byteLength === 5) { + return (view.getUint8(0) * (2 ** 32)) + view.getUint32(1); + } + + if (byteLength === 4) { + return view.getUint32(0); + } + + if (byteLength === 3) { + return (view.getUint8(0) * (2 ** 16)) + view.getUint16(1); + } + + if (byteLength === 2) { + return view.getUint16(0); + } + + if (byteLength === 1) { + return view.getUint8(0); + } + + throw new Error("Invalid DataView byteLength."); +} + +/** +Find the index of the first occurrence of the given sequence of bytes (`value`) within the given `Uint8Array` (`array`). + +Replacement for [`Buffer#indexOf`](https://nodejs.org/api/buffer.html#bufindexofvalue-byteoffset-encoding). `Uint8Array#indexOf` only takes a number which is different from Buffer's `indexOf` implementation. + +@example +``` +import {indexOf} from 'uint8array-extras'; + +const byteArray = new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef]); + +console.log(indexOf(byteArray, new Uint8Array([0x78, 0x90]))); +//=> 3 +``` +*/ +export function indexOf(array: Uint8Array, value: Uint8Array): number { + const arrayLength = array.length; + const valueLength = value.length; + + if (valueLength === 0) { + return -1; + } + + if (valueLength > arrayLength) { + return -1; + } + + const validOffsetLength = arrayLength - valueLength; + + for (let index = 0; index <= validOffsetLength; index++) { + let isMatch = true; + for (let index2 = 0; index2 < valueLength; index2++) { + if (array[index + index2] !== value[index2]) { + isMatch = false; + break; + } + } + + if (isMatch) { + return index; + } + } + + return -1; +} + +/** +Checks if the given sequence of bytes (`value`) is within the given `Uint8Array` (`array`). + +Returns true if the value is included, otherwise false. + +Replacement for [`Buffer#includes`](https://nodejs.org/api/buffer.html#bufincludesvalue-byteoffset-encoding). `Uint8Array#includes` only takes a number which is different from Buffer's `includes` implementation. + +``` +import {includes} from 'uint8array-extras'; + +const byteArray = new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef]); + +console.log(includes(byteArray, new Uint8Array([0x78, 0x90]))); +//=> true +``` +*/ +export function includes(array: Uint8Array, value: Uint8Array): boolean { + return indexOf(array, value) !== -1; +} diff --git a/packages/shared/src/utils.ts b/packages/shared/src/utils.ts index 7ebc171d30b6..640527a5966b 100644 --- a/packages/shared/src/utils.ts +++ b/packages/shared/src/utils.ts @@ -76,11 +76,6 @@ export function isEnumMember(enumeration: unknown, value: number): boolean { return typeof (enumeration as any)[value] === "string"; } -/** Skips the first n bytes of a buffer and returns the rest */ -export function skipBytes(buf: Buffer, n: number): Buffer { - return Buffer.from(buf.subarray(n)); -} - /** * Returns a throttled version of the given function. No matter how often the throttled version is called, * the underlying function is only called at maximum every `intervalMs` milliseconds. diff --git a/packages/testing/src/MockController.ts b/packages/testing/src/MockController.ts index cfec0742cde7..f2df234a56c4 100644 --- a/packages/testing/src/MockController.ts +++ b/packages/testing/src/MockController.ts @@ -213,7 +213,7 @@ export class MockController { /** Gets called when parsed/chunked data is received from the serial port */ private async serialOnData( data: - | Buffer + | Uint8Array | MessageHeaders.ACK | MessageHeaders.CAN | MessageHeaders.NAK, @@ -385,7 +385,7 @@ export class MockController { /** Sends a message header (ACK/NAK/CAN) to the host/driver */ private sendHeaderToHost(data: MessageHeaders): void { - this.serial.emitData(Buffer.from([data])); + this.serial.emitData(Uint8Array.from([data])); } /** Sends a raw buffer to the host/driver and expect an ACK */ @@ -393,7 +393,7 @@ export class MockController { msg: Message, fromNode?: MockNode, ): Promise { - let data: Buffer; + let data: Uint8Array; if (fromNode) { data = msg.serialize({ nodeIdType: this.encodingContext.nodeIdType, @@ -410,7 +410,7 @@ export class MockController { } /** Sends a raw buffer to the host/driver and expect an ACK */ - public async sendToHost(data: Buffer): Promise { + public async sendToHost(data: Uint8Array): Promise { this.serial.emitData(data); // TODO: make the timeout match the configured ACK timeout await this.expectHostACK(1000); @@ -423,7 +423,7 @@ export class MockController { if (this.corruptACK) { const highNibble = randomInt(1, 0xf) << 4; this.serial.emitData( - Buffer.from([highNibble | MessageHeaders.ACK]), + Uint8Array.from([highNibble | MessageHeaders.ACK]), ); } else { this.sendHeaderToHost(MessageHeaders.ACK); @@ -563,7 +563,7 @@ export interface MockControllerBehavior { */ onHostData?: ( controller: MockController, - data: Buffer, + data: Uint8Array, ) => Promise | boolean | undefined; /** Gets called when a message from the host is received. Return `true` to indicate that the message has been handled. */ onHostMessage?: ( diff --git a/packages/transformers/src/validateArgs/reason.ts b/packages/transformers/src/validateArgs/reason.ts index 4a5f419a929d..de60aa8cf7a9 100644 --- a/packages/transformers/src/validateArgs/reason.ts +++ b/packages/transformers/src/validateArgs/reason.ts @@ -45,6 +45,10 @@ export interface ExpectedBuffer { type: "buffer"; } +export interface ExpectedUint8Array { + type: "uint8array"; +} + export interface ExpectedClass { type: "class"; name: string; @@ -118,6 +122,7 @@ export type Reason = | ExpectedObject | ExpectedDate | ExpectedBuffer + | ExpectedUint8Array | ExpectedClass | ExpectedNonPrimitive | MissingObjectProperty diff --git a/packages/transformers/src/validateArgs/visitor-type-check.ts b/packages/transformers/src/validateArgs/visitor-type-check.ts index 4985be70a896..fd9359e4e2af 100644 --- a/packages/transformers/src/validateArgs/visitor-type-check.ts +++ b/packages/transformers/src/validateArgs/visitor-type-check.ts @@ -114,6 +114,38 @@ function visitBufferType(type: ts.ObjectType, visitorContext: VisitorContext) { }); } +function visitUint8ArrayType( + type: ts.ObjectType, + visitorContext: VisitorContext, +) { + const name = VisitorTypeName.visitType(type, visitorContext, { + type: "type-check", + }); + const f = visitorContext.factory; + return VisitorUtils.setFunctionIfNotExists(name, visitorContext, () => { + return VisitorUtils.createAssertionFunction( + f.createPrefixUnaryExpression( + ts.SyntaxKind.ExclamationToken, + f.createCallExpression( + f.createPropertyAccessExpression( + f.createCallExpression( + f.createIdentifier("require"), + undefined, + [f.createStringLiteral("@zwave-js/shared/safe")], + ), + f.createIdentifier("isUint8Array"), + ), + undefined, + [VisitorUtils.objectIdentifier], + ), + ), + { type: "uint8array" }, + name, + visitorContext, + ); + }); +} + function visitClassType(type: ts.ObjectType, visitorContext: VisitorContext) { const f = visitorContext.factory; const name = VisitorTypeName.visitType(type, visitorContext, { @@ -687,6 +719,8 @@ function visitObjectType(type: ts.ObjectType, visitorContext: VisitorContext) { return visitDateType(type, visitorContext); } else if (VisitorUtils.checkIsNodeBuffer(type)) { return visitBufferType(type, visitorContext); + } else if (VisitorUtils.checkIsUint8Array(type)) { + return visitUint8ArrayType(type, visitorContext); } else if (VisitorUtils.checkIsIgnoredIntrinsic(type)) { // This is an intrinsic type we can't check properly, so we just ignore it. return VisitorUtils.getIgnoredTypeFunction(visitorContext); diff --git a/packages/transformers/src/validateArgs/visitor-type-name.ts b/packages/transformers/src/validateArgs/visitor-type-name.ts index e21a26fc0bd6..251e611d3814 100644 --- a/packages/transformers/src/validateArgs/visitor-type-name.ts +++ b/packages/transformers/src/validateArgs/visitor-type-name.ts @@ -110,6 +110,8 @@ function visitObjectType( return visitTupleObjectType(type, visitorContext, mode); } else if (checkIsNodeBuffer(type)) { return "_buffer"; + } else if (VisitorUtils.checkIsUint8Array(type)) { + return "_uint8array"; } else if ( visitorContext.checker.getIndexTypeOfType(type, ts.IndexKind.Number) ) { diff --git a/packages/transformers/src/validateArgs/visitor-utils.ts b/packages/transformers/src/validateArgs/visitor-utils.ts index 5fc1e65ef75e..8247d2a149ee 100644 --- a/packages/transformers/src/validateArgs/visitor-utils.ts +++ b/packages/transformers/src/validateArgs/visitor-utils.ts @@ -84,6 +84,18 @@ export function checkIsNodeBuffer(type: ts.ObjectType): boolean { ); } +export function checkIsUint8Array(type: ts.ObjectType): boolean { + return ( + type.symbol !== undefined + && type.symbol.valueDeclaration !== undefined + && type.symbol.name === "Uint8Array" + && !!( + ts.getCombinedModifierFlags(type.symbol.valueDeclaration) + & ts.ModifierFlags.Ambient + ) + ); +} + export function checkIsIgnoredIntrinsic(type: ts.ObjectType): boolean { return ( type.symbol !== undefined diff --git a/packages/transformers/test/fixtures/testUint8Array.ts b/packages/transformers/test/fixtures/testUint8Array.ts new file mode 100644 index 000000000000..337ba7794640 --- /dev/null +++ b/packages/transformers/test/fixtures/testUint8Array.ts @@ -0,0 +1,35 @@ +/* eslint-disable no-restricted-globals */ +import { validateArgs } from "@zwave-js/transformers"; +import assert from "node:assert"; + +class Test { + @validateArgs() + foo(arg1: Uint8Array): void { + arg1; + return void 0; + } +} + +const test = new Test(); +// These should not throw +test.foo(new Uint8Array()); +test.foo(Uint8Array.from([0xaa, 0xbb, 0xcc])); +// These should also not throw +test.foo(Buffer.alloc(0)); + +// These should throw +assert.throws( + // @ts-expect-error + () => test.foo("string"), + /arg1 is not a Uint8Array/, +); +assert.throws( + // @ts-expect-error + () => test.foo(undefined), + /arg1 is not a Uint8Array/, +); +assert.throws( + // @ts-expect-error + () => test.foo({ type: "Buffer", data: [170, 187, 204] }), + /arg1 is not a Uint8Array/, +); diff --git a/packages/zwave-js/src/lib/controller/Controller.ts b/packages/zwave-js/src/lib/controller/Controller.ts index d83a8634f391..7d371d2149d5 100644 --- a/packages/zwave-js/src/lib/controller/Controller.ts +++ b/packages/zwave-js/src/lib/controller/Controller.ts @@ -355,11 +355,13 @@ import { } from "@zwave-js/serial/serialapi"; import type { TransmitReport } from "@zwave-js/serial/serialapi"; import { + Bytes, Mixin, type ReadonlyObjectKeyMap, type ReadonlyThrowingMap, type ThrowingMap, TypedEventEmitter, + areUint8ArraysEqual, cloneDeep, createThrowingMap, flatMap, @@ -556,11 +558,11 @@ export class ZWaveController return this._ownNodeId; } - private _dsk: Buffer | undefined; + private _dsk: Uint8Array | undefined; /** * The device specific key (DSK) of the controller in binary format. */ - public get dsk(): Buffer { + public get dsk(): Uint8Array { if (this._dsk == undefined) { const keyPair = this.driver.getLearnModeAuthenticatedKeyPair(); const publicKey = extractRawECDHPublicKey(keyPair.publicKey); @@ -847,7 +849,7 @@ export class ZWaveController } /** Returns the node with the given DSK */ - public getNodeByDSK(dsk: Buffer | string): ZWaveNode | undefined { + public getNodeByDSK(dsk: Uint8Array | string): ZWaveNode | undefined { try { if (typeof dsk === "string") dsk = dskFromString(dsk); } catch (e) { @@ -860,7 +862,7 @@ export class ZWaveController throw e; } for (const node of this._nodes.values()) { - if (node.dsk?.equals(dsk)) return node; + if (node.dsk && Bytes.view(node.dsk).equals(dsk)) return node; } } @@ -2658,7 +2660,8 @@ export class ZWaveController // Check if the node is on the provisioning list const entry = this.provisioningList.find((entry) => { if ( - !nwiHomeIdFromDSK(dskFromString(entry.dsk)).equals( + !areUint8ArraysEqual( + nwiHomeIdFromDSK(dskFromString(entry.dsk)), msg.nwiHomeId, ) ) { @@ -3562,7 +3565,7 @@ export class ZWaveController await abort(); return SecurityBootstrapFailure.NodeCanceled; } - const nodePublicKey = pubKeyResponse.publicKey; + const nodePublicKey = Bytes.from(pubKeyResponse.publicKey); // This is the starting point of the timer TAI2. const timerStartTAI2 = Date.now(); @@ -7287,7 +7290,7 @@ export class ZWaveController */ private async firmwareUpdateNVMWrite( offset: number, - buffer: Buffer, + buffer: Uint8Array, ): Promise { await this.driver.sendMessage( new FirmwareUpdateNVM_WriteRequest({ @@ -7362,7 +7365,7 @@ export class ZWaveController public async externalNVMReadBuffer( offset: number, length: number, - ): Promise { + ): Promise { const ret = await this.driver.sendMessage( new ExtNVMReadLongBufferRequest({ offset, @@ -7382,7 +7385,7 @@ export class ZWaveController public async externalNVMReadBuffer700( offset: number, length: number, - ): Promise<{ buffer: Buffer; endOfFile: boolean }> { + ): Promise<{ buffer: Uint8Array; endOfFile: boolean }> { const ret = await this.driver.sendMessage( new NVMOperationsReadRequest({ offset, @@ -7420,7 +7423,7 @@ export class ZWaveController public async externalNVMReadBufferExt( offset: number, length: number, - ): Promise<{ buffer: Buffer; endOfFile: boolean }> { + ): Promise<{ buffer: Uint8Array; endOfFile: boolean }> { const ret = await this.driver.sendMessage< ExtendedNVMOperationsResponse >( @@ -7465,7 +7468,7 @@ export class ZWaveController */ public async externalNVMWriteBuffer( offset: number, - buffer: Buffer, + buffer: Uint8Array, ): Promise { const ret = await this.driver.sendMessage< ExtNVMWriteLongBufferResponse @@ -7490,7 +7493,7 @@ export class ZWaveController */ public async externalNVMWriteBuffer700( offset: number, - buffer: Buffer, + buffer: Uint8Array, ): Promise<{ endOfFile: boolean }> { const ret = await this.driver.sendMessage( new NVMOperationsWriteRequest({ @@ -7531,7 +7534,7 @@ export class ZWaveController */ public async externalNVMWriteBufferExt( offset: number, - buffer: Buffer, + buffer: Uint8Array, ): Promise<{ endOfFile: boolean }> { const ret = await this.driver.sendMessage< ExtendedNVMOperationsResponse @@ -7671,7 +7674,7 @@ export class ZWaveController */ public async backupNVMRaw( onProgress?: (bytesRead: number, total: number) => void, - ): Promise { + ): Promise { this.driver.controllerLog.print("Backing up NVM..."); // Turn Z-Wave radio off to avoid having the protocol write to the NVM while dumping it @@ -7685,7 +7688,7 @@ export class ZWaveController // Disable watchdog to prevent resets during NVM access await this.stopWatchdog(); - let ret: Buffer; + let ret: Uint8Array; try { if (this.sdkVersionGte("7.0")) { ret = await this.backupNVMRaw700(onProgress); @@ -7711,7 +7714,7 @@ export class ZWaveController private async backupNVMRaw500( onProgress?: (bytesRead: number, total: number) => void, - ): Promise { + ): Promise { const size = nvmSizeToBufferSize((await this.getNVMId()).memorySize); if (!size) { throw new ZWaveError( @@ -7720,7 +7723,7 @@ export class ZWaveController ); } - const ret = Buffer.allocUnsafe(size); + const ret = new Bytes(size); let offset = 0; // Try reading the maximum size at first, the Serial API should return chunks in a size it supports // For some reason, there is no documentation and no official command for this @@ -7736,7 +7739,7 @@ export class ZWaveController chunkSize = 48; continue; } - chunk.copy(ret, offset); + ret.set(chunk, offset); offset += chunk.length; if (chunkSize > chunk.length) chunkSize = chunk.length; @@ -7748,12 +7751,12 @@ export class ZWaveController private async backupNVMRaw700( onProgress?: (bytesRead: number, total: number) => void, - ): Promise { + ): Promise { let open: () => Promise; let read: ( offset: number, length: number, - ) => Promise<{ buffer: Buffer; endOfFile: boolean }>; + ) => Promise<{ buffer: Uint8Array; endOfFile: boolean }>; let close: () => Promise; if ( @@ -7778,7 +7781,7 @@ export class ZWaveController // Open NVM for reading const size = await open(); - const ret = Buffer.allocUnsafe(size); + const ret = new Bytes(size); let offset = 0; // Try reading the maximum size at first, the Serial API should return chunks in a size it supports // For some reason, there is no documentation and no official command for this @@ -7795,7 +7798,7 @@ export class ZWaveController chunkSize = 48; continue; } - chunk.copy(ret, offset); + ret.set(chunk, offset); offset += chunk.length; if (chunkSize > chunk.length) chunkSize = chunk.length; @@ -7825,7 +7828,7 @@ export class ZWaveController * @param restoreProgress Can be used to monitor the progress of the restore operation, which may take several seconds up to a few minutes depending on the NVM size */ public async restoreNVM( - nvmData: Buffer, + nvmData: Uint8Array, convertProgress?: (bytesRead: number, total: number) => void, restoreProgress?: (bytesWritten: number, total: number) => void, ): Promise { @@ -7849,7 +7852,7 @@ export class ZWaveController this.driver.controllerLog.print( "Converting NVM to target format...", ); - let targetNVM: Buffer; + let targetNVM: Uint8Array; if (this.sdkVersionGte("7.0")) { targetNVM = await this.backupNVMRaw700(convertProgress); } else { @@ -7893,7 +7896,7 @@ export class ZWaveController * @param onProgress Can be used to monitor the progress of the operation, which may take several seconds up to a few minutes depending on the NVM size */ public async restoreNVMRaw( - nvmData: Buffer, + nvmData: Uint8Array, onProgress?: (bytesWritten: number, total: number) => void, ): Promise { this.driver.controllerLog.print("Restoring NVM..."); @@ -7953,7 +7956,7 @@ export class ZWaveController } private async restoreNVMRaw500( - nvmData: Buffer, + nvmData: Uint8Array, onProgress?: (bytesWritten: number, total: number) => void, ): Promise { const size = nvmSizeToBufferSize((await this.getNVMId()).memorySize); @@ -7965,7 +7968,7 @@ export class ZWaveController } else if (size !== nvmData.length) { // This might be a converted NVM buffer which contains only the first relevant part. // The first two bytes must point to the last byte in the buffer then - const actualSize = 1 + nvmData.readUInt16BE(0); + const actualSize = 1 + Bytes.view(nvmData).readUInt16BE(0); if (actualSize !== nvmData.length) { throw new ZWaveError( "The given data does not match the NVM size - cannot restore!", @@ -7974,13 +7977,15 @@ export class ZWaveController } // Now we only need to figure out which part of the NVM needs to be overwritten when restoring - const oldSize = 1 - + (await this.externalNVMReadBuffer(0, 2)).readUInt16BE(0); + const firstTwoNVMBytes = Bytes.view( + await this.externalNVMReadBuffer(0, 2), + ); + const oldSize = 1 + firstTwoNVMBytes.readUInt16BE(0); if (oldSize > actualSize) { // Pad the rest with 0xff - nvmData = Buffer.concat([ + nvmData = Bytes.concat([ nvmData, - Buffer.alloc(oldSize - actualSize, 0xff), + Bytes.alloc(oldSize - actualSize, 0xff), ]); } } @@ -8004,17 +8009,17 @@ export class ZWaveController } private async restoreNVMRaw700( - nvmData: Buffer, + nvmData: Uint8Array, onProgress?: (bytesWritten: number, total: number) => void, ): Promise { let open: () => Promise; let read: ( offset: number, length: number, - ) => Promise<{ buffer: Buffer; endOfFile: boolean }>; + ) => Promise<{ buffer: Uint8Array; endOfFile: boolean }>; let write: ( offset: number, - buffer: Buffer, + buffer: Uint8Array, ) => Promise<{ endOfFile: boolean }>; let close: () => Promise; @@ -8442,7 +8447,7 @@ export class ZWaveController * **WARNING:** A failure during this process may put your controller in recovery mode, rendering it unusable until a correct firmware image is uploaded. Use at your own risk! */ public async firmwareUpdateOTW( - data: Buffer, + data: Uint8Array, ): Promise { // Don't let two firmware updates happen in parallel if (this.isAnyOTAFirmwareUpdateInProgress()) { @@ -8487,7 +8492,7 @@ export class ZWaveController } private async firmwareUpdateOTW500( - data: Buffer, + data: Uint8Array, ): Promise { this._firmwareUpdateInProgress = true; let turnedRadioOff = false; @@ -8575,7 +8580,7 @@ export class ZWaveController } private async firmwareUpdateOTW700( - data: Buffer, + data: Uint8Array, ): Promise { this._firmwareUpdateInProgress = true; let destroy = false; @@ -8619,9 +8624,11 @@ export class ZWaveController const BLOCK_SIZE = 128; if (data.length % BLOCK_SIZE !== 0) { // Pad the data to a multiple of BLOCK_SIZE - data = Buffer.concat([ + data = Bytes.concat([ data, - Buffer.alloc(BLOCK_SIZE - (data.length % BLOCK_SIZE), 0xff), + new Bytes(BLOCK_SIZE - (data.length % BLOCK_SIZE)).fill( + 0xff, + ), ]); } const numFragments = Math.ceil(data.length / BLOCK_SIZE); @@ -9043,7 +9050,7 @@ export class ZWaveController // For the first part of the bootstrapping, a temporary key needs to be used this.driver["_securityManager"] = new SecurityManager({ ownNodeId: this._ownNodeId!, - networkKey: Buffer.alloc(16, 0), + networkKey: new Uint8Array(16).fill(0), nonceTimeout: this.driver.options.timeouts.nonce, }); @@ -9188,7 +9195,7 @@ export class ZWaveController return SecurityBootstrapFailure.NoKeysConfigured; } - const receivedKeys = new Map(); + const receivedKeys = new Map(); const deleteTempKey = () => { // Whatever happens, no further communication needs the temporary key @@ -9333,7 +9340,7 @@ export class ZWaveController ? this.driver.getLearnModeAuthenticatedKeyPair() : generateECDHKeyPair(); const publicKey = extractRawECDHPublicKey(keyPair.publicKey); - const transmittedPublicKey = Buffer.from(publicKey); + const transmittedPublicKey = Bytes.from(publicKey); if (requiresAuthentication) { // Authentication requires obfuscating the public key transmittedPublicKey.writeUInt16BE(0x0000, 0); diff --git a/packages/zwave-js/src/lib/controller/NVMIO.ts b/packages/zwave-js/src/lib/controller/NVMIO.ts index cdee587e9a6c..1a01c1dbf746 100644 --- a/packages/zwave-js/src/lib/controller/NVMIO.ts +++ b/packages/zwave-js/src/lib/controller/NVMIO.ts @@ -70,7 +70,7 @@ export class SerialNVMIO500 implements NVMIO { async read( offset: number, length: number, - ): Promise<{ buffer: Buffer; endOfFile: boolean }> { + ): Promise<{ buffer: Uint8Array; endOfFile: boolean }> { // Ensure we're not reading out of bounds const size = this.size; if (offset < 0 || offset >= size) { @@ -96,7 +96,7 @@ export class SerialNVMIO500 implements NVMIO { async write( offset: number, - data: Buffer, + data: Uint8Array, ): Promise<{ bytesWritten: number; endOfFile: boolean }> { // Ensure we're not writing out of bounds const size = this.size; @@ -162,10 +162,10 @@ export class SerialNVMIO700 implements NVMIO { private _read: ( offset: number, length: number, - ) => Promise<{ buffer: Buffer; endOfFile: boolean }>; + ) => Promise<{ buffer: Uint8Array; endOfFile: boolean }>; private _write: ( offset: number, - buffer: Buffer, + buffer: Uint8Array, ) => Promise<{ endOfFile: boolean }>; private _close: () => Promise; @@ -209,7 +209,7 @@ export class SerialNVMIO700 implements NVMIO { async read( offset: number, length: number, - ): Promise<{ buffer: Buffer; endOfFile: boolean }> { + ): Promise<{ buffer: Uint8Array; endOfFile: boolean }> { // Ensure we're not reading out of bounds const size = this.size; if (offset < 0 || offset >= size) { @@ -229,7 +229,7 @@ export class SerialNVMIO700 implements NVMIO { async write( offset: number, - data: Buffer, + data: Uint8Array, ): Promise<{ bytesWritten: number; endOfFile: boolean }> { // Ensure we're not writing out of bounds const size = this.size; diff --git a/packages/zwave-js/src/lib/driver/Bootloader.ts b/packages/zwave-js/src/lib/driver/Bootloader.ts index 132122ac4502..e402ec48e6cc 100644 --- a/packages/zwave-js/src/lib/driver/Bootloader.ts +++ b/packages/zwave-js/src/lib/driver/Bootloader.ts @@ -1,5 +1,6 @@ import { CRC16_CCITT, ZWaveError, ZWaveErrorCodes } from "@zwave-js/core"; import { XModemMessageHeaders } from "@zwave-js/serial"; +import { Bytes } from "@zwave-js/shared/safe"; export enum BootloaderState { Menu, @@ -9,7 +10,7 @@ export enum BootloaderState { /** Encapsulates information about the currently active bootloader */ export class Bootloader { public constructor( - private writeSerial: (data: Buffer) => Promise, + private writeSerial: (data: Uint8Array) => Promise, public readonly version: string, options: { num: number; option: string }[], ) { @@ -40,33 +41,33 @@ export class Bootloader { public async beginUpload(): Promise { await this.writeSerial( - Buffer.from(this.uploadOption.toString(), "ascii"), + Bytes.from(this.uploadOption.toString(), "ascii"), ); } public async runApplication(): Promise { - await this.writeSerial(Buffer.from(this.runOption.toString(), "ascii")); + await this.writeSerial(Bytes.from(this.runOption.toString(), "ascii")); } public async uploadFragment( fragmentNumber: number, - data: Buffer, + data: Uint8Array, ): Promise { - const command = Buffer.concat([ - Buffer.from([ + const command = Bytes.concat([ + Bytes.from([ XModemMessageHeaders.SOF, fragmentNumber & 0xff, 0xff - (fragmentNumber & 0xff), ]), data, - Buffer.allocUnsafe(2), + new Bytes(2), ]); - command.writeUint16BE(CRC16_CCITT(data, 0x0000), command.length - 2); + command.writeUInt16BE(CRC16_CCITT(data, 0x0000), command.length - 2); await this.writeSerial(command); } public async finishUpload(): Promise { - await this.writeSerial(Buffer.from([XModemMessageHeaders.EOT])); + await this.writeSerial(Bytes.from([XModemMessageHeaders.EOT])); } } diff --git a/packages/zwave-js/src/lib/driver/Driver.ts b/packages/zwave-js/src/lib/driver/Driver.ts index e82be5ffdc36..7901e6e66c28 100644 --- a/packages/zwave-js/src/lib/driver/Driver.ts +++ b/packages/zwave-js/src/lib/driver/Driver.ts @@ -166,13 +166,16 @@ import { } from "@zwave-js/serial/serialapi"; import { AsyncQueue, + Bytes, type ThrowingMap, TypedEventEmitter, + areUint8ArraysEqual, buffer2hex, cloneDeep, createWrappingCounter, getErrorMessage, isDocker, + isUint8Array, mergeDeep, noop, num2hex, @@ -419,7 +422,7 @@ function checkOptions(options: ZWaveOptions): void { ZWaveErrorCodes.Driver_InvalidOptions, ); } - if (keys.findIndex(([, k]) => k.equals(key)) !== i) { + if (keys.findIndex(([, k]) => areUint8ArraysEqual(k, key)) !== i) { throw new ZWaveError( `The security key for class ${secClass} was used multiple times!`, ZWaveErrorCodes.Driver_InvalidOptions, @@ -992,7 +995,7 @@ export class Driver extends TypedEventEmitter public getLearnModeAuthenticatedKeyPair(): KeyPair { if (this._learnModeAuthenticatedKeyPair == undefined) { // Try restoring from cache - const privateKey = this.cacheGet( + const privateKey = this.cacheGet( cacheKeys.controller.privateKey, ); if (privateKey) { @@ -1834,11 +1837,11 @@ export class Driver extends TypedEventEmitter ].map( (sc) => ([ sc, - this.cacheGet( + this.cacheGet( cacheKeys.controller.securityKeysLongRange(sc), ), - ] as [SecurityClass, Buffer | undefined]), - ).filter((v): v is [SecurityClass, Buffer] => + ] as [SecurityClass, Uint8Array | undefined]), + ).filter((v): v is [SecurityClass, Uint8Array] => v[1] != undefined ); if (securityKeysLongRange.length) { @@ -1878,7 +1881,7 @@ export class Driver extends TypedEventEmitter ); } } else { - const s0Key = this.cacheGet( + const s0Key = this.cacheGet( cacheKeys.controller.securityKeys(SecurityClass.S0_Legacy), ); if (s0Key) { @@ -1908,11 +1911,11 @@ export class Driver extends TypedEventEmitter const securityKeys = securityClassOrder.map( (sc) => ([ sc, - this.cacheGet( + this.cacheGet( cacheKeys.controller.securityKeys(sc), ), - ] as [SecurityClass, Buffer | undefined]), - ).filter((v): v is [SecurityClass, Buffer] => + ] as [SecurityClass, Uint8Array | undefined]), + ).filter((v): v is [SecurityClass, Uint8Array] => v[1] != undefined ); if (securityKeys.length) { @@ -2785,7 +2788,7 @@ export class Driver extends TypedEventEmitter event: ccArgs.eventLabel, }; if (ccArgs.parameters) { - if (Buffer.isBuffer(ccArgs.parameters)) { + if (isUint8Array(ccArgs.parameters)) { msg.parameters = buffer2hex(ccArgs.parameters); } else if (ccArgs.parameters instanceof Duration) { msg.duration = ccArgs.parameters.toString(); @@ -3273,7 +3276,7 @@ export class Driver extends TypedEventEmitter } // Preserve the private key for the authenticated learn mode ECDH key pair - const oldPrivateKey = this.cacheGet( + const oldPrivateKey = this.cacheGet( cacheKeys.controller.privateKey, ); @@ -3490,7 +3493,7 @@ export class Driver extends TypedEventEmitter */ private async serialport_onData( data: - | Buffer + | Uint8Array | MessageHeaders.ACK | MessageHeaders.CAN | MessageHeaders.NAK, @@ -3762,7 +3765,7 @@ export class Driver extends TypedEventEmitter /** Handles a decoding error and returns the desired reply to the stick */ private handleDecodeError( e: Error, - data: Buffer, + data: Uint8Array, msg: Message | undefined, ): MessageHeaders | undefined { if (isZWaveError(e)) { @@ -3817,8 +3820,7 @@ export class Driver extends TypedEventEmitter typeof e.context === "string" ? ` (Reason: ${e.context})` : "" - }: -0x${data.toString("hex")}`, + }:\n${buffer2hex(data)}`, "warn", ); } @@ -6434,11 +6436,11 @@ ${handlers.length} left`, */ private writeHeader(header: MessageHeaders): Promise { // ACK, CAN, NAK - return this.writeSerial(Buffer.from([header])); + return this.writeSerial(Uint8Array.from([header])); } /** Sends a raw datagram to the serialport (if that is open) */ - private async writeSerial(data: Buffer): Promise { + private async writeSerial(data: Uint8Array): Promise { return this.serial?.writeAsync(data); } @@ -7277,7 +7279,7 @@ ${handlers.length} left`, // It would be nicer to not hardcode the command here, but since we're switching stream parsers // mid-command - thus ignoring the ACK, we can't really use the existing communication machinery - const promise = this.writeSerial(Buffer.from("01030027db", "hex")); + const promise = this.writeSerial(Bytes.from("01030027db", "hex")); this.serial!.mode = ZWaveSerialMode.Bootloader; await promise; diff --git a/packages/zwave-js/src/lib/driver/Driver.unit.test.ts b/packages/zwave-js/src/lib/driver/Driver.unit.test.ts index c75d67da76b6..6a5019e55742 100644 --- a/packages/zwave-js/src/lib/driver/Driver.unit.test.ts +++ b/packages/zwave-js/src/lib/driver/Driver.unit.test.ts @@ -1,7 +1,7 @@ import { ZWaveErrorCodes, assertZWaveError } from "@zwave-js/core"; // import { Message, MessageType, messageTypes } from "@zwave-js/serial"; import { MockSerialPort } from "@zwave-js/serial/mock"; -import { mergeDeep } from "@zwave-js/shared"; +import { Bytes, mergeDeep } from "@zwave-js/shared"; import test from "ava"; import proxyquire from "proxyquire"; import sinon from "sinon"; @@ -182,11 +182,11 @@ test.serial("the constructor should throw on duplicate security keys", (t) => { () => { driver = new Driver("/dev/test", { securityKeys: { - S0_Legacy: Buffer.from( + S0_Legacy: Bytes.from( "0102030405060708090a0b0c0d0e0f10", "hex", ), - S2_Unauthenticated: Buffer.from( + S2_Unauthenticated: Bytes.from( "0102030405060708090a0b0c0d0e0f10", "hex", ), @@ -371,7 +371,7 @@ test("The exported driver presets work", (t) => { // // ); // // t.true(resolvedSpy.notCalled); // // // receive the ACK -// // await serialport.receiveData(Buffer.from([MessageHeaders.ACK])); +// // await serialport.receiveData(Uint8Array.from([MessageHeaders.ACK])); // // const msg = await promise; // // expect(msg).toBeInstanceOf(MockResponseMessage); diff --git a/packages/zwave-js/src/lib/driver/DriverMock.ts b/packages/zwave-js/src/lib/driver/DriverMock.ts index fc5a4eda279a..29bd2b3b69f9 100644 --- a/packages/zwave-js/src/lib/driver/DriverMock.ts +++ b/packages/zwave-js/src/lib/driver/DriverMock.ts @@ -37,7 +37,7 @@ export function createAndStartDriverWithMockPort( MockBinding.reset(); MockBinding.createPort(portAddress, { record: true, - readyData: Buffer.from([]), + readyData: new Uint8Array(), }); // This will be called when the driver has opened the serial port diff --git a/packages/zwave-js/src/lib/driver/MessageGenerators.ts b/packages/zwave-js/src/lib/driver/MessageGenerators.ts index 867da99ea8a6..4b2e87d86962 100644 --- a/packages/zwave-js/src/lib/driver/MessageGenerators.ts +++ b/packages/zwave-js/src/lib/driver/MessageGenerators.ts @@ -524,7 +524,7 @@ export const secureMessageGeneratorS0: MessageGeneratorImplementation< let additionalTimeoutMs: number | undefined; // Try to get a free nonce before requesting a new one - let nonce: Buffer | undefined = secMan.getFreeNonce(nodeId); + let nonce: Uint8Array | undefined = secMan.getFreeNonce(nodeId); if (!nonce) { // No free nonce, request a new one const cc = new SecurityCCNonceGet({ diff --git a/packages/zwave-js/src/lib/driver/NetworkCache.ts b/packages/zwave-js/src/lib/driver/NetworkCache.ts index 31d74e2117f0..28eb8ef1b93c 100644 --- a/packages/zwave-js/src/lib/driver/NetworkCache.ts +++ b/packages/zwave-js/src/lib/driver/NetworkCache.ts @@ -12,7 +12,7 @@ import { securityClassOrder, } from "@zwave-js/core"; import type { FileSystem } from "@zwave-js/host"; -import { getEnumMemberName, num2hex, pickDeep } from "@zwave-js/shared"; +import { Bytes, getEnumMemberName, num2hex, pickDeep } from "@zwave-js/shared"; import { isArray, isObject } from "alcalzone-shared/typeguards"; import path from "node:path"; import { @@ -362,10 +362,10 @@ function tryParseAssociationAddress( function tryParseBuffer( value: unknown, -): Buffer | undefined { +): Uint8Array | undefined { if (typeof value === "string") { try { - return Buffer.from(value, "hex"); + return Bytes.from(value, "hex"); } catch { // ignore } @@ -528,7 +528,7 @@ export function serializeNetworkCacheValue( return ret; } case "dsk": { - return dskToString(value as Buffer); + return dskToString(value as Uint8Array); } case "lastSeen": { // Dates are stored as timestamps @@ -536,13 +536,13 @@ export function serializeNetworkCacheValue( } case "deviceConfigHash": { - return (value as Buffer).toString("hex"); + return Bytes.view(value as Uint8Array).toString("hex"); } } // Other dynamic properties if (key.startsWith("controller.securityKeys.")) { - return (value as Buffer).toString("hex"); + return Bytes.view(value as Uint8Array).toString("hex"); } // Other fixed properties @@ -583,7 +583,7 @@ export function serializeNetworkCacheValue( return ret; } case cacheKeys.controller.privateKey: { - return (value as Buffer).toString("hex"); + return Bytes.view(value as Uint8Array).toString("hex"); } } diff --git a/packages/zwave-js/src/lib/driver/SerialAPICommandMachine.ts b/packages/zwave-js/src/lib/driver/SerialAPICommandMachine.ts index a527f22c612e..a5867330b147 100644 --- a/packages/zwave-js/src/lib/driver/SerialAPICommandMachine.ts +++ b/packages/zwave-js/src/lib/driver/SerialAPICommandMachine.ts @@ -41,7 +41,7 @@ export type SerialAPICommandError = export interface SerialAPICommandContext { msg: Message; - data: Buffer; + data: Uint8Array; attempts: number; maxAttempts: number; lastError?: SerialAPICommandError; @@ -88,7 +88,7 @@ export type SerialAPICommandDoneData = export interface SerialAPICommandServiceImplementations { timestamp: () => number; - sendData: (data: Buffer) => Promise; + sendData: (data: Uint8Array) => Promise; sendDataAbort: () => Promise; notifyRetry: ( lastError: SerialAPICommandError | undefined, @@ -141,7 +141,7 @@ export type SerialAPICommandMachineParams = { export function getSerialAPICommandMachineConfig( message: Message, - messageData: Buffer, + messageData: Uint8Array, { timestamp, logOutgoingMessage, @@ -450,7 +450,7 @@ export function getSerialAPICommandMachineOptions( export function createSerialAPICommandMachine( message: Message, - messageData: Buffer, + messageData: Uint8Array, implementations: SerialAPICommandServiceImplementations, params: SerialAPICommandMachineParams, ): SerialAPICommandMachine { diff --git a/packages/zwave-js/src/lib/driver/ZWaveOptions.ts b/packages/zwave-js/src/lib/driver/ZWaveOptions.ts index 76633f027a3f..0fc7ac7f8706 100644 --- a/packages/zwave-js/src/lib/driver/ZWaveOptions.ts +++ b/packages/zwave-js/src/lib/driver/ZWaveOptions.ts @@ -152,18 +152,18 @@ export interface ZWaveOptions extends ZWaveHostOptions { * Specify the security keys to use for encryption (Z-Wave Classic). Each one must be a Buffer of exactly 16 bytes. */ securityKeys?: { - S2_AccessControl?: Buffer; - S2_Authenticated?: Buffer; - S2_Unauthenticated?: Buffer; - S0_Legacy?: Buffer; + S2_AccessControl?: Uint8Array; + S2_Authenticated?: Uint8Array; + S2_Unauthenticated?: Uint8Array; + S0_Legacy?: Uint8Array; }; /** * Specify the security keys to use for encryption (Z-Wave Long Range). Each one must be a Buffer of exactly 16 bytes. */ securityKeysLongRange?: { - S2_AccessControl?: Buffer; - S2_Authenticated?: Buffer; + S2_AccessControl?: Uint8Array; + S2_Authenticated?: Uint8Array; }; /** diff --git a/packages/zwave-js/src/lib/driver/mDNSDiscovery.ts b/packages/zwave-js/src/lib/driver/mDNSDiscovery.ts index ae15b6643aac..bd72bad2bebc 100644 --- a/packages/zwave-js/src/lib/driver/mDNSDiscovery.ts +++ b/packages/zwave-js/src/lib/driver/mDNSDiscovery.ts @@ -1,3 +1,4 @@ +import { Bytes, isUint8Array } from "@zwave-js/shared"; import { isArray, isObject } from "alcalzone-shared/typeguards"; import createMDNSServer from "mdns-server"; @@ -53,8 +54,8 @@ export function discoverRemoteSerialPorts( const info: Record = {}; if (!!txt && isArray(txt.data)) { const strings = (txt.data as unknown[]) - .filter((d) => Buffer.isBuffer(d)) - .map((d) => d.toString()) + .filter((d) => isUint8Array(d)) + .map((d) => Bytes.view(d).toString("utf8")) .filter((d) => d.includes("=")); for (const string of strings) { diff --git a/packages/zwave-js/src/lib/node/Node.ts b/packages/zwave-js/src/lib/node/Node.ts index 66e7b13b4009..0932ca2b0644 100644 --- a/packages/zwave-js/src/lib/node/Node.ts +++ b/packages/zwave-js/src/lib/node/Node.ts @@ -213,6 +213,7 @@ import { } from "@zwave-js/serial/serialapi"; import { containsCC } from "@zwave-js/serial/serialapi"; import { + Bytes, Mixin, type TypedEventEmitter, cloneDeep, @@ -234,6 +235,7 @@ import { isArray, isObject } from "alcalzone-shared/typeguards"; import { EventEmitter } from "node:events"; import path from "node:path"; import { setTimeout as wait } from "node:timers/promises"; +import { isUint8Array } from "node:util/types"; import semver from "semver"; import { RemoveNodeReason } from "../controller/Inclusion"; import { determineNIF } from "../controller/NodeInformationFrame"; @@ -338,11 +340,11 @@ export class ZWaveNode extends ZWaveNodeMixins implements QuerySecurityClasses { * The device specific key (DSK) of this node in binary format. * This is only set if included with Security S2. */ - public get dsk(): Buffer | undefined { + public get dsk(): Uint8Array | undefined { return this.driver.cacheGet(cacheKeys.node(this.id).dsk); } /** @internal */ - public set dsk(value: Buffer | undefined) { + public set dsk(value: Uint8Array | undefined) { const cacheKey = cacheKeys.node(this.id).dsk; this.driver.cacheSet(cacheKey, value); } @@ -549,11 +551,11 @@ export class ZWaveNode extends ZWaveNodeMixins implements QuerySecurityClasses { * @internal * The hash of the device config that was applied during the last interview. */ - public get deviceConfigHash(): Buffer | undefined { + public get deviceConfigHash(): Uint8Array | undefined { return this.driver.cacheGet(cacheKeys.node(this.id).deviceConfigHash); } - private set deviceConfigHash(value: Buffer | undefined) { + private set deviceConfigHash(value: Uint8Array | undefined) { this.driver.cacheSet(cacheKeys.node(this.id).deviceConfigHash, value); } @@ -4390,7 +4392,7 @@ protocol version: ${this.protocolVersion}`; if (value === 0) { // Generic idle notification, this contains a value to be reset if ( - Buffer.isBuffer(command.eventParameters) + isUint8Array(command.eventParameters) && command.eventParameters.length ) { // The target value is the first byte of the event parameters @@ -6284,7 +6286,7 @@ ${formatRouteHealthCheckSummary(this.id, otherNode.id, summary)}`, // If it was, a change in hash means the config has changed if (actualHash && this.deviceConfigHash) { - return !actualHash.equals(this.deviceConfigHash); + return !Bytes.view(actualHash).equals(this.deviceConfigHash); } return true; } diff --git a/packages/zwave-js/src/lib/node/VirtualEndpoint.test.ts b/packages/zwave-js/src/lib/node/VirtualEndpoint.test.ts index 51b6dcd4d116..0c802e634042 100644 --- a/packages/zwave-js/src/lib/node/VirtualEndpoint.test.ts +++ b/packages/zwave-js/src/lib/node/VirtualEndpoint.test.ts @@ -7,7 +7,7 @@ import { } from "@zwave-js/core"; import { FunctionType } from "@zwave-js/serial"; import type { MockSerialPort } from "@zwave-js/serial/mock"; -import type { ThrowingMap } from "@zwave-js/shared"; +import { Bytes, type ThrowingMap } from "@zwave-js/shared"; import ava, { type ExecutionContext, type TestFn } from "ava"; import { setTimeout as wait } from "node:timers/promises"; import { ZWaveController } from "../controller/Controller"; @@ -61,7 +61,7 @@ test.serial( (t) => { const { driver } = t.context; const broadcast = driver.controller.getBroadcastNode(); - assertZWaveError(t, () => broadcast.createAPI(0xbada55), { + assertZWaveError(t, () => broadcast.createAPI(0xbada55 as any), { errorCode: ZWaveErrorCodes.CC_NoAPI, messageMatches: "no associated API", }); @@ -277,7 +277,7 @@ test.serial( // └─[BasicCCSet] t.deepEqual( serialport.lastWrite, - Buffer.from("010a0013ff0320016325017c", "hex"), + Bytes.from("010a0013ff0320016325017c", "hex"), ); }, ); @@ -297,7 +297,7 @@ test.serial( // └─[BasicCCSet] t.deepEqual( serialport.lastWrite, - Buffer.from("010c001402020303200163250181", "hex"), + Bytes.from("010c001402020303200163250181", "hex"), ); }, ); diff --git a/packages/zwave-js/src/lib/node/_Types.ts b/packages/zwave-js/src/lib/node/_Types.ts index 9a996dee8a2b..7e217441e0a0 100644 --- a/packages/zwave-js/src/lib/node/_Types.ts +++ b/packages/zwave-js/src/lib/node/_Types.ts @@ -168,7 +168,7 @@ export interface ZWaveNotificationCallbackArgs_EntryControlCC { dataType: EntryControlDataTypes; /** A human-readable label for the data type */ dataTypeLabel: string; - eventData?: Buffer | string; + eventData?: Uint8Array | string; } /** diff --git a/packages/zwave-js/src/lib/node/mixins/70_FirmwareUpdate.ts b/packages/zwave-js/src/lib/node/mixins/70_FirmwareUpdate.ts index e84a1cec48ec..778e3ab6b683 100644 --- a/packages/zwave-js/src/lib/node/mixins/70_FirmwareUpdate.ts +++ b/packages/zwave-js/src/lib/node/mixins/70_FirmwareUpdate.ts @@ -640,7 +640,7 @@ export abstract class FirmwareUpdateMixin extends SchedulePollMixin /** Kicks off a firmware update of a single target. Returns whether the node accepted resuming and non-secure transfer */ private async *beginFirmwareUpdateInternal( - data: Buffer, + data: Uint8Array, target: number, meta: FirmwareUpdateMetaData, fragmentSize: number, @@ -761,7 +761,7 @@ export abstract class FirmwareUpdateMixin extends SchedulePollMixin private async sendCorruptedFirmwareUpdateReport( reportNum: number, - fragment: Buffer, + fragment: Uint8Array, nonSecureTransfer: boolean = false, ): Promise { try { @@ -792,7 +792,7 @@ export abstract class FirmwareUpdateMixin extends SchedulePollMixin } private async *doFirmwareUpdateInternal( - data: Buffer, + data: Uint8Array, fragmentSize: number, nonSecureTransfer: boolean, abortContext: AbortFirmwareUpdateContext, diff --git a/packages/zwave-js/src/lib/node/mockCCBehaviors/UserCode.ts b/packages/zwave-js/src/lib/node/mockCCBehaviors/UserCode.ts index c72a8369a610..48721fc0e62d 100644 --- a/packages/zwave-js/src/lib/node/mockCCBehaviors/UserCode.ts +++ b/packages/zwave-js/src/lib/node/mockCCBehaviors/UserCode.ts @@ -17,6 +17,7 @@ import { UserCodeCCUsersNumberReport, } from "@zwave-js/cc/UserCodeCC"; import { CRC16_CCITT, CommandClasses } from "@zwave-js/core/safe"; +import { Bytes } from "@zwave-js/shared"; import { type MockNodeBehavior, type UserCodeCCCapabilities, @@ -262,14 +263,14 @@ const respondToUserCodeUserCodeChecksumGet: MockNodeBehavior = { ), }; if (capabilities.supportsUserCodeChecksum) { - let data = Buffer.allocUnsafe(0); + let data = new Bytes(); for (let i = 1; i <= capabilities.numUsers; i++) { const status = self.state.get( StateKeys.userIdStatus(i), ) as UserIDStatus; let code = (self.state.get(StateKeys.userCode(i)) ?? "") as | string - | Buffer; + | Bytes; if ( status === undefined || status === UserIDStatus.Available @@ -277,14 +278,14 @@ const respondToUserCodeUserCodeChecksumGet: MockNodeBehavior = { ) { continue; } - const tmp = Buffer.allocUnsafe(3 + code.length); + const tmp = new Bytes(3 + code.length); tmp.writeUInt16BE(i, 0); tmp[2] = status; if (typeof code === "string") { - code = Buffer.from(code, "ascii"); + code = Bytes.from(code, "ascii"); } - code.copy(tmp, 3); - data = Buffer.concat([data, tmp]); + tmp.set(code, 3); + data = Bytes.concat([data, tmp]); } const checksum = data.length > 0 ? CRC16_CCITT(data) : 0x0000; diff --git a/packages/zwave-js/src/lib/test/cc-specific/meterReportPropertyKeyName.test.ts b/packages/zwave-js/src/lib/test/cc-specific/meterReportPropertyKeyName.test.ts index daf61bbefdec..76f043bc7608 100644 --- a/packages/zwave-js/src/lib/test/cc-specific/meterReportPropertyKeyName.test.ts +++ b/packages/zwave-js/src/lib/test/cc-specific/meterReportPropertyKeyName.test.ts @@ -1,5 +1,6 @@ // repro from https://github.com/zwave-js/zwave-js-ui/issues/101#issuecomment-749007701 +import { Bytes } from "@zwave-js/shared"; import path from "node:path"; import { integrationTest } from "../integrationTestSuite"; @@ -22,7 +23,7 @@ integrationTest( await Promise.all([ valueAddedPromise, mockController.sendToHost( - Buffer.from( + Bytes.from( "0116000400020e3202214400013707012d00013707d5004d", "hex", ), diff --git a/packages/zwave-js/src/lib/test/cc-specific/notificationEnums.test.ts b/packages/zwave-js/src/lib/test/cc-specific/notificationEnums.test.ts index bec1f61186de..6a6d72ff5fb4 100644 --- a/packages/zwave-js/src/lib/test/cc-specific/notificationEnums.test.ts +++ b/packages/zwave-js/src/lib/test/cc-specific/notificationEnums.test.ts @@ -53,7 +53,7 @@ integrationTest( nodeId: mockController.ownNodeId, notificationType: 0x0f, notificationEvent: 0x01, - eventParameters: Buffer.from([0x00]), // Off / Closed + eventParameters: Uint8Array.from([0x00]), // Off / Closed }); await mockNode.sendToController( createMockZWaveRequestFrame(cc, { @@ -70,7 +70,7 @@ integrationTest( nodeId: mockController.ownNodeId, notificationType: 0x0f, notificationEvent: 0x01, - eventParameters: Buffer.from([0x01]), // On / Open + eventParameters: Uint8Array.from([0x01]), // On / Open }); await mockNode.sendToController( createMockZWaveRequestFrame(cc, { @@ -128,7 +128,7 @@ integrationTest( nodeId: mockController.ownNodeId, notificationType: 0x06, notificationEvent: 0x16, - eventParameters: Buffer.from([0x00]), // open in regular position + eventParameters: Uint8Array.from([0x00]), // open in regular position }); await mockNode.sendToController( createMockZWaveRequestFrame(cc, { @@ -145,7 +145,7 @@ integrationTest( nodeId: mockController.ownNodeId, notificationType: 0x06, notificationEvent: 0x16, - eventParameters: Buffer.from([0x01]), // open in tilt position + eventParameters: Uint8Array.from([0x01]), // open in tilt position }); await mockNode.sendToController( createMockZWaveRequestFrame(cc, { @@ -244,7 +244,7 @@ integrationTest("The 'simple' Door state value works correctly", { nodeId: mockController.ownNodeId, notificationType: 0x06, notificationEvent: 0x16, // Window/door is open - eventParameters: Buffer.from([0x01]), // ... in tilt position + eventParameters: Uint8Array.from([0x01]), // ... in tilt position }); await mockNode.sendToController( createMockZWaveRequestFrame(cc, { @@ -263,7 +263,7 @@ integrationTest("The 'simple' Door state value works correctly", { nodeId: mockController.ownNodeId, notificationType: 0x06, notificationEvent: 0x16, // Window/door is open - eventParameters: Buffer.from([0x00]), // ... in regular position + eventParameters: Uint8Array.from([0x00]), // ... in regular position }); await mockNode.sendToController( createMockZWaveRequestFrame(cc, { @@ -350,7 +350,7 @@ integrationTest("The synthetic 'Door tilt state' value works correctly", { nodeId: mockController.ownNodeId, notificationType: 0x06, notificationEvent: 0x16, // Window/door is open - eventParameters: Buffer.from([0x00]), // ... in regular position + eventParameters: Uint8Array.from([0x00]), // ... in regular position }); await mockNode.sendToController( createMockZWaveRequestFrame(cc, { @@ -370,7 +370,7 @@ integrationTest("The synthetic 'Door tilt state' value works correctly", { nodeId: mockController.ownNodeId, notificationType: 0x06, notificationEvent: 0x16, // Window/door is open - eventParameters: Buffer.from([0x01]), // ... in tilt position + eventParameters: Uint8Array.from([0x01]), // ... in tilt position }); await mockNode.sendToController( createMockZWaveRequestFrame(cc, { @@ -391,7 +391,7 @@ integrationTest("The synthetic 'Door tilt state' value works correctly", { nodeId: mockController.ownNodeId, notificationType: 0x06, notificationEvent: 0x16, // Window/door is open - eventParameters: Buffer.from([0x00]), // ... in regular position + eventParameters: Uint8Array.from([0x00]), // ... in regular position }); await mockNode.sendToController( createMockZWaveRequestFrame(cc, { @@ -410,7 +410,7 @@ integrationTest("The synthetic 'Door tilt state' value works correctly", { nodeId: mockController.ownNodeId, notificationType: 0x06, notificationEvent: 0x16, // Window/door is open - eventParameters: Buffer.from([0x01]), // ... in tilt position + eventParameters: Uint8Array.from([0x01]), // ... in tilt position }); await mockNode.sendToController( createMockZWaveRequestFrame(cc, { @@ -447,7 +447,7 @@ integrationTest("The synthetic 'Door tilt state' value works correctly", { nodeId: mockController.ownNodeId, notificationType: 0x06, notificationEvent: 0x16, // Window/door is open - eventParameters: Buffer.from([0x01]), // ... in tilt position + eventParameters: Uint8Array.from([0x01]), // ... in tilt position }); await mockNode.sendToController( createMockZWaveRequestFrame(cc, { @@ -524,7 +524,7 @@ integrationTest( nodeId: mockController.ownNodeId, notificationType: 0x05, notificationEvent: 0x07, - eventParameters: Buffer.from([0x02]), // Below low threshold + eventParameters: Uint8Array.from([0x02]), // Below low threshold }); await mockNode.sendToController( createMockZWaveRequestFrame(cc, { diff --git a/packages/zwave-js/src/lib/test/cc/API.test.ts b/packages/zwave-js/src/lib/test/cc/API.test.ts index 5770a3b76845..97762861ac6e 100644 --- a/packages/zwave-js/src/lib/test/cc/API.test.ts +++ b/packages/zwave-js/src/lib/test/cc/API.test.ts @@ -51,6 +51,6 @@ test.after.always(async (t) => { test.serial(`supportsCommand() returns NOT_KNOWN by default`, (t) => { const { node2, driver } = t.context; - const API = new DummyCCAPI(node2); + const API = new DummyCCAPI(driver, node2); t.is(API.supportsCommand(null as any), NOT_KNOWN); }); diff --git a/packages/zwave-js/src/lib/test/cc/AssociationCC.test.ts b/packages/zwave-js/src/lib/test/cc/AssociationCC.test.ts index dd74763c5d38..5419f60855f0 100644 --- a/packages/zwave-js/src/lib/test/cc/AssociationCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/AssociationCC.test.ts @@ -9,11 +9,12 @@ import { CommandClass, } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; +import { Bytes } from "@zwave-js/shared/safe"; import test from "ava"; -function buildCCBuffer(payload: Buffer): Buffer { - return Buffer.concat([ - Buffer.from([ +function buildCCBuffer(payload: Uint8Array): Uint8Array { + return Bytes.concat([ + Uint8Array.from([ CommandClasses.Association, // CC ]), payload, @@ -25,7 +26,7 @@ test("the SupportedGroupingsGet command should serialize correctly", (t) => { nodeId: 1, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ AssociationCommand.SupportedGroupingsGet, // CC Command ]), ); @@ -34,7 +35,7 @@ test("the SupportedGroupingsGet command should serialize correctly", (t) => { test("the SupportedGroupingsReport command should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ AssociationCommand.SupportedGroupingsReport, // CC Command 7, // # of groups ]), @@ -55,7 +56,7 @@ test("the Set command should serialize correctly", (t) => { nodeIds: [1, 2, 5], }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ AssociationCommand.Set, // CC Command 5, // group id // Node IDs @@ -72,7 +73,7 @@ test("the Get command should serialize correctly", (t) => { groupId: 9, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ AssociationCommand.Get, // CC Command 9, // group ID ]), @@ -82,7 +83,7 @@ test("the Get command should serialize correctly", (t) => { test("the Report command should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ AssociationCommand.Report, // CC Command 5, // group id 9, // max nodes @@ -112,7 +113,7 @@ test("the Remove command should serialize correctly", (t) => { nodeIds: [1, 2, 5], }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ AssociationCommand.Remove, // CC Command 5, // group id // Node IDs @@ -130,7 +131,7 @@ test("the Remove command should serialize correctly (empty node list)", (t) => { groupId: 5, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ AssociationCommand.Remove, // CC Command 5, // group id ]), @@ -141,7 +142,7 @@ test("the Remove command should serialize correctly (empty node list)", (t) => { // test("deserializing an unsupported command should return an unspecified version of AssociationCC", (t) => { // const serializedCC = buildCCBuffer( // 1, -// Buffer.from([255]), // not a valid command +// Uint8Array.from([255]), // not a valid command // ); // const cc: any = new AssociationCC({ // data: serializedCC, diff --git a/packages/zwave-js/src/lib/test/cc/AssociationGroupInfoCC.test.ts b/packages/zwave-js/src/lib/test/cc/AssociationGroupInfoCC.test.ts index 3e924a7af981..35b1c004af67 100644 --- a/packages/zwave-js/src/lib/test/cc/AssociationGroupInfoCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/AssociationGroupInfoCC.test.ts @@ -12,11 +12,12 @@ import { CommandClass, } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; +import { Bytes } from "@zwave-js/shared/safe"; import test from "ava"; -function buildCCBuffer(payload: Buffer): Buffer { - return Buffer.concat([ - Buffer.from([ +function buildCCBuffer(payload: Uint8Array): Uint8Array { + return Bytes.concat([ + Uint8Array.from([ CommandClasses["Association Group Information"], // CC ]), payload, @@ -29,7 +30,7 @@ test("the NameGet command should serialize correctly", (t) => { groupId: 7, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ AssociationGroupInfoCommand.NameGet, // CC Command 7, // group id ]), @@ -39,7 +40,7 @@ test("the NameGet command should serialize correctly", (t) => { test("the NameReport command should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ AssociationGroupInfoCommand.NameReport, // CC Command 7, // group id 6, // name length @@ -70,7 +71,7 @@ test("the InfoGet command should serialize correctly (no flag set)", (t) => { refreshCache: false, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ AssociationGroupInfoCommand.InfoGet, // CC Command 0, // flags 7, // group id @@ -87,7 +88,7 @@ test("the InfoGet command should serialize correctly (refresh cache flag set)", refreshCache: true, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ AssociationGroupInfoCommand.InfoGet, // CC Command 0b1000_0000, // flags 7, // group id @@ -104,7 +105,7 @@ test("the InfoGet command should serialize correctly (list mode flag set)", (t) refreshCache: false, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ AssociationGroupInfoCommand.InfoGet, // CC Command 0b0100_0000, // flags 0, // group id is ignored @@ -115,7 +116,7 @@ test("the InfoGet command should serialize correctly (list mode flag set)", (t) test("the Info Report command should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ AssociationGroupInfoCommand.InfoReport, // CC Command 0b1100_0000 | 2, // Flags | group count 1, // group id @@ -162,7 +163,7 @@ test("the CommandListGet command should serialize correctly", (t) => { allowCache: true, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ AssociationGroupInfoCommand.CommandListGet, // CC Command 0b1000_0000, // allow cache 6, // group id @@ -173,7 +174,7 @@ test("the CommandListGet command should serialize correctly", (t) => { test("the CommandListReport command should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ AssociationGroupInfoCommand.CommandListReport, // CC Command 7, // group id 5, // list length in bytes @@ -202,7 +203,7 @@ test("the CommandListReport command should be deserialized correctly", (t) => { test("deserializing an unsupported command should return an unspecified version of AssociationGroupInfoCC", (t) => { const serializedCC = buildCCBuffer( - Buffer.from([255]), // not a valid command + Uint8Array.from([255]), // not a valid command ); const cc = CommandClass.parse( serializedCC, diff --git a/packages/zwave-js/src/lib/test/cc/BasicCC.test.ts b/packages/zwave-js/src/lib/test/cc/BasicCC.test.ts index 84c702d7648d..24d0e2a9c210 100644 --- a/packages/zwave-js/src/lib/test/cc/BasicCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/BasicCC.test.ts @@ -10,15 +10,16 @@ import { } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; import { createTestingHost } from "@zwave-js/host"; +import { Bytes } from "@zwave-js/shared/safe"; import test from "ava"; import * as nodeUtils from "../../node/utils"; import { type CreateTestNodeOptions, createTestNode } from "../mocks"; const host = createTestingHost(); -function buildCCBuffer(payload: Buffer): Buffer { - return Buffer.concat([ - Buffer.from([ +function buildCCBuffer(payload: Uint8Array): Uint8Array { + return Bytes.concat([ + Uint8Array.from([ CommandClasses.Basic, // CC ]), payload, @@ -28,7 +29,7 @@ function buildCCBuffer(payload: Buffer): Buffer { test("the Get command should serialize correctly", (t) => { const basicCC = new BasicCCGet({ nodeId: 1 }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ BasicCommand.Get, // CC Command ]), ); @@ -37,7 +38,7 @@ test("the Get command should serialize correctly", (t) => { test("the Get command should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ BasicCommand.Get, // CC Command ]), ); @@ -55,7 +56,7 @@ test("the Set command should serialize correctly", (t) => { targetValue: 55, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ BasicCommand.Set, // CC Command 55, // target value ]), @@ -65,7 +66,7 @@ test("the Set command should serialize correctly", (t) => { test("the Report command (v1) should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ BasicCommand.Report, // CC Command 55, // current value ]), @@ -83,7 +84,7 @@ test("the Report command (v1) should be deserialized correctly", (t) => { test("the Report command (v2) should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ BasicCommand.Report, // CC Command 55, // current value 66, // target value @@ -104,7 +105,7 @@ test("the Report command (v2) should be deserialized correctly", (t) => { test("deserializing an unsupported command should return an unspecified version of BasicCC", (t) => { const serializedCC = buildCCBuffer( - Buffer.from([255]), // not a valid command + Uint8Array.from([255]), // not a valid command ); const basicCC = CommandClass.parse( serializedCC, diff --git a/packages/zwave-js/src/lib/test/cc/BatteryCC.test.ts b/packages/zwave-js/src/lib/test/cc/BatteryCC.test.ts index d2efa97ee728..9ead114b25eb 100644 --- a/packages/zwave-js/src/lib/test/cc/BatteryCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/BatteryCC.test.ts @@ -8,11 +8,12 @@ import { CommandClass, } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; +import { Bytes } from "@zwave-js/shared"; import test from "ava"; test("the Get command should serialize correctly", (t) => { const batteryCC = new BatteryCCGet({ nodeId: 1 }); - const expected = Buffer.from([ + const expected = Bytes.from([ CommandClasses.Battery, // CC BatteryCommand.Get, // CC Command ]); @@ -20,7 +21,7 @@ test("the Get command should serialize correctly", (t) => { }); test("the Report command (v1) should be deserialized correctly: when the battery is not low", (t) => { - const ccData = Buffer.from([ + const ccData = Uint8Array.from([ CommandClasses.Battery, // CC BatteryCommand.Report, // CC Command 55, // current value @@ -36,7 +37,7 @@ test("the Report command (v1) should be deserialized correctly: when the battery }); test("the Report command (v1) should be deserialized correctly: when the battery is low", (t) => { - const ccData = Buffer.from([ + const ccData = Uint8Array.from([ CommandClasses.Battery, // CC BatteryCommand.Report, // CC Command 0xff, // current value @@ -52,7 +53,7 @@ test("the Report command (v1) should be deserialized correctly: when the battery }); test("the Report command (v2) should be deserialized correctly: all flags set", (t) => { - const ccData = Buffer.from([ + const ccData = Uint8Array.from([ CommandClasses.Battery, // CC BatteryCommand.Report, // CC Command 55, // current value @@ -73,7 +74,7 @@ test("the Report command (v2) should be deserialized correctly: all flags set", }); test("the Report command (v2) should be deserialized correctly: charging status", (t) => { - const ccData = Buffer.from([ + const ccData = Uint8Array.from([ CommandClasses.Battery, // CC BatteryCommand.Report, // CC Command 55, @@ -90,7 +91,7 @@ test("the Report command (v2) should be deserialized correctly: charging status" }); test("the Report command (v2) should be deserialized correctly: recharge or replace", (t) => { - const ccData = Buffer.from([ + const ccData = Uint8Array.from([ CommandClasses.Battery, // CC BatteryCommand.Report, // CC Command 55, @@ -107,7 +108,7 @@ test("the Report command (v2) should be deserialized correctly: recharge or repl }); test("deserializing an unsupported command should return an unspecified version of BatteryCC", (t) => { - const serializedCC = Buffer.from([ + const serializedCC = Uint8Array.from([ CommandClasses.Battery, // CC 255, // not a valid command ]); diff --git a/packages/zwave-js/src/lib/test/cc/BinarySensorCC.test.ts b/packages/zwave-js/src/lib/test/cc/BinarySensorCC.test.ts index 08320b741ea4..24f23b34fd7f 100644 --- a/packages/zwave-js/src/lib/test/cc/BinarySensorCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/BinarySensorCC.test.ts @@ -9,11 +9,12 @@ import { CommandClass, } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; +import { Bytes } from "@zwave-js/shared/safe"; import test from "ava"; -function buildCCBuffer(payload: Buffer): Buffer { - return Buffer.concat([ - Buffer.from([ +function buildCCBuffer(payload: Uint8Array): Uint8Array { + return Bytes.concat([ + Uint8Array.from([ CommandClasses["Binary Sensor"], // CC ]), payload, @@ -23,7 +24,7 @@ function buildCCBuffer(payload: Buffer): Buffer { test("the Get command should serialize correctly (no sensor type)", (t) => { const cc = new BinarySensorCCGet({ nodeId: 1 }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ BinarySensorCommand.Get, // CC Command BinarySensorType.Any, // sensor type ]), @@ -37,14 +38,14 @@ test("the Get command should serialize correctly", (t) => { sensorType: BinarySensorType.CO, }); const expected = buildCCBuffer( - Buffer.from([BinarySensorCommand.Get, BinarySensorType.CO]), + Uint8Array.from([BinarySensorCommand.Get, BinarySensorType.CO]), ); t.deepEqual(cc.serialize({} as any), expected); }); test("the Report command (v1) should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ BinarySensorCommand.Report, // CC Command 0xff, // current value ]), @@ -60,7 +61,7 @@ test("the Report command (v1) should be deserialized correctly", (t) => { test("the Report command (v2) should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ BinarySensorCommand.Report, // CC Command 0x00, // current value BinarySensorType.CO2, @@ -79,7 +80,7 @@ test("the Report command (v2) should be deserialized correctly", (t) => { test("the SupportedGet command should serialize correctly", (t) => { const cc = new BinarySensorCCSupportedGet({ nodeId: 1 }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ BinarySensorCommand.SupportedGet, // CC Command ]), ); @@ -88,7 +89,7 @@ test("the SupportedGet command should serialize correctly", (t) => { test("the SupportedReport command should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ BinarySensorCommand.SupportedReport, // CC Command 0b10101010, 0b10, @@ -111,7 +112,7 @@ test("the SupportedReport command should be deserialized correctly", (t) => { test("deserializing an unsupported command should return an unspecified version of BinarySensorCC", (t) => { const serializedCC = buildCCBuffer( - Buffer.from([255]), // not a valid command + Uint8Array.from([255]), // not a valid command ); const cc = CommandClass.parse( serializedCC, diff --git a/packages/zwave-js/src/lib/test/cc/BinarySwitchCC.test.ts b/packages/zwave-js/src/lib/test/cc/BinarySwitchCC.test.ts index 755c4fa39760..880a9f615b8b 100644 --- a/packages/zwave-js/src/lib/test/cc/BinarySwitchCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/BinarySwitchCC.test.ts @@ -8,11 +8,12 @@ import { } from "@zwave-js/cc"; import { CommandClasses, Duration } from "@zwave-js/core"; import { type GetSupportedCCVersion } from "@zwave-js/host"; +import { Bytes } from "@zwave-js/shared/safe"; import test from "ava"; -function buildCCBuffer(payload: Buffer): Buffer { - return Buffer.concat([ - Buffer.from([ +function buildCCBuffer(payload: Uint8Array): Uint8Array { + return Bytes.concat([ + Uint8Array.from([ CommandClasses["Binary Switch"], // CC ]), payload, @@ -22,7 +23,7 @@ function buildCCBuffer(payload: Buffer): Buffer { test("the Get command should serialize correctly", (t) => { const cc = new BinarySwitchCCGet({ nodeId: 1 }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ BinarySwitchCommand.Get, // CC Command ]), ); @@ -35,7 +36,7 @@ test("the Set command should serialize correctly (no duration)", (t) => { targetValue: false, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ BinarySwitchCommand.Set, // CC Command 0x00, // target value 0xff, // default duration @@ -58,7 +59,7 @@ test("the Set command should serialize correctly", (t) => { duration, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ BinarySwitchCommand.Set, // CC Command 0xff, // target value, duration.serializeSet(), @@ -75,7 +76,7 @@ test("the Set command should serialize correctly", (t) => { test("the Report command (v1) should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ BinarySwitchCommand.Report, // CC Command 0xff, // current value ]), @@ -93,7 +94,7 @@ test("the Report command (v1) should be deserialized correctly", (t) => { test("the Report command (v2) should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ BinarySwitchCommand.Report, // CC Command 0xff, // current value 0x00, // target value @@ -114,7 +115,7 @@ test("the Report command (v2) should be deserialized correctly", (t) => { test("deserializing an unsupported command should return an unspecified version of BinarySwitchCC", (t) => { const serializedCC = buildCCBuffer( - Buffer.from([255]), // not a valid command + Uint8Array.from([255]), // not a valid command ); const cc = CommandClass.parse( serializedCC, diff --git a/packages/zwave-js/src/lib/test/cc/CRC16CC.test.ts b/packages/zwave-js/src/lib/test/cc/CRC16CC.test.ts index 027a74774a00..d0bde449dd31 100644 --- a/packages/zwave-js/src/lib/test/cc/CRC16CC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/CRC16CC.test.ts @@ -7,6 +7,7 @@ import { InvalidCC, isEncapsulatingCommandClass, } from "@zwave-js/cc"; +import { Bytes } from "@zwave-js/shared/safe"; import test from "ava"; test("should be detected as an encapsulating CC", (t) => { @@ -23,7 +24,7 @@ test("should match the specs", (t) => { const basicCCGet = new BasicCCGet({ nodeId: 1 }); const crc16 = CRC16CC.encapsulate(basicCCGet); const serialized = crc16.serialize({} as any); - const expected = Buffer.from("560120024d26", "hex"); + const expected = Bytes.from("560120024d26", "hex"); t.deepEqual(serialized, expected); }); diff --git a/packages/zwave-js/src/lib/test/cc/CentralSceneCC.test.ts b/packages/zwave-js/src/lib/test/cc/CentralSceneCC.test.ts index 3bdb279111b4..f8bf1c69ab14 100644 --- a/packages/zwave-js/src/lib/test/cc/CentralSceneCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/CentralSceneCC.test.ts @@ -11,11 +11,12 @@ import { CommandClass, } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; +import { Bytes } from "@zwave-js/shared/safe"; import test from "ava"; -function buildCCBuffer(payload: Buffer): Buffer { - return Buffer.concat([ - Buffer.from([ +function buildCCBuffer(payload: Uint8Array): Uint8Array { + return Bytes.concat([ + Uint8Array.from([ CommandClasses["Central Scene"], // CC ]), payload, @@ -27,7 +28,7 @@ test("the ConfigurationGet command should serialize correctly", (t) => { nodeId: 1, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ CentralSceneCommand.ConfigurationGet, // CC Command ]), ); @@ -40,7 +41,7 @@ test("the ConfigurationSet command should serialize correctly (flags set)", (t) slowRefresh: true, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ CentralSceneCommand.ConfigurationSet, // CC Command 0b1000_0000, ]), @@ -54,7 +55,7 @@ test("the ConfigurationSet command should serialize correctly (flags not set)", slowRefresh: false, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ CentralSceneCommand.ConfigurationSet, // CC Command 0, ]), @@ -64,7 +65,7 @@ test("the ConfigurationSet command should serialize correctly (flags not set)", test("the ConfigurationReport command should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ CentralSceneCommand.ConfigurationReport, // CC Command 0b1000_0000, ]), @@ -83,7 +84,7 @@ test("the SupportedGet command should serialize correctly", (t) => { nodeId: 1, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ CentralSceneCommand.SupportedGet, // CC Command ]), ); @@ -92,7 +93,7 @@ test("the SupportedGet command should serialize correctly", (t) => { test("the SupportedReport command should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ CentralSceneCommand.SupportedReport, // CC Command 2, // # of scenes 0b1_0000_10_0, // slow refresh, 2 bytes per scene, not identical @@ -118,7 +119,7 @@ test("the SupportedReport command should be deserialized correctly", (t) => { test("the Notification command should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ CentralSceneCommand.Notification, // CC Command 7, // sequence number 0b1000_0000 | CentralSceneKeys.KeyPressed4x, // slow refresh @@ -140,7 +141,7 @@ test("the Notification command should be deserialized correctly", (t) => { test("the Notification command should be deserialized correctly (KeyHeldDown)", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ CentralSceneCommand.Notification, // CC Command 7, // sequence number 0b1000_0000 | CentralSceneKeys.KeyHeldDown, // slow refresh @@ -161,7 +162,7 @@ test("the Notification command should be deserialized correctly (KeyHeldDown)", test("deserializing an unsupported command should return an unspecified version of CentralSceneCC", (t) => { const serializedCC = buildCCBuffer( - Buffer.from([255]), // not a valid command + Uint8Array.from([255]), // not a valid command ); const cc = CommandClass.parse( serializedCC, diff --git a/packages/zwave-js/src/lib/test/cc/ColorSwitchCC.test.ts b/packages/zwave-js/src/lib/test/cc/ColorSwitchCC.test.ts index da917686d044..dad7c467392d 100644 --- a/packages/zwave-js/src/lib/test/cc/ColorSwitchCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/ColorSwitchCC.test.ts @@ -18,11 +18,12 @@ import { assertZWaveError, } from "@zwave-js/core"; import { type GetSupportedCCVersion } from "@zwave-js/host"; +import { Bytes } from "@zwave-js/shared/safe"; import test from "ava"; -function buildCCBuffer(payload: Buffer): Buffer { - return Buffer.concat([ - Buffer.from([ +function buildCCBuffer(payload: Uint8Array): Uint8Array { + return Bytes.concat([ + Uint8Array.from([ CommandClasses["Color Switch"], // CC ]), payload, @@ -34,7 +35,7 @@ test("the SupportedGet command should serialize correctly", (t) => { nodeId: 1, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ ColorSwitchCommand.SupportedGet, // CC Command ]), ); @@ -43,7 +44,7 @@ test("the SupportedGet command should serialize correctly", (t) => { test("the SupportedReport command should deserialize correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ ColorSwitchCommand.SupportedReport, // CC Command 0b0001_1111, 0b0000_0001, @@ -76,7 +77,7 @@ test("the Get command should serialize correctly", (t) => { colorComponent: ColorComponent.Red, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ ColorSwitchCommand.Get, // CC Command 2, // Color Component ]), @@ -86,7 +87,7 @@ test("the Get command should serialize correctly", (t) => { test("the Report command should deserialize correctly (version 1)", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ ColorSwitchCommand.Report, // CC Command 0b0000_0010, // color: red 0b1111_1111, // value: 255 @@ -106,7 +107,7 @@ test("the Report command should deserialize correctly (version 1)", (t) => { test("the Report command should deserialize correctly (version 3)", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ ColorSwitchCommand.Report, // CC Command 0b0000_0010, // color: red 0b1000_0000, // currentValue: 128 @@ -135,7 +136,7 @@ test("the Set command should serialize correctly (without duration)", (t) => { }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ ColorSwitchCommand.Set, // CC Command // WARN: This is sensitive to the order in which javascript serializes the colorTable keys. 0b0000_0010, // reserved + count @@ -165,7 +166,7 @@ test("the Set command should serialize correctly (version 2)", (t) => { }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ ColorSwitchCommand.Set, // CC Command // WARN: This is sensitive to the order in which javascript serializes the colorTable keys. 0b0000_0010, // reserved + count @@ -195,7 +196,7 @@ test("the StartLevelChange command should serialize correctly", (t) => { duration: new Duration(1, "seconds"), }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ ColorSwitchCommand.StartLevelChange, 0b0010_0000, // up/down: 0, ignoreStartLevel: 1 0b0000_0010, // color: red @@ -219,7 +220,7 @@ test("the StopLevelChange command should serialize correctly", (t) => { }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ ColorSwitchCommand.StopLevelChange, // CC Command 0b0000_0010, // color: red ]), diff --git a/packages/zwave-js/src/lib/test/cc/CommandClass.test.ts b/packages/zwave-js/src/lib/test/cc/CommandClass.test.ts index 2c0c532cc1da..bd961eeb39f8 100644 --- a/packages/zwave-js/src/lib/test/cc/CommandClass.test.ts +++ b/packages/zwave-js/src/lib/test/cc/CommandClass.test.ts @@ -11,6 +11,7 @@ import { } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; import { SendDataRequest } from "@zwave-js/serial/serialapi"; +import { Bytes } from "@zwave-js/shared/safe"; import test from "ava"; @implementedVersion(7) @@ -30,7 +31,7 @@ test(`creating and serializing should work for unspecified commands`, (t) => { nodeId: 2, ccId: 0x5d, ccCommand: 0x02, - payload: Buffer.from([1, 2, 3]), + payload: Uint8Array.from([1, 2, 3]), }); const msg = new SendDataRequest({ command: cc, @@ -38,28 +39,28 @@ test(`creating and serializing should work for unspecified commands`, (t) => { }); t.deepEqual( msg.serialize({} as any), - Buffer.from("010c001302055d0201020325fe63", "hex"), + Bytes.from("010c001302055d0201020325fe63", "hex"), ); }); test("parse() returns an un-specialized instance when receiving a non-implemented CC", (t) => { // This is a Node Provisioning CC. Change it when that CC is implemented const cc = CommandClass.parse( - Buffer.from("78030100", "hex"), + Bytes.from("78030100", "hex"), { sourceNodeId: 5 } as any, ); t.is(cc.constructor, CommandClass); t.is(cc.nodeId, 5); t.is(cc.ccId, 0x78); t.is(cc.ccCommand, 0x03); - t.deepEqual(cc.payload, Buffer.from([0x01, 0x00])); + t.deepEqual(cc.payload, Bytes.from([0x01, 0x00])); }); test("parse() does not throw when the CC is implemented", (t) => { t.notThrows(() => // CRC-16 with BasicCC CommandClass.parse( - Buffer.from("560120024d26", "hex"), + Bytes.from("560120024d26", "hex"), { sourceNodeId: 5 } as any, ) ); diff --git a/packages/zwave-js/src/lib/test/cc/DoorLockCC.test.ts b/packages/zwave-js/src/lib/test/cc/DoorLockCC.test.ts index 3167154c3c41..9f6dab243801 100644 --- a/packages/zwave-js/src/lib/test/cc/DoorLockCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/DoorLockCC.test.ts @@ -15,11 +15,12 @@ import { import { DoorLockCCValues } from "@zwave-js/cc/DoorLockCC"; import { CommandClasses, Duration } from "@zwave-js/core"; import { createTestingHost } from "@zwave-js/host"; +import { Bytes } from "@zwave-js/shared/safe"; import test from "ava"; -function buildCCBuffer(payload: Buffer): Buffer { - return Buffer.concat([ - Buffer.from([ +function buildCCBuffer(payload: Uint8Array): Uint8Array { + return Bytes.concat([ + Uint8Array.from([ CommandClasses["Door Lock"], // CC ]), payload, @@ -43,7 +44,7 @@ valueDB2.setValue(DoorLockCCValues.latchSupported.id, true); test("the OperationGet command should serialize correctly", (t) => { const cc = new DoorLockCCOperationGet({ nodeId: 1 }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ DoorLockCommand.OperationGet, // CC Command ]), ); @@ -56,7 +57,7 @@ test("the OperationSet command should serialize correctly", (t) => { mode: DoorLockMode.OutsideUnsecured, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ DoorLockCommand.OperationSet, // CC Command 0x20, // target value ]), @@ -66,7 +67,7 @@ test("the OperationSet command should serialize correctly", (t) => { test("the OperationReport command (v1-v3) should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ DoorLockCommand.OperationReport, // CC Command DoorLockMode.InsideUnsecuredWithTimeout, // lock mode 0b1000_0010, // handles mode @@ -94,7 +95,7 @@ test("the OperationReport command (v1-v3) should be deserialized correctly", (t) test("the OperationReport command (v4) should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ DoorLockCommand.OperationReport, // CC Command DoorLockMode.OutsideUnsecured, // lock mode 0b0100_1111, // handles mode @@ -133,7 +134,7 @@ test("the OperationReport command (v4) should be deserialized correctly", (t) => test("the ConfigurationGet command should serialize correctly", (t) => { const cc = new DoorLockCCConfigurationGet({ nodeId: 1 }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ DoorLockCommand.ConfigurationGet, // CC Command ]), ); @@ -142,7 +143,7 @@ test("the ConfigurationGet command should serialize correctly", (t) => { test("the ConfigurationReport command (v1-v3) should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ DoorLockCommand.ConfigurationReport, // CC Command DoorLockOperationType.Timed, // operation type 0b1000_0010, // handles mode @@ -178,7 +179,7 @@ test("the ConfigurationReport command (v1-v3) should be deserialized correctly", test("the ConfigurationReport command must ignore invalid timeouts (constant)", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ DoorLockCommand.ConfigurationReport, // CC Command DoorLockOperationType.Constant, // operation type 0b1000_0010, // handles mode @@ -197,7 +198,7 @@ test("the ConfigurationReport command must ignore invalid timeouts (constant)", test("the ConfigurationReport command must ignore invalid timeouts (invalid minutes)", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ DoorLockCommand.ConfigurationReport, // CC Command DoorLockOperationType.Constant, // operation type 0b1000_0010, // handles mode @@ -216,7 +217,7 @@ test("the ConfigurationReport command must ignore invalid timeouts (invalid minu test("the ConfigurationReport command must ignore invalid timeouts (invalid seconds)", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ DoorLockCommand.ConfigurationReport, // CC Command DoorLockOperationType.Constant, // operation type 0b1000_0010, // handles mode @@ -235,7 +236,7 @@ test("the ConfigurationReport command must ignore invalid timeouts (invalid seco test("the ConfigurationReport command (v4) should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ DoorLockCommand.ConfigurationReport, // CC Command DoorLockOperationType.Timed, // operation type 0b1000_0010, // handles mode @@ -274,7 +275,7 @@ test("the ConfigurationSet command (v4) should serialize correctly", (t) => { twistAssist: true, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ DoorLockCommand.ConfigurationSet, // CC Command DoorLockOperationType.Timed, 0b1110_0101, @@ -293,7 +294,7 @@ test("the ConfigurationSet command (v4) should serialize correctly", (t) => { test("the CapabilitiesGet command should serialize correctly", (t) => { const cc = new DoorLockCCCapabilitiesGet({ nodeId: 1 }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ DoorLockCommand.CapabilitiesGet, // CC Command ]), ); @@ -302,7 +303,7 @@ test("the CapabilitiesGet command should serialize correctly", (t) => { test("the CapabilitiesReport command should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ DoorLockCommand.CapabilitiesReport, // CC Command 1, // bit mask length 0b110, // operation types @@ -343,7 +344,7 @@ test("the CapabilitiesReport command should be deserialized correctly", (t) => { // test("the Report command (v2) should be deserialized correctly", (t) => { // const ccData = buildCCBuffer( // 1, -// Buffer.from([ +// Uint8Array.from([ // DoorLockCommand.Report, // CC Command // 55, // current value // 66, // target value @@ -361,7 +362,7 @@ test("the CapabilitiesReport command should be deserialized correctly", (t) => { // test("deserializing an unsupported command should return an unspecified version of DoorLockCC", (t) => { // const serializedCC = buildCCBuffer( // 1, -// Buffer.from([255]), // not a valid command +// Uint8Array.from([255]), // not a valid command // ); // const cc: any = new DoorLockCC({ // data: serializedCC, diff --git a/packages/zwave-js/src/lib/test/cc/DoorLockLoggingCC.test.ts b/packages/zwave-js/src/lib/test/cc/DoorLockLoggingCC.test.ts index f6a783a63637..5b3f3eabfe0c 100644 --- a/packages/zwave-js/src/lib/test/cc/DoorLockLoggingCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/DoorLockLoggingCC.test.ts @@ -8,11 +8,12 @@ import { DoorLockLoggingEventType, } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; +import { Bytes } from "@zwave-js/shared/safe"; import test from "ava"; -function buildCCBuffer(payload: Buffer): Buffer { - return Buffer.concat([ - Buffer.from([ +function buildCCBuffer(payload: Uint8Array): Uint8Array { + return Bytes.concat([ + Uint8Array.from([ CommandClasses["Door Lock Logging"], // CC ]), payload, @@ -24,7 +25,7 @@ test("the RecordsCountGet command should serialize correctly", (t) => { nodeId: 1, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ DoorLockLoggingCommand.RecordsSupportedGet, // CC Command ]), ); @@ -33,7 +34,7 @@ test("the RecordsCountGet command should serialize correctly", (t) => { test("the RecordsCountReport command should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ DoorLockLoggingCommand.RecordsSupportedReport, // CC Command 0x14, // max records supported (20) ]), @@ -53,7 +54,7 @@ test("the RecordGet command should serialize correctly", (t) => { recordNumber: 1, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ DoorLockLoggingCommand.RecordGet, // CC Command 1, // Record Number ]), @@ -63,7 +64,7 @@ test("the RecordGet command should serialize correctly", (t) => { test("the RecordReport command should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ DoorLockLoggingCommand.RecordReport, // CC Command 7, // record number 0x07, // year 1/2 diff --git a/packages/zwave-js/src/lib/test/cc/EntryControlCC.test.ts b/packages/zwave-js/src/lib/test/cc/EntryControlCC.test.ts index 6c2de3081d77..ffae8d2f79d1 100644 --- a/packages/zwave-js/src/lib/test/cc/EntryControlCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/EntryControlCC.test.ts @@ -13,11 +13,12 @@ import { EntryControlEventTypes, } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; +import { Bytes } from "@zwave-js/shared/safe"; import test from "ava"; -function buildCCBuffer(payload: Buffer): Buffer { - return Buffer.concat([ - Buffer.from([ +function buildCCBuffer(payload: Uint8Array): Uint8Array { + return Bytes.concat([ + Uint8Array.from([ CommandClasses["Entry Control"], // CC ]), payload, @@ -26,8 +27,8 @@ function buildCCBuffer(payload: Buffer): Buffer { test("the Notification command should deserialize correctly", (t) => { const data = buildCCBuffer( - Buffer.concat([ - Buffer.from([ + Bytes.concat([ + Uint8Array.from([ EntryControlCommand.Notification, // CC Command 0x1, 0x2, @@ -39,7 +40,7 @@ test("the Notification command should deserialize correctly", (t) => { 52, ]), // Required padding for ASCII - Buffer.alloc(12, 0xff), + new Uint8Array(12).fill(0xff), ]), ); @@ -60,7 +61,7 @@ test("the ConfigurationGet command should serialize correctly", (t) => { nodeId: 1, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ EntryControlCommand.ConfigurationGet, // CC Command ]), ); @@ -74,7 +75,7 @@ test("the ConfigurationSet command should serialize correctly", (t) => { keyCacheTimeout: 2, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ EntryControlCommand.ConfigurationSet, // CC Command 0x1, 0x2, @@ -85,7 +86,7 @@ test("the ConfigurationSet command should serialize correctly", (t) => { test("the ConfigurationReport command should be deserialize correctly", (t) => { const data = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ EntryControlCommand.ConfigurationReport, // CC Command 0x1, 0x2, @@ -107,7 +108,7 @@ test("the EventSupportedGet command should serialize correctly", (t) => { nodeId: 1, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ EntryControlCommand.EventSupportedGet, // CC Command ]), ); @@ -116,7 +117,7 @@ test("the EventSupportedGet command should serialize correctly", (t) => { test("the EventSupportedReport command should be deserialize correctly", (t) => { const data = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ EntryControlCommand.EventSupportedReport, // CC Command 1, 0b00000100, @@ -154,7 +155,7 @@ test("the EventSupportedReport command should be deserialize correctly", (t) => test("the KeySupportedGet command should serialize correctly", (t) => { const cc = new EntryControlCCKeySupportedGet({ nodeId: 1 }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ EntryControlCommand.KeySupportedGet, // CC Command ]), ); @@ -163,7 +164,7 @@ test("the KeySupportedGet command should serialize correctly", (t) => { test("the KeySupportedReport command should be deserialize correctly", (t) => { const data = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ EntryControlCommand.KeySupportedReport, // CC Command 1, 0b01011010, diff --git a/packages/zwave-js/src/lib/test/cc/FibaroCC.test.ts b/packages/zwave-js/src/lib/test/cc/FibaroCC.test.ts index bb8fe9bc9488..7828406cd8b5 100644 --- a/packages/zwave-js/src/lib/test/cc/FibaroCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/FibaroCC.test.ts @@ -6,11 +6,12 @@ import { FibaroVenetianBlindCCSet, } from "@zwave-js/cc/manufacturerProprietary/FibaroCC"; import { CommandClasses } from "@zwave-js/core"; +import { Bytes } from "@zwave-js/shared/safe"; import test from "ava"; -function buildCCBuffer(payload: Buffer): Buffer { - return Buffer.concat([ - Buffer.from([ +function buildCCBuffer(payload: Uint8Array): Uint8Array { + return Bytes.concat([ + Uint8Array.from([ CommandClasses["Manufacturer Proprietary"], // CC // Manufacturer ID 0x01, @@ -28,7 +29,7 @@ test("the Set Tilt command should serialize correctly", (t) => { tilt: 99, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ FibaroVenetianBlindCCCommand.Set, 0x01, // with Tilt, no Position 0x00, // Position @@ -40,7 +41,7 @@ test("the Set Tilt command should serialize correctly", (t) => { test("the Report command should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ FibaroVenetianBlindCCCommand.Report, 0x03, // with Tilt and Position 0x00, // Position @@ -78,7 +79,7 @@ test("FibaroVenetianBlindCCSet => FibaroVenetianBlindCCReport = unexpected", (t) }); const ccResponse = CommandClass.parse( buildCCBuffer( - Buffer.from([ + Uint8Array.from([ FibaroVenetianBlindCCCommand.Report, 0x03, // with Tilt and Position 0x01, // Position @@ -97,7 +98,7 @@ test("FibaroVenetianBlindCCGet => FibaroVenetianBlindCCReport = expected", (t) = }); const ccResponse = CommandClass.parse( buildCCBuffer( - Buffer.from([ + Uint8Array.from([ FibaroVenetianBlindCCCommand.Report, 0x03, // with Tilt and Position 0x01, // Position diff --git a/packages/zwave-js/src/lib/test/cc/HumidityControlModeCC.test.ts b/packages/zwave-js/src/lib/test/cc/HumidityControlModeCC.test.ts index f859eb75dd44..bbb1efec50cd 100644 --- a/packages/zwave-js/src/lib/test/cc/HumidityControlModeCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/HumidityControlModeCC.test.ts @@ -11,14 +11,15 @@ import { import { HumidityControlModeCCValues } from "@zwave-js/cc/HumidityControlModeCC"; import { CommandClasses, enumValuesToMetadataStates } from "@zwave-js/core"; import { createTestingHost } from "@zwave-js/host"; +import { Bytes } from "@zwave-js/shared/safe"; import test from "ava"; const host = createTestingHost(); const nodeId = 2; -function buildCCBuffer(payload: Buffer): Buffer { - return Buffer.concat([ - Buffer.from([ +function buildCCBuffer(payload: Uint8Array): Uint8Array { + return Bytes.concat([ + Uint8Array.from([ CommandClasses["Humidity Control Mode"], // CC ]), payload, @@ -30,7 +31,7 @@ test("the Get command should serialize correctly", (t) => { nodeId, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ HumidityControlModeCommand.Get, // CC Command ]), ); @@ -43,7 +44,7 @@ test("the Set command should serialize correctly", (t) => { mode: HumidityControlMode.Auto, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ HumidityControlModeCommand.Set, // CC Command 0x03, // target value ]), @@ -53,7 +54,7 @@ test("the Set command should serialize correctly", (t) => { test("the Report command should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ HumidityControlModeCommand.Report, // CC Command HumidityControlMode.Auto, // current value ]), @@ -69,7 +70,7 @@ test("the Report command should be deserialized correctly", (t) => { test("the Report command should set the correct value", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ HumidityControlModeCommand.Report, // CC Command HumidityControlMode.Auto, // current value ]), @@ -89,7 +90,7 @@ test("the Report command should set the correct value", (t) => { test("the Report command should set the correct metadata", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ HumidityControlModeCommand.Report, // CC Command HumidityControlMode.Auto, // current value ]), @@ -115,7 +116,7 @@ test("the SupportedGet command should serialize correctly", (t) => { nodeId, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ HumidityControlModeCommand.SupportedGet, // CC Command ]), ); @@ -124,7 +125,7 @@ test("the SupportedGet command should serialize correctly", (t) => { test("the SupportedReport command should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ HumidityControlModeCommand.SupportedReport, // CC Command (1 << HumidityControlMode.Off) | (1 << HumidityControlMode.Auto), ]), @@ -143,7 +144,7 @@ test("the SupportedReport command should be deserialized correctly", (t) => { test("the SupportedReport command should set the correct metadata", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ HumidityControlModeCommand.SupportedReport, // CC Command (1 << HumidityControlMode.Off) | (1 << HumidityControlMode.Auto), ]), diff --git a/packages/zwave-js/src/lib/test/cc/HumidityControlOperatingStateCC.test.ts b/packages/zwave-js/src/lib/test/cc/HumidityControlOperatingStateCC.test.ts index ab4b15c30b97..5ebddf7440ff 100644 --- a/packages/zwave-js/src/lib/test/cc/HumidityControlOperatingStateCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/HumidityControlOperatingStateCC.test.ts @@ -6,11 +6,12 @@ import { HumidityControlOperatingStateCommand, } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; +import { Bytes } from "@zwave-js/shared/safe"; import test from "ava"; -function buildCCBuffer(payload: Buffer): Buffer { - return Buffer.concat([ - Buffer.from([ +function buildCCBuffer(payload: Uint8Array): Uint8Array { + return Bytes.concat([ + Uint8Array.from([ CommandClasses["Humidity Control Operating State"], // CC ]), payload, @@ -22,7 +23,7 @@ test("the Get command should serialize correctly", (t) => { nodeId: 1, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ HumidityControlOperatingStateCommand.Get, // CC Command ]), ); @@ -31,7 +32,7 @@ test("the Get command should serialize correctly", (t) => { test("the Report command should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ HumidityControlOperatingStateCommand.Report, // CC Command HumidityControlOperatingState.Humidifying, // state ]), diff --git a/packages/zwave-js/src/lib/test/cc/HumidityControlSetpointCC.test.ts b/packages/zwave-js/src/lib/test/cc/HumidityControlSetpointCC.test.ts index 56c0de1fe87d..519e5efb3052 100644 --- a/packages/zwave-js/src/lib/test/cc/HumidityControlSetpointCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/HumidityControlSetpointCC.test.ts @@ -14,11 +14,12 @@ import { } from "@zwave-js/cc"; import { CommandClasses, encodeFloatWithScale } from "@zwave-js/core"; import { createTestingHost } from "@zwave-js/host"; +import { Bytes } from "@zwave-js/shared/safe"; import test from "ava"; -function buildCCBuffer(payload: Buffer): Buffer { - return Buffer.concat([ - Buffer.from([ +function buildCCBuffer(payload: Uint8Array): Uint8Array { + return Bytes.concat([ + Uint8Array.from([ CommandClasses["Humidity Control Setpoint"], // CC ]), payload, @@ -34,7 +35,7 @@ test("the Get command should serialize correctly", (t) => { setpointType: HumidityControlSetpointType.Humidifier, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ HumidityControlSetpointCommand.Get, // CC Command HumidityControlSetpointType.Humidifier, // type ]), @@ -50,8 +51,8 @@ test("the Set command should serialize correctly", (t) => { scale: 1, }); const expected = buildCCBuffer( - Buffer.concat([ - Buffer.from([ + Bytes.concat([ + Uint8Array.from([ HumidityControlSetpointCommand.Set, // CC Command HumidityControlSetpointType.Humidifier, // type ]), @@ -63,8 +64,8 @@ test("the Set command should serialize correctly", (t) => { test("the Report command should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.concat([ - Buffer.from([ + Bytes.concat([ + Uint8Array.from([ HumidityControlSetpointCommand.Report, // CC Command HumidityControlSetpointType.Humidifier, // type ]), @@ -89,8 +90,8 @@ test("the Report command should be deserialized correctly", (t) => { test("the Report command should set the correct value", (t) => { const ccData = buildCCBuffer( - Buffer.concat([ - Buffer.from([ + Bytes.concat([ + Uint8Array.from([ HumidityControlSetpointCommand.Report, // CC Command HumidityControlSetpointType.Humidifier, // type ]), @@ -120,8 +121,8 @@ test("the Report command should set the correct value", (t) => { test("the Report command should set the correct metadata", (t) => { const ccData = buildCCBuffer( - Buffer.concat([ - Buffer.from([ + Bytes.concat([ + Uint8Array.from([ HumidityControlSetpointCommand.Report, // CC Command HumidityControlSetpointType.Humidifier, // type ]), @@ -152,7 +153,7 @@ test("the SupportedGet command should serialize correctly", (t) => { nodeId: nodeId, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ HumidityControlSetpointCommand.SupportedGet, // CC Command ]), ); @@ -161,7 +162,7 @@ test("the SupportedGet command should serialize correctly", (t) => { test("the SupportedReport command should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ HumidityControlSetpointCommand.SupportedReport, // CC Command (1 << HumidityControlSetpointType.Humidifier) | (1 << HumidityControlSetpointType.Auto), @@ -181,7 +182,7 @@ test("the SupportedReport command should be deserialized correctly", (t) => { test("the SupportedReport command should set the correct value", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ HumidityControlSetpointCommand.SupportedReport, // CC Command (1 << HumidityControlSetpointType.Humidifier) | (1 << HumidityControlSetpointType.Auto), @@ -209,7 +210,7 @@ test("the ScaleSupportedGet command should serialize correctly", (t) => { setpointType: HumidityControlSetpointType.Auto, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ HumidityControlSetpointCommand.ScaleSupportedGet, // CC Command HumidityControlSetpointType.Auto, // type ]), @@ -219,7 +220,7 @@ test("the ScaleSupportedGet command should serialize correctly", (t) => { test("the ScaleSupportedReport command should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ HumidityControlSetpointCommand.ScaleSupportedReport, // CC Command 0b11, // percent + absolute ]), @@ -248,7 +249,7 @@ test("the CapabilitiesGet command should serialize correctly", (t) => { setpointType: HumidityControlSetpointType.Auto, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ HumidityControlSetpointCommand.CapabilitiesGet, // CC Command HumidityControlSetpointType.Auto, // type ]), @@ -258,8 +259,8 @@ test("the CapabilitiesGet command should serialize correctly", (t) => { test("the CapabilitiesReport command should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.concat([ - Buffer.from([ + Bytes.concat([ + Uint8Array.from([ HumidityControlSetpointCommand.CapabilitiesReport, // CC Command HumidityControlSetpointType.Humidifier, // type ]), @@ -282,8 +283,8 @@ test("the CapabilitiesReport command should be deserialized correctly", (t) => { test("the CapabilitiesReport command should set the correct metadata", (t) => { const ccData = buildCCBuffer( - Buffer.concat([ - Buffer.from([ + Bytes.concat([ + Uint8Array.from([ HumidityControlSetpointCommand.CapabilitiesReport, // CC Command HumidityControlSetpointType.Humidifier, // type ]), diff --git a/packages/zwave-js/src/lib/test/cc/IndicatorCC.test.ts b/packages/zwave-js/src/lib/test/cc/IndicatorCC.test.ts index bd6e42323bdb..ed3f54e1b240 100644 --- a/packages/zwave-js/src/lib/test/cc/IndicatorCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/IndicatorCC.test.ts @@ -9,12 +9,13 @@ import { import { IndicatorCCValues } from "@zwave-js/cc/IndicatorCC"; import { CommandClasses } from "@zwave-js/core"; import { createTestingHost } from "@zwave-js/host"; +import { Bytes } from "@zwave-js/shared/safe"; import test from "ava"; import { createTestNode } from "../mocks"; -function buildCCBuffer(payload: Buffer): Buffer { - return Buffer.concat([ - Buffer.from([ +function buildCCBuffer(payload: Uint8Array): Uint8Array { + return Bytes.concat([ + Uint8Array.from([ CommandClasses.Indicator, // CC ]), payload, @@ -26,7 +27,7 @@ const host = createTestingHost(); test("the Get command (V1) should serialize correctly", (t) => { const cc = new IndicatorCCGet({ nodeId: 1 }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ IndicatorCommand.Get, // CC Command ]), ); @@ -39,7 +40,7 @@ test("the Get command (V2) should serialize correctly", (t) => { indicatorId: 5, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ IndicatorCommand.Get, // CC Command 5, ]), @@ -53,7 +54,7 @@ test("the Set command (v1) should serialize correctly", (t) => { value: 23, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ IndicatorCommand.Set, // CC Command 23, // value ]), @@ -78,7 +79,7 @@ test("the Set command (v2) should serialize correctly", (t) => { ], }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ IndicatorCommand.Set, // CC Command 0, 2, // object count @@ -95,7 +96,7 @@ test("the Set command (v2) should serialize correctly", (t) => { test("the Report command (v1) should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ IndicatorCommand.Report, // CC Command 55, // value ]), @@ -112,7 +113,7 @@ test("the Report command (v1) should be deserialized correctly", (t) => { test("the Report command (v2) should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ IndicatorCommand.Report, // CC Command 0, 2, // object count @@ -149,7 +150,7 @@ test("the Report command (v2) should be deserialized correctly", (t) => { test("deserializing an unsupported command should return an unspecified version of IndicatorCC", (t) => { const serializedCC = buildCCBuffer( - Buffer.from([255]), // not a valid command + Uint8Array.from([255]), // not a valid command ); const cc = CommandClass.parse( serializedCC, diff --git a/packages/zwave-js/src/lib/test/cc/LanguageCC.test.ts b/packages/zwave-js/src/lib/test/cc/LanguageCC.test.ts index 434a6309e9bd..08d1ba312896 100644 --- a/packages/zwave-js/src/lib/test/cc/LanguageCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/LanguageCC.test.ts @@ -7,11 +7,12 @@ import { LanguageCommand, } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; +import { Bytes } from "@zwave-js/shared/safe"; import test from "ava"; -function buildCCBuffer(payload: Buffer): Buffer { - return Buffer.concat([ - Buffer.from([ +function buildCCBuffer(payload: Uint8Array): Uint8Array { + return Bytes.concat([ + Uint8Array.from([ CommandClasses.Language, // CC ]), payload, @@ -21,7 +22,7 @@ function buildCCBuffer(payload: Buffer): Buffer { test("the Get command should serialize correctly", (t) => { const cc = new LanguageCCGet({ nodeId: 1 }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ LanguageCommand.Get, // CC Command ]), ); @@ -34,7 +35,7 @@ test("the Set command should serialize correctly (w/o country code)", (t) => { language: "deu", }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ LanguageCommand.Set, // CC Command // "deu" 0x64, @@ -52,7 +53,7 @@ test("the Set command should serialize correctly (w/ country code)", (t) => { country: "DE", }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ LanguageCommand.Set, // CC Command // "deu" 0x64, @@ -68,7 +69,7 @@ test("the Set command should serialize correctly (w/ country code)", (t) => { test("the Report command should be deserialized correctly (w/o country code)", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ LanguageCommand.Report, // CC Command // "deu" 0x64, @@ -88,7 +89,7 @@ test("the Report command should be deserialized correctly (w/o country code)", ( test("the Report command should be deserialized correctly (w/ country code)", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ LanguageCommand.Report, // CC Command // "deu" 0x64, @@ -111,7 +112,7 @@ test("the Report command should be deserialized correctly (w/ country code)", (t test("deserializing an unsupported command should return an unspecified version of LanguageCC", (t) => { const serializedCC = buildCCBuffer( - Buffer.from([255]), // not a valid command + Uint8Array.from([255]), // not a valid command ); const cc = CommandClass.parse( serializedCC, diff --git a/packages/zwave-js/src/lib/test/cc/ManufacturerSpecificCC.test.ts b/packages/zwave-js/src/lib/test/cc/ManufacturerSpecificCC.test.ts index 111562efa31e..6759ea9b8ecc 100644 --- a/packages/zwave-js/src/lib/test/cc/ManufacturerSpecificCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/ManufacturerSpecificCC.test.ts @@ -5,11 +5,12 @@ import { ManufacturerSpecificCommand, } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; +import { Bytes } from "@zwave-js/shared/safe"; import test from "ava"; -function buildCCBuffer(payload: Buffer): Buffer { - return Buffer.concat([ - Buffer.from([ +function buildCCBuffer(payload: Uint8Array): Uint8Array { + return Bytes.concat([ + Uint8Array.from([ CommandClasses["Manufacturer Specific"], // CC ]), payload, @@ -19,7 +20,7 @@ function buildCCBuffer(payload: Buffer): Buffer { test("the Get command should serialize correctly", (t) => { const cc = new ManufacturerSpecificCCGet({ nodeId: 1 }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ ManufacturerSpecificCommand.Get, // CC Command ]), ); @@ -28,7 +29,7 @@ test("the Get command should serialize correctly", (t) => { test("the Report command (v1) should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ ManufacturerSpecificCommand.Report, // CC Command 0x01, 0x02, diff --git a/packages/zwave-js/src/lib/test/cc/MeterCC.test.ts b/packages/zwave-js/src/lib/test/cc/MeterCC.test.ts index 14806e0a3181..7e7d515b6006 100644 --- a/packages/zwave-js/src/lib/test/cc/MeterCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/MeterCC.test.ts @@ -15,13 +15,14 @@ import { assertZWaveError, } from "@zwave-js/core"; import { type GetSupportedCCVersion, createTestingHost } from "@zwave-js/host"; +import { Bytes } from "@zwave-js/shared/safe"; import test from "ava"; import * as nodeUtils from "../../node/utils"; import { createTestNode } from "../mocks"; -function buildCCBuffer(payload: Buffer): Buffer { - return Buffer.concat([ - Buffer.from([ +function buildCCBuffer(payload: Uint8Array): Uint8Array { + return Bytes.concat([ + Uint8Array.from([ CommandClasses.Meter, // CC ]), payload, @@ -34,7 +35,7 @@ const node2 = createTestNode(host, { id: 2 }); test("the Get command (V1) should serialize correctly", (t) => { const cc = new MeterCCGet({ nodeId: 1 }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MeterCommand.Get, // CC Command ]), ); @@ -50,7 +51,7 @@ test("the Get command (V1) should serialize correctly", (t) => { test("the Get command (V2) should serialize correctly", (t) => { const cc = new MeterCCGet({ nodeId: 1, scale: 0x03 }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MeterCommand.Get, // CC Command 0b11_000, // Scale ]), @@ -67,7 +68,7 @@ test("the Get command (V2) should serialize correctly", (t) => { test("the Get command (V3) should serialize correctly", (t) => { const cc = new MeterCCGet({ nodeId: 1, scale: 0x06 }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MeterCommand.Get, // CC Command 0b110_000, // Scale ]), @@ -84,7 +85,7 @@ test("the Get command (V3) should serialize correctly", (t) => { test("the Get command (V4) should serialize correctly", (t) => { const cc = new MeterCCGet({ nodeId: 1, scale: 0x0f }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MeterCommand.Get, // CC Command 0b111_000, // Scale 1 0x1, // Scale 2 @@ -102,7 +103,7 @@ test("the Get command (V4) should serialize correctly", (t) => { test("the SupportedGet command should serialize correctly", (t) => { const cc = new MeterCCSupportedGet({ nodeId: 1 }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MeterCommand.SupportedGet, // CC Command ]), ); @@ -112,7 +113,7 @@ test("the SupportedGet command should serialize correctly", (t) => { test("the Reset command (V2) should serialize correctly", (t) => { const cc = new MeterCCReset({ nodeId: 1 }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MeterCommand.Reset, // CC Command ]), ); @@ -128,7 +129,7 @@ test("the Reset command (V6) should serialize correctly", (t) => { targetValue: 12.3, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MeterCommand.Reset, // CC Command 0b0_00_00111, // scale (2), rate type, type 0b001_11_001, // precision, scale, size @@ -140,7 +141,7 @@ test("the Reset command (V6) should serialize correctly", (t) => { test("the Report command (V1) should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MeterCommand.Report, // CC Command 0x03, // Meter type 0b001_10_001, // precision, scale, size @@ -163,7 +164,7 @@ test("the Report command (V1) should be deserialized correctly", (t) => { test("the Report command (V2) should be deserialized correctly (no time delta)", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MeterCommand.Report, // CC Command 0b0_10_00011, // Rate type, Meter type 0b001_10_001, // precision, scale, size @@ -188,7 +189,7 @@ test("the Report command (V2) should be deserialized correctly (no time delta)", test("the Report command (V2) should be deserialized correctly (with time delta)", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MeterCommand.Report, // CC Command 0b0_10_00011, // Rate type, Meter type 0b001_10_001, // precision, scale, size @@ -214,7 +215,7 @@ test("the Report command (V2) should be deserialized correctly (with time delta) test("the Report command (V3) should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MeterCommand.Report, // CC Command 0b1_10_00001, // Scale(2), Rate type, Meter type 0b001_10_001, // precision, Scale (1:0), size @@ -235,7 +236,7 @@ test("the Report command (V3) should be deserialized correctly", (t) => { test("the Report command (V4) should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MeterCommand.Report, // CC Command 0b1_10_00001, // Scale(2), Rate type, Meter type 0b001_11_001, // precision, Scale (1:0), size @@ -257,7 +258,7 @@ test("the Report command (V4) should be deserialized correctly", (t) => { test("the Report command should validate that a known meter type is given", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MeterCommand.Report, // CC Command 0b1_10_11111, // Scale(2), Rate type, Meter type 0b001_11_001, // precision, Scale (1:0), size @@ -283,7 +284,7 @@ test("the Report command should validate that a known meter type is given", (t) test("the Report command should validate that a known meter scale is given", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MeterCommand.Report, // CC Command 0b1_10_00100, // Scale(2), Rate type, Meter type 0b001_11_001, // precision, Scale (1:0), size @@ -322,7 +323,7 @@ test("the value IDs should be translated correctly", (t) => { test("the SupportedReport command (V2/V3) should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MeterCommand.SupportedReport, // CC Command 0b1_00_10101, // supports reset, type 0b01101110, // supported scales @@ -342,7 +343,7 @@ test("the SupportedReport command (V2/V3) should be deserialized correctly", (t) test("the SupportedReport command (V4/V5) should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MeterCommand.SupportedReport, // CC Command 0b1_10_10101, // supports reset, rate types,type 0b1_0000001, // more scale types, supported scales @@ -365,7 +366,7 @@ test("the SupportedReport command (V4/V5) should be deserialized correctly", (t) // test("the SupportedReport command (V4/V5) should be deserialized correctly", (t) => { // const ccData = buildCCBuffer( -// Buffer.from([ +// Uint8Array.from([ // MeterCommand.SupportedReport, // CC Command // 0b1_10_10101, // supports reset, rate types,type // 0b1_0000001, // more scale types, supported scales @@ -387,7 +388,7 @@ test("the SupportedReport command (V4/V5) should be deserialized correctly", (t) test("deserializing an unsupported command should return an unspecified version of MeterCC", (t) => { const serializedCC = buildCCBuffer( - Buffer.from([255]), // not a valid command + Uint8Array.from([255]), // not a valid command ); const cc = CommandClass.parse( serializedCC, diff --git a/packages/zwave-js/src/lib/test/cc/MultiChannelAssociationCC.test.ts b/packages/zwave-js/src/lib/test/cc/MultiChannelAssociationCC.test.ts index bef3e1dfb1c7..accb9dce7ff7 100644 --- a/packages/zwave-js/src/lib/test/cc/MultiChannelAssociationCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/MultiChannelAssociationCC.test.ts @@ -9,11 +9,12 @@ import { MultiChannelAssociationCommand, } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; +import { Bytes } from "@zwave-js/shared/safe"; import test from "ava"; -function buildCCBuffer(payload: Buffer): Buffer { - return Buffer.concat([ - Buffer.from([ +function buildCCBuffer(payload: Uint8Array): Uint8Array { + return Bytes.concat([ + Uint8Array.from([ CommandClasses["Multi Channel Association"], // CC ]), payload, @@ -25,7 +26,7 @@ test("the SupportedGroupingsGet command should serialize correctly", (t) => { nodeId: 1, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MultiChannelAssociationCommand.SupportedGroupingsGet, // CC Command ]), ); @@ -34,7 +35,7 @@ test("the SupportedGroupingsGet command should serialize correctly", (t) => { test("the SupportedGroupingsReport command should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MultiChannelAssociationCommand.SupportedGroupingsReport, // CC Command 7, // # of groups ]), @@ -55,7 +56,7 @@ test("the Set command should serialize correctly (node IDs only)", (t) => { nodeIds: [1, 2, 5], }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MultiChannelAssociationCommand.Set, // CC Command 5, // group id // Node IDs @@ -83,7 +84,7 @@ test("the Set command should serialize correctly (endpoint addresses only)", (t) ], }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MultiChannelAssociationCommand.Set, // CC Command 5, // group id 0, // Endpoint marker @@ -115,7 +116,7 @@ test("the Set command should serialize correctly (both options)", (t) => { ], }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MultiChannelAssociationCommand.Set, // CC Command 5, // group id // Node IDs: @@ -140,7 +141,7 @@ test("the Get command should serialize correctly", (t) => { groupId: 9, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MultiChannelAssociationCommand.Get, // CC Command 9, // group ID ]), @@ -150,7 +151,7 @@ test("the Get command should serialize correctly", (t) => { test("the Report command should be deserialized correctly (node IDs only)", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MultiChannelAssociationCommand.Report, // CC Command 5, // group id 9, // max nodes @@ -176,7 +177,7 @@ test("the Report command should be deserialized correctly (node IDs only)", (t) test("the Report command should be deserialized correctly (endpoint addresses only)", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MultiChannelAssociationCommand.Report, // CC Command 5, // group id 9, // max nodes @@ -211,7 +212,7 @@ test("the Report command should be deserialized correctly (endpoint addresses on test("the Report command should be deserialized correctly (both options)", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MultiChannelAssociationCommand.Report, // CC Command 5, // group id 9, // max nodes @@ -254,7 +255,7 @@ test("the Remove command should serialize correctly (node IDs only)", (t) => { nodeIds: [1, 2, 5], }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MultiChannelAssociationCommand.Remove, // CC Command 5, // group id // Node IDs @@ -282,7 +283,7 @@ test("the Remove command should serialize correctly (endpoint addresses only)", ], }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MultiChannelAssociationCommand.Remove, // CC Command 5, // group id 0, // Endpoint marker @@ -314,7 +315,7 @@ test("the Remove command should serialize correctly (both options)", (t) => { ], }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MultiChannelAssociationCommand.Remove, // CC Command 5, // group id // Node IDs: @@ -339,7 +340,7 @@ test("the Remove command should serialize correctly (both empty)", (t) => { groupId: 5, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MultiChannelAssociationCommand.Remove, // CC Command 5, // group id ]), @@ -350,7 +351,7 @@ test("the Remove command should serialize correctly (both empty)", (t) => { // test("deserializing an unsupported command should return an unspecified version of MultiChannelAssociationCC", (t) => { // const serializedCC = buildCCBuffer( // 1, -// Buffer.from([255]), // not a valid command +// Uint8Array.from([255]), // not a valid command // ); // const cc: any = new MultiChannelAssociationCC({ // data: serializedCC, diff --git a/packages/zwave-js/src/lib/test/cc/MultiChannelCC.test.ts b/packages/zwave-js/src/lib/test/cc/MultiChannelCC.test.ts index 87db658c682e..18953361e37c 100644 --- a/packages/zwave-js/src/lib/test/cc/MultiChannelCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/MultiChannelCC.test.ts @@ -16,11 +16,12 @@ import { isEncapsulatingCommandClass, } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; +import { Bytes } from "@zwave-js/shared/safe"; import test from "ava"; -function buildCCBuffer(payload: Buffer): Buffer { - return Buffer.concat([ - Buffer.from([ +function buildCCBuffer(payload: Uint8Array): Uint8Array { + return Bytes.concat([ + Uint8Array.from([ CommandClasses["Multi Channel"], // CC ]), payload, @@ -39,7 +40,7 @@ test("is an encapsulating CommandClass", (t) => { test("the EndPointGet command should serialize correctly", (t) => { const cc = new MultiChannelCCEndPointGet({ nodeId: 1 }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MultiChannelCommand.EndPointGet, // CC Command ]), ); @@ -52,7 +53,7 @@ test("the CapabilityGet command should serialize correctly", (t) => { requestedEndpoint: 7, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MultiChannelCommand.CapabilityGet, // CC Command 7, // EndPoint ]), @@ -67,7 +68,7 @@ test("the EndPointFind command should serialize correctly", (t) => { specificClass: 0x02, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MultiChannelCommand.EndPointFind, // CC Command 0x01, // genericClass 0x02, // specificClass @@ -84,7 +85,7 @@ test("the CommandEncapsulation command should serialize correctly", (t) => { }); cc = MultiChannelCC.encapsulate(cc); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MultiChannelCommand.CommandEncapsulation, // CC Command 0, // source EP 7, // destination @@ -102,7 +103,7 @@ test("the AggregatedMembersGet command should serialize correctly", (t) => { requestedEndpoint: 6, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MultiChannelCommand.AggregatedMembersGet, // CC Command 6, // EndPoint ]), @@ -131,7 +132,7 @@ test("the CommandEncapsulation command should also accept V1CommandEncapsulation // test("the Report command (v2) should be deserialized correctly", (t) => { // const ccData = buildCCBuffer( // 1, -// Buffer.from([ +// Uint8Array.from([ // MultiChannelCommand.Report, // CC Command // 55, // current value // 66, // target value @@ -148,7 +149,7 @@ test("the CommandEncapsulation command should also accept V1CommandEncapsulation test("deserializing an unsupported command should return an unspecified version of MultiChannelCC", (t) => { const serializedCC = buildCCBuffer( - Buffer.from([255]), // not a valid command + Uint8Array.from([255]), // not a valid command ); const cc = CommandClass.parse( serializedCC, diff --git a/packages/zwave-js/src/lib/test/cc/MultilevelSwitchCC.test.ts b/packages/zwave-js/src/lib/test/cc/MultilevelSwitchCC.test.ts index 753e22153ba5..32704955ace6 100644 --- a/packages/zwave-js/src/lib/test/cc/MultilevelSwitchCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/MultilevelSwitchCC.test.ts @@ -11,11 +11,12 @@ import { } from "@zwave-js/cc"; import { CommandClasses, Duration } from "@zwave-js/core"; import { type GetSupportedCCVersion } from "@zwave-js/host"; +import { Bytes } from "@zwave-js/shared/safe"; import test from "ava"; -function buildCCBuffer(payload: Buffer): Buffer { - return Buffer.concat([ - Buffer.from([ +function buildCCBuffer(payload: Uint8Array): Uint8Array { + return Bytes.concat([ + Uint8Array.from([ CommandClasses["Multilevel Switch"], // CC ]), payload, @@ -25,7 +26,7 @@ function buildCCBuffer(payload: Buffer): Buffer { test("the Get command should serialize correctly", (t) => { const cc = new MultilevelSwitchCCGet({ nodeId: 1 }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MultilevelSwitchCommand.Get, // CC Command ]), ); @@ -38,7 +39,7 @@ test("the Set command should serialize correctly (no duration)", (t) => { targetValue: 55, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MultilevelSwitchCommand.Set, // CC Command 55, // target value, 0xff, // default duration @@ -60,7 +61,7 @@ test("the Set command (V2) should serialize correctly", (t) => { duration: new Duration(2, "minutes"), }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MultilevelSwitchCommand.Set, // CC Command 55, // target value, 0x81, // 2 minutes @@ -77,7 +78,7 @@ test("the Set command (V2) should serialize correctly", (t) => { test("the Report command (V1) should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MultilevelSwitchCommand.Report, // CC Command 55, // current value ]), @@ -95,7 +96,7 @@ test("the Report command (V1) should be deserialized correctly", (t) => { test("the Report command (v4) should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MultilevelSwitchCommand.Report, // CC Command 55, // current value 66, // target value @@ -119,7 +120,7 @@ test("the StopLevelChange command should serialize correctly", (t) => { nodeId: 1, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MultilevelSwitchCommand.StopLevelChange, // CC Command ]), ); @@ -135,7 +136,7 @@ test("the StartLevelChange command (V2) should serialize correctly (down, ignore duration: new Duration(3, "seconds"), }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MultilevelSwitchCommand.StartLevelChange, // CC Command 0b011_00000, // down, ignore start level 50, // start level @@ -156,7 +157,7 @@ test("the SupportedGet command should serialize correctly", (t) => { nodeId: 1, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ MultilevelSwitchCommand.SupportedGet, // CC Command ]), ); @@ -165,7 +166,7 @@ test("the SupportedGet command should serialize correctly", (t) => { test("deserializing an unsupported command should return an unspecified version of MultilevelSwitchCC", (t) => { const serializedCC = buildCCBuffer( - Buffer.from([255]), // not a valid command + Uint8Array.from([255]), // not a valid command ); const cc = CommandClass.parse( serializedCC, diff --git a/packages/zwave-js/src/lib/test/cc/NoOperationCC.test.ts b/packages/zwave-js/src/lib/test/cc/NoOperationCC.test.ts index 4c2aedc3533e..49b9162678c4 100644 --- a/packages/zwave-js/src/lib/test/cc/NoOperationCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/NoOperationCC.test.ts @@ -1,10 +1,11 @@ import { NoOperationCC } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; +import { Bytes } from "@zwave-js/shared/safe"; import test from "ava"; -function buildCCBuffer(payload: Buffer): Buffer { - return Buffer.concat([ - Buffer.from([ +function buildCCBuffer(payload: Uint8Array): Uint8Array { + return Bytes.concat([ + Uint8Array.from([ CommandClasses["No Operation"], // CC ]), payload, @@ -14,14 +15,14 @@ function buildCCBuffer(payload: Buffer): Buffer { test("the CC should serialize correctly", (t) => { const cc = new NoOperationCC({ nodeId: 1 }); const expected = buildCCBuffer( - Buffer.from([]), // No command! + Uint8Array.from([]), // No command! ); t.deepEqual(cc.serialize({} as any), expected); }); test("the CC should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([]), // No command! + Uint8Array.from([]), // No command! ); t.notThrows(() => new NoOperationCC({ nodeId: 2, data: ccData, context: {} as any }) diff --git a/packages/zwave-js/src/lib/test/cc/PowerlevelCC.test.ts b/packages/zwave-js/src/lib/test/cc/PowerlevelCC.test.ts index 54222320b630..d4ee905113b5 100644 --- a/packages/zwave-js/src/lib/test/cc/PowerlevelCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/PowerlevelCC.test.ts @@ -8,11 +8,12 @@ import { PowerlevelCommand, } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; +import { Bytes } from "@zwave-js/shared/safe"; import test from "ava"; -function buildCCBuffer(payload: Buffer): Buffer { - return Buffer.concat([ - Buffer.from([ +function buildCCBuffer(payload: Uint8Array): Uint8Array { + return Bytes.concat([ + Uint8Array.from([ CommandClasses.Powerlevel, // CC ]), payload, @@ -22,7 +23,7 @@ function buildCCBuffer(payload: Buffer): Buffer { test("the Get command should serialize correctly", (t) => { const cc = new PowerlevelCCGet({ nodeId: 1 }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ PowerlevelCommand.Get, // CC Command ]), ); @@ -35,7 +36,7 @@ test("the Set NormalPower command should serialize correctly", (t) => { powerlevel: Powerlevel["Normal Power"], }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ PowerlevelCommand.Set, // CC Command Powerlevel["Normal Power"], // powerlevel 0, // timeout (ignored) @@ -51,7 +52,7 @@ test("the Set NormalPower command with timeout should serialize correctly", (t) timeout: 50, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ PowerlevelCommand.Set, // CC Command Powerlevel["Normal Power"], // powerlevel 0x00, // timeout ignored @@ -67,7 +68,7 @@ test("the Set Custom power command should serialize correctly", (t) => { timeout: 50, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ PowerlevelCommand.Set, // CC Command Powerlevel["-1 dBm"], // powerlevel 50, // timeout @@ -78,7 +79,7 @@ test("the Set Custom power command should serialize correctly", (t) => { test("the Report command should be deserialized correctly (NormalPower)", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ PowerlevelCommand.Report, // CC Command Powerlevel["Normal Power"], // powerlevel 50, // timeout (ignored because NormalPower) @@ -96,7 +97,7 @@ test("the Report command should be deserialized correctly (NormalPower)", (t) => test("the Report command should be deserialized correctly (custom power)", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ PowerlevelCommand.Report, // CC Command Powerlevel["-3 dBm"], // powerlevel 50, // timeout (ignored because NormalPower) @@ -114,7 +115,7 @@ test("the Report command should be deserialized correctly (custom power)", (t) = test("deserializing an unsupported command should return an unspecified version of PowerlevelCC", (t) => { const serializedCC = buildCCBuffer( - Buffer.from([255]), // not a valid command + Uint8Array.from([255]), // not a valid command ); const cc = CommandClass.parse( serializedCC, diff --git a/packages/zwave-js/src/lib/test/cc/SceneActivationCC.test.ts b/packages/zwave-js/src/lib/test/cc/SceneActivationCC.test.ts index d9eddc9e718b..5788db123906 100644 --- a/packages/zwave-js/src/lib/test/cc/SceneActivationCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/SceneActivationCC.test.ts @@ -5,11 +5,12 @@ import { SceneActivationCommand, } from "@zwave-js/cc"; import { CommandClasses, Duration } from "@zwave-js/core"; +import { Bytes } from "@zwave-js/shared/safe"; import test from "ava"; -function buildCCBuffer(payload: Buffer): Buffer { - return Buffer.concat([ - Buffer.from([ +function buildCCBuffer(payload: Uint8Array): Uint8Array { + return Bytes.concat([ + Uint8Array.from([ CommandClasses["Scene Activation"], // CC ]), payload, @@ -22,7 +23,7 @@ test("the Set command (without Duration) should serialize correctly", (t) => { sceneId: 55, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ SceneActivationCommand.Set, // CC Command 55, // id 0xff, // default duration @@ -38,7 +39,7 @@ test("the Set command (with Duration) should serialize correctly", (t) => { dimmingDuration: new Duration(1, "minutes"), }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ SceneActivationCommand.Set, // CC Command 56, // id 0x80, // 1 minute @@ -49,7 +50,7 @@ test("the Set command (with Duration) should serialize correctly", (t) => { test("the Set command should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ SceneActivationCommand.Set, // CC Command 15, // id 0x00, // 0 seconds @@ -67,7 +68,7 @@ test("the Set command should be deserialized correctly", (t) => { test("deserializing an unsupported command should return an unspecified version of SceneActivationCC", (t) => { const serializedCC = buildCCBuffer( - Buffer.from([255]), // not a valid command + Uint8Array.from([255]), // not a valid command ); const cc = CommandClass.parse( serializedCC, diff --git a/packages/zwave-js/src/lib/test/cc/SceneActuatorConfigurationCC.test.ts b/packages/zwave-js/src/lib/test/cc/SceneActuatorConfigurationCC.test.ts index ed40a7b1cb21..49e431cb378d 100644 --- a/packages/zwave-js/src/lib/test/cc/SceneActuatorConfigurationCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/SceneActuatorConfigurationCC.test.ts @@ -7,11 +7,12 @@ import { SceneActuatorConfigurationCommand, } from "@zwave-js/cc"; import { CommandClasses, Duration } from "@zwave-js/core"; +import { Bytes } from "@zwave-js/shared/safe"; import test from "ava"; -function buildCCBuffer(payload: Buffer): Buffer { - return Buffer.concat([ - Buffer.from([ +function buildCCBuffer(payload: Uint8Array): Uint8Array { + return Bytes.concat([ + Uint8Array.from([ CommandClasses["Scene Actuator Configuration"], // CC ]), payload, @@ -24,7 +25,7 @@ test("the Get command should serialize correctly", (t) => { sceneId: 1, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ SceneActuatorConfigurationCommand.Get, // CC Command 1, ]), @@ -40,7 +41,7 @@ test("the Set command should serialize correctly with level", (t) => { dimmingDuration: Duration.parseSet(0x05)!, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ SceneActuatorConfigurationCommand.Set, // CC Command 2, 0x05, // dimmingDuration @@ -59,7 +60,7 @@ test("the Set command should serialize correctly with undefined level", (t) => { dimmingDuration: Duration.parseSet(0x05)!, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ SceneActuatorConfigurationCommand.Set, // CC Command 2, // nodeId 0x05, // dimmingDuration @@ -72,7 +73,7 @@ test("the Set command should serialize correctly with undefined level", (t) => { test("the Report command (v1) should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ SceneActuatorConfigurationCommand.Report, // CC Command 55, // sceneId 0x50, // level @@ -92,7 +93,7 @@ test("the Report command (v1) should be deserialized correctly", (t) => { test("deserializing an unsupported command should return an unspecified version of SceneActuatorConfigurationCC", (t) => { const serializedCC = buildCCBuffer( - Buffer.from([255]), // not a valid command + Uint8Array.from([255]), // not a valid command ); const cc = CommandClass.parse( serializedCC, diff --git a/packages/zwave-js/src/lib/test/cc/SceneControllerConfigurationCC.test.ts b/packages/zwave-js/src/lib/test/cc/SceneControllerConfigurationCC.test.ts index 64966795cb65..a713161c7d77 100644 --- a/packages/zwave-js/src/lib/test/cc/SceneControllerConfigurationCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/SceneControllerConfigurationCC.test.ts @@ -7,11 +7,12 @@ import { SceneControllerConfigurationCommand, } from "@zwave-js/cc"; import { CommandClasses, Duration } from "@zwave-js/core"; +import { Bytes } from "@zwave-js/shared/safe"; import test from "ava"; -function buildCCBuffer(payload: Buffer): Buffer { - return Buffer.concat([ - Buffer.from([ +function buildCCBuffer(payload: Uint8Array): Uint8Array { + return Bytes.concat([ + Uint8Array.from([ CommandClasses["Scene Controller Configuration"], // CC ]), payload, @@ -24,7 +25,7 @@ test("the Get command should serialize correctly", (t) => { groupId: 1, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ SceneControllerConfigurationCommand.Get, // CC Command 0b0000_0001, ]), @@ -40,7 +41,7 @@ test("the Set command should serialize correctly", (t) => { dimmingDuration: Duration.parseSet(0x05)!, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ SceneControllerConfigurationCommand.Set, // CC Command 3, // groupId 240, // sceneId @@ -58,7 +59,7 @@ test("the Set command should serialize correctly with undefined duration", (t) = dimmingDuration: undefined, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ SceneControllerConfigurationCommand.Set, // CC Command 3, // groupId 240, // sceneId @@ -70,7 +71,7 @@ test("the Set command should serialize correctly with undefined duration", (t) = test("the Report command (v1) should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ SceneControllerConfigurationCommand.Report, // CC Command 3, // groupId 240, // sceneId @@ -90,7 +91,7 @@ test("the Report command (v1) should be deserialized correctly", (t) => { test("deserializing an unsupported command should return an unspecified version of SceneControllerConfigurationCC", (t) => { const serializedCC = buildCCBuffer( - Buffer.from([255]), // not a valid command + Uint8Array.from([255]), // not a valid command ); const cc = CommandClass.parse( serializedCC, diff --git a/packages/zwave-js/src/lib/test/cc/ThermostatFanModeCC.test.ts b/packages/zwave-js/src/lib/test/cc/ThermostatFanModeCC.test.ts index ea0b4e98dac1..991b5c82c882 100644 --- a/packages/zwave-js/src/lib/test/cc/ThermostatFanModeCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/ThermostatFanModeCC.test.ts @@ -7,11 +7,12 @@ import { ThermostatFanModeCommand, } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; +import { Bytes } from "@zwave-js/shared/safe"; import test from "ava"; -function buildCCBuffer(payload: Buffer): Buffer { - return Buffer.concat([ - Buffer.from([ +function buildCCBuffer(payload: Uint8Array): Uint8Array { + return Bytes.concat([ + Uint8Array.from([ CommandClasses["Thermostat Fan Mode"], // CC ]), payload, @@ -21,7 +22,7 @@ function buildCCBuffer(payload: Buffer): Buffer { test("the Get command should serialize correctly", (t) => { const cc = new ThermostatFanModeCCGet({ nodeId: 5 }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ ThermostatFanModeCommand.Get, // CC Command ]), ); @@ -35,7 +36,7 @@ test("the Set command should serialize correctly (off = false)", (t) => { off: false, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ ThermostatFanModeCommand.Set, // CC Command 0x04, // target value ]), @@ -50,7 +51,7 @@ test("the Set command should serialize correctly (off = true)", (t) => { off: true, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ ThermostatFanModeCommand.Set, // CC Command 0b1000_0100, // target value ]), @@ -60,7 +61,7 @@ test("the Set command should serialize correctly (off = true)", (t) => { test("the Report command should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ ThermostatFanModeCommand.Report, // CC Command 0b1000_0010, // Off bit set to 1 and Auto high mode ]), diff --git a/packages/zwave-js/src/lib/test/cc/ThermostatFanStateCC.test.ts b/packages/zwave-js/src/lib/test/cc/ThermostatFanStateCC.test.ts index 1766069a644c..3b328c2bbc8d 100644 --- a/packages/zwave-js/src/lib/test/cc/ThermostatFanStateCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/ThermostatFanStateCC.test.ts @@ -7,11 +7,12 @@ import { ThermostatFanStateCommand, } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; +import { Bytes } from "@zwave-js/shared/safe"; import test from "ava"; -function buildCCBuffer(payload: Buffer): Buffer { - return Buffer.concat([ - Buffer.from([ +function buildCCBuffer(payload: Uint8Array): Uint8Array { + return Bytes.concat([ + Uint8Array.from([ CommandClasses["Thermostat Fan State"], // CC ]), payload, @@ -21,7 +22,7 @@ function buildCCBuffer(payload: Buffer): Buffer { test("the Get command should serialize correctly", (t) => { const cc = new ThermostatFanStateCCGet({ nodeId: 1 }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ ThermostatFanStateCommand.Get, // CC Command ]), ); @@ -30,7 +31,7 @@ test("the Get command should serialize correctly", (t) => { test("the Report command (v1 - v2) should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ ThermostatFanStateCommand.Report, // CC Command ThermostatFanState["Idle / off"], // state ]), @@ -46,7 +47,7 @@ test("the Report command (v1 - v2) should be deserialized correctly", (t) => { test("deserializing an unsupported command should return an unspecified version of ThermostatFanStateCC", (t) => { const serializedCC = buildCCBuffer( - Buffer.from([255]), // not a valid command + Uint8Array.from([255]), // not a valid command ); const cc = CommandClass.parse( serializedCC, diff --git a/packages/zwave-js/src/lib/test/cc/TimeCC.test.ts b/packages/zwave-js/src/lib/test/cc/TimeCC.test.ts index d9f24ae44afa..bcb08ef280ca 100644 --- a/packages/zwave-js/src/lib/test/cc/TimeCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/TimeCC.test.ts @@ -8,11 +8,12 @@ import { TimeCommand, } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; +import { Bytes } from "@zwave-js/shared/safe"; import test from "ava"; -function buildCCBuffer(payload: Buffer): Buffer { - return Buffer.concat([ - Buffer.from([ +function buildCCBuffer(payload: Uint8Array): Uint8Array { + return Bytes.concat([ + Uint8Array.from([ CommandClasses.Time, // CC ]), payload, @@ -22,7 +23,7 @@ function buildCCBuffer(payload: Buffer): Buffer { test("the TimeGet command should serialize correctly", (t) => { const cc = new TimeCCTimeGet({ nodeId: 1 }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ TimeCommand.TimeGet, // CC Command ]), ); @@ -31,7 +32,7 @@ test("the TimeGet command should serialize correctly", (t) => { test("the TimeReport command should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ TimeCommand.TimeReport, // CC Command 14, 23, @@ -52,7 +53,7 @@ test("the TimeReport command should be deserialized correctly", (t) => { test("the DateGet command should serialize correctly", (t) => { const cc = new TimeCCDateGet({ nodeId: 1 }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ TimeCommand.DateGet, // CC Command ]), ); @@ -61,7 +62,7 @@ test("the DateGet command should serialize correctly", (t) => { test("the DateReport command should be deserialized correctly", (t) => { const ccData = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ TimeCommand.DateReport, // CC Command 0x07, 0xc5, @@ -82,7 +83,7 @@ test("the DateReport command should be deserialized correctly", (t) => { test("deserializing an unsupported command should return an unspecified version of TimeCC", (t) => { const serializedCC = buildCCBuffer( - Buffer.from([255]), // not a valid command + Uint8Array.from([255]), // not a valid command ); const cc = CommandClass.parse( serializedCC, diff --git a/packages/zwave-js/src/lib/test/cc/WakeUpCC.test.ts b/packages/zwave-js/src/lib/test/cc/WakeUpCC.test.ts index ddb6d59d538c..fe0513c63f52 100644 --- a/packages/zwave-js/src/lib/test/cc/WakeUpCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/WakeUpCC.test.ts @@ -4,6 +4,7 @@ import { WakeUpCCNoMoreInformation, } from "@zwave-js/cc"; import { generateAuthKey, generateEncryptionKey } from "@zwave-js/core"; +import { Bytes } from "@zwave-js/shared/safe"; import test from "ava"; import { randomBytes } from "node:crypto"; @@ -29,7 +30,7 @@ test("SecurityCC/WakeUpCCNoMoreInformation should expect NO response", (t) => { // The nonce needed to decode it const nonce = randomBytes(8); // The network key needed to decode it - const networkKey = Buffer.from("0102030405060708090a0b0c0d0e0f10", "hex"); + const networkKey = Bytes.from("0102030405060708090a0b0c0d0e0f10", "hex"); const securityManager = { getNonce: () => nonce, diff --git a/packages/zwave-js/src/lib/test/cc/ZWavePlusCC.test.ts b/packages/zwave-js/src/lib/test/cc/ZWavePlusCC.test.ts index 4933a967c143..63850a0eaae3 100644 --- a/packages/zwave-js/src/lib/test/cc/ZWavePlusCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/ZWavePlusCC.test.ts @@ -1,10 +1,11 @@ import { ZWavePlusCCGet, ZWavePlusCommand } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; +import { Bytes } from "@zwave-js/shared/safe"; import test from "ava"; -function buildCCBuffer(payload: Buffer): Buffer { - return Buffer.concat([ - Buffer.from([ +function buildCCBuffer(payload: Uint8Array): Uint8Array { + return Bytes.concat([ + Uint8Array.from([ CommandClasses["Z-Wave Plus Info"], // CC ]), payload, @@ -16,7 +17,7 @@ test("The Get command should serialize correctly", (t) => { nodeId: 1, }); const expected = buildCCBuffer( - Buffer.from([ + Uint8Array.from([ ZWavePlusCommand.Get, // CC Command ]), ); diff --git a/packages/zwave-js/src/lib/test/driver/SerialLogger.test.ts b/packages/zwave-js/src/lib/test/driver/SerialLogger.test.ts index 2b006fd6e78f..9a37e114c051 100644 --- a/packages/zwave-js/src/lib/test/driver/SerialLogger.test.ts +++ b/packages/zwave-js/src/lib/test/driver/SerialLogger.test.ts @@ -4,6 +4,7 @@ import { } from "@zwave-js/core"; import { SpyTransport, assertMessage } from "@zwave-js/core/test"; import { SerialLogger } from "@zwave-js/serial"; +import { Bytes } from "@zwave-js/shared/safe"; import colors from "ansi-colors"; import ava, { type TestFn } from "ava"; import { pseudoRandomBytes } from "node:crypto"; @@ -113,7 +114,7 @@ for (const msg of ["ACK", "NAK", "CAN"] as const) { test.serial("logs raw data correctly: short buffer, inbound", (t) => { const { serialLogger, spyTransport } = t.context; - serialLogger.data("inbound", Buffer.from([1, 2, 3, 4, 5, 6, 7, 8])); + serialLogger.data("inbound", Uint8Array.from([1, 2, 3, 4, 5, 6, 7, 8])); const alignRight = " ".repeat(80 - 30); assertMessage(t, spyTransport, { message: `« 0x0102030405060708 ${alignRight}(8 bytes)`, @@ -122,7 +123,7 @@ test.serial("logs raw data correctly: short buffer, inbound", (t) => { test.serial("logs raw data correctly: short buffer, outbound", (t) => { const { serialLogger, spyTransport } = t.context; - serialLogger.data("outbound", Buffer.from([0x55, 4, 3, 2, 1])); + serialLogger.data("outbound", Uint8Array.from([0x55, 4, 3, 2, 1])); const alignRight = " ".repeat(80 - 24); assertMessage(t, spyTransport, { message: `» 0x5504030201 ${alignRight}(5 bytes)`, @@ -162,7 +163,7 @@ test.serial("correctly groups very long lines", (t) => { test.serial("logs discarded data correctly", (t) => { const { serialLogger, spyTransport } = t.context; - serialLogger.discarded(Buffer.from("02020202020202", "hex")); + serialLogger.discarded(Bytes.from("02020202020202", "hex")); const alignRight = " ".repeat(80 - 53); assertMessage(t, spyTransport, { message: diff --git a/packages/zwave-js/src/lib/test/driver/assemblePartialCCs.test.ts b/packages/zwave-js/src/lib/test/driver/assemblePartialCCs.test.ts index d1f0be6db1f1..f1757640238a 100644 --- a/packages/zwave-js/src/lib/test/driver/assemblePartialCCs.test.ts +++ b/packages/zwave-js/src/lib/test/driver/assemblePartialCCs.test.ts @@ -25,7 +25,7 @@ test.beforeEach(async (t) => { loadConfiguration: false, skipNodeInterview: true, securityKeys: { - S0_Legacy: Buffer.alloc(16, 0xff), + S0_Legacy: new Uint8Array(16).fill(0xff), }, beforeStartup(mockPort) { const controller = new MockController({ serial: mockPort }); @@ -63,7 +63,7 @@ test.serial( (t) => { const { driver } = t.context; const cc = CommandClass.parse( - Buffer.from([ + Uint8Array.from([ CommandClasses.Association, AssociationCommand.Report, 1, @@ -87,7 +87,7 @@ test.serial( (t) => { const { driver } = t.context; const cc = CommandClass.parse( - Buffer.from([ + Uint8Array.from([ CommandClasses.Association, AssociationCommand.Report, 1, @@ -111,7 +111,7 @@ test.serial( (t) => { const { driver } = t.context; const cc1 = CommandClass.parse( - Buffer.from([ + Uint8Array.from([ CommandClasses.Association, AssociationCommand.Report, 1, @@ -124,7 +124,7 @@ test.serial( { sourceNodeId: 2 } as any, ) as AssociationCCReport; const cc2 = CommandClass.parse( - Buffer.from([ + Uint8Array.from([ CommandClasses.Association, AssociationCommand.Report, 1, @@ -189,7 +189,7 @@ test.serial("supports nested partial/partial CCs (part 1)", (t) => { encapsulated: {} as any, }); cc.encapsulated = undefined as any; - cc["decryptedCCBytes"] = Buffer.from([ + cc["decryptedCCBytes"] = Uint8Array.from([ CommandClasses.Association, AssociationCommand.Report, 1, @@ -212,7 +212,7 @@ test.serial("supports nested partial/partial CCs (part 2)", (t) => { encapsulated: {} as any, }); cc.encapsulated = undefined as any; - cc["decryptedCCBytes"] = Buffer.from([ + cc["decryptedCCBytes"] = Uint8Array.from([ CommandClasses.Association, AssociationCommand.Report, 1, @@ -233,7 +233,7 @@ test.serial( (t) => { const { driver } = t.context; const cc = CommandClass.parse( - Buffer.from([ + Uint8Array.from([ CommandClasses.Association, AssociationCommand.Report, 1, @@ -263,7 +263,7 @@ test.serial( (t) => { const { driver } = t.context; const cc = CommandClass.parse( - Buffer.from([ + Uint8Array.from([ CommandClasses.Association, AssociationCommand.Report, 1, @@ -293,7 +293,7 @@ test.serial( (t) => { const { driver } = t.context; const cc = CommandClass.parse( - Buffer.from([ + Uint8Array.from([ CommandClasses.Association, AssociationCommand.Report, 1, @@ -321,7 +321,7 @@ test.serial( test.serial("passes other errors during merging through", (t) => { const { driver } = t.context; const cc = CommandClass.parse( - Buffer.from([ + Uint8Array.from([ CommandClasses.Association, AssociationCommand.Report, 1, diff --git a/packages/zwave-js/src/lib/test/driver/bootloaderDetection.test.ts b/packages/zwave-js/src/lib/test/driver/bootloaderDetection.test.ts index 2512c8985a4d..5af44a799a40 100644 --- a/packages/zwave-js/src/lib/test/driver/bootloaderDetection.test.ts +++ b/packages/zwave-js/src/lib/test/driver/bootloaderDetection.test.ts @@ -1,3 +1,4 @@ +import { Bytes } from "@zwave-js/shared"; import { type MockControllerBehavior } from "@zwave-js/testing"; import { setTimeout as wait } from "node:timers/promises"; import { integrationTest } from "../integrationTestSuite"; @@ -21,10 +22,10 @@ integrationTest( // ) { // I've seen logs with as few as 5 bytes in the first chunk self.serial.emitData( - Buffer.from("\0\r\nGeck", "ascii"), + Bytes.from("\0\r\nGeck", "ascii"), ); await wait(20); - self.serial.emitData(Buffer.from( + self.serial.emitData(Bytes.from( `o Bootloader v2.05.01 1. upload gbl 2. run diff --git a/packages/zwave-js/src/lib/test/driver/computeNetCCPayloadSize.test.ts b/packages/zwave-js/src/lib/test/driver/computeNetCCPayloadSize.test.ts index 7591f70e8848..a44fc72e6362 100644 --- a/packages/zwave-js/src/lib/test/driver/computeNetCCPayloadSize.test.ts +++ b/packages/zwave-js/src/lib/test/driver/computeNetCCPayloadSize.test.ts @@ -22,7 +22,7 @@ test.beforeEach(async (t) => { loadConfiguration: false, skipNodeInterview: true, securityKeys: { - S0_Legacy: Buffer.alloc(16, 0xff), + S0_Legacy: new Uint8Array(16).fill(0xff), }, beforeStartup(mockPort) { const controller = new MockController({ serial: mockPort }); diff --git a/packages/zwave-js/src/lib/test/driver/handleNonImplementedCCs.test.ts b/packages/zwave-js/src/lib/test/driver/handleNonImplementedCCs.test.ts index 4da3efc61d06..d33ec54f0938 100644 --- a/packages/zwave-js/src/lib/test/driver/handleNonImplementedCCs.test.ts +++ b/packages/zwave-js/src/lib/test/driver/handleNonImplementedCCs.test.ts @@ -1,5 +1,6 @@ import { CommandClass } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; +import { Bytes } from "@zwave-js/shared"; import { createMockZWaveRequestFrame } from "@zwave-js/testing"; import { integrationTest } from "../integrationTestSuite"; @@ -26,7 +27,7 @@ integrationTest( nodeId: mockController.ownNodeId, ccId: CommandClasses["Anti-Theft"], ccCommand: 0x02, // Get - payload: Buffer.from([0x00, 0x01]), // Technically invalid + payload: Uint8Array.from([0x00, 0x01]), // Technically invalid }); await mockNode.sendToController( createMockZWaveRequestFrame(cc, { @@ -38,7 +39,7 @@ integrationTest( t.like(result, { ccId: CommandClasses["Anti-Theft"], ccCommand: 0x02, - payload: Buffer.from([0x00, 0x01]), + payload: Bytes.from([0x00, 0x01]), }); }, }, diff --git a/packages/zwave-js/src/lib/test/driver/handleUnsolicited.test.ts b/packages/zwave-js/src/lib/test/driver/handleUnsolicited.test.ts index 8cdb370f39ee..b90e82eaac4f 100644 --- a/packages/zwave-js/src/lib/test/driver/handleUnsolicited.test.ts +++ b/packages/zwave-js/src/lib/test/driver/handleUnsolicited.test.ts @@ -1,7 +1,7 @@ import { BasicCCValues } from "@zwave-js/cc/BasicCC"; import { MessageHeaders } from "@zwave-js/serial"; import type { MockSerialPort } from "@zwave-js/serial/mock"; -import { type ThrowingMap, createThrowingMap } from "@zwave-js/shared"; +import { Bytes, type ThrowingMap, createThrowingMap } from "@zwave-js/shared"; import ava, { type TestFn } from "ava"; import { setTimeout as wait } from "node:timers/promises"; import type { Driver } from "../../driver/Driver"; @@ -63,8 +63,8 @@ test.serial( const valueId = BasicCCValues.currentValue.id; t.is(node2.getValue(valueId), undefined); - const ACK = Buffer.from([MessageHeaders.ACK]); - serialport.receiveData(Buffer.from("01090004000203200105d7", "hex")); + const ACK = Uint8Array.from([MessageHeaders.ACK]); + serialport.receiveData(Bytes.from("01090004000203200105d7", "hex")); // « [Node 002] [REQ] [ApplicationCommand] // └─[BasicCCSet] // target value: 5 @@ -97,7 +97,7 @@ test.serial( const valueId = BasicCCValues.currentValue.id; t.is(node2.getValue(valueId), undefined); - const ACK = Buffer.from([MessageHeaders.ACK]); + const ACK = Uint8Array.from([MessageHeaders.ACK]); // Step 1: Send a ping and receive the response node2.ping(); @@ -108,7 +108,7 @@ test.serial( // └─[NoOperationCC] t.deepEqual( serialport.lastWrite, - Buffer.from("010800130201002501c3", "hex"), + Bytes.from("010800130201002501c3", "hex"), ); await wait(10); serialport.receiveData(ACK); @@ -117,7 +117,7 @@ test.serial( // We're now waiting for a response. The next command must not get lost - serialport.receiveData(Buffer.from("01090004000203200105d7", "hex")); + serialport.receiveData(Bytes.from("01090004000203200105d7", "hex")); // « [Node 002] [REQ] [ApplicationCommand] // └─[BasicCCSet] // target value: 5 @@ -150,7 +150,7 @@ test.serial( const valueId = BasicCCValues.currentValue.id; t.is(node2.getValue(valueId), undefined); - const ACK = Buffer.from([MessageHeaders.ACK]); + const ACK = Uint8Array.from([MessageHeaders.ACK]); // Step 1: Send a ping and receive the response node2.ping(); @@ -161,7 +161,7 @@ test.serial( // └─[NoOperationCC] t.deepEqual( serialport.lastWrite, - Buffer.from("010800130201002501c3", "hex"), + Bytes.from("010800130201002501c3", "hex"), ); await wait(10); serialport.receiveData(ACK); @@ -170,7 +170,7 @@ test.serial( // « [RES] [SendData] // was sent: true - serialport.receiveData(Buffer.from("0104011301e8", "hex")); + serialport.receiveData(Bytes.from("0104011301e8", "hex")); // » [ACK] t.deepEqual(serialport.lastWrite, ACK); @@ -178,7 +178,7 @@ test.serial( // We're now waiting for a callback. The next command must not get lost - serialport.receiveData(Buffer.from("01090004000203200105d7", "hex")); + serialport.receiveData(Bytes.from("01090004000203200105d7", "hex")); // « [Node 002] [REQ] [ApplicationCommand] // └─[BasicCCSet] // target value: 5 diff --git a/packages/zwave-js/src/lib/test/driver/invalidPayloadLog.test.ts b/packages/zwave-js/src/lib/test/driver/invalidPayloadLog.test.ts index 0b06e19af19e..9469a9e9a11c 100644 --- a/packages/zwave-js/src/lib/test/driver/invalidPayloadLog.test.ts +++ b/packages/zwave-js/src/lib/test/driver/invalidPayloadLog.test.ts @@ -5,7 +5,7 @@ import { import { SpyTransport, assertMessage } from "@zwave-js/core/test"; import { MessageHeaders } from "@zwave-js/serial"; import type { MockSerialPort } from "@zwave-js/serial/mock"; -import type { ThrowingMap } from "@zwave-js/shared"; +import { Bytes, type ThrowingMap } from "@zwave-js/shared"; import ava, { type TestFn } from "ava"; import MockDate from "mockdate"; import { setTimeout as wait } from "node:timers/promises"; @@ -98,13 +98,13 @@ test("when an invalid CC is received, this is printed in the logs", async (t) => node33["isFrequentListening"] = false; node33.markAsAlive(); - const ACK = Buffer.from([MessageHeaders.ACK]); + const ACK = Uint8Array.from([MessageHeaders.ACK]); // « [Node 033] [REQ] [ApplicationCommand] // └─[BinarySensorCCReport] // type: Motion // value: true - serialport.receiveData(Buffer.from("010800040021043003e5", "hex")); + serialport.receiveData(Bytes.from("010800040021043003e5", "hex")); await wait(10); assertMessage(t, spyTransport, { callNumber: 1, diff --git a/packages/zwave-js/src/lib/test/driver/receiveApplicationCommandHandlerBridge.test.ts b/packages/zwave-js/src/lib/test/driver/receiveApplicationCommandHandlerBridge.test.ts index e1f6b2379589..7175b103c9bf 100644 --- a/packages/zwave-js/src/lib/test/driver/receiveApplicationCommandHandlerBridge.test.ts +++ b/packages/zwave-js/src/lib/test/driver/receiveApplicationCommandHandlerBridge.test.ts @@ -1,7 +1,7 @@ import { CommandClasses, SecurityManager } from "@zwave-js/core"; import { MessageHeaders } from "@zwave-js/serial"; import type { MockSerialPort } from "@zwave-js/serial/mock"; -import type { ThrowingMap } from "@zwave-js/shared"; +import { Bytes, type ThrowingMap } from "@zwave-js/shared"; import ava, { type TestFn } from "ava"; import { setTimeout as wait } from "node:timers/promises"; import type { Driver } from "../../driver/Driver"; @@ -21,7 +21,7 @@ test.beforeEach(async (t) => { const { driver, serialport } = await createAndStartDriver({ securityKeys: { - S0_Legacy: Buffer.alloc(16, 0), + S0_Legacy: new Uint8Array(16).fill(0), }, }); @@ -73,7 +73,7 @@ test("Node responses in a BridgeApplicationCommandRequest should be understood", }); node3.markAsAlive(); - const ACK = Buffer.from([MessageHeaders.ACK]); + const ACK = Uint8Array.from([MessageHeaders.ACK]); const getNoncePromise = node3.commandClasses.Security.getNonce(); await wait(1); @@ -83,7 +83,7 @@ test("Node responses in a BridgeApplicationCommandRequest should be understood", // └─[SecurityCCNonceGet] t.deepEqual( serialport.lastWrite, - Buffer.from("0109001303029840250118", "hex"), + Bytes.from("0109001303029840250118", "hex"), ); await wait(10); serialport.receiveData(ACK); @@ -92,7 +92,7 @@ test("Node responses in a BridgeApplicationCommandRequest should be understood", // « [RES] [SendData] // was sent: true - serialport.receiveData(Buffer.from("0104011301e8", "hex")); + serialport.receiveData(Bytes.from("0104011301e8", "hex")); // » [ACK] t.deepEqual(serialport.lastWrite, ACK); @@ -102,7 +102,7 @@ test("Node responses in a BridgeApplicationCommandRequest should be understood", // callback id: 1 // transmit status: OK serialport.receiveData( - Buffer.from( + Bytes.from( "011800130100000100c17f7f7f7f000003000000000301000034", "hex", ), @@ -114,10 +114,10 @@ test("Node responses in a BridgeApplicationCommandRequest should be understood", // BridgeApplicationCommandRequest serialport.receiveData( - Buffer.from("011300a80001030a98803e55e4b714973b9e00c18b", "hex"), + Bytes.from("011300a80001030a98803e55e4b714973b9e00c18b", "hex"), ); // » [ACK] t.deepEqual(serialport.lastWrite, ACK); - t.deepEqual(await getNoncePromise, Buffer.from("3e55e4b714973b9e", "hex")); + t.deepEqual(await getNoncePromise, Bytes.from("3e55e4b714973b9e", "hex")); }); diff --git a/packages/zwave-js/src/lib/test/driver/receiveMessages.test.ts b/packages/zwave-js/src/lib/test/driver/receiveMessages.test.ts index 43e3d0d066b8..bddc867966ea 100644 --- a/packages/zwave-js/src/lib/test/driver/receiveMessages.test.ts +++ b/packages/zwave-js/src/lib/test/driver/receiveMessages.test.ts @@ -1,5 +1,6 @@ import { WakeUpCCIntervalSet } from "@zwave-js/cc/WakeUpCC"; import { ApplicationCommandRequest } from "@zwave-js/serial/serialapi"; +import { Bytes } from "@zwave-js/shared"; import { MockController } from "@zwave-js/testing"; import ava, { type TestFn } from "ava"; import type { Driver } from "../../driver/Driver"; @@ -56,7 +57,7 @@ test.serial( async (t) => { const { controller } = t.context; // This buffer contains a SendData transmit report and a ManufacturerSpecific report - const data = Buffer.from( + const data = Bytes.from( "010700130f000002e6010e000400020872050086000200828e", "hex", ); diff --git a/packages/zwave-js/src/lib/test/driver/sendDataFailThrow.test.ts b/packages/zwave-js/src/lib/test/driver/sendDataFailThrow.test.ts index afbe59c4f6fb..3fcd44426203 100644 --- a/packages/zwave-js/src/lib/test/driver/sendDataFailThrow.test.ts +++ b/packages/zwave-js/src/lib/test/driver/sendDataFailThrow.test.ts @@ -1,7 +1,7 @@ import { BasicCCSet } from "@zwave-js/cc"; import { MessageHeaders } from "@zwave-js/serial"; import type { MockSerialPort } from "@zwave-js/serial/mock"; -import type { ThrowingMap } from "@zwave-js/shared"; +import { Bytes, type ThrowingMap } from "@zwave-js/shared"; import ava, { type TestFn } from "ava"; import { setTimeout as wait } from "node:timers/promises"; import type { Driver } from "../../driver/Driver"; @@ -65,7 +65,7 @@ test.serial( node2["isFrequentListening"] = false; node2.markAsAlive(); - const ACK = Buffer.from([MessageHeaders.ACK]); + const ACK = Uint8Array.from([MessageHeaders.ACK]); const command = new BasicCCSet({ nodeId: 2, @@ -83,7 +83,7 @@ test.serial( // └─ targetValue: 99 t.deepEqual( serialport.lastWrite, - Buffer.from("010a00130203200163250181", "hex"), + Bytes.from("010a00130203200163250181", "hex"), ); await wait(10); serialport.receiveData(ACK); @@ -92,7 +92,7 @@ test.serial( // « [RES] [SendData] // was sent: true - serialport.receiveData(Buffer.from("0104011301e8", "hex")); + serialport.receiveData(Bytes.from("0104011301e8", "hex")); // » [ACK] t.deepEqual(serialport.lastWrite, ACK); @@ -101,7 +101,7 @@ test.serial( // « [REQ] [SendData] // callback id: 1 // transmit status: NoACK - serialport.receiveData(Buffer.from("0107001301010002e9", "hex")); + serialport.receiveData(Bytes.from("0107001301010002e9", "hex")); t.deepEqual(serialport.lastWrite, ACK); await t.throwsAsync(promise); @@ -130,7 +130,7 @@ test.serial( node2["isFrequentListening"] = false; node2.markAsAlive(); - const ACK = Buffer.from([MessageHeaders.ACK]); + const ACK = Uint8Array.from([MessageHeaders.ACK]); const command = new BasicCCSet({ nodeId: 2, @@ -150,7 +150,7 @@ test.serial( // └─ targetValue: 99 t.deepEqual( serialport.lastWrite, - Buffer.from("010f00a90102032001632500000000013f", "hex"), + Bytes.from("010f00a90102032001632500000000013f", "hex"), ); await wait(10); serialport.receiveData(ACK); @@ -159,7 +159,7 @@ test.serial( // « [RES] [SendDataBridge] // was sent: true - serialport.receiveData(Buffer.from("010401a90152", "hex")); + serialport.receiveData(Bytes.from("010401a90152", "hex")); // » [ACK] t.deepEqual(serialport.lastWrite, ACK); @@ -169,7 +169,7 @@ test.serial( // callback id: 1 // transmit status: NoACK serialport.receiveData( - Buffer.from( + Bytes.from( "011800a90101019e007f7f7f7f7f0101070000000002070000ac", "hex", ), diff --git a/packages/zwave-js/src/lib/test/driver/successfulPingChangeNodeStatus.test.ts b/packages/zwave-js/src/lib/test/driver/successfulPingChangeNodeStatus.test.ts index abf3170b0979..8f95779e9a5a 100644 --- a/packages/zwave-js/src/lib/test/driver/successfulPingChangeNodeStatus.test.ts +++ b/packages/zwave-js/src/lib/test/driver/successfulPingChangeNodeStatus.test.ts @@ -1,6 +1,7 @@ import { MessageHeaders } from "@zwave-js/serial"; import type { MockSerialPort } from "@zwave-js/serial/mock"; import { + Bytes, type ThrowingMap, createThrowingMap, getEnumMemberName, @@ -99,7 +100,7 @@ for ( } t.is(node4.status, initialStatus); - const ACK = Buffer.from([MessageHeaders.ACK]); + const ACK = Uint8Array.from([MessageHeaders.ACK]); const pingPromise = node4.ping(); await wait(1); @@ -109,7 +110,7 @@ for ( // └─[NoOperationCC] t.deepEqual( serialport.lastWrite, - Buffer.from("010800130401002501c5", "hex"), + Bytes.from("010800130401002501c5", "hex"), ); await wait(10); serialport.receiveData(ACK); @@ -118,7 +119,7 @@ for ( // « [RES] [SendData] // was sent: true - serialport.receiveData(Buffer.from("0104011301e8", "hex")); + serialport.receiveData(Bytes.from("0104011301e8", "hex")); // » [ACK] t.deepEqual(serialport.lastWrite, ACK); @@ -128,7 +129,7 @@ for ( // callback id: 1 // transmit status: OK serialport.receiveData( - Buffer.from( + Bytes.from( "011800130100000100bd7f7f7f7f010103000000000201000049", "hex", ), diff --git a/packages/zwave-js/src/lib/test/integrationTestSuiteShared.ts b/packages/zwave-js/src/lib/test/integrationTestSuiteShared.ts index d7c453d9e692..68ae205f27e1 100644 --- a/packages/zwave-js/src/lib/test/integrationTestSuiteShared.ts +++ b/packages/zwave-js/src/lib/test/integrationTestSuiteShared.ts @@ -1,4 +1,5 @@ import { type MockPortBinding } from "@zwave-js/serial/mock"; +import { Bytes } from "@zwave-js/shared/safe"; import { MockController, type MockControllerOptions, @@ -39,16 +40,16 @@ export function prepareDriver( } : {}), securityKeys: { - S0_Legacy: Buffer.from("0102030405060708090a0b0c0d0e0f10", "hex"), - S2_Unauthenticated: Buffer.from( + S0_Legacy: Bytes.from("0102030405060708090a0b0c0d0e0f10", "hex"), + S2_Unauthenticated: Bytes.from( "11111111111111111111111111111111", "hex", ), - S2_Authenticated: Buffer.from( + S2_Authenticated: Bytes.from( "22222222222222222222222222222222", "hex", ), - S2_AccessControl: Buffer.from( + S2_AccessControl: Bytes.from( "33333333333333333333333333333333", "hex", ), diff --git a/packages/zwave-js/src/lib/test/messages.ts b/packages/zwave-js/src/lib/test/messages.ts index d37819af6ce2..51b87a9a37b5 100644 --- a/packages/zwave-js/src/lib/test/messages.ts +++ b/packages/zwave-js/src/lib/test/messages.ts @@ -1,8 +1,9 @@ import type { Message } from "@zwave-js/serial"; import { MessageType } from "@zwave-js/serial"; +import { Bytes } from "@zwave-js/shared"; const defaultImplementations = { - serialize: () => Buffer.from([1, 2, 3]), + serialize: () => Bytes.from([1, 2, 3]), tryGetNode: () => undefined, getNodeId: () => undefined, toLogEntry: () => ({ tags: [] }), diff --git a/packages/zwave-js/src/lib/test/node/Node.handleCommand.test.ts b/packages/zwave-js/src/lib/test/node/Node.handleCommand.test.ts index d21207e9b0cf..16d13c722b0f 100644 --- a/packages/zwave-js/src/lib/test/node/Node.handleCommand.test.ts +++ b/packages/zwave-js/src/lib/test/node/Node.handleCommand.test.ts @@ -2,6 +2,7 @@ import { CommandClass, EntryControlCommand } from "@zwave-js/cc"; import { BinarySwitchCCReport } from "@zwave-js/cc/BinarySwitchCC"; import { type EntryControlCCNotification } from "@zwave-js/cc/EntryControlCC"; import { type CommandClassInfo, CommandClasses } from "@zwave-js/core"; +import { Bytes } from "@zwave-js/shared"; import test from "ava"; import sinon from "sinon"; import type { Driver } from "../../driver/Driver"; @@ -95,8 +96,8 @@ test.serial( const spy = sinon.spy(); node.on("notification", spy); - const buf = Buffer.concat([ - Buffer.from([ + const buf = Bytes.concat([ + [ CommandClasses["Entry Control"], EntryControlCommand.Notification, // CC Command 0x5, @@ -107,9 +108,9 @@ test.serial( 50, 51, 52, - ]), + ], // Required padding for ASCII - Buffer.alloc(12, 0xff), + new Uint8Array(12).fill(0xff), ]); const command = CommandClass.parse( diff --git a/packages/zwave-js/src/lib/zniffer/MPDU.ts b/packages/zwave-js/src/lib/zniffer/MPDU.ts index 55e81b0c8bd2..7996a4f122de 100644 --- a/packages/zwave-js/src/lib/zniffer/MPDU.ts +++ b/packages/zwave-js/src/lib/zniffer/MPDU.ts @@ -25,6 +25,7 @@ import { import { parseRSSI } from "@zwave-js/serial/serialapi"; import { type AllOrNone, + Bytes, buffer2hex, pick, staticExtends, @@ -103,7 +104,7 @@ function formatRoute( } export interface MPDUOptions { - data: Buffer; + data: Bytes; frameInfo: ZnifferFrameInfo; } @@ -114,7 +115,7 @@ export interface MPDU { ackRequested: boolean; headerType: MPDUHeaderType; sequenceNumber: number; - payload: Buffer; + payload: Bytes; } export function parseMPDU( @@ -201,7 +202,7 @@ export class LongRangeMPDU implements MPDU { public readonly sequenceNumber: number; public readonly noiseFloor: RSSI; public readonly txPower: number; - public payload!: Buffer; + public payload!: Bytes; public static from(msg: ZnifferDataMessage): LongRangeMPDU { return new LongRangeMPDU({ @@ -397,8 +398,8 @@ export class ZWaveMPDU implements MPDU { public readonly beamingInfo: BeamingInfo; public readonly sequenceNumber: number; - protected readonly destinationBuffer!: Buffer; - public payload!: Buffer; + protected readonly destinationBuffer!: Bytes; + public payload!: Bytes; public static from(msg: ZnifferDataMessage): ZWaveMPDU { return new ZWaveMPDU({ @@ -752,7 +753,7 @@ export class SearchResultExplorerZWaveMPDU extends ExplorerZWaveMPDU { ]; // This frame contains no payload - this.payload = Buffer.allocUnsafe(0); + this.payload = new Bytes(); } /** The node ID that sent the explorer frame that's being answered here */ @@ -907,7 +908,7 @@ export class LongRangeBeamStart { const txPower = data[1] >>> 4; this.txPower = longRangeBeamPowerToDBm(txPower); - this.destinationNodeId = data.readUint16BE(1) & 0x0fff; + this.destinationNodeId = data.readUInt16BE(1) & 0x0fff; this.homeIdHash = data[3]; } @@ -987,7 +988,7 @@ export type ZWaveFrame = type: ZWaveFrameType.Singlecast; destinationNodeId: number; ackRequested: boolean; - payload: Buffer | CommandClass; + payload: Uint8Array | CommandClass; } // Only present in routed frames: & AllOrNone< @@ -1026,13 +1027,13 @@ export type ZWaveFrame = type: ZWaveFrameType.Broadcast; destinationNodeId: typeof NODE_ID_BROADCAST; ackRequested: boolean; - payload: Buffer | CommandClass; + payload: Uint8Array | CommandClass; } | { // Multicast frame, not routed type: ZWaveFrameType.Multicast; destinationNodeIds: number[]; - payload: Buffer | CommandClass; + payload: Uint8Array | CommandClass; } | { // Ack frame, not routed @@ -1043,7 +1044,7 @@ export type ZWaveFrame = // Different kind of explorer frames & ({ type: ZWaveFrameType.ExplorerNormal; - payload: Buffer | CommandClass; + payload: Uint8Array | CommandClass; } | { type: ZWaveFrameType.ExplorerSearchResult; searchingNodeId: number; @@ -1053,7 +1054,7 @@ export type ZWaveFrame = } | { type: ZWaveFrameType.ExplorerInclusionRequest; networkHomeId: number; - payload: Buffer | CommandClass; + payload: Uint8Array | CommandClass; }) // Common fields for all explorer frames & { @@ -1092,7 +1093,7 @@ export type LongRangeFrame = // Singlecast frame type: LongRangeFrameType.Singlecast; ackRequested: boolean; - payload: Buffer | CommandClass; + payload: Uint8Array | CommandClass; } | { // Broadcast frame. This is technically a singlecast frame, @@ -1100,13 +1101,13 @@ export type LongRangeFrame = type: LongRangeFrameType.Broadcast; destinationNodeId: typeof NODE_ID_BROADCAST_LR; ackRequested: boolean; - payload: Buffer | CommandClass; + payload: Uint8Array | CommandClass; } | { // Acknowledgement frame type: LongRangeFrameType.Ack; incomingRSSI: RSSI; - payload: Buffer; + payload: Uint8Array; } ); @@ -1170,7 +1171,7 @@ export type CorruptedFrame = { protocolDataRate: ZnifferProtocolDataRate; - payload: Buffer; + payload: Uint8Array; }; export function mpduToFrame(mpdu: MPDU, payloadCC?: CommandClass): Frame { diff --git a/packages/zwave-js/src/lib/zniffer/Zniffer.ts b/packages/zwave-js/src/lib/zniffer/Zniffer.ts index b28d0fc33a7e..f8f5240164ef 100644 --- a/packages/zwave-js/src/lib/zniffer/Zniffer.ts +++ b/packages/zwave-js/src/lib/zniffer/Zniffer.ts @@ -58,6 +58,7 @@ import { isZWaveSerialPortImplementation, } from "@zwave-js/serial"; import { + Bytes, TypedEventEmitter, getEnumMemberName, isEnumMember, @@ -94,8 +95,8 @@ const logo: string = ` export interface ZnifferEventCallbacks { ready: () => void; error: (err: Error) => void; - frame: (frame: Frame, rawData: Buffer) => void; - "corrupted frame": (err: CorruptedFrame, rawData: Buffer) => void; + frame: (frame: Frame, rawData: Uint8Array) => void; + "corrupted frame": (err: CorruptedFrame, rawData: Uint8Array) => void; } export type ZnifferEvents = Extract; @@ -172,14 +173,14 @@ function tryConvertRSSI( interface CapturedData { timestamp: Date; - rawData: Buffer; - frameData: Buffer; + rawData: Uint8Array; + frameData: Uint8Array; parsedFrame?: Frame | CorruptedFrame; } export interface CapturedFrame { timestamp: Date; - frameData: Buffer; + frameData: Uint8Array; parsedFrame: Frame | CorruptedFrame; } @@ -455,11 +456,11 @@ supported frequencies: ${ * Is called when the serial port has received a Zniffer frame */ private serialport_onData( - data: Buffer, + data: Uint8Array, ): void { let msg: ZnifferMessage | undefined; try { - msg = ZnifferMessage.from({ data }); + msg = ZnifferMessage.parse(data); } catch (e: any) { console.error(e); return; @@ -946,9 +947,9 @@ supported frequencies: ${ */ public getCaptureAsZLFBuffer( frameFilter?: (frame: CapturedFrame) => boolean, - ): Buffer { + ): Uint8Array { // Mimics the current Zniffer software, without using features like sessions and comments - const header = Buffer.alloc(2048, 0); + const header = new Bytes(2048).fill(0); header[0] = 0x68; // zniffer version header.writeUInt16BE(0x2312, 0x07fe); // checksum let filteredFrames = this._capturedFrames; @@ -964,7 +965,7 @@ supported frequencies: ${ }) ); } - return Buffer.concat([ + return Bytes.concat([ header, ...filteredFrames.map(captureToZLFEntry), ]); @@ -1014,8 +1015,8 @@ supported frequencies: ${ function captureToZLFEntry( capture: CapturedData, -): Buffer { - const buffer = Buffer.alloc(14 + capture.rawData.length, 0); +): Uint8Array { + const buffer = new Bytes(14 + capture.rawData.length).fill(0); // Convert the date to a .NET datetime let ticks = BigInt(capture.timestamp.getTime()) * 10000n + 621355968000000000n; @@ -1027,7 +1028,7 @@ function captureToZLFEntry( buffer[8] = direction | 0x01; // dir + session ID buffer[9] = capture.rawData.length; // bytes 10-12 are empty - capture.rawData.copy(buffer, 13); + buffer.set(capture.rawData, 13); buffer[buffer.length - 1] = 0xfe; // end of frame return buffer; } diff --git a/packages/zwave-js/src/mockServer.ts b/packages/zwave-js/src/mockServer.ts index 91f238493ea5..eabf1fa5b3d6 100644 --- a/packages/zwave-js/src/mockServer.ts +++ b/packages/zwave-js/src/mockServer.ts @@ -128,7 +128,7 @@ export class MockServer { socket.pipe(this.serialport); this.serialport.on("data", (chunk) => { if (typeof chunk === "number") { - socket.write(Buffer.from([chunk])); + socket.write(Uint8Array.from([chunk])); } else { socket.write(chunk); }