enip-ts
Version:
Typescript implementation of the Ethernet/IP™ protocol.
198 lines • 8.66 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SocketController = void 0;
const net_1 = require("net");
const stream_1 = require("stream");
const encapsulation_1 = require("./encapsulation");
const header_1 = require("./encapsulation/header");
const cpf_1 = require("./encapsulation/cpf");
const EIP_PORT = 44818;
/**
* Low Level Ethernet/IP
*/
class SocketController {
state = {
TCPState: "unconnected",
session: { id: 0, state: "unconnected" },
connection: { id: 0, state: "unconnected", seq_num: 0 },
error: { code: 0, msg: '' }
};
socket;
events;
port;
constructor(timeout, port = EIP_PORT) {
this.socket = new net_1.Socket();
if (timeout !== undefined)
this.socket.setTimeout(timeout);
this.events = new stream_1.EventEmitter();
this.port = port;
}
/**
* Initializes Session with Desired IP Address or FQDN
* and Returns a Promise with the Established Session ID
* @param IpAddress IP Address or FQDN of the Controller
* @param timeout Timeout in Milliseconds for Session Registration
*/
async connect(IpAddress, timeout = 10000) {
this.state.session.state = "establishing";
this.state.TCPState = "establishing";
/**
* Connects to the controller using a raw TCP Socket
* and register ourselves using RegisterSession Method
*/
const { connectResult, TCPState } = await new Promise((resolve) => {
this.socket.connect(this.port, IpAddress);
this.socket.once("connect", () => {
resolve({ connectResult: true, TCPState: "established" });
});
this.socket.once("error", () => () => {
resolve({ connectResult: false, TCPState: "unconnected" });
});
});
this.state.TCPState = TCPState;
if (connectResult === true && this.state.TCPState === "established") {
//Adding Sockets events
this.socket.on("data", (data) => { this.handleData(data); });
this.socket.once("close", (hadError) => { this.handleClose(hadError); });
this.socket.once("timeout", () => { this.handleClose(false); });
this.socket.once("error", (error) => { this.handleClose(true); });
this.socket.write(encapsulation_1.Encapsulation.registerSession());
const sessionID = await new Promise((resolve) => {
setTimeout(() => resolve(undefined), timeout);
this.events.on("Session Registered", (sessionid) => {
this.state.session.state = "established";
resolve(sessionid);
});
this.events.on("Session Registration Failed", error => {
this.state.error.code = error.code;
this.state.error.msg = "Failed to register Session";
resolve(undefined);
});
});
this.events.removeAllListeners("Session Registered");
this.events.removeAllListeners("Session Registration Failed");
return sessionID;
}
else {
this.state.TCPState = "unconnected";
this.state.session.state = "unconnected";
this.state.error.msg = "Failed to connect to socket";
return undefined;
}
}
/**
* Writes Ethernet/IP Data to Socket as an Unconnected Message or a Transport Class 1 Datagram
* @param data Data to be sent
* @param connected If the message should be sent as a connected message
* @param timeout Timeout in Milliseconds for the response
*/
async write(data, connected = false, timeout) {
if (this.state.session.state = "established") {
if (connected === true) {
if (this.state.connection.state === "established") {
(this.state.connection.seq_num > 0xffff) ? this.state.connection.seq_num = 0 : this.state.connection.seq_num++;
}
else {
throw new Error("Connected message request, but no connection established. Forgot forwardOpen?");
}
}
if (this.state.session.id) {
//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;
}
else {
console.log("ts-enip: Session not registered");
return false;
}
}
else {
return false;
}
}
/**
* 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));
}
}
/**
* Socket data event handler
* @param data Data received from the socket
*/
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_1.Commands.RegisterSession: {
this.state.session.state = "established";
this.state.session.id = encapsulatedData.session;
this.events.emit("Session Registered", this.state.session.id);
break;
}
case encapsulation_1.Commands.UnregisterSession: {
this.state.session.state = "unconnected";
this.events.emit("Session Unregistered");
break;
}
case encapsulation_1.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_1.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);
}
}
}
/**
* Handle socket close
* @param _hadError If the socket closed due to an error
*/
handleClose(_hadError) {
this.state.session.state = "unconnected";
this.state.TCPState = "unconnected";
this.events.emit("close");
this.socket.removeAllListeners("data");
}
}
exports.SocketController = SocketController;
//# sourceMappingURL=index.js.map