Skip to content

Commit c8c7e36

Browse files
committed
Merge pull request #332 from uProxy/trevj-cloud-fixes
better error handling and logging for cloud
2 parents 7a5b4c9 + 50575cb commit c8c7e36

File tree

5 files changed

+223
-161
lines changed

5 files changed

+223
-161
lines changed

Gruntfile.coffee

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ taskManager.add 'browserifySpecs', [
125125
'browserify:datachannelSpec'
126126
'browserify:poolSpec'
127127
'browserify:tcpSpec'
128+
'browserify:linefeederSpec'
128129
'browserify:queueSpec'
129130
'browserify:turnFrontEndMessagesSpec'
130131
'browserify:turnFrontEndSpec'
@@ -589,6 +590,7 @@ module.exports = (grunt) ->
589590
churnCovSpec: Rule.addCoverageToBrowserify(Rule.browserifySpec 'churn/churn')
590591
handlerSpec: Rule.browserifySpec 'handler/queue'
591592
handlerCovSpec: Rule.addCoverageToBrowserify(Rule.browserifySpec 'handler/queue')
593+
linefeederSpec: Rule.browserifySpec 'net/linefeeder'
592594
loggingProviderSpec: Rule.browserifySpec 'loggingprovider/loggingprovider'
593595
loggingProviderCovSpec: Rule.addCoverageToBrowserify(Rule.browserifySpec 'loggingprovider/loggingprovider')
594596
loggingSpec: Rule.browserifySpec 'logging/logging'

src/cloud/social/provider.ts

Lines changed: 41 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
/// <reference path='../../../../third_party/typings/ssh2/ssh2.d.ts' />
55

66
import arraybuffers = require('../../arraybuffers/arraybuffers');
7+
import linefeeder = require('../../net/linefeeder');
78
import logging = require('../../logging/logging');
9+
import queue = require('../../handler/queue');
810

911
// https://github.com/borisyankov/DefinitelyTyped/blob/master/ssh2/ssh2-tests.ts
1012
import * as ssh2 from 'ssh2';
@@ -280,7 +282,7 @@ export class CloudSocialProvider {
280282
// the instance (safe because all we've done is run ping).
281283
log.info('new proxying session %1', payload.proxyingId);
282284
if (!(destinationClientId in this.savedContacts_)) {
283-
return Promise.reject('unknown client ' + destinationClientId);
285+
return Promise.reject(new Error('unknown client ' + destinationClientId));
284286
}
285287
return this.reconnect_(this.savedContacts_[destinationClientId].invite).then(
286288
(connection: Connection) => {
@@ -293,14 +295,14 @@ export class CloudSocialProvider {
293295
connection.sendMessage(JSON.stringify(payload));
294296
});
295297
} else {
296-
return Promise.reject('unknown client ' + destinationClientId);
298+
return Promise.reject(new Error('unknown client ' + destinationClientId));
297299
}
298300
}
299301
} else {
300-
return Promise.reject('message has no or wrong type field');
302+
return Promise.reject(new Error('message has no or wrong type field'));
301303
}
302304
} catch (e) {
303-
return Promise.reject('could not de-serialise message: ' + e.message);
305+
return Promise.reject(new Error('could not de-serialise message: ' + e.message));
304306
}
305307
}
306308

@@ -349,7 +351,7 @@ export class CloudSocialProvider {
349351
// Return nothing for type checking purposes.
350352
});
351353
} catch (e) {
352-
return Promise.reject('could not parse invite code: ' + e.message);
354+
return Promise.reject(new Error('could not parse invite code: ' + e.message));
353355
}
354356
}
355357

@@ -395,7 +397,7 @@ class Connection {
395397
// TODO: timeout
396398
public connect = (): Promise<void> => {
397399
if (this.state_ !== ConnectionState.NEW) {
398-
return Promise.reject('can only connect in NEW state');
400+
return Promise.reject(new Error('can only connect in NEW state'));
399401
}
400402
this.state_ = ConnectionState.CONNECTING;
401403

@@ -424,49 +426,45 @@ class Connection {
424426
ZORK_HOST, ZORK_PORT, (e: Error, stream: ssh2.Channel) => {
425427
if (e) {
426428
this.close();
427-
R('error establishing tunnel: ' + e.toString());
429+
R(new Error('error establishing tunnel: ' + e.toString()));
430+
return;
428431
}
429432
this.setState_(ConnectionState.WAITING_FOR_PING);
430433

431434
this.stream_ = stream;
432435

433-
// TODO: add error handler for stream
434-
let leftover = new ArrayBuffer(0);
435-
this.stream_.on('data', (data: Buffer) => {
436-
leftover = arraybuffers.concat([leftover,
437-
arraybuffers.bufferToArrayBuffer(data)]);
438-
let i = arraybuffers.indexOf(leftover, Connection.COMMAND_DELIMITER);
439-
while (i !== -1) {
440-
let parts = arraybuffers.split(leftover, i);
441-
let reply = arraybuffers.arrayBufferToString(parts[0]).trim();
442-
leftover = parts[1].slice(1);
443-
i = arraybuffers.indexOf(leftover, Connection.COMMAND_DELIMITER);
444-
445-
switch (this.state_) {
446-
case ConnectionState.WAITING_FOR_PING:
447-
if (reply === 'ping') {
448-
this.setState_(ConnectionState.ESTABLISHED);
449-
F();
450-
} else {
451-
this.close();
452-
R(new Error('did not receive ping from server on login: ' +
453-
reply));
454-
}
455-
break;
456-
case ConnectionState.ESTABLISHED:
457-
try {
458-
this.received_(JSON.parse(reply));
459-
} catch (e) {
460-
log.warn('%1: could not de-serialise signalling message: %2',
461-
this.invite_, reply);
462-
}
463-
break;
464-
default:
465-
log.warn('%1: did not expect message in state %2: %3',
466-
this.name_, ConnectionState[this.state_], reply);
436+
var bufferQueue = new queue.Queue<ArrayBuffer, void>();
437+
new linefeeder.LineFeeder(bufferQueue).setSyncHandler((reply: string) => {
438+
log.debug('%1: received message: %2', this.name_, reply);
439+
switch (this.state_) {
440+
case ConnectionState.WAITING_FOR_PING:
441+
if (reply === 'ping') {
442+
this.setState_(ConnectionState.ESTABLISHED);
443+
F();
444+
} else {
467445
this.close();
468-
}
446+
R(new Error('did not receive ping from server on login: ' +
447+
reply));
448+
}
449+
break;
450+
case ConnectionState.ESTABLISHED:
451+
try {
452+
this.received_(JSON.parse(reply));
453+
} catch (e) {
454+
log.warn('%1: could not de-serialise signalling message: %2',
455+
this.invite_, reply);
456+
}
457+
break;
458+
default:
459+
log.warn('%1: did not expect message in state %2: %3',
460+
this.name_, ConnectionState[this.state_], reply);
461+
this.close();
469462
}
463+
});
464+
465+
// TODO: add error handler for stream
466+
stream.on('data', (buffer: Buffer) => {
467+
bufferQueue.handle(arraybuffers.bufferToArrayBuffer(buffer));
470468
}).on('error', (e: Error) => {
471469
// This occurs when:
472470
// - host cannot be reached, e.g. non-existant hostname
@@ -529,7 +527,7 @@ class Connection {
529527
// Fetches the server's description, i.e. /banner.
530528
public getBanner = (): Promise<string> => {
531529
if (this.state_ !== ConnectionState.ESTABLISHED) {
532-
return Promise.reject('can only fetch banner in ESTABLISHED state');
530+
return Promise.reject(new Error('can only fetch banner in ESTABLISHED state'));
533531
}
534532
return new Promise<string>((F, R) => {
535533
this.client_.exec('cat /banner', (e: Error, stream: ssh2.Channel) => {

src/net/linefeeder.spec.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/// <reference path='../../../third_party/typings/jasmine/jasmine.d.ts' />
2+
3+
import arraybuffers = require('../arraybuffers/arraybuffers');
4+
import linefeeder = require('./linefeeder');
5+
import queue = require('../handler/queue');
6+
7+
describe('LineFeeder', function() {
8+
var bufferQueue: queue.Queue<ArrayBuffer, void>;
9+
var lines: linefeeder.LineFeeder;
10+
11+
beforeEach(() => {
12+
bufferQueue = new queue.Queue<ArrayBuffer, void>();
13+
lines = new linefeeder.LineFeeder(bufferQueue);
14+
});
15+
16+
it('one and done', (done) => {
17+
var s = 'hello world';
18+
bufferQueue.handle(arraybuffers.stringToArrayBuffer(s + '\n'));
19+
20+
lines.setSyncNextHandler((result: string) => {
21+
expect(result).toEqual(s);
22+
done();
23+
});
24+
});
25+
26+
it('dangling lines', (done) => {
27+
var s = 'hello world';
28+
bufferQueue.handle(arraybuffers.stringToArrayBuffer('hello '));
29+
bufferQueue.handle(arraybuffers.stringToArrayBuffer('world\n'));
30+
31+
lines.setSyncNextHandler((result: string) => {
32+
expect(result).toEqual(s);
33+
done();
34+
});
35+
});
36+
37+
it('multiple lines in one buffer', (done) => {
38+
bufferQueue.handle(arraybuffers.stringToArrayBuffer('a\nb\n'));
39+
40+
lines.setSyncNextHandler((result: string) => {
41+
expect(result).toEqual('a');
42+
lines.setSyncNextHandler((result: string) => {
43+
expect(result).toEqual('b');
44+
done();
45+
});
46+
});
47+
});
48+
});

src/net/linefeeder.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/// <reference path='../../../third_party/typings/es6-promise/es6-promise.d.ts' />
2+
3+
import arraybuffers = require('../arraybuffers/arraybuffers');
4+
import queue = require('../handler/queue');
5+
6+
// Transforms a raw network-like ArrayBuffer-based queue into
7+
// a telnet-style string-based queue.
8+
export class LineFeeder extends queue.Queue<string, void> {
9+
private static DELIMITER = arraybuffers.decodeByte(
10+
arraybuffers.stringToArrayBuffer('\n'));
11+
12+
constructor(source: queue.Queue<ArrayBuffer, void>) {
13+
super();
14+
let leftover = new ArrayBuffer(0);
15+
source.setSyncHandler((buffer: ArrayBuffer) => {
16+
leftover = arraybuffers.concat([leftover, buffer]);
17+
let i = arraybuffers.indexOf(leftover, LineFeeder.DELIMITER);
18+
while (i !== -1) {
19+
let parts = arraybuffers.split(leftover, i);
20+
let line = arraybuffers.arrayBufferToString(parts[0]);
21+
leftover = parts[1].slice(1);
22+
i = arraybuffers.indexOf(leftover, LineFeeder.DELIMITER);
23+
this.handle(line);
24+
}
25+
});
26+
}
27+
}

0 commit comments

Comments
 (0)