UNPKG

@webarray/esphome-native-api

Version:

TypeScript/Node.js client for ESPHome native API with encryption and deep sleep support

156 lines 5.18 kB
"use strict"; /** * Protocol utilities for ESPHome Native API * Handles message framing, encoding, and decoding */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ProtocolHandler = void 0; exports.createPingRequest = createPingRequest; exports.createPingResponse = createPingResponse; const varint_1 = __importDefault(require("varint")); const types_1 = require("../types"); const debug_1 = __importDefault(require("debug")); const debug = (0, debug_1.default)('esphome:protocol'); /** * ESPHome protocol message frame structure: * - 1 byte: 0x00 (preamble) * - VarInt: message length (excluding type) * - VarInt: message type * - N bytes: protobuf encoded message */ class ProtocolHandler { constructor() { this.buffer = Buffer.alloc(0); this.MAX_MESSAGE_SIZE = 1024 * 1024; // 1MB max message size } /** * Encode a message for transmission */ encodeMessage(type, data) { const typeBytes = varint_1.default.encode(type); const lengthBytes = varint_1.default.encode(data.length); // Create frame: preamble + length + type + data const frame = Buffer.concat([ Buffer.from([0x00]), // Preamble Buffer.from(lengthBytes), Buffer.from(typeBytes), data, ]); debug('Encoded message type %d, length %d, total frame %d bytes', type, data.length, frame.length); return frame; } /** * Add data to the internal buffer and try to decode messages */ addData(data) { this.buffer = Buffer.concat([this.buffer, data]); const messages = []; while (this.buffer.length > 0) { const message = this.tryDecodeMessage(); if (!message) { break; } messages.push(message); } return messages; } /** * Try to decode a single message from the buffer */ tryDecodeMessage() { // Need at least preamble + 1 byte for length varint if (this.buffer.length < 2) { return null; } // Check preamble if (this.buffer[0] !== 0x00) { // Skip invalid bytes until we find a preamble const preambleIndex = this.buffer.indexOf(0x00); if (preambleIndex === -1) { // No preamble found, clear buffer debug('No preamble found in buffer, clearing %d bytes', this.buffer.length); this.buffer = Buffer.alloc(0); return null; } // Skip to preamble debug('Skipping %d invalid bytes to preamble', preambleIndex); this.buffer = this.buffer.slice(preambleIndex); } // Try to decode message length let lengthValue; let lengthBytes; try { lengthValue = varint_1.default.decode(this.buffer, 1); lengthBytes = varint_1.default.decode.bytes || 0; } catch (err) { // Not enough bytes for length varint return null; } // Validate message length if (lengthValue > this.MAX_MESSAGE_SIZE) { throw new types_1.ProtocolError(`Message too large: ${lengthValue} bytes`); } // Check if we have enough bytes for type varint const typeOffset = 1 + lengthBytes; if (this.buffer.length <= typeOffset) { return null; } // Try to decode message type let typeValue; let typeBytes; try { typeValue = varint_1.default.decode(this.buffer, typeOffset); typeBytes = varint_1.default.decode.bytes || 0; } catch (err) { // Not enough bytes for type varint return null; } // Calculate total message size const dataOffset = typeOffset + typeBytes; const totalSize = dataOffset + lengthValue; // Check if we have the complete message if (this.buffer.length < totalSize) { return null; } // Extract message data const messageData = this.buffer.slice(dataOffset, totalSize); // Remove processed message from buffer this.buffer = this.buffer.slice(totalSize); debug('Decoded message type %d, length %d', typeValue, lengthValue); return { type: typeValue, data: messageData, }; } /** * Clear the internal buffer */ clearBuffer() { this.buffer = Buffer.alloc(0); } /** * Get the current buffer size */ getBufferSize() { return this.buffer.length; } } exports.ProtocolHandler = ProtocolHandler; /** * Create a simple ping request message */ function createPingRequest() { return Buffer.alloc(0); // Ping request has no data } /** * Create a simple pong response message */ function createPingResponse() { return Buffer.alloc(0); // Ping response has no data } //# sourceMappingURL=protocol.js.map