@webarray/esphome-native-api
Version:
TypeScript/Node.js client for ESPHome native API with encryption and deep sleep support
156 lines • 5.18 kB
JavaScript
"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