Updated macOS agent, more multiplexor work.
Ylian Saint-Hilaire committed
Apr 27, 2020 at 13:58 UTC
3cc6d8d29717dc0ce1d13942fc9994214d2267db
3 files changed
+181
-61
agents/meshagent_osx-x86-64
Binary files a/agents/meshagent_osx-x86-64 and b/agents/meshagent_osx-x86-64 differ
meshdesktopmultiplex.js
+178
-57
@@ -59,27 +59,31 @@ MNG_ENCAPSULATE_AGENT_COMMAND = 70
59
60
function CreateDesktopDecoder() {
61
var obj = {};
62
- obj.agent = null;
63
- obj.viewers = [];
64
- obj.width = 0;
65
- obj.height = 0;
66
- obj.swidth = 0;
67
- obj.sheight = 0;
68
- obj.screen = null;
69
- obj.counter = 1;
70
- obj.imagesCount = 0;
71
- obj.imagesCounters = {};
72
- obj.images = {};
73
- obj.lastScreenSizeCmd = null;
74
- obj.lastScreenSizeCounter = 0;
75
- obj.firstData = null;
76
- obj.lastData = null;
77
- obj.lastDisplayInfoData = null;
78
- obj.desktopPaused = true;
79
- obj.imageCompression = 50;
80
- obj.imageScaling = 1024;
81
- obj.imageFrameRate = 50;
82
-
62
+ obj.agent = null; // Reference to the connection object that is the agent.
63
+ obj.viewers = []; // Array of references to all viewers.
64
+ obj.viewersSendingCount = 0; // Number of viewers currently activaly sending something.
65
+ obj.width = 0; // Current width of the display in pixels.
66
+ obj.height = 0; // Current height of the display in pixels.
67
+ obj.swidth = 0; // Current width of the display in tiles.
68
+ obj.sheight = 0; // Current height of the display in tiles.
69
+ obj.screen = null; // The main screen, (x * y) --> tile index. Indicates this image is covering each tile on the screen.
70
+ obj.counter = 1; // The main counter, used as index for the obj.images table when now images come in.
71
+ obj.imagesCount = 0; // Total number of images in the obj.images table.
72
+ obj.imagesCounters = {}; // Main table of indexes --> tile count, the number of tiles still using this image.
73
+ obj.images = {}; // Main table of indexes --> image data object.
74
+ obj.lastScreenSizeCmd = null; // Pointer to the last screen size command from the agent.
75
+ obj.lastScreenSizeCounter = 0; // Index into the image table of the screen size command, this is generally also the first command.
76
+ obj.firstData = null; // Index in the image table of the first image in the table, generally this points to the display resolution command.
77
+ obj.lastData = null; // Index in the images table of the last image in the table.
78
+ obj.lastDisplayInfoData = null; // Pointer to the last display information command from the agent (Number of displays).
79
+ obj.desktopPaused = true; // Current desktop pause state, it's true if all viewers are paused.
80
+ obj.imageCompression = 50; // Current image compression, this is the highest value of all viewers.
81
+ obj.imageScaling = 1024; // Current image scaling, this is the highest value of all viewers.
82
+ obj.imageFrameRate = 50; // Current framerate setting, this is the lowest values of all viewers.
83
+ obj.protocolOptions = null; // Set to the protocol options of the first viewer that connected.
84
+ obj.viewerConnected = false; // Set to true if one viewer attempted to connect to the agent.
85
+
86
+ // Add an agent or viewer
87
obj.addPeer = function (peer) {
88
if (peer.req.query.browser) {
89
console.log('addPeer-viewer');
@@ -93,7 +97,10 @@ function CreateDesktopDecoder() {
97
peer.imageCompression = 30;
98
peer.imageScaling = 1024;
99
peer.imageFrameRate = 100;
100
+ peer.lastImageNumberSent = null;
101
peer.dataPtr = obj.firstData;
102
+ peer.sending = false;
103
+ peer.sendQueue = [];
104
} else {
105
console.log('addPeer-agent');
106
if (obj.agent != null) return false;
@@ -102,11 +109,20 @@ function CreateDesktopDecoder() {
109
obj.agent = peer;
110
111
// Setup the agent
105
-
112
+ peer.sending = false;
113
+ peer.sendQueue = [];
114
+ //peer.ws.send('{"tsid":10,"type":"options"}');
115
+ //peer.ws.send('2');
116
+
117
+ if (obj.viewerConnected == true) {
118
+ if (obj.protocolOptions != null) { obj.sendToAgent(JSON.stringify(obj.protocolOptions)); } // Send connection options
119
+ obj.sendToAgent('2'); // Send remote desktop connect
120
+ }
121
}
122
return true;
123
}
109
-
124
+
125
+ // Remove an agent or viewer
126
obj.removePeer = function (peer) {
127
if (peer == agent) {
128
console.log('removePeer-agent');
@@ -125,7 +141,75 @@ function CreateDesktopDecoder() {
141
}
142
return true;
143
}
128
-
144
+
145
+ // Send data to the agent or queue it up for sending
146
+ obj.sendToAgent = function (data) {
147
+ if (obj.agent == null) return;
148
+ //console.log('SendToAgent', data.length);
149
+ if (obj.agent.sending) {
150
+ obj.agent.sendQueue.push(data);
151
+ // TODO: Flow control, stop all viewers
152
+ } else {
153
+ obj.agent.ws.send(data, sendAgentNext);
154
+ }
155
+ }
156
+
157
+ // Send more data to the agent
158
+ function sendAgentNext() {
159
+ if (obj.agent.sendQueue.length > 0) {
160
+ // Send from the pending send queue
161
+ obj.agent.ws.send(obj.agent.sendQueue.shift(), sendAgentNext);
162
+ } else {
163
+ // Nothing to send
164
+ obj.agent.sending = false;
165
+ // TODO: Flow control, start all viewers
166
+ }
167
+ }
168
+
169
+ // Send this command to all viewers
170
+ obj.sendToAllViewers = function (data) {
171
+ for (var i in obj.viewers) { obj.sendToViewer(obj.viewers[i], data); }
172
+ }
173
+
174
+ // Send data to the viewer or queue it up for sending
175
+ obj.sendToViewer = function (viewer, data) {
176
+ if (viewer == null) return;
177
+ //console.log('SendToViewer', data.length);
178
+ if (viewer.sending) {
179
+ viewer.sendQueue.push(data);
180
+ // TODO: Flow control, stop the agent
181
+ } else {
182
+ viewer.sending = true;
183
+ obj.viewersSendingCount++;
184
+ viewer.ws.send(data, function () { sendViewerNext(viewer); });
185
+ }
186
+ }
187
+
188
+ // Send more data to the viewer
189
+ function sendViewerNext(viewer) {
190
+ if (viewer.sendQueue.length > 0) {
191
+ // Send from the pending send queue
192
+ if (viewer.sending == false) { viewer.sending = true; obj.viewersSendingCount++; }
193
+ viewer.ws.send(viewer.sendQueue.shift(), function () { sendViewerNext(viewer); });
194
+ } else {
195
+ if (viewer.dataPtr != null) {
196
+ // Send the next image
197
+ if ((viewer.lastImageNumberSent != null) && ((viewer.lastImageNumberSent + 1) != (viewer.dataPtr))) { console.log('SVIEW-S1', viewer.lastImageNumberSent, viewer.dataPtr); } // DEBUG
198
+ var image = obj.images[viewer.dataPtr];
199
+ viewer.lastImageNumberSent = viewer.dataPtr;
200
+ if ((image.next != null) && ((viewer.dataPtr + 1) != image.next)) { console.log('SVIEW-S2', viewer.dataPtr, image.next); } // DEBUG
201
+ viewer.dataPtr = image.next;
202
+ if (viewer.sending == false) { viewer.sending = true; obj.viewersSendingCount++; }
203
+ viewer.ws.send(image.data, function () { sendViewerNext(viewer); });
204
+ } else {
205
+ // Nothing to send
206
+ viewer.sending = false;
207
+ obj.viewersSendingCount--;
208
+ // TODO: Flow control, start agent
209
+ }
210
+ }
211
+ }
212
+
213
// Process data coming from the agent or any viewers
214
obj.processData = function (peer, data) {
215
if (peer == obj.agent) { obj.processAgentData(data); } else { obj.processViewerData(peer, data); }
@@ -133,16 +217,37 @@ function CreateDesktopDecoder() {
217
218
// Process incoming viewer data
219
obj.processViewerData = function (viewer, data) {
136
- //console.log('ViewerData', data.length);
220
+ if (typeof data == 'string') {
221
+ if (data == '2') {
222
+ if (obj.viewerConnected == false) {
223
+ if (obj.agent != null) {
224
+ if (obj.protocolOptions != null) { obj.sendToAgent(JSON.stringify(obj.protocolOptions)); } // Send connection options
225
+ obj.sendToAgent('2'); // Send remote desktop connect
226
+ }
227
+ obj.viewerConnected = true;
228
+ }
229
+ return;
230
+ }
231
+ var json = null;
232
+ try { json = JSON.parse(data); } catch (ex) { }
233
+ if (json == null) return;
234
+ if ((json.type == 'options') && (obj.protocolOptions == null)) { obj.protocolOptions = json; }
235
+ return;
236
+ }
237
+
238
+ //console.log('ViewerData', data.length, typeof data, data);
239
if ((typeof data != 'object') || (data.length < 4)) return; // Ignore all control traffic for now (WebRTC)
240
var command = data.readUInt16BE(0);
241
var cmdsize = data.readUInt16BE(2);
242
+ //console.log('ViewerData', data.length, command, cmdsize);
243
switch (command) {
244
case 1:// Key Events, forward to agent
245
//console.log('Viewer-Keys');
246
+ obj.sendToAgent(data);
247
break;
248
case 2:// Mouse events, forward to agent
249
//console.log('Viewer-Mouse');
250
+ obj.sendToAgent(data);
251
break;
252
case 5:// Compression
253
if (data.length < 10) return;
@@ -166,14 +271,21 @@ function CreateDesktopDecoder() {
271
obj.imageCompression = viewersimageCompression;
272
obj.imageScaling = viewersimageScaling;
273
obj.imageFrameRate = viewersimageFrameRate
169
- console.log('Send-Agent-Compression', obj.imageCompression, obj.imageScaling, obj.imageFrameRate);
170
- // obj.send(String.fromCharCode(0x00, 0x05, 0x00, 0x0A, type, obj.CompressionLevel) + obj.shortToStr(obj.ScalingLevel) + obj.shortToStr(obj.FrameRateTimer));
274
+ //console.log('Send-Agent-Compression', obj.imageCompression, obj.imageScaling, obj.imageFrameRate);
275
+ var cmd = Buffer.alloc(10);
276
+ cmd.writeUInt16BE(5, 0); // Command 5, compression
277
+ cmd.writeUInt16BE(10, 2); // Command size, 10 bytes long
278
+ cmd[4] = 1; // Image type, 1 = JPEN
279
+ cmd[5] = obj.imageCompression; // Image compression level
280
+ cmd.writeUInt16BE(obj.imageScaling, 6); // Scaling level
281
+ cmd.writeUInt16BE(obj.imageFrameRate, 8); // Frame rate timer
282
+ obj.sendToAgent(cmd);
283
}
284
break;
285
case 6:// Refresh, handle this on the server
174
- console.log('Viewer-Refresh');
286
+ //console.log('Viewer-Refresh');
287
viewer.dataPtr = obj.firstData; // Start over
176
- // TODO
288
+ if (viewer.sending == false) { sendViewerNext(viewer); }
289
break;
290
case 8:// Pause and unpause
291
if (data.length != 5) break;
@@ -185,15 +297,13 @@ function CreateDesktopDecoder() {
297
for (var i in obj.viewers) { if (obj.viewers[i].desktopPaused == false) { viewersPaused = false; }; }
298
if (viewersPaused != obj.desktopPaused) {
299
obj.desktopPaused = viewersPaused;
188
- console.log('Send-Agent-' + ((viewersPaused == 1)?'Pause':'UnPause'));
189
- // TODO
190
- }
191
- if (viewer.desktopPaused == false) {
192
- viewer.dataPtr = obj.firstData; // Start over
193
- // TODO
300
+ //console.log('Send-Agent-' + ((viewersPaused == true) ? 'Pause' : 'UnPause'));
301
+ data[4] = (viewersPaused == true) ? 1 : 0;
302
+ obj.sendToAgent(data);
303
}
304
break;
305
case 10:// CTRL-ALT-DEL, forward to agent
306
+ obj.sendToAgent(data);
307
break;
308
case 14:// Touch setup
309
break;
@@ -202,13 +312,14 @@ function CreateDesktopDecoder() {
312
break;
313
}
314
}
205
-
315
+
316
// Process incoming agent data
317
obj.processAgentData = function (data) {
208
- //console.log('AgentData', data.length);
318
if ((typeof data != 'object') || (data.length < 4)) return; // Ignore all control traffic for now (WebRTC)
319
+ const jumboData = data;
320
var command = data.readUInt16BE(0);
321
var cmdsize = data.readUInt16BE(2);
322
+ //console.log('AgentData', data.length, command, cmdsize);
323
if ((command == 27) && (cmdsize == 8)) {
324
// Jumbo packet
325
if (data.length >= 12) {
@@ -232,33 +343,47 @@ function CreateDesktopDecoder() {
343
//console.log("Tile", x, y, dimensions.width, dimensions.height);
344
345
// Keep a reference to this image & how many tiles it covers
235
- obj.images[obj.counter] = { next: null, prev: obj.lastData, data: data };
346
+ obj.images[obj.counter] = { next: null, prev: obj.lastData, data: jumboData };
347
obj.images[obj.lastData].next = obj.counter;
348
obj.lastData = obj.counter;
349
obj.imagesCounters[obj.counter] = (sw * sh);
350
obj.imagesCount++;
351
if (obj.imagesCount == 2000000000) { obj.imagesCount = 1; } // Loop the counter if needed
241
-
352
+
353
+ //console.log('Adding Image ' + obj.counter);
354
+
355
+ var skips = [];
356
+
357
// Update the screen with the correct pointers.
358
for (var i = 0; i < sw; i++) {
359
for (var j = 0; j < sh; j++) {
245
- var k = ((obj.swidth * (j + sy)) + (i + sx)), oi = obj.screen[k];
360
+ var k = ((obj.swidth * (j + sy)) + (i + sx));
361
+ const oi = obj.screen[k];
362
+ obj.screen[k] = obj.counter;
363
if ((oi != null) && (--obj.imagesCounters[oi] == 0)) {
364
// Remove data from the link list
365
obj.imagesCount--;
366
var d = obj.images[oi];
367
+ //console.log('Removing Image', oi, obj.images[oi].prev, obj.images[oi].next);
368
obj.images[d.prev].next = d.next;
369
obj.images[d.next].prev = d.prev;
370
delete obj.images[oi];
371
delete obj.imagesCounters[oi];
372
373
// If any viewers are currently on image "oi" must be moved to "d.next"
256
- // TODO
374
+ for (var l in obj.viewers) { const v = obj.viewers[l]; if (v.dataPtr == oi) { skips.push(oi); v.dataPtr = d.next; } }
375
}
258
- obj.screen[k] = obj.counter;
376
}
377
}
261
-
378
+
379
+ if (skips.length > 0) { console.log('SKIPS', skips.length); }
380
+
381
+ // Any viewer on dataPtr null, change to this image
382
+ for (var i in obj.viewers) {
383
+ const v = obj.viewers[i];
384
+ if (v.dataPtr == null) { v.dataPtr = obj.counter; if (v.sending == false) { sendViewerNext(v); } }
385
+ }
386
+
387
// Debug, display the link list
388
//var xx = '', xptr = obj.firstData;
389
//while (xptr != null) { xx += '>' + xptr; xptr = obj.images[xptr].next; }
@@ -284,14 +409,15 @@ function CreateDesktopDecoder() {
409
obj.imagesCount = 0;
410
obj.imagesCounters = {};
411
obj.images = {};
287
- obj.images[obj.counter] = { next: null, prev: null, data: data};
412
+ obj.images[obj.counter] = { next: null, prev: null, data: data };
413
obj.firstData = obj.counter;
414
obj.lastData = obj.counter;
415
416
// Add viewers must be set to start at "obj.counter"
417
for (var i in obj.viewers) {
293
- obj.viewers[i].dataPtr = obj.counter;
294
- // TODO
418
+ const v = obj.viewers[i];
419
+ v.dataPtr = obj.counter;
420
+ if (v.sending == false) { sendViewerNext(v); }
421
}
422
423
//console.log("ScreenSize", obj.width, obj.height, obj.swidth, obj.sheight, obj.swidth * obj.sheight);
@@ -299,8 +425,7 @@ function CreateDesktopDecoder() {
425
case 11: // GetDisplays
426
// Store and send this to all viewers right away
427
obj.lastDisplayInfoData = data;
302
- // TODO
303
-
428
+ obj.sendToAllViewers(data);
429
break;
430
case 14: // KVM_INIT_TOUCH
431
break;
@@ -309,22 +434,16 @@ function CreateDesktopDecoder() {
434
case 16: // MNG_KVM_CONNECTCOUNT
435
break;
436
case 17: // MNG_KVM_MESSAGE
312
-
437
// Send this to all viewers right away
314
- // TODO
315
-
438
+ obj.sendToAllViewers(data);
439
break;
440
case 65: // Alert
318
-
441
// Send this to all viewers right away
320
- // TODO
321
-
442
+ obj.sendToAllViewers(data);
443
break;
444
case 88: // MNG_KVM_MOUSE_CURSOR
324
-
445
// Send this to all viewers right away
326
- // TODO
327
-
446
+ obj.sendToAllViewers(data);
447
break;
448
default:
449
console.log('Un-handled agent command: ' + command);
@@ -636,6 +755,7 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
755
// If this data was received by the agent, decode it.
756
if (this.me.deskDecoder != null) { this.me.deskDecoder.processData(this.me, data); }
757
758
+ /*
759
//console.log(typeof data, data.length);
760
if (this.peer != null) {
761
//if (typeof data == 'string') { console.log('Relay: ' + data); } else { console.log('Relay:' + data.length + ' byte(s)'); }
@@ -651,6 +771,7 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
771
}
772
} catch (ex) { console.log(ex); }
773
}
774
+ */
775
});
776
777
// If error, close both sides of the relay.
public/scripts/agent-desktop-0.0.2.js
+3
-4
@@ -180,11 +180,10 @@ var CreateAgentRemoteDesktop = function (canvasid, scrolldiv) {
180
181
obj.ProcessData = function (str) {
182
var ptr = 0;
183
- //console.log('x0', str.length);
183
while (ptr < str.length) {
185
- //console.log('x1', ptr, str.length);
186
- ptr += obj.ProcessDataEx(str.substring(ptr));
187
- //console.log('x2', ptr, str.length);
184
+ var r = obj.ProcessDataEx(str.substring(ptr));
185
+ if ((r == null) || (r == 0)) break;
186
+ ptr += r;
187
}
188
}
189