quiche_apps/
args.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
// Copyright (C) 2020, Cloudflare, Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright notice,
//       this list of conditions and the following disclaimer.
//
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

use super::common::alpns;

pub trait Args {
    fn with_docopt(docopt: &docopt::Docopt) -> Self;
}

/// Contains commons arguments for creating a quiche QUIC connection.
pub struct CommonArgs {
    pub alpns: Vec<&'static [u8]>,
    pub max_data: u64,
    pub max_window: u64,
    pub max_stream_data: u64,
    pub max_stream_window: u64,
    pub max_streams_bidi: u64,
    pub max_streams_uni: u64,
    pub idle_timeout: u64,
    pub early_data: bool,
    pub dump_packet_path: Option<String>,
    pub no_grease: bool,
    pub cc_algorithm: String,
    pub disable_hystart: bool,
    pub dgrams_enabled: bool,
    pub dgram_count: u64,
    pub dgram_data: String,
    pub max_active_cids: u64,
    pub enable_active_migration: bool,
    pub max_field_section_size: Option<u64>,
    pub qpack_max_table_capacity: Option<u64>,
    pub qpack_blocked_streams: Option<u64>,
    pub initial_cwnd_packets: u64,
}

/// Creates a new `CommonArgs` structure using the provided [`Docopt`].
///
/// The `Docopt` usage String needs to include the following:
///
/// --http-version VERSION      HTTP version to use.
/// --max-data BYTES            Connection-wide flow control limit.
/// --max-window BYTES          Connection-wide max receiver window.
/// --max-stream-data BYTES     Per-stream flow control limit.
/// --max-stream-window BYTES   Per-stream max receiver window.
/// --max-streams-bidi STREAMS  Number of allowed concurrent streams.
/// --max-streams-uni STREAMS   Number of allowed concurrent streams.
/// --dump-packets PATH         Dump the incoming packets in PATH.
/// --no-grease                 Don't send GREASE.
/// --cc-algorithm NAME         Set a congestion control algorithm.
/// --disable-hystart           Disable HyStart++.
/// --dgram-proto PROTO         DATAGRAM application protocol.
/// --dgram-count COUNT         Number of DATAGRAMs to send.
/// --dgram-data DATA           DATAGRAM data to send.
/// --max-active-cids NUM       Maximum number of active Connection IDs.
/// --enable-active-migration   Enable active connection migration.
/// --max-field-section-size BYTES  Max size of uncompressed field section.
/// --qpack-max-table-capacity BYTES  Max capacity of dynamic QPACK decoding.
/// --qpack-blocked-streams STREAMS  Limit of blocked streams while decoding.
/// --initial-cwnd-packets      Size of initial congestion window, in packets.
///
/// [`Docopt`]: https://docs.rs/docopt/1.1.0/docopt/
impl Args for CommonArgs {
    fn with_docopt(docopt: &docopt::Docopt) -> Self {
        let args = docopt.parse().unwrap_or_else(|e| e.exit());

        let http_version = args.get_str("--http-version");
        let dgram_proto = args.get_str("--dgram-proto");
        let (alpns, dgrams_enabled) = match (http_version, dgram_proto) {
            ("HTTP/0.9", "none") => (alpns::HTTP_09.to_vec(), false),

            ("HTTP/0.9", _) =>
                panic!("Unsupported HTTP version and DATAGRAM protocol."),

            ("HTTP/3", "none") => (alpns::HTTP_3.to_vec(), false),

            ("HTTP/3", "oneway") => (alpns::HTTP_3.to_vec(), true),

            ("all", "none") => (
                [alpns::HTTP_3.as_slice(), &alpns::HTTP_09]
                    .concat()
                    .to_vec(),
                false,
            ),

            (..) => panic!("Unsupported HTTP version and DATAGRAM protocol."),
        };

        let dgram_count = args.get_str("--dgram-count");
        let dgram_count = dgram_count.parse::<u64>().unwrap();

        let dgram_data = args.get_str("--dgram-data").to_string();

        let max_data = args.get_str("--max-data");
        let max_data = max_data.parse::<u64>().unwrap();

        let max_window = args.get_str("--max-window");
        let max_window = max_window.parse::<u64>().unwrap();

        let max_stream_data = args.get_str("--max-stream-data");
        let max_stream_data = max_stream_data.parse::<u64>().unwrap();

        let max_stream_window = args.get_str("--max-stream-window");
        let max_stream_window = max_stream_window.parse::<u64>().unwrap();

        let max_streams_bidi = args.get_str("--max-streams-bidi");
        let max_streams_bidi = max_streams_bidi.parse::<u64>().unwrap();

        let max_streams_uni = args.get_str("--max-streams-uni");
        let max_streams_uni = max_streams_uni.parse::<u64>().unwrap();

        let idle_timeout = args.get_str("--idle-timeout");
        let idle_timeout = idle_timeout.parse::<u64>().unwrap();

        let early_data = args.get_bool("--early-data");

        let dump_packet_path = if args.get_str("--dump-packets") != "" {
            Some(args.get_str("--dump-packets").to_string())
        } else {
            None
        };

        let no_grease = args.get_bool("--no-grease");

        let cc_algorithm = args.get_str("--cc-algorithm");

        let disable_hystart = args.get_bool("--disable-hystart");

        let max_active_cids = args.get_str("--max-active-cids");
        let max_active_cids = max_active_cids.parse::<u64>().unwrap();

        let enable_active_migration = args.get_bool("--enable-active-migration");

        let max_field_section_size =
            if args.get_str("--max-field-section-size") != "" {
                Some(
                    args.get_str("--max-field-section-size")
                        .parse::<u64>()
                        .unwrap(),
                )
            } else {
                None
            };

        let qpack_max_table_capacity =
            if args.get_str("--qpack-max-table-capacity") != "" {
                Some(
                    args.get_str("--qpack-max-table-capacity")
                        .parse::<u64>()
                        .unwrap(),
                )
            } else {
                None
            };

        let qpack_blocked_streams =
            if args.get_str("--qpack-blocked-streams") != "" {
                Some(
                    args.get_str("--qpack-blocked-streams")
                        .parse::<u64>()
                        .unwrap(),
                )
            } else {
                None
            };

        let initial_cwnd_packets = args
            .get_str("--initial-cwnd-packets")
            .parse::<u64>()
            .unwrap();

        CommonArgs {
            alpns,
            max_data,
            max_window,
            max_stream_data,
            max_stream_window,
            max_streams_bidi,
            max_streams_uni,
            idle_timeout,
            early_data,
            dump_packet_path,
            no_grease,
            cc_algorithm: cc_algorithm.to_string(),
            disable_hystart,
            dgrams_enabled,
            dgram_count,
            dgram_data,
            max_active_cids,
            enable_active_migration,
            max_field_section_size,
            qpack_max_table_capacity,
            qpack_blocked_streams,
            initial_cwnd_packets,
        }
    }
}

impl Default for CommonArgs {
    fn default() -> Self {
        CommonArgs {
            alpns: alpns::HTTP_3.to_vec(),
            max_data: 10000000,
            max_window: 25165824,
            max_stream_data: 1000000,
            max_stream_window: 16777216,
            max_streams_bidi: 100,
            max_streams_uni: 100,
            idle_timeout: 30000,
            early_data: false,
            dump_packet_path: None,
            no_grease: false,
            cc_algorithm: "cubic".to_string(),
            disable_hystart: false,
            dgrams_enabled: false,
            dgram_count: 0,
            dgram_data: "quack".to_string(),
            max_active_cids: 2,
            enable_active_migration: false,
            max_field_section_size: None,
            qpack_max_table_capacity: None,
            qpack_blocked_streams: None,
            initial_cwnd_packets: 10,
        }
    }
}

pub const CLIENT_USAGE: &str = "Usage:
  quiche-client [options] URL...
  quiche-client -h | --help

Options:
  --method METHOD          Use the given HTTP request method [default: GET].
  --body FILE              Send the given file as request body.
  --max-data BYTES         Connection-wide flow control limit [default: 10000000].
  --max-window BYTES       Connection-wide max receiver window [default: 25165824].
  --max-stream-data BYTES  Per-stream flow control limit [default: 1000000].
  --max-stream-window BYTES   Per-stream max receiver window [default: 16777216].
  --max-streams-bidi STREAMS  Number of allowed concurrent streams [default: 100].
  --max-streams-uni STREAMS   Number of allowed concurrent streams [default: 100].
  --idle-timeout TIMEOUT   Idle timeout in milliseconds [default: 30000].
  --wire-version VERSION   The version number to send to the server [default: babababa].
  --http-version VERSION   HTTP version to use [default: all].
  --early-data             Enable sending early data.
  --dgram-proto PROTO      DATAGRAM application protocol to use [default: none].
  --dgram-count COUNT      Number of DATAGRAMs to send [default: 0].
  --dgram-data DATA        Data to send for certain types of DATAGRAM application protocol [default: quack].
  --dump-packets PATH      Dump the incoming packets as files in the given directory.
  --dump-responses PATH    Dump response payload as files in the given directory.
  --dump-json              Dump response headers and payload to stdout in JSON format.
  --max-json-payload BYTES  Per-response payload limit when dumping JSON [default: 10000].
  --connect-to ADDRESS     Override the server's address.
  --no-verify              Don't verify server's certificate.
  --trust-origin-ca-pem <file>  Path to the pem file of the origin's CA, if not publicly trusted.
  --no-grease              Don't send GREASE.
  --cc-algorithm NAME      Specify which congestion control algorithm to use [default: cubic].
  --disable-hystart        Disable HyStart++.
  --max-active-cids NUM    The maximum number of active Connection IDs we can support [default: 2].
  --enable-active-migration   Enable active connection migration.
  --perform-migration      Perform connection migration on another source port.
  -H --header HEADER ...   Add a request header.
  -n --requests REQUESTS   Send the given number of identical requests [default: 1].
  --send-priority-update   Send HTTP/3 priority updates if the query string params 'u' or 'i' are present in URLs
  --max-field-section-size BYTES    Max size of uncompressed field section. Default is unlimited.
  --qpack-max-table-capacity BYTES  Max capacity of dynamic QPACK decoding.. Any value other that 0 is currently unsupported.
  --qpack-blocked-streams STREAMS   Limit of blocked streams while decoding. Any value other that 0 is currently unsupported.
  --session-file PATH      File used to cache a TLS session for resumption.
  --source-port PORT       Source port to use when connecting to the server [default: 0].
  --initial-cwnd-packets PACKETS   The initial congestion window size in terms of packet count [default: 10].
  -h --help                Show this screen.
";

/// Application-specific arguments that compliment the `CommonArgs`.
pub struct ClientArgs {
    pub version: u32,
    pub dump_response_path: Option<String>,
    pub dump_json: Option<usize>,
    pub urls: Vec<url::Url>,
    pub reqs_cardinal: u64,
    pub req_headers: Vec<String>,
    pub no_verify: bool,
    pub trust_origin_ca_pem: Option<String>,
    pub body: Option<Vec<u8>>,
    pub method: String,
    pub connect_to: Option<String>,
    pub session_file: Option<String>,
    pub source_port: u16,
    pub perform_migration: bool,
    pub send_priority_update: bool,
}

impl Args for ClientArgs {
    fn with_docopt(docopt: &docopt::Docopt) -> Self {
        let args = docopt.parse().unwrap_or_else(|e| e.exit());

        let version = args.get_str("--wire-version");
        let version = u32::from_str_radix(version, 16).unwrap();

        let dump_response_path = if args.get_str("--dump-responses") != "" {
            Some(args.get_str("--dump-responses").to_string())
        } else {
            None
        };

        let dump_json = args.get_bool("--dump-json");
        let dump_json = if dump_json {
            let max_payload = args.get_str("--max-json-payload");
            let max_payload = max_payload.parse::<usize>().unwrap();
            Some(max_payload)
        } else {
            None
        };

        // URLs (can be multiple).
        let urls: Vec<url::Url> = args
            .get_vec("URL")
            .into_iter()
            .map(|x| url::Url::parse(x).unwrap())
            .collect();

        // Request headers (can be multiple).
        let req_headers = args
            .get_vec("--header")
            .into_iter()
            .map(|x| x.to_string())
            .collect();

        let reqs_cardinal = args.get_str("--requests");
        let reqs_cardinal = reqs_cardinal.parse::<u64>().unwrap();

        let no_verify = args.get_bool("--no-verify");

        let trust_origin_ca_pem = args.get_str("--trust-origin-ca-pem");
        let trust_origin_ca_pem = if !trust_origin_ca_pem.is_empty() {
            Some(trust_origin_ca_pem.to_string())
        } else {
            None
        };

        let body = if args.get_bool("--body") {
            std::fs::read(args.get_str("--body")).ok()
        } else {
            None
        };

        let method = args.get_str("--method").to_string();

        let connect_to = if args.get_bool("--connect-to") {
            Some(args.get_str("--connect-to").to_string())
        } else {
            None
        };

        let session_file = if args.get_bool("--session-file") {
            Some(args.get_str("--session-file").to_string())
        } else {
            None
        };

        let source_port = args.get_str("--source-port");
        let source_port = source_port.parse::<u16>().unwrap();

        let perform_migration = args.get_bool("--perform-migration");

        let send_priority_update = args.get_bool("--send-priority-update");

        ClientArgs {
            version,
            dump_response_path,
            dump_json,
            urls,
            reqs_cardinal,
            req_headers,
            no_verify,
            trust_origin_ca_pem,
            body,
            method,
            connect_to,
            session_file,
            source_port,
            perform_migration,
            send_priority_update,
        }
    }
}

impl Default for ClientArgs {
    fn default() -> Self {
        ClientArgs {
            version: 0xbabababa,
            dump_response_path: None,
            dump_json: None,
            urls: vec![],
            req_headers: vec![],
            reqs_cardinal: 1,
            no_verify: false,
            trust_origin_ca_pem: None,
            body: None,
            method: "GET".to_string(),
            connect_to: None,
            session_file: None,
            source_port: 0,
            perform_migration: false,
            send_priority_update: false,
        }
    }
}

pub const SERVER_USAGE: &str = "Usage:
  quiche-server [options]
  quiche-server -h | --help

Options:
  --listen <addr>             Listen on the given IP:port [default: 127.0.0.1:4433]
  --cert <file>               TLS certificate path [default: src/bin/cert.crt]
  --key <file>                TLS certificate key path [default: src/bin/cert.key]
  --root <dir>                Root directory [default: src/bin/root/]
  --index <name>              The file that will be used as index [default: index.html].
  --name <str>                Name of the server [default: quic.tech]
  --max-data BYTES            Connection-wide flow control limit [default: 10000000].
  --max-window BYTES          Connection-wide max receiver window [default: 25165824].
  --max-stream-data BYTES     Per-stream flow control limit [default: 1000000].
  --max-stream-window BYTES   Per-stream max receiver window [default: 16777216].
  --max-streams-bidi STREAMS  Number of allowed concurrent streams [default: 100].
  --max-streams-uni STREAMS   Number of allowed concurrent streams [default: 100].
  --idle-timeout TIMEOUT      Idle timeout in milliseconds [default: 30000].
  --dump-packets PATH         Dump the incoming packets as files in the given directory.
  --early-data                Enable receiving early data.
  --no-retry                  Disable stateless retry.
  --no-grease                 Don't send GREASE.
  --http-version VERSION      HTTP version to use [default: all].
  --dgram-proto PROTO         DATAGRAM application protocol to use [default: none].
  --dgram-count COUNT         Number of DATAGRAMs to send [default: 0].
  --dgram-data DATA           Data to send for certain types of DATAGRAM application protocol [default: brrr].
  --cc-algorithm NAME         Specify which congestion control algorithm to use [default: cubic].
  --disable-hystart           Disable HyStart++.
  --max-active-cids NUM       The maximum number of active Connection IDs we can support [default: 2].
  --enable-active-migration   Enable active connection migration.
  --max-field-section-size BYTES    Max size of uncompressed HTTP/3 field section. Default is unlimited.
  --qpack-max-table-capacity BYTES  Max capacity of QPACK dynamic table decoding. Any value other that 0 is currently unsupported.
  --qpack-blocked-streams STREAMS   Limit of streams that can be blocked while decoding. Any value other that 0 is currently unsupported.
  --disable-gso               Disable GSO (linux only).
  --disable-pacing            Disable pacing (linux only).
  --initial-cwnd-packets PACKETS      The initial congestion window size in terms of packet count [default: 10].
  -h --help                   Show this screen.
";

// Application-specific arguments that compliment the `CommonArgs`.
pub struct ServerArgs {
    pub listen: String,
    pub no_retry: bool,
    pub root: String,
    pub index: String,
    pub cert: String,
    pub key: String,
    pub disable_gso: bool,
    pub disable_pacing: bool,
    pub enable_pmtud: bool,
}

impl Args for ServerArgs {
    fn with_docopt(docopt: &docopt::Docopt) -> Self {
        let args = docopt.parse().unwrap_or_else(|e| e.exit());

        let listen = args.get_str("--listen").to_string();
        let no_retry = args.get_bool("--no-retry");
        let root = args.get_str("--root").to_string();
        let index = args.get_str("--index").to_string();
        let cert = args.get_str("--cert").to_string();
        let key = args.get_str("--key").to_string();
        let disable_gso = args.get_bool("--disable-gso");
        let disable_pacing = args.get_bool("--disable-pacing");
        let enable_pmtud = args.get_bool("--enable-pmtud");

        ServerArgs {
            listen,
            no_retry,
            root,
            index,
            cert,
            key,
            disable_gso,
            disable_pacing,
            enable_pmtud,
        }
    }
}