eufy-security-client-fork
Version:
Client to comunicate with Eufy-Security devices
193 lines • 7.47 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PushClientParser = void 0;
/* eslint-disable @typescript-eslint/no-non-null-assertion */
const path = require("path");
const ts_log_1 = require("ts-log");
const protobuf_typescript_1 = require("protobuf-typescript");
const tiny_typed_emitter_1 = require("tiny-typed-emitter");
const models_1 = require("./models");
class PushClientParser extends tiny_typed_emitter_1.TypedEmitter {
constructor(log = ts_log_1.dummyLogger) {
super();
this.state = models_1.ProcessingState.MCS_VERSION_TAG_AND_SIZE;
this.data = Buffer.alloc(0);
this.isWaitingForData = true;
this.sizePacketSoFar = 0;
this.messageSize = 0;
this.messageTag = 0;
this.handshakeComplete = false;
this.log = log;
}
resetState() {
this.state = models_1.ProcessingState.MCS_VERSION_TAG_AND_SIZE;
this.data = Buffer.alloc(0);
this.isWaitingForData = true;
this.sizePacketSoFar = 0;
this.messageSize = 0;
this.messageTag = 0;
this.handshakeComplete = false;
this.removeAllListeners();
}
static async init(log = ts_log_1.dummyLogger) {
this.proto = await (0, protobuf_typescript_1.load)(path.join(__dirname, "./proto/mcs.proto"));
return new PushClientParser(log);
}
handleData(newData) {
this.data = Buffer.concat([this.data, newData]);
if (this.isWaitingForData) {
this.isWaitingForData = false;
this.waitForData();
}
}
waitForData() {
const minBytesNeeded = this.getMinBytesNeeded();
// If we don't have all bytes yet, wait some more
if (this.data.length < minBytesNeeded) {
this.isWaitingForData = true;
return;
}
else {
this.handleFullMessage();
}
}
handleFullMessage() {
switch (this.state) {
case models_1.ProcessingState.MCS_VERSION_TAG_AND_SIZE:
this.onGotVersion();
break;
case models_1.ProcessingState.MCS_TAG_AND_SIZE:
this.onGotMessageTag();
break;
case models_1.ProcessingState.MCS_SIZE:
this.onGotMessageSize();
break;
case models_1.ProcessingState.MCS_PROTO_BYTES:
this.onGotMessageBytes();
break;
default:
this.log.warn("Unknown state", { state: this.state });
break;
}
}
onGotVersion() {
const version = this.data.readInt8(0);
this.data = this.data.slice(1);
if (version < 41 && version !== 38) {
throw new Error(`Got wrong version: ${version}`);
}
// Process the LoginResponse message tag.
this.onGotMessageTag();
}
onGotMessageTag() {
this.messageTag = this.data.readInt8(0);
this.data = this.data.slice(1);
this.onGotMessageSize();
}
onGotMessageSize() {
let incompleteSizePacket = false;
const reader = new protobuf_typescript_1.BufferReader(this.data);
try {
this.messageSize = reader.int32();
}
catch (error) {
if (error instanceof Error && error.message.startsWith("index out of range:")) {
incompleteSizePacket = true;
}
else {
throw new Error(error);
}
}
if (incompleteSizePacket) {
this.sizePacketSoFar = reader.pos;
this.state = models_1.ProcessingState.MCS_SIZE;
this.waitForData();
return;
}
this.data = this.data.slice(reader.pos);
this.sizePacketSoFar = 0;
if (this.messageSize > 0) {
this.state = models_1.ProcessingState.MCS_PROTO_BYTES;
this.waitForData();
}
else {
this.onGotMessageBytes();
}
}
onGotMessageBytes() {
const protobuf = this.buildProtobufFromTag(this.messageTag);
if (this.messageSize === 0) {
this.emit("message", { tag: this.messageTag, object: {} });
this.getNextMessage();
return;
}
if (this.data.length < this.messageSize) {
this.state = models_1.ProcessingState.MCS_PROTO_BYTES;
this.waitForData();
return;
}
const buffer = this.data.slice(0, this.messageSize);
this.data = this.data.slice(this.messageSize);
const message = protobuf.decode(buffer);
const object = protobuf.toObject(message, {
longs: String,
enums: String,
bytes: Buffer,
});
this.emit("message", { tag: this.messageTag, object: object });
if (this.messageTag === models_1.MessageTag.LoginResponse) {
if (this.handshakeComplete) {
this.log.error("Unexpected login response!");
}
else {
this.handshakeComplete = true;
}
}
this.getNextMessage();
}
getNextMessage() {
this.messageTag = 0;
this.messageSize = 0;
this.state = models_1.ProcessingState.MCS_TAG_AND_SIZE;
this.waitForData();
}
getMinBytesNeeded() {
switch (this.state) {
case models_1.ProcessingState.MCS_VERSION_TAG_AND_SIZE:
return 1 + 1 + 1;
case models_1.ProcessingState.MCS_TAG_AND_SIZE:
return 1 + 1;
case models_1.ProcessingState.MCS_SIZE:
return this.sizePacketSoFar + 1;
case models_1.ProcessingState.MCS_PROTO_BYTES:
return this.messageSize;
default:
throw new Error(`Unknown state: ${this.state}`);
}
}
buildProtobufFromTag(messageTag) {
switch (messageTag) {
case models_1.MessageTag.HeartbeatPing:
return PushClientParser.proto.lookupType("mcs_proto.HeartbeatPing");
case models_1.MessageTag.HeartbeatAck:
return PushClientParser.proto.lookupType("mcs_proto.HeartbeatAck");
case models_1.MessageTag.LoginRequest:
return PushClientParser.proto.lookupType("mcs_proto.LoginRequest");
case models_1.MessageTag.LoginResponse:
return PushClientParser.proto.lookupType("mcs_proto.LoginResponse");
case models_1.MessageTag.Close:
return PushClientParser.proto.lookupType("mcs_proto.Close");
case models_1.MessageTag.IqStanza:
return PushClientParser.proto.lookupType("mcs_proto.IqStanza");
case models_1.MessageTag.DataMessageStanza:
return PushClientParser.proto.lookupType("mcs_proto.DataMessageStanza");
case models_1.MessageTag.StreamErrorStanza:
return PushClientParser.proto.lookupType("mcs_proto.StreamErrorStanza");
default:
throw new Error(`Unknown tag: ${this.messageTag}`);
}
}
}
exports.PushClientParser = PushClientParser;
PushClientParser.proto = null;
//# sourceMappingURL=parser.js.map