summaryrefslogtreecommitdiffstats
path: root/third_party/rust/neqo-transport/src/connection/tests/close.rs
blob: 5351dd0d5c8cec785a087bde4de38773370af84e (plain)
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
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use std::time::Duration;

use test_fixture::{datagram, now};

use super::{
    super::{Connection, Output, State},
    connect, connect_force_idle, default_client, default_server, send_something,
};
use crate::{
    tparams::{self, TransportParameter},
    AppError, ConnectionError, Error, ERROR_APPLICATION_CLOSE,
};

fn assert_draining(c: &Connection, expected: &Error) {
    assert!(c.state().closed());
    if let State::Draining {
        error: ConnectionError::Transport(error),
        ..
    } = c.state()
    {
        assert_eq!(error, expected);
    } else {
        panic!();
    }
}

#[test]
fn connection_close() {
    let mut client = default_client();
    let mut server = default_server();
    connect(&mut client, &mut server);

    let now = now();

    client.close(now, 42, "");

    let out = client.process(None, now);

    server.process_input(&out.dgram().unwrap(), now);
    assert_draining(&server, &Error::PeerApplicationError(42));
}

#[test]
fn connection_close_with_long_reason_string() {
    let mut client = default_client();
    let mut server = default_server();
    connect(&mut client, &mut server);

    let now = now();
    // Create a long string and use it as the close reason.
    let long_reason = String::from_utf8([0x61; 2048].to_vec()).unwrap();
    client.close(now, 42, long_reason);

    let out = client.process(None, now);

    server.process_input(&out.dgram().unwrap(), now);
    assert_draining(&server, &Error::PeerApplicationError(42));
}

// During the handshake, an application close should be sanitized.
#[test]
fn early_application_close() {
    let mut client = default_client();
    let mut server = default_server();

    // One flight each.
    let dgram = client.process(None, now()).dgram();
    assert!(dgram.is_some());
    let dgram = server.process(dgram.as_ref(), now()).dgram();
    assert!(dgram.is_some());

    server.close(now(), 77, String::new());
    assert!(server.state().closed());
    let dgram = server.process(None, now()).dgram();
    assert!(dgram.is_some());

    client.process_input(&dgram.unwrap(), now());
    assert_draining(&client, &Error::PeerError(ERROR_APPLICATION_CLOSE));
}

#[test]
fn bad_tls_version() {
    let mut client = default_client();
    // Do a bad, bad thing.
    client
        .crypto
        .tls
        .set_option(neqo_crypto::Opt::Tls13CompatMode, true)
        .unwrap();
    let mut server = default_server();

    let dgram = client.process(None, now()).dgram();
    assert!(dgram.is_some());
    let dgram = server.process(dgram.as_ref(), now()).dgram();
    assert_eq!(
        *server.state(),
        State::Closed(ConnectionError::Transport(Error::ProtocolViolation))
    );
    assert!(dgram.is_some());
    client.process_input(&dgram.unwrap(), now());
    assert_draining(&client, &Error::PeerError(Error::ProtocolViolation.code()));
}

/// Test the interaction between the loss recovery timer
/// and the closing timer.
#[test]
fn closing_timers_interation() {
    let mut client = default_client();
    let mut server = default_server();
    connect(&mut client, &mut server);

    let mut now = now();

    // We're going to induce time-based loss recovery so that timer is set.
    let _p1 = send_something(&mut client, now);
    let p2 = send_something(&mut client, now);
    let ack = server.process(Some(&p2), now).dgram();
    assert!(ack.is_some()); // This is an ACK.

    // After processing the ACK, we should be on the loss recovery timer.
    let cb = client.process(ack.as_ref(), now).callback();
    assert_ne!(cb, Duration::from_secs(0));
    now += cb;

    // Rather than let the timer pop, close the connection.
    client.close(now, 0, "");
    let client_close = client.process(None, now).dgram();
    assert!(client_close.is_some());
    // This should now report the end of the closing period, not a
    // zero-duration wait driven by the (now defunct) loss recovery timer.
    let client_close_timer = client.process(None, now).callback();
    assert_ne!(client_close_timer, Duration::from_secs(0));
}

#[test]
fn closing_and_draining() {
    const APP_ERROR: AppError = 7;
    let mut client = default_client();
    let mut server = default_server();
    connect(&mut client, &mut server);

    // Save a packet from the client for later.
    let p1 = send_something(&mut client, now());

    // Close the connection.
    client.close(now(), APP_ERROR, "");
    let client_close = client.process(None, now()).dgram();
    assert!(client_close.is_some());
    let client_close_timer = client.process(None, now()).callback();
    assert_ne!(client_close_timer, Duration::from_secs(0));

    // The client will spit out the same packet in response to anything it receives.
    let p3 = send_something(&mut server, now());
    let client_close2 = client.process(Some(&p3), now()).dgram();
    assert_eq!(
        client_close.as_ref().unwrap().len(),
        client_close2.as_ref().unwrap().len()
    );

    // After this time, the client should transition to closed.
    let end = client.process(None, now() + client_close_timer);
    assert_eq!(end, Output::None);
    assert_eq!(
        *client.state(),
        State::Closed(ConnectionError::Application(APP_ERROR))
    );

    // When the server receives the close, it too should generate CONNECTION_CLOSE.
    let server_close = server.process(client_close.as_ref(), now()).dgram();
    assert!(server.state().closed());
    assert!(server_close.is_some());
    // .. but it ignores any further close packets.
    let server_close_timer = server.process(client_close2.as_ref(), now()).callback();
    assert_ne!(server_close_timer, Duration::from_secs(0));
    // Even a legitimate packet without a close in it.
    let server_close_timer2 = server.process(Some(&p1), now()).callback();
    assert_eq!(server_close_timer, server_close_timer2);

    let end = server.process(None, now() + server_close_timer);
    assert_eq!(end, Output::None);
    assert_eq!(
        *server.state(),
        State::Closed(ConnectionError::Transport(Error::PeerApplicationError(
            APP_ERROR
        )))
    );
}

/// Test that a client can handle a stateless reset correctly.
#[test]
fn stateless_reset_client() {
    let mut client = default_client();
    let mut server = default_server();
    server
        .set_local_tparam(
            tparams::STATELESS_RESET_TOKEN,
            TransportParameter::Bytes(vec![77; 16]),
        )
        .unwrap();
    connect_force_idle(&mut client, &mut server);

    client.process_input(&datagram(vec![77; 21]), now());
    assert_draining(&client, &Error::StatelessReset);
}