Skip to content

Commit 9f2722a

Browse files
committed
wip: working ts example for client
* Related #20 [ci skip]
1 parent 5211515 commit 9f2722a

File tree

1 file changed

+288
-0
lines changed

1 file changed

+288
-0
lines changed

clientTest.ts

Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
import dgram from 'dgram';
2+
import * as utils from './src/utils';
3+
import { buildQuicheConfig } from './src/config';
4+
import { quiche } from './src/native';
5+
import * as testsUtils from './tests/utils';
6+
import { promise } from './src/utils';
7+
import { SendInfo } from './src/native/types';
8+
import { clearTimeout } from 'timers';
9+
10+
11+
async function main () {
12+
const MAX_DATAGRAM_SIZE = 1350;
13+
const buf = Buffer.alloc(65535, 0);
14+
const out = Buffer.alloc(quiche.MAX_DATAGRAM_SIZE, 0);
15+
const message = Buffer.from('Hello!');
16+
const emptyBuffer = Buffer.alloc(0, 0);
17+
18+
const socket = dgram.createSocket({
19+
type: 'udp4',
20+
reuseAddr: false,
21+
})
22+
23+
const host = "127.0.0.1";
24+
const port = 4433;
25+
const localPort = 55555;
26+
const STREAMS = 1000;
27+
const MESSAGES = 200;
28+
29+
type StreamData = {
30+
messagesLeft: number;
31+
}
32+
const streamMap: Map<number, StreamData> = new Map();
33+
34+
const socketBind = utils.promisify(socket.bind).bind(socket);
35+
const socketClose = utils.promisify(socket.close).bind(socket);
36+
const socketSend = utils.promisify(socket.send).bind(socket);
37+
38+
const { p: errorP, rejectP: rejectErrorP } = utils.promise();
39+
socket.once('error', rejectErrorP);
40+
await Promise.race([
41+
errorP,
42+
socketBind(localPort)
43+
])
44+
console.log('bound');
45+
46+
const config = buildQuicheConfig({
47+
verifyPeer: false,
48+
applicationProtos: [
49+
"hq-interop",
50+
"hq-29",
51+
"hq-28",
52+
"hq-27",
53+
"http/0.9",
54+
],
55+
maxIdleTimeout: 5000,
56+
maxRecvUdpPayloadSize: MAX_DATAGRAM_SIZE,
57+
maxSendUdpPayloadSize: MAX_DATAGRAM_SIZE,
58+
initialMaxData: 10_000_000,
59+
initialMaxStreamDataBidiLocal: 1_000_000_000,
60+
initialMaxStreamDataBidiRemote: 1_000_000_000,
61+
initialMaxStreamsBidi: 100000,
62+
initialMaxStreamsUni: 100000,
63+
disableActiveMigration: true,
64+
65+
enableEarlyData: false,
66+
grease: false,
67+
logKeys: "tmp/key.log",
68+
supportedPrivateKeyAlgos: undefined,
69+
tlsConfig: undefined,
70+
verifyFromPemFile: undefined,
71+
verifyPem: undefined
72+
})
73+
74+
const crypto = {
75+
key: await testsUtils.generateKey(),
76+
ops: {
77+
sign: testsUtils.sign,
78+
verify: testsUtils.verify,
79+
randomBytes: testsUtils.randomBytes,
80+
},
81+
};
82+
83+
const scidBuffer = new ArrayBuffer(quiche.MAX_CONN_ID_LEN);
84+
await crypto.ops.randomBytes(scidBuffer);
85+
86+
const conn = quiche.Connection.connect(
87+
null,
88+
Buffer.from(scidBuffer),
89+
{
90+
host,
91+
port: localPort,
92+
},
93+
{
94+
host,
95+
port,
96+
},
97+
config,
98+
);
99+
100+
let timeout: NodeJS.Timeout | null = null;
101+
let deadline: number = Infinity;
102+
let req_sent = false;
103+
let receivedEvent = promise();
104+
let timeoutEvent = promise();
105+
let writableEvent = promise();
106+
107+
108+
109+
const clearTimer = () => {
110+
if (timeout != null){
111+
// console.log('cleared timeout!');
112+
clearTimeout(timeout);
113+
timeout = null;
114+
deadline = Infinity;
115+
}
116+
}
117+
118+
const checkTimeout = () => {
119+
const time = conn.timeout();
120+
// console.log('time: ', time)
121+
if (time == null ) {
122+
// console.log('timer cleared');
123+
//clear timeout
124+
clearTimer();
125+
} else if(time == 0) {
126+
// instant timeout
127+
// console.log('instant timeout');
128+
clearTimer();
129+
setImmediate(handleTimeout);
130+
} else {
131+
// Update the timer
132+
const newDeadline = Date.now() + time;
133+
if (newDeadline < deadline) {
134+
// console.log('updating timer', time);
135+
clearTimer();
136+
deadline = newDeadline;
137+
timeout = setTimeout(handleTimeout, time);
138+
}
139+
}
140+
}
141+
const handleTimeout = () => {
142+
// console.log('timed out!');
143+
conn.onTimeout();
144+
deadline = Infinity;
145+
checkTimeout();
146+
timeoutEvent.resolveP();
147+
timeoutEvent = promise();
148+
}
149+
150+
const handleRead = (
151+
data: Buffer,
152+
remoteInfo: dgram.RemoteInfo,
153+
) => {
154+
// console.log('received data!', data.byteLength);
155+
const recvInfo = {
156+
to: {
157+
host,
158+
port: localPort,
159+
},
160+
from: {
161+
host,
162+
port: remoteInfo.port,
163+
},
164+
};
165+
conn.recv(data, recvInfo);
166+
receivedEvent.resolveP();
167+
receivedEvent = promise();
168+
checkTimeout();
169+
}
170+
socket.on('message', handleRead);
171+
172+
const [write, sendInfo] = conn.send(out);
173+
174+
await socketSend(out, 0, write, sendInfo.to.port, sendInfo.to.host).catch((e) => {
175+
console.error(e);
176+
throw e;
177+
});
178+
179+
checkTimeout();
180+
181+
writableEvent.resolveP();
182+
while(true) {
183+
// Waiting for events to happen.
184+
// Writable events will be happening most of the time.
185+
await Promise.race([
186+
receivedEvent.p,
187+
timeoutEvent.p,
188+
writableEvent.p,
189+
]);
190+
if (conn.isClosed()) {
191+
console.log('Connection closed', conn.stats());
192+
break;
193+
}
194+
// initialize streams
195+
if (conn.isEstablished() && !req_sent) {
196+
for (let i = 0; i < STREAMS; i++) {
197+
const streamId = i * 4;
198+
199+
// console.log('creating stream!');
200+
conn.streamSend(streamId, emptyBuffer, false);
201+
streamMap.set(streamId, {
202+
messagesLeft: MESSAGES,
203+
});
204+
}
205+
console.log('done creating streams');
206+
req_sent = true;
207+
}
208+
209+
// process readable data
210+
for (const streamId of conn.readable()) {
211+
// consume messages, no processing
212+
while(true) {
213+
try {
214+
const [_read, fin] = conn.streamRecv(streamId, buf);
215+
if (fin) console.log('stream finished: ', streamId);
216+
} catch (e) {
217+
if (e.message == 'Done') break;
218+
throw e;
219+
}
220+
}
221+
}
222+
223+
// process writable
224+
let writables = false;
225+
for (const streamId of conn.writable()) {
226+
writables = true;
227+
const streamData = streamMap.get(streamId);
228+
if (streamData == null) throw Error('Missing stream data');
229+
if (streamData.messagesLeft > 0) streamData.messagesLeft--;
230+
const fin = streamData.messagesLeft <= 0;
231+
232+
// send messages
233+
if (fin) {
234+
try {
235+
conn.streamSend(streamId, emptyBuffer, true);
236+
streamMap.delete(streamId);
237+
if (streamMap.size === 0) console.log('finished all streams')
238+
} catch (e) {
239+
if(e.message == 'Done') {
240+
// console.log('sending returned done');
241+
continue
242+
}
243+
throw e;
244+
}
245+
} else {
246+
try {
247+
conn.streamSend(streamId, message, false);
248+
// console.log('wrote message', streamId)
249+
} catch (e) {
250+
if(e.message == 'Done') {
251+
// console.log('sending returned done');
252+
continue;
253+
}
254+
throw e;
255+
}
256+
}
257+
}
258+
if (writables) writableEvent.resolveP();
259+
else writableEvent = promise();
260+
261+
// Processing outgoing packets
262+
while(true) {
263+
let write: number;
264+
let sendInfo: SendInfo;
265+
try {
266+
[write, sendInfo] = conn.send(out);
267+
} catch (e) {
268+
if (e.message == 'Done') break;
269+
throw e;
270+
}
271+
await socketSend(out, 0, write, sendInfo.to.port, sendInfo.to.host);
272+
// console.log('packet sent');
273+
checkTimeout();
274+
}
275+
276+
if (conn.isClosed()) {
277+
console.log('Connection closed', conn.stats());
278+
break;
279+
}
280+
}
281+
console.log('ended');
282+
if(conn.isTimedOut()) console.log('connection timed out');
283+
console.log('errors? ', conn.peerError(), conn.localError());
284+
285+
await socketClose();
286+
}
287+
288+
main().then(() => {});

0 commit comments

Comments
 (0)