UNPKG

enip-ts

Version:

Typescript implementation of the Ethernet/IP™ protocol.

198 lines 8.66 kB
"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