Skip to main content

polyoxide_venue/
socket.rs

1//! The socket half of the class table: close codes and handshake statuses.
2//!
3//! This crate depends on no socket library, so the table for a transport
4//! error's own kinds is a macro, [`impl_ws_classification!`](crate::impl_ws_classification),
5//! which expands it against `polyoxide_ws::WsError` in the venue crate: an
6//! I/O error (a TLS EOF included), a protocol error and a closed connection
7//! are [`Network`](Class::Network); a bad URL, a malformed handshake request,
8//! a TLS configuration error, an attack-attempt detection, a capacity limit,
9//! a use after close and an operation the transport cannot perform are
10//! [`InvalidRequest`](Class::InvalidRequest); a
11//! server close goes through [`class_for_close_code`]; a handshake refused
12//! with an HTTP status goes through [`class_for_handshake_status`]; anything
13//! else is [`Network`](Class::Network).
14
15use std::sync::Arc;
16
17use crate::{class_for_status, Class};
18
19/// The class of a socket the server closed, from the close frame's code.
20///
21/// | Close code | Class |
22/// | --- | --- |
23/// | 1002, 1003, 1007, 1008, 1009, 1010, 4000–4999 | [`VenueRefusal`](Class::VenueRefusal), with the code in decimal |
24/// | everything else, and no code at all | [`Network`](Class::Network) |
25///
26/// The `VenueRefusal` codes say the server objected to what it was sent: a
27/// protocol violation, an unacceptable or oversized message, a policy breach,
28/// or an application's own reason. Every other code, 1000, 1001, 1006 and
29/// 1011–1013 among them, says the connection ended without the request being
30/// at fault.
31pub fn class_for_close_code(code: Option<u16>) -> Class {
32    match code {
33        Some(code @ (1002 | 1003 | 1007..=1010 | 4000..=4999)) => Class::VenueRefusal {
34            code: Some(Arc::from(code.to_string())),
35        },
36        _ => Class::Network,
37    }
38}
39
40/// The class of a WebSocket upgrade the server refused with an HTTP status.
41///
42/// The status rule ([`class_for_status`]), with any status outside 400–599 a
43/// [`Decode`](Class::Decode): the server answered, but not with an upgrade.
44pub fn class_for_handshake_status(status: u16) -> Class {
45    class_for_status(status).unwrap_or(Class::Decode)
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51
52    fn refusal(code: &str) -> Class {
53        Class::VenueRefusal {
54            code: Some(Arc::from(code)),
55        }
56    }
57
58    #[test]
59    fn every_close_code_row() {
60        let rows = [
61            // The codes the table names.
62            (Some(1000), Class::Network),
63            (Some(1001), Class::Network),
64            (Some(1006), Class::Network),
65            (Some(1011), Class::Network),
66            (Some(1012), Class::Network),
67            (Some(1013), Class::Network),
68            (Some(1002), refusal("1002")),
69            (Some(1003), refusal("1003")),
70            (Some(1007), refusal("1007")),
71            (Some(1008), refusal("1008")),
72            (Some(1009), refusal("1009")),
73            (Some(1010), refusal("1010")),
74            (Some(4000), refusal("4000")),
75            (Some(4404), refusal("4404")),
76            (Some(4999), refusal("4999")),
77            // The ones it does not.
78            (None, Class::Network),
79            (Some(1004), Class::Network),
80            (Some(1005), Class::Network),
81            (Some(1014), Class::Network),
82            (Some(1015), Class::Network),
83            (Some(2999), Class::Network),
84            (Some(3000), Class::Network),
85            (Some(3999), Class::Network),
86            (Some(5000), Class::Network),
87        ];
88        for (code, class) in rows {
89            assert_eq!(class_for_close_code(code), class, "close code {code:?}");
90        }
91    }
92
93    #[test]
94    fn a_handshake_status_follows_the_status_rule() {
95        let rows = [
96            (101, Class::Decode),
97            (200, Class::Decode),
98            (301, Class::Decode),
99            (400, Class::VenueRefusal { code: None }),
100            (401, Class::Unauthorized),
101            (403, Class::Unauthorized),
102            (404, Class::VenueRefusal { code: None }),
103            (418, Class::Restricted),
104            (429, Class::RateLimited { retry_after: None }),
105            (451, Class::Restricted),
106            (502, Class::Unavailable { code: None }),
107            (503, Class::Unavailable { code: None }),
108        ];
109        for (status, class) in rows {
110            assert_eq!(class_for_handshake_status(status), class, "status {status}");
111        }
112    }
113}