Skip to content

Commit e325865

Browse files
committed
feat: connections and streams now include a metadata getter for the connection info
* This is to match the stream interface for Polykey's agnostic RPC streams. * Also including a cancel method to match that interface * Fixes #16 [ci skip]
1 parent eddd860 commit e325865

File tree

8 files changed

+323
-54
lines changed

8 files changed

+323
-54
lines changed

src/QUICConnection.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type { QUICConfig } from './config';
66
import type { Host, Port, RemoteInfo, StreamId } from './types';
77
import type { Connection, ConnectionErrorCode, SendInfo } from './native/types';
88
import type { StreamCodeToReason, StreamReasonToCode } from './types';
9+
import type { ConnectionMetadata } from './types';
910
import {
1011
CreateDestroy,
1112
ready,
@@ -299,6 +300,21 @@ class QUICConnection extends EventTarget {
299300
return this.socket.port;
300301
}
301302

303+
public get remoteInfo(): ConnectionMetadata {
304+
const derCerts = this.conn.peerCertChain();
305+
const remoteCertificates =
306+
derCerts != null
307+
? derCerts.map((der) => utils.certificateDERToPEM(der))
308+
: null;
309+
return {
310+
remoteCertificates,
311+
localHost: this.localHost,
312+
localPort: this.localPort,
313+
remoteHost: this.remoteHost,
314+
remotePort: this.remotePort,
315+
};
316+
}
317+
302318
/**
303319
* This provides the ability to destroy with a specific error. This will wait for the connection to fully drain.
304320
*/

src/QUICSocket.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,7 @@ class QUICSocket extends EventTarget {
138138
// Each send/recv/timeout may result in a destruction
139139
if (!conn[destroyed]) {
140140
// Ignore any errors, concurrent with destruction
141-
await conn.send().catch((e) => {
142-
this.logger.error(`not destroyed send ${e.message}`);
143-
});
141+
await conn.send().catch(() => {});
144142
}
145143
};
146144

src/QUICStream.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type {
44
StreamId,
55
StreamReasonToCode,
66
StreamCodeToReason,
7+
ConnectionMetadata,
78
} from './types';
89
import type { Connection } from './native/types';
910
import { ReadableStream, WritableStream } from 'stream/web';
@@ -203,6 +204,21 @@ class QUICStream
203204
return this._recvPaused;
204205
}
205206

207+
/**
208+
* Connection information including hosts, ports and cert data.
209+
*/
210+
public get remoteInfo(): ConnectionMetadata {
211+
return this.connection.remoteInfo;
212+
}
213+
214+
/**
215+
* Duplicating `remoteInfo` functionality.
216+
* This strictly exists to work with agnostic RPC stream interface.
217+
*/
218+
public get meta(): ConnectionMetadata {
219+
return this.connection.remoteInfo;
220+
}
221+
206222
/**
207223
* This method can be arrived top-down or bottom-up:
208224
*
@@ -236,6 +252,21 @@ class QUICStream
236252
this.logger.info(`Destroyed ${this.constructor.name}`);
237253
}
238254

255+
/**
256+
* Used to cancel the streams. This function is synchronous but triggers some asynchronous events.
257+
*/
258+
public cancel(reason?: any): void {
259+
reason = reason ?? new errors.ErrorQUICStreamCancel();
260+
if (!this._recvClosed) {
261+
this.readableController.error(reason);
262+
void this.closeRecv(true, reason);
263+
}
264+
if (!this._sendClosed) {
265+
this.writableController.error(reason);
266+
void this.closeSend(true, reason);
267+
}
268+
}
269+
239270
/**
240271
* External push is converted to internal pull
241272
* Internal system decides when to unblock

src/errors.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ class ErrorQUICStreamClose<T> extends ErrorQUICStream<T> {
9191
static description = 'QUIC Stream force close';
9292
}
9393

94+
class ErrorQUICStreamCancel<T> extends ErrorQUICStream<T> {
95+
static description = 'QUIC Stream was cancelled without a provided reason';
96+
}
97+
9498
class ErrorQUICStreamUnexpectedClose<T> extends ErrorQUICStream<T> {
9599
static description = 'QUIC Stream closed early with no reason given';
96100
}
@@ -122,6 +126,7 @@ export {
122126
ErrorQUICStreamDestroyed,
123127
ErrorQUICStreamLocked,
124128
ErrorQUICStreamClose,
129+
ErrorQUICStreamCancel,
125130
ErrorQUICStreamUnexpectedClose,
126131
ErrorQUICUndefinedBehaviour,
127132
};

src/types.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,14 @@ type StreamCodeToReason = (
8888
code: number,
8989
) => any | PromiseLike<any>;
9090

91+
type ConnectionMetadata = {
92+
remoteCertificates: Array<string> | null;
93+
localHost: Host;
94+
localPort: Port;
95+
remoteHost: Host;
96+
remotePort: Port;
97+
};
98+
9199
export type {
92100
Opaque,
93101
Callback,
@@ -104,4 +112,5 @@ export type {
104112
RemoteInfo,
105113
StreamReasonToCode,
106114
StreamCodeToReason,
115+
ConnectionMetadata,
107116
};

src/utils.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,24 @@ function never(): never {
251251
throw new errors.ErrorQUICUndefinedBehaviour();
252252
}
253253

254+
function certificateDERToPEM(der: Uint8Array): string {
255+
const data = Buffer.from(der);
256+
const contents =
257+
data
258+
.toString('base64')
259+
.replace(/(.{64})/g, '$1\n')
260+
.trimEnd() + '\n';
261+
return `-----BEGIN CERTIFICATE-----\n${contents}-----END CERTIFICATE-----\n`;
262+
}
263+
264+
function certificatePEMsToCertChainPem(pems: Array<string>): string {
265+
let certChainPEM = '';
266+
for (const pem of pems) {
267+
certChainPEM += pem;
268+
}
269+
return certChainPEM;
270+
}
271+
254272
export {
255273
isIPv4,
256274
isIPv6,
@@ -268,4 +286,6 @@ export {
268286
encodeConnectionId,
269287
decodeConnectionId,
270288
never,
289+
certificateDERToPEM,
290+
certificatePEMsToCertChainPem,
271291
};

0 commit comments

Comments
 (0)