st-ethernet-ip
Version:
A simple node interface for Ethernet/IP.
868 lines (739 loc) • 30.7 kB
text/typescript
import { ENIP, CIP, enipConnection, enipError, enipTCP, enipSession } from "../../enip";
import dateFormat from "dateformat";
import TagGroup from "../../tag-group";
import { promiseTimeout } from "../../utilities";
import Queue from "task-easy";
const compare = (obj1, obj2) => {
if (obj1.priority > obj2.priority) return true;
else if (obj1.priority < obj2.priority) return false;
else return obj1.timestamp.getTime() < obj2.timestamp.getTime();
};
type controllerState = {
name: string,
serial_number: number,
slot: number,
time: Date,
path: Buffer,
version: string,
status: number,
run: boolean,
program: boolean,
faulted: boolean,
minorRecoverableFault: boolean,
minorUnrecoverableFault: boolean,
majorRecoverableFault: boolean,
majorUnrecoverableFault: boolean,
io_faulted: boolean
}
class Controller extends ENIP {
OTconnectionID: number;
TOconnectionID: number;
configInstance: any;
outputInstance: any;
inputInstance: any;
tryingToConnect: boolean;
state: {
TCP: enipTCP,
session: enipSession,
connection: enipConnection,
error: enipError
controller: controllerState,
subs: TagGroup,
scanning: boolean,
scan_rate: number,
connectedMessaging: boolean,
timeout_sp: number,
rpi: number,
fwd_open_serial: number,
unconnectedSendTimeout: number,
}
workers: {
read: any;
write: any;
group: any;
}
constructor(connectedMessaging: boolean = true, configInstance: any, outputInstance: any, inputInstance: any) {
super();
this.OTconnectionID = 0;
this.TOconnectionID = 0;
this.configInstance = configInstance;
this.outputInstance = outputInstance;
this.inputInstance = inputInstance;
this.tryingToConnect = false;
this.state = {
...this.state,
controller: {
name: null,
serial_number: null,
slot: null,
time: null,
path: null,
version: null,
status: null,
run: false,
program: false,
faulted: false,
minorRecoverableFault: false,
minorUnrecoverableFault: false,
majorRecoverableFault: false,
majorUnrecoverableFault: false,
io_faulted: false
},
subs: new TagGroup(),
scanning: false,
scan_rate: 200, //ms,
connectedMessaging,
timeout_sp: 10000, //ms
rpi: 10,
fwd_open_serial: 0,
};
this.workers = {
read: new Queue(compare),
write: new Queue(compare),
group: new Queue(compare),
};
}
// region Property Definitions
/**
* Returns the Scan Rate of Subscription Tags
*
* @memberof Controller
* @returns {number} ms
*/
get scan_rate() {
return this.state.scan_rate;
}
/**
* Returns the Timeout Setpoint
*
* @memberof Controller
* @returns {number} ms
*/
get timeout_sp() {
return this.state.timeout_sp;
}
/**
* Sets the Timeout Setpoint
*
* @memberof Controller
*/
set timeout_sp(sp) {
if (typeof sp !== "number") throw new Error("timeout_sp must be of Type <number>");
this.state.timeout_sp = Math.trunc(sp);
}
/**
* Returns the Rpi
*
* @memberof Controller
* @returns {number} ms
*/
get rpi() {
return this.state.rpi;
}
/**
* Sets the Rpi
*
* @memberof Controller
*/
set rpi(sp) {
if (typeof sp !== "number") throw new Error("Rpi must be of Type <number>");
if (sp < 8) throw new Error("Rpi a minimum of 8ms");
this.state.rpi = Math.trunc(sp);
}
/**
* Get the status of Scan Group
*
* @readonly
* @memberof Controller
*/
get scanning() {
return this.state.scanning;
}
/**
* Returns the connected / unconnected messaging mode
*
* @memberof Controller
* @returns {boolean} true, if connected messaging; false, if unconnected messaging
*/
get connectedMessaging() {
return this.state.connectedMessaging;
}
/**
* Sets the Mode to connected / unconnected messaging
*
* @memberof Controller
*/
set connectedMessaging(conn) {
if (typeof conn !== "boolean") throw new Error("connectedMessaging must be of type <boolean>");
this.state.connectedMessaging= conn;
}
/**
* Gets the Controller Properties Object
*
* @readonly
* @memberof Controller
* @returns {object}
*/
get properties() {
return this.state.controller;
}
/**
* Fetches the last timestamp retrieved from the controller
* in human readable form
*
* @readonly
* @memberof Controller
*/
get time() {
return dateFormat(this.state.controller.time, "mmmm dd, yyyy - hh:MM:ss TT");
}
// endregion
// region Public Method Definitions
/**
* Initializes Session with Desired IP Address
* and Returns a Promise with the Established Session ID
*
* @param IP_ADDR - IPv4 Address (can also accept a FQDN, provided port forwarding is configured correctly.)
* @param SLOT - Controller Slot Number (0 if CompactLogix), or a Buffer representing the whole routing path
* @returns Promise that resolves after connection
*/
async connect(IP_ADDR: string, SLOT: number | Buffer = 0, localAddress: string): Promise<number> {
const { PORT } = CIP.EPATH.segments;
const BACKPLANE = 1;
if (typeof SLOT === "number") {
this.state.controller.slot = SLOT;
this.state.controller.path = PORT.build(BACKPLANE, SLOT);
} else if (Buffer.isBuffer(SLOT)) {
this.state.controller.path = SLOT;
} else {
throw new Error("Invalid slot parameter type, must be either a number or a Buffer");
}
await super.connect(IP_ADDR, this.timeout_sp, localAddress);
this._initializeControllerEventHandlers(); // Connect sendRRData Event
this.OTconnectionID = await this.forwardOpen();
return this.OTconnectionID;
}
/**
* Disconnects the PLC instance gracefully by issuing forwardClose, UnregisterSession
* and then destroying the socket
* and Returns a Promise indicating a success or failure or the disconnection
*
* @memberof Controller
* @returns {Promise}
*/
async disconnect() {
if (super.established_conn === true) {
const closeid = await this.forwardClose();
if(!closeid) throw new Error("Failed to End Connected EIP Session with Forward Close Request");
}
super.destroy();
this._removeControllerEventHandlers();
return "disconnected";
}
/**
* Writes a forwardOpen Request and retrieves the connection ID used for
* connected messages.
* @memberof Controller
* @returns {Promise}
*/
async forwardOpen() {
const { FORWARD_OPEN } = CIP.MessageRouter.services;
const { LOGICAL } = CIP.EPATH.segments;
const { owner, connectionType, fixedVar, priority} = CIP.ConnectionManager;
// Build Connection Manager Object Logical Path Buffer
const cmPath = Buffer.concat([
LOGICAL.build(LOGICAL.types.ClassID, 0x06), // Connection Manager Object (0x01)
LOGICAL.build(LOGICAL.types.InstanceID, 0x01) // Instance ID (0x01)
]);
// Message Router to Embed in UCMM
const MR = CIP.MessageRouter.build(FORWARD_OPEN, cmPath, Buffer.from([]));
// Create connection parameters
const paramsOT = CIP.ConnectionManager.build_connectionParameters(owner.Exclusive, connectionType.PointToPoint, priority.Scheduled, fixedVar.Fixed, 6 + this.outputInstance.size);
const paramsTO = CIP.ConnectionManager.build_connectionParameters(owner.Exclusive, connectionType.PointToPoint, priority.Scheduled, fixedVar.Fixed, 2 + this.inputInstance.size);
this.state.fwd_open_serial = getRandomInt(32767);
const forwardOpenData = CIP.ConnectionManager.build_forwardOpen(this.state.rpi * 1000, paramsOT, 1000 , 32, this.state.fwd_open_serial);
const ioPath = this._ioPath();
// This is the Connection Path data unit (Vol.1 Table 3-5.21)
const connectionPath = Buffer.concat([
Buffer.from([Math.ceil(ioPath.length/2)]), //Path size in WORDS
ioPath
]);
// Next 2 lines change the result of CIP.ConnectionManager.build_forwardOpen
forwardOpenData.writeUInt8(1, 34); // Change to Class 1
forwardOpenData.writeUInt16LE(paramsTO, 32); // Change T=>O connection parameters
const forwardOpenPacket = Buffer.concat([
MR,
forwardOpenData,
connectionPath
]);
super.establishing_conn = true;
super.established_conn = false;
super.write_cip(forwardOpenPacket); // We need to bypass unconnected send for now
const readPropsErr = new Error("TIMEOUT occurred while trying forwardOpen Request.");
// Wait for Response
const data = await promiseTimeout(
new Promise((resolve, reject) => {
this.on("Forward Open", (err, data) => {
if (err) reject(err);
resolve(data);
});
}),
this.state.timeout_sp,
readPropsErr
);
this.removeAllListeners("Forward Open");
const OTconnID = data.readUInt32LE(0); // first 4 Bytes are O->T connection ID
super.id_conn = OTconnID;
super.established_conn = true;
super.establishing_conn = false;
this.TOconnectionID = forwardOpenData.readUInt32LE(6);
this.OTconnectionID = OTconnID;
return OTconnID;
}
/**
* Generates path to IO assembly instances
*/
_ioPath() {
const LOGICAL = CIP.EPATH.segments.LOGICAL;
const path = LOGICAL.build(LOGICAL.types.Special, 0x04);
const vendorID = Buffer.from([0x00, 0x00]);
const deviceType = Buffer.from([0x00, 0x00]);
const productCode = Buffer.from([0x00, 0x00]);
const compatMajorRevision = Buffer.from([0x00]);
const minorRevision = Buffer.from([0x00]);
const electronicKey = Buffer.concat([path, vendorID, deviceType, productCode, compatMajorRevision, minorRevision]);
//Assembly Object Class
const assemblyObjectClass = LOGICAL.build(LOGICAL.types.ClassID, 0x04);
// Config Instance
const configInstance = LOGICAL.build(LOGICAL.types.InstanceID, this.configInstance.assembly);
// Output Instance
const pointOT = LOGICAL.build(LOGICAL.types.ConnPoint, this.outputInstance.assembly);
// Input Instance
const pointTO = LOGICAL.build(LOGICAL.types.ConnPoint, this.inputInstance.assembly);
// Config Data
let cipPath;
if (this.configInstance.size > 0 && Buffer.isBuffer(this.configInstance.data) && this.configInstance.size === this.configInstance.data.length) {
const DATA = CIP.EPATH.segments.DATA;
cipPath = Buffer.concat([assemblyObjectClass, configInstance, pointOT, pointTO, DATA.build(this.configInstance.data, false)]);
} else {
cipPath = Buffer.concat([assemblyObjectClass, configInstance, pointOT, pointTO]);
}
return(Buffer.concat([electronicKey, cipPath]));
}
/**
* Writes a forwardClose Request and retrieves the connection ID used for
* connected messages.
*/
async forwardClose() {
const { FORWARD_CLOSE } = CIP.MessageRouter.services;
const { LOGICAL } = CIP.EPATH.segments;
// Build Connection Manager Object Logical Path Buffer
const cmPath = Buffer.concat([
LOGICAL.build(LOGICAL.types.ClassID, 0x06), // Connection Manager Object (0x01)
LOGICAL.build(LOGICAL.types.InstanceID, 0x01) // Instance ID (0x01)
]);
// Message Router to Embed in UCMM
const MR = CIP.MessageRouter.build(FORWARD_CLOSE, cmPath, Buffer.from([]));
const forwardCloseData = CIP.ConnectionManager.build_forwardClose(1000 , 0x3333, 0x1337, this.state.fwd_open_serial);
// Build MR Path in order to send the message to the CPU
const mrPath = Buffer.concat([
LOGICAL.build(LOGICAL.types.ClassID, 0x02), // Message Router Object (0x02)
LOGICAL.build(LOGICAL.types.InstanceID, 0x01) // Instance ID (0x01)
]);
// Concatenate path to CPU and how to reach the message router
const portPath = Buffer.concat([
this.state.controller.path,
mrPath
]);
// This is the Connection Path data unit (Vol.1 Table 3-5.21)
const connectionPath = Buffer.concat([
Buffer.from([Math.ceil(portPath.length/2)]), //Path size in WORDS
Buffer.from([0x00]), // Padding
portPath
]);
// Fully assembled packet
const forwardClosePacket = Buffer.concat([
MR,
forwardCloseData,
connectionPath
]);
super.write_cip(forwardClosePacket); // We need to bypass unconnected send for now
const readPropsErr = new Error("TIMEOUT occurred while trying forwardClose Request.");
// Wait for Response
const data = await promiseTimeout(
new Promise((resolve, reject) => {
this.on("Forward Close", (err, data) => {
if (err) reject(err);
resolve(data);
});
}),
this.state.timeout_sp,
readPropsErr
);
this.removeAllListeners("Forward Close");
const OTconnID = data.readUInt32LE(0); // first 4 Bytes are O->T connection ID
super.id_conn = OTconnID;
super.established_conn = false;
super.establishing_conn = true;
return OTconnID;
}
/**
* Writes Ethernet/IP Data to Socket as an Unconnected Message
* or a Transport Class 1 Datagram
*
* NOTE: Cant Override Socket Write due to net.Socket.write
* implementation. =[. Thus, I am spinning up a new Method to
* handle it. Dont Use Enip.write, use this function instead.
*
* @override
* @param {buffer} data - Message Router Packet Buffer
* @param {boolean} [connected=false]
* @param {number} [timeout=10] - Timeout (sec)
* @param {function} [cb=null] - Callback to be Passed to Parent.Write()
* @memberof ENIP
*/
write_cip(data: Buffer, connected?: boolean, timeout: number = 10, cb: any = null) {
const { UnconnectedSend } = CIP;
let msg: Buffer;
if (!connected) {
connected = super.established_conn;
}
if (connected === false) {
msg = UnconnectedSend.build(data, this.state.controller.path);
} else {
msg = data;
}
super.write_cip(msg, connected, timeout, cb);
}
/**
* Reads Controller Identity Object
*
* @memberof Controller
* @returns {Promise}
*/
async readControllerProps() {
const { GET_ATTRIBUTE_ALL } = CIP.MessageRouter.services;
const { LOGICAL } = CIP.EPATH.segments;
// Build Identity Object Logical Path Buffer
const identityPath = Buffer.concat([
LOGICAL.build(LOGICAL.types.ClassID, 0x01), // Identity Object (0x01)
LOGICAL.build(LOGICAL.types.InstanceID, 0x01) // Instance ID (0x01)
]);
// Message Router to Embed in UCMM
const MR = CIP.MessageRouter.build(GET_ATTRIBUTE_ALL, identityPath, Buffer.from([]));
this.write_cip(MR);
const readPropsErr = new Error("TIMEOUT occurred while reading Controller Props.");
// Wait for Response
const data = await promiseTimeout(
new Promise((resolve, reject) => {
this.on("Get Attribute All", (err, data) => {
if (err) reject(err);
resolve(data);
});
}),
this.state.timeout_sp,
readPropsErr
);
this.removeAllListeners("Get Attribute All");
// Parse Returned Buffer
this.state.controller.serial_number = data.readUInt32LE(10);
const nameBuf = Buffer.alloc(data.length - 15);
data.copy(nameBuf, 0, 15);
this.state.controller.name = nameBuf.toString("utf8");
const major = data.readUInt8(6);
const minor = data.readUInt8(7);
this.state.controller.version = `${major}.${minor}`;
let status = data.readUInt16LE(8);
this.state.controller.status = status;
status &= 0x0ff0;
this.state.controller.faulted = (status & 0x0f00) === 0 ? false : true;
this.state.controller.minorRecoverableFault = (status & 0x0100) === 0 ? false : true;
this.state.controller.minorUnrecoverableFault = (status & 0x0200) === 0 ? false : true;
this.state.controller.majorRecoverableFault = (status & 0x0400) === 0 ? false : true;
this.state.controller.majorUnrecoverableFault = (status & 0x0800) === 0 ? false : true;
status &= 0x0f00;
this.state.controller.io_faulted = status >> 4 === 2 ? true : false;
this.state.controller.faulted = status >> 4 === 2 ? true : this.state.controller.faulted;
}
// region Private Methods
/**
* Initialized Controller Specific Event Handlers
*
* @memberof Controller
*/
_initializeControllerEventHandlers() {
this.on("SendRRData Received", this._handleSendRRDataReceived);
this.on("SendUnitData Received", this._handleSendUnitDataReceived);
}
/**
* Remove Controller Specific Event Handlers
*
* @memberof Controller
*/
_removeControllerEventHandlers() {
this.removeAllListeners("SendRRData Received");
this.removeAllListeners("SendUnitData Received");
}
// region Event Handlers
/**
* @typedef EncapsulationData
* @type {Object}
* @property {number} commandCode - Ecapsulation Command Code
* @property {string} command - Encapsulation Command String Interpretation
* @property {number} length - Length of Encapsulated Data
* @property {number} session - Session ID
* @property {number} statusCode - Status Code
* @property {string} status - Status Code String Interpretation
* @property {number} options - Options (Typically 0x00)
* @property {Buffer} data - Encapsulated Data Buffer
*/
/*****************************************************************/
/**
* @typedef MessageRouter
* @type {Object}
* @property {number} service - Reply Service Code
* @property {number} generalStatusCode - General Status Code (Vol 1 - Appendix B)
* @property {number} extendedStatusLength - Length of Extended Status (In 16-bit Words)
* @property {Array} extendedStatus - Extended Status
* @property {Buffer} data - Status Code
*/
/*****************************************************************/
/**
* Handles SendRRData Event Emmitted by Parent and Routes
* incoming Message
*
* @param {Array} srrd - Array of Common Packet Formatted Objects
* @memberof Controller
*/
_handleSendRRDataReceived(srrd) {
const { service, generalStatusCode, extendedStatus, data } = CIP.MessageRouter.parse(
srrd[1].data
);
const {
GET_INSTANCE_ATTRIBUTE_LIST,
GET_ATTRIBUTES,
GET_ATTRIBUTE_SINGLE,
GET_ATTRIBUTE_ALL,
SET_ATTRIBUTE_SINGLE,
READ_TAG,
READ_TAG_FRAGMENTED,
WRITE_TAG,
WRITE_TAG_FRAGMENTED,
READ_MODIFY_WRITE_TAG,
MULTIPLE_SERVICE_PACKET,
FORWARD_OPEN,
FORWARD_CLOSE
} = CIP.MessageRouter.services;
let error = generalStatusCode !== 0 ? { generalStatusCode, extendedStatus } : null;
// Route Incoming Message Responses
/* eslint-disable indent */
switch (service - 0x80) {
case FORWARD_CLOSE:
this.emit("Forward Close", error, data);
this.emit("Read Modify Write Tag", error, data);
break;
case FORWARD_OPEN:
this.emit("Forward Open", error, data);
break;
case GET_INSTANCE_ATTRIBUTE_LIST:
this.emit("Get Instance Attribute List", error, data);
break;
case GET_ATTRIBUTES:
this.emit("Get Attributes", error, data);
break;
case GET_ATTRIBUTE_SINGLE:
this.emit("Get Attribute Single", error, data);
break;
case GET_ATTRIBUTE_ALL:
this.emit("Get Attribute All", error, data);
break;
case SET_ATTRIBUTE_SINGLE:
this.emit("Set Attribute Single", error, data);
break;
case READ_TAG:
this.emit("Read Tag", error, data);
break;
case READ_TAG_FRAGMENTED:
this.emit("Read Tag Fragmented", error, data);
break;
case WRITE_TAG:
this.emit("Write Tag", error, data);
break;
case WRITE_TAG_FRAGMENTED:
this.emit("Write Tag Fragmented", error, data);
break;
case READ_MODIFY_WRITE_TAG:
this.emit("Read Modify Write Tag", error, data);
this.emit("Forward Close", error, data);
break;
case MULTIPLE_SERVICE_PACKET: {
// If service errored then propogate error
if (error && error.generalStatusCode !== 30) {
this.emit("Multiple Service Packet", error, data);
break;
}
// Get Number of Services to be Enclosed
let services = data.readUInt16LE(0);
let offsets = [];
let responses = [];
// Build Array of Buffer Offsets
for (let i = 0; i < services; i++) {
offsets.push(data.readUInt16LE(i * 2 + 2));
}
// Gather Messages within Buffer
for (let i = 0; i < offsets.length - 1; i++) {
const length = offsets[i + 1] - offsets[i];
let buf = Buffer.alloc(length);
data.copy(buf, 0, offsets[i], offsets[i + 1]);
// Parse Message Data
const msgData = CIP.MessageRouter.parse(buf);
if (msgData.generalStatusCode !== 0 && error.generalStatusCode !== 30) {
error = {
generalStatusCode: msgData.generalStatusCode,
extendedStatus: msgData.extendedStatus
};
}
responses.push(msgData);
}
// Handle Final Message
const length = data.length - offsets[offsets.length - 1];
let buf = Buffer.alloc(length);
data.copy(buf, 0, offsets[offsets.length - 1]);
const msgData = CIP.MessageRouter.parse(buf);
if (msgData.generalStatusCode !== 0) {
error = {
generalStatusCode: msgData.generalStatusCode,
extendedStatus: msgData.extendedStatus
};
}
responses.push(msgData);
this.emit("Multiple Service Packet", error, responses);
break;
}
default:
this.emit("Unknown Reply", { generalStatusCode: 0x99, extendedStatus: [] }, data);
break;
}
/* eslint-enable indent */
}
_handleSendUnitDataReceived(sud) {
let sudnew = sud[1].data.slice(2); // First 2 bytes are Connection sequence number
const { service, generalStatusCode, extendedStatus, data } = CIP.MessageRouter.parse(
sudnew
);
const {
GET_ATTRIBUTE_SINGLE,
GET_ATTRIBUTES,
GET_ATTRIBUTE_ALL,
GET_INSTANCE_ATTRIBUTE_LIST,
SET_ATTRIBUTE_SINGLE,
READ_TAG,
READ_TAG_FRAGMENTED,
WRITE_TAG,
WRITE_TAG_FRAGMENTED,
READ_MODIFY_WRITE_TAG,
MULTIPLE_SERVICE_PACKET,
FORWARD_OPEN,
FORWARD_CLOSE
} = CIP.MessageRouter.services;
let error = generalStatusCode !== 0 ? { generalStatusCode, extendedStatus } : null;
// Route Incoming Message Responses
/* eslint-disable indent */
switch (service - 0x80) {
case FORWARD_CLOSE:
this.emit("Forward Close", error, data);
this.emit("Read Modify Write Tag", error, data);
break;
case FORWARD_OPEN:
this.emit("Forward Open", error, data);
break;
case GET_ATTRIBUTES:
this.emit("Get Attributes", error, data);
break;
case GET_ATTRIBUTE_SINGLE:
this.emit("Get Attribute Single", error, data);
break;
case GET_ATTRIBUTE_ALL:
this.emit("Get Attribute All", error, data);
break;
case SET_ATTRIBUTE_SINGLE:
this.emit("Set Attribute Single", error, data);
break;
case GET_INSTANCE_ATTRIBUTE_LIST:
this.emit("Get Instance Attribute List", error, data);
break;
case READ_TAG:
this.emit("Read Tag", error, data);
break;
case READ_TAG_FRAGMENTED:
this.emit("Read Tag Fragmented", error, data);
break;
case WRITE_TAG:
this.emit("Write Tag", error, data);
break;
case WRITE_TAG_FRAGMENTED:
this.emit("Write Tag Fragmented", error, data);
break;
case READ_MODIFY_WRITE_TAG:
this.emit("Read Modify Write Tag", error, data);
this.emit("Forward Close", error, data);
break;
case MULTIPLE_SERVICE_PACKET: {
// If service errored then propogate error
if (error && error.generalStatusCode !== 30) {
this.emit("Multiple Service Packet", error, data);
break;
}
// Get Number of Services to be Enclosed
let services = data.readUInt16LE(0);
let offsets = [];
let responses = [];
// Build Array of Buffer Offsets
for (let i = 0; i < services; i++) {
offsets.push(data.readUInt16LE(i * 2 + 2));
}
// Gather Messages within Buffer
for (let i = 0; i < offsets.length - 1; i++) {
const length = offsets[i + 1] - offsets[i];
let buf = Buffer.alloc(length);
data.copy(buf, 0, offsets[i], offsets[i + 1]);
// Parse Message Data
const msgData = CIP.MessageRouter.parse(buf);
if (msgData.generalStatusCode !== 0) {
error = {
generalStatusCode: msgData.generalStatusCode,
extendedStatus: msgData.extendedStatus
};
}
responses.push(msgData);
}
// Handle Final Message
const length = data.length - offsets[offsets.length - 1];
let buf = Buffer.alloc(length);
data.copy(buf, 0, offsets[offsets.length - 1]);
const msgData = CIP.MessageRouter.parse(buf);
if (msgData.generalStatusCode !== 0) {
error = {
generalStatusCode: msgData.generalStatusCode,
extendedStatus: msgData.extendedStatus
};
}
responses.push(msgData);
this.emit("Multiple Service Packet", error, responses);
break;
}
default:
this.emit("Unknown Reply", { generalStatusCode: 0x99, extendedStatus: [] }, data);
break;
}
/* eslint-enable indent */
}
// _handleSessionRegistrationFailed(error) {
// // TODO: Implement Handler if Necessary
// }
// endregion
}
function getRandomInt(max) {
return Math.floor(Math.random() * Math.floor(max));
}
module.exports = Controller;