dsc-itv2-client 1.0.23 → 1.0.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/ITV2Client.js +145 -13
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsc-itv2-client",
3
3
  "author": "fajitacat",
4
- "version": "1.0.23",
4
+ "version": "1.0.24",
5
5
  "description": "Reverse engineered DSC ITV2 Protocol Client Library for TL280R Communicator - Monitor and control DSC alarm panels",
6
6
  "main": "src/index.js",
7
7
  "type": "module",
package/src/ITV2Client.js CHANGED
@@ -5,6 +5,7 @@
5
5
 
6
6
  import {EventEmitter} from 'events';
7
7
  import dgram from 'dgram';
8
+ import net from 'net';
8
9
  import {ITv2Session, CMD, CMD_NAMES} from './itv2-session.js';
9
10
  import {parseType1Initializer, type2InitializerTransform} from './itv2-crypto.js';
10
11
  import {EventHandler} from './event-handler.js';
@@ -22,18 +23,23 @@ export class ITV2Client extends EventEmitter {
22
23
  this.integrationId = options.integrationId;
23
24
  this.accessCode = options.accessCode;
24
25
  this.masterCode = options.masterCode || '5555';
25
- this.port = options.port || 3073;
26
+ this.port = options.port || 3073; // UDP polling port
27
+ this.tcpPort = options.tcpPort || 3072; // TCP notification port
26
28
  this.logLevel = options.logLevel || (options.debug === true ? 'verbose' : 'minimal'); // 'silent', 'minimal', 'verbose'
27
29
  this.encryptionType = options.encryptionType || null; // null = auto-detect, 1 = Type 1, 2 = Type 2
28
30
 
29
31
  // Internal state
30
- this.server = null;
32
+ this.server = null; // UDP server
33
+ this.tcpServer = null; // TCP server
34
+ this.tcpClients = new Map(); // Active TCP connections
31
35
  this.session = null;
32
36
  this.eventHandler = new EventHandler();
33
37
  this.handshakeState = 'WAITING';
34
38
  this.panelAddress = null;
35
39
  this.panelPort = null;
36
40
  this.detectedEncryptionType = null; // Set during handshake
41
+ this._lastTransport = 'udp'; // Track last packet source
42
+ this._lastTcpSocket = null; // Track TCP socket for responses
37
43
 
38
44
  // Bind methods
39
45
  this._handlePacket = this._handlePacket.bind(this);
@@ -69,6 +75,9 @@ export class ITV2Client extends EventEmitter {
69
75
 
70
76
  this.server.bind(this.port, '0.0.0.0');
71
77
 
78
+ // Start TCP server for notification port
79
+ this._startTcpServer();
80
+
72
81
  // Register signal handlers (only once)
73
82
  if (!this._boundShutdown) {
74
83
  this._boundShutdown = async () => {
@@ -81,6 +90,97 @@ export class ITV2Client extends EventEmitter {
81
90
  });
82
91
  }
83
92
 
93
+ /**
94
+ * Start TCP server for notification port (3072)
95
+ */
96
+ _startTcpServer() {
97
+ this.tcpServer = net.createServer((socket) => {
98
+ const clientId = `${socket.remoteAddress}:${socket.remotePort}`;
99
+ this._logMinimal(`[TCP] Client connected: ${clientId}`);
100
+ this.tcpClients.set(clientId, socket);
101
+
102
+ // Store panel address from TCP connection too
103
+ if (!this.panelAddress) {
104
+ this.panelAddress = socket.remoteAddress;
105
+ }
106
+
107
+ let buffer = Buffer.alloc(0);
108
+
109
+ socket.on('data', (data) => {
110
+ this._logMinimal(`[TCP] Received ${data.length} bytes from ${clientId}`);
111
+ this._logMinimal(`[TCP] Raw data: ${data.toString('hex')}`);
112
+
113
+ // Accumulate data in buffer
114
+ buffer = Buffer.concat([buffer, data]);
115
+
116
+ // Try to parse complete packets (look for 0x7E...0x7F framing)
117
+ this._processTcpBuffer(buffer, socket, (remaining) => {
118
+ buffer = remaining;
119
+ });
120
+ });
121
+
122
+ socket.on('close', () => {
123
+ this._logMinimal(`[TCP] Client disconnected: ${clientId}`);
124
+ this.tcpClients.delete(clientId);
125
+ });
126
+
127
+ socket.on('error', (err) => {
128
+ this._log(`[TCP] Socket error for ${clientId}: ${err.message}`);
129
+ this.tcpClients.delete(clientId);
130
+ });
131
+ });
132
+
133
+ this.tcpServer.on('error', (err) => {
134
+ this._log(`[TCP] Server error: ${err.message}`);
135
+ this.emit('error', err);
136
+ });
137
+
138
+ this.tcpServer.listen(this.tcpPort, '0.0.0.0', () => {
139
+ this._logMinimal(`[TCP] Server listening on port ${this.tcpPort}`);
140
+ this.emit('tcp:listening', { port: this.tcpPort });
141
+ });
142
+ }
143
+
144
+ /**
145
+ * Process TCP buffer looking for complete ITV2 packets
146
+ */
147
+ _processTcpBuffer(buffer, socket, callback) {
148
+ // Look for Integration ID prefix followed by 0x7E
149
+ const idLength = 12; // Integration ID is 12 ASCII chars
150
+
151
+ while (buffer.length > idLength + 2) {
152
+ // Find start marker (0x7E) after integration ID
153
+ const startIdx = buffer.indexOf(0x7E, idLength - 1);
154
+ if (startIdx === -1) {
155
+ // No start marker found, keep last 12 bytes in case ID is split
156
+ callback(buffer.slice(Math.max(0, buffer.length - idLength)));
157
+ return;
158
+ }
159
+
160
+ // Find end marker (0x7F)
161
+ const endIdx = buffer.indexOf(0x7F, startIdx + 1);
162
+ if (endIdx === -1) {
163
+ // No end marker yet, wait for more data
164
+ callback(buffer);
165
+ return;
166
+ }
167
+
168
+ // Extract complete packet (including ID prefix)
169
+ const packetStart = Math.max(0, startIdx - idLength);
170
+ const packet = buffer.slice(packetStart, endIdx + 1);
171
+
172
+ this._logMinimal(`[TCP] Complete packet (${packet.length} bytes): ${packet.toString('hex')}`);
173
+
174
+ // Process the packet same as UDP
175
+ this._handlePacket(packet, 'tcp', socket);
176
+
177
+ // Continue with remaining buffer
178
+ buffer = buffer.slice(endIdx + 1);
179
+ }
180
+
181
+ callback(buffer);
182
+ }
183
+
84
184
  /**
85
185
  * Stop the client and close the session gracefully
86
186
  */
@@ -105,6 +205,23 @@ export class ITV2Client extends EventEmitter {
105
205
  this.server = null;
106
206
  }
107
207
 
208
+ // Close TCP server
209
+ if (this.tcpServer) {
210
+ // Close all TCP client connections
211
+ for (const [clientId, socket] of this.tcpClients) {
212
+ socket.destroy();
213
+ }
214
+ this.tcpClients.clear();
215
+
216
+ await new Promise((resolve) => {
217
+ this.tcpServer.close(() => {
218
+ this._log('[Shutdown] TCP server closed');
219
+ resolve();
220
+ });
221
+ });
222
+ this.tcpServer = null;
223
+ }
224
+
108
225
  // Clear session and encryption state to prevent stale keys on reconnect
109
226
  if (this.session) {
110
227
  this.session.disableAes();
@@ -277,19 +394,29 @@ export class ITV2Client extends EventEmitter {
277
394
  }
278
395
 
279
396
  _sendPacket(packet) {
280
- if (!this.panelAddress || !this.panelPort) {
281
- this._log('[Error] No panel address/port set');
282
- return;
283
- }
397
+ // Use the same transport that the last packet came from
398
+ if (this._lastTransport === 'tcp' && this._lastTcpSocket) {
399
+ this._log(`\n[TCP] TX (${packet.length} bytes)`);
400
+ if (this.debug) {
401
+ this._hexDump(packet);
402
+ }
403
+ this._lastTcpSocket.write(packet);
404
+ } else {
405
+ // UDP (default)
406
+ if (!this.panelAddress || !this.panelPort) {
407
+ this._log('[Error] No panel address/port set');
408
+ return;
409
+ }
284
410
 
285
- this._log(`\n[UDP] TX to ${this.panelAddress}:${this.panelPort} (${packet.length} bytes)`);
286
- if (this.debug) {
287
- this._hexDump(packet);
411
+ this._log(`\n[UDP] TX to ${this.panelAddress}:${this.panelPort} (${packet.length} bytes)`);
412
+ if (this.debug) {
413
+ this._hexDump(packet);
414
+ }
415
+ this.server.send(packet, this.panelPort, this.panelAddress);
288
416
  }
289
- this.server.send(packet, this.panelPort, this.panelAddress);
290
417
  }
291
418
 
292
- _handlePacket(data) {
419
+ _handlePacket(data, transport = 'udp', tcpSocket = null) {
293
420
  try {
294
421
  if (!this.session) {
295
422
  // Create session with appropriate logger based on log level
@@ -304,8 +431,13 @@ export class ITV2Client extends EventEmitter {
304
431
  return;
305
432
  }
306
433
 
307
- // Verbose logging only
308
- this._log(`\n[UDP] RX from ${this.panelAddress}:${this.panelPort} (${data.length} bytes)`);
434
+ // Store transport info for responses
435
+ this._lastTransport = transport;
436
+ this._lastTcpSocket = tcpSocket;
437
+
438
+ // Verbose logging
439
+ const source = transport === 'tcp' ? 'TCP' : `UDP ${this.panelAddress}:${this.panelPort}`;
440
+ this._log(`\n[${transport.toUpperCase()}] RX from ${source} (${data.length} bytes)`);
309
441
  this._hexDump(data);
310
442
  this._log(`[Packet] Integration ID: ${parsed.integrationId}`);
311
443
  this._log(`[Packet] Sender Seq: ${parsed.senderSequence}, Receiver Seq: ${parsed.receiverSequence}`);