enip-ts
Version:
Typescript implementation of the Ethernet/IP™ protocol.
180 lines • 7.54 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Client = void 0;
const stream_1 = require("stream");
const messageRouter_1 = require("../enip/cip/messageRouter");
const path_1 = require("../enip/cip/path");
const encapsulation_1 = require("../enip/encapsulation");
const header_1 = require("../enip/encapsulation/header");
const cpf_1 = require("../enip/encapsulation/cpf");
const encapsulation_2 = require("../enip/encapsulation");
class Client {
socket;
events;
dataVector;
state = {
TCPState: "established",
session: { id: 0, state: "established" },
connection: { id: 0, seq_num: 0, state: "established" },
error: { code: 0, msg: "" }
};
constructor(socket, vector) {
this.socket = socket;
this.events = new stream_1.EventEmitter();
this.dataVector = vector;
this.socket.on("data", this.handleData.bind(this));
this.socket.on("close", this.handleClose.bind(this));
this.events.on("SendRRData Received", this.handlePacket.bind(this));
}
/** Ignore empty packets */
handlePacket(packets) {
for (const packet of packets) {
if (packet.data.length == 0)
continue;
this.sendReponse(path_1.CIP.parse(packet.data));
}
}
/** Send a response computed by Vector function */
sendReponse(packet) {
const vectorFunction = this.dataVector[packet.service];
if (vectorFunction === undefined)
throw Error("Vector function undefined");
const dataToSend = vectorFunction(packet);
if (dataToSend === undefined) {
// Set service to Reply
const mask = 0x80;
packet.service |= mask;
const MR = messageRouter_1.MessageRouter.build(packet.service, Buffer.from([0x04]), Buffer.alloc(0));
this.write(MR, false, 50);
return;
}
// TODO: this should be simplyfied using vector functions
switch (packet.service) {
case 0x0E: {
const MR = messageRouter_1.MessageRouter.build(0x8E, Buffer.from([0x00]), dataToSend);
this.write(MR, false, 50);
break;
}
case 0x10: {
const MR = messageRouter_1.MessageRouter.build(0x90, Buffer.from([0x00]), dataToSend);
this.write(MR, false, 50);
break;
}
default: {
throw Error("Service code " + packet.service + " not implemented");
}
}
}
/**
* Writes Ethernet/IP Data to Socket as an Unconnected Message
* or a Transport Class 1 Datagram
*/
async write(data, connected = false, timeout) {
if (this.state.session.state != "established")
return false;
if (this.state.session.id === undefined)
return false;
if (connected === true) {
if (this.state.connection.state === "established") {
let { seq_num } = this.state.connection;
(seq_num > 0xffff) ? seq_num = 0 : seq_num++;
}
else {
throw new Error("Connected message request, but no connection established. Forgot forwardOpen?");
}
}
//If the packet should be connected, send UnitData otherwise send RRData
const packet = (connected) ?
encapsulation_1.Encapsulation.sendUnitData(this.state.session.id, data, this.state.connection.id, this.state.connection.seq_num) :
encapsulation_1.Encapsulation.sendRRData(this.state.session.id, data, timeout ?? 10);
const write = await new Promise((resolve, reject) => {
this.socket.write(packet, (err) => {
//timeout rejection
setTimeout(() => reject(false), timeout ?? 10000);
resolve(err === undefined ? true : false);
});
});
return write;
}
/**
* Socket data event handler
*/
handleData(data) {
const encapsulatedData = header_1.Header.parse(data);
const { statusCode, status, commandCode } = encapsulatedData;
if (statusCode !== 0) {
console.log(`Error <${statusCode}>: `, status);
this.state.error.code = statusCode;
this.state.error.msg = status;
this.events.emit("Session Registration Failed", this.state.error);
}
else {
this.state.error.code = 0;
this.state.error.msg = '';
switch (commandCode) {
case encapsulation_2.Commands.RegisterSession: {
this.state.session.state = "established";
this.state.session.id = 0x01;
this.events.emit("Session Registered", this.state.session.id);
this.socket.write(encapsulation_1.Encapsulation.registerSession(this.state.session.id));
break;
}
case encapsulation_2.Commands.UnregisterSession: {
this.state.session.state = "unconnected";
this.events.emit("Session Unregistered");
break;
}
case encapsulation_2.Commands.SendRRData: {
let buf1 = Buffer.alloc(encapsulatedData.length - 6); // length of Data - Interface Handle <UDINT> and Timeout <UINT>
encapsulatedData.data.copy(buf1, 0, 6);
const srrd = cpf_1.CPF.parse(buf1);
this.events.emit("SendRRData Received", srrd);
break;
}
case encapsulation_2.Commands.SendUnitData: {
let buf2 = Buffer.alloc(encapsulatedData.length - 6); // length of Data - Interface Handle <UDINT> and Timeout <UINT>
encapsulatedData.data.copy(buf2, 0, 6);
const sud = cpf_1.CPF.parse(buf2);
this.events.emit("SendUnitData Received", sud);
break;
}
default:
{
this.events.emit("Unhandled Encapsulated Command Received", encapsulatedData);
console.log(encapsulatedData);
}
}
}
}
/**
* Sends Unregister Session Command and Destroys Underlying TCP Socket
* @deprecated
*/
destroy(_error) {
if (this.state.session.id != 0 && this.state.session.state === "established" && this.state.TCPState !== "unconnected") {
this.socket.write(encapsulation_1.Encapsulation.unregisterSession(this.state.session.id), (_err) => {
this.state.session.state = "unconnected";
});
}
}
/**
* Sends an UnregisterSession command
*/
close() {
if (this.state.session.id != 0 && this.state.session.state === "established" && this.state.TCPState !== "unconnected") {
this.socket.write(encapsulation_1.Encapsulation.unregisterSession(this.state.session.id));
}
}
/**
* Handle socket close
*/
handleClose(_hadError) {
this.state.session.state = "unconnected";
this.state.TCPState = "unconnected";
this.socket.removeAllListeners("data");
this.socket.removeAllListeners("close");
this.socket.removeAllListeners("error");
}
}
exports.Client = Client;
//# sourceMappingURL=client.js.map