Skip to content

Commit d7c834e

Browse files
committed
feat: ErrorWebSocketConnectionTLS
1 parent 9a6bb58 commit d7c834e

File tree

5 files changed

+117
-31
lines changed

5 files changed

+117
-31
lines changed

src/WebSocketConnection.ts

Lines changed: 69 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -220,12 +220,17 @@ class WebSocketConnection {
220220
isBinary: boolean,
221221
) => {
222222
if (!isBinary || data instanceof Array) {
223+
const reason = "WebSocket received data received that wasn't binary";
223224
this.dispatchEvent(
224225
new events.EventWebSocketConnectionError({
225226
detail: new errors.ErrorWebSocketConnectionLocal(
226-
"data received isn't binary",
227+
reason,
227228
{
228229
cause: new errors.ErrorWebSocketUndefinedBehaviour(),
230+
data: {
231+
errorCode: utils.ConnectionErrorCode.InternalServerError,
232+
reason
233+
}
229234
},
230235
),
231236
}),
@@ -243,12 +248,17 @@ class WebSocketConnection {
243248
remainder = postStreamIdRemainder;
244249
} catch (e) {
245250
// TODO: domain specific error
251+
const reason = "Parsing streamId failed"
246252
this.dispatchEvent(
247-
new events.EventWebSocketConnectionError('parsing StreamId failed', {
253+
new events.EventWebSocketConnectionError({
248254
detail: new errors.ErrorWebSocketConnectionLocal(
249-
"data received isn't binary",
255+
reason,
250256
{
251257
cause: e,
258+
data: {
259+
errorCode: utils.ConnectionErrorCode.InternalServerError,
260+
reason
261+
}
252262
},
253263
),
254264
}),
@@ -331,14 +341,12 @@ class WebSocketConnection {
331341
const errorCode = utils.ConnectionErrorCode.InternalServerError;
332342
const reason = 'An error occurred on the underlying WebSocket instance';
333343
this.closeSocket(errorCode, reason);
334-
const e_ = new errors.ErrorWebSocketConnectionLocal(reason, {
344+
const e_ = new errors.ErrorWebSocketConnectionInternal(reason, {
345+
cause: err,
335346
data: {
336347
errorCode,
337348
reason,
338349
},
339-
cause: new errors.ErrorWebSocketConnectionInternal(reason, {
340-
cause: err,
341-
}),
342350
});
343351
this.dispatchEvent(
344352
new events.EventWebSocketConnectionError({
@@ -506,20 +514,57 @@ class WebSocketConnection {
506514
}
507515
// Handle connection failure - Dispatch ConnectionError -> ConnectionClose -> rejectSecureEstablishedP
508516
const openErrorHandler = (e) => {
509-
const errorCode = utils.ConnectionErrorCode.InternalServerError;
510-
const reason = 'WebSocket could not open due to internal error';
517+
let e_: errors.ErrorWebSocketConnection<any>;
518+
let reason: string;
519+
let errorCode: number;
520+
switch (e.code) {
521+
case "UNABLE_TO_VERIFY_LEAF_SIGNATURE":
522+
errorCode = utils.ConnectionErrorCode.TLSHandshake;
523+
reason = 'WebSocket could not open due to failure to verify a peer\'s TLS certificate';
524+
e_ = new errors.ErrorWebSocketConnectionLocalTLS(
525+
reason,
526+
{
527+
cause: e,
528+
data: {
529+
errorCode,
530+
reason,
531+
}
532+
}
533+
);
534+
break;
535+
case "ECONNRESET":
536+
reason = 'WebSocket could not open due to socket closure by peer';
537+
errorCode = utils.ConnectionErrorCode.AbnormalClosure,
538+
e_ = new errors.ErrorWebSocketConnectionPeer(
539+
reason,
540+
{
541+
cause: e,
542+
data: {
543+
errorCode,
544+
reason
545+
}
546+
}
547+
);
548+
break;
549+
default:
550+
reason = 'WebSocket could not open due to internal error';
551+
errorCode = utils.ConnectionErrorCode.InternalServerError,
552+
e_ = new errors.ErrorWebSocketConnectionLocal(
553+
reason,
554+
{
555+
cause: e,
556+
data: {
557+
errorCode,
558+
reason
559+
}
560+
}
561+
);
562+
break;
563+
}
511564
this.closeSocket(errorCode, reason);
512565
this.dispatchEvent(
513566
new events.EventWebSocketConnectionError({
514-
detail: new errors.ErrorWebSocketConnectionLocal(reason, {
515-
cause: new errors.ErrorWebSocketConnectionInternal(reason, {
516-
cause: e,
517-
}),
518-
data: {
519-
errorCode,
520-
reason,
521-
},
522-
}),
567+
detail: e_
523568
}),
524569
);
525570
};
@@ -555,8 +600,11 @@ class WebSocketConnection {
555600
const errorCode = utils.ConnectionErrorCode.TLSHandshake;
556601
const reason =
557602
'Failed connection due to custom verification callback';
558-
this.closeSocket(errorCode, reason);
559-
const e_ = new errors.ErrorWebSocketConnectionLocal(reason, {
603+
// request.destroy() will make the socket dispatch a 'close' event,
604+
// so I'm setting socketLocallyClosed to true, as that is what is happening.
605+
this.socketLocallyClosed = true;
606+
request.destroy(e);
607+
const e_ = new errors.ErrorWebSocketConnectionLocalTLS(reason, {
560608
cause: e,
561609
data: {
562610
errorCode,
@@ -578,6 +626,7 @@ class WebSocketConnection {
578626
try {
579627
await Promise.race([this.secureEstablishedP, abortP]);
580628
} catch (e) {
629+
// This happens if a timeout occurs.
581630
if (ctx.signal.aborted) {
582631
const errorCode = utils.ConnectionErrorCode.ProtocolError;
583632
const reason =

src/WebSocketServer.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,8 @@ class WebSocketServer {
355355
await this.config.verifyCallback(peerCertChain, ca);
356356
return done(true);
357357
} catch (e) {
358-
return done(false, 525, 'TLS Handshake Failed');
358+
info.req.destroy(e);
359+
return;
359360
}
360361
}
361362
done(true);

src/errors.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { AbstractError } from '@matrixai/errors';
1+
import { AbstractError, POJO } from '@matrixai/errors';
2+
import { ConnectionError } from './types';
23

34
class ErrorWebSocket<T> extends AbstractError<T> {
45
static description = 'WebSocket error';
@@ -70,12 +71,42 @@ class ErrorWebSocketConnectionInternal<T> extends ErrorWebSocketConnection<T> {
7071
static description = 'WebSocket Connection internal error';
7172
}
7273

74+
/**
75+
* Note that TlsFail error codes are documented here:
76+
* https://github.com/google/boringssl/blob/master/include/openssl/ssl.h
77+
*/
7378
class ErrorWebSocketConnectionLocal<T> extends ErrorWebSocketConnection<T> {
7479
static description = 'WebSocket Connection local error';
80+
declare data: POJO & ConnectionError;
81+
constructor(
82+
message: string = '',
83+
options: {
84+
timestamp?: Date;
85+
data: POJO & ConnectionError;
86+
cause?: T;
87+
}
88+
) {
89+
super(message, options);
90+
}
91+
}
92+
93+
class ErrorWebSocketConnectionLocalTLS<T> extends ErrorWebSocketConnectionLocal<T> {
94+
static description = 'WebSocket Connection local TLS error';
7595
}
7696

7797
class ErrorWebSocketConnectionPeer<T> extends ErrorWebSocketConnection<T> {
7898
static description = 'WebSocket Connection peer error';
99+
declare data: POJO & ConnectionError;
100+
constructor(
101+
message: string = '',
102+
options: {
103+
timestamp?: Date;
104+
data: POJO & ConnectionError;
105+
cause?: T;
106+
}
107+
) {
108+
super(message, options);
109+
}
79110
}
80111

81112
// Stream
@@ -145,6 +176,7 @@ export {
145176
ErrorWebSocketConnectionStartTimeOut,
146177
ErrorWebSocketConnectionKeepAliveTimeOut,
147178
ErrorWebSocketConnectionLocal,
179+
ErrorWebSocketConnectionLocalTLS,
148180
ErrorWebSocketConnectionPeer,
149181
ErrorWebSocketConnectionInternal,
150182
ErrorWebSocketStream,

src/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,11 @@ interface Parsed<T> {
162162
remainder: Uint8Array;
163163
}
164164

165+
type ConnectionError = {
166+
errorCode: number;
167+
reason: string;
168+
};
169+
165170
export type {
166171
Opaque,
167172
Callback,
@@ -180,4 +185,5 @@ export type {
180185
WebSocketClientConfigInput,
181186
WebSocketServerConfigInput,
182187
Parsed,
188+
ConnectionError
183189
};

tests/WebSocketClient.test.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,8 @@ describe(WebSocketClient.name, () => {
109109
},
110110
}),
111111
).rejects.toHaveProperty(
112-
['cause', 'name'],
113-
errors.ErrorWebSocketConnectionInternal.name,
112+
['name'],
113+
errors.ErrorWebSocketConnectionLocal.name,
114114
);
115115
});
116116
test('client times out with ctx timer while starting', async () => {
@@ -393,7 +393,7 @@ describe(WebSocketClient.name, () => {
393393
verifyPeer: true,
394394
},
395395
}),
396-
).toReject();
396+
).rejects.toHaveProperty('name', errors.ErrorWebSocketConnectionLocalTLS.name);
397397
await server.stop();
398398
});
399399
test('graceful failure verifying client', async () => {
@@ -422,7 +422,7 @@ describe(WebSocketClient.name, () => {
422422
verifyPeer: false,
423423
},
424424
}),
425-
).toReject();
425+
).rejects.toHaveProperty('name', errors.ErrorWebSocketConnectionPeer.name);
426426

427427
await server.stop();
428428
});
@@ -440,6 +440,7 @@ describe(WebSocketClient.name, () => {
440440
await server.start({
441441
host: localhost,
442442
});
443+
server.addEventListener(errors.ErrorWebSocketConnectionLocalTLS.name, (e) => console.log(e))
443444
// Connection should fail
444445
await expect(
445446
WebSocketClient.createWebSocketClient({
@@ -452,7 +453,7 @@ describe(WebSocketClient.name, () => {
452453
verifyPeer: true,
453454
},
454455
}),
455-
).toReject();
456+
).rejects.toHaveProperty('name', errors.ErrorWebSocketConnectionLocalTLS.name);
456457

457458
await server.stop();
458459
});
@@ -617,10 +618,7 @@ describe(WebSocketClient.name, () => {
617618
verifyPeer: false,
618619
},
619620
}),
620-
).rejects.toHaveProperty(
621-
['cause', 'name'],
622-
errors.ErrorWebSocketConnectionInternal.name,
623-
);
621+
).rejects.toHaveProperty('name', 'ErrorWebSocketConnectionPeer');
624622

625623
// // Server connection is never emitted
626624
await Promise.race([

0 commit comments

Comments
 (0)