sqlx_core_oldapi/mysql/protocol/connect/
handshake.rs

1use bytes::buf::Chain;
2use bytes::{Buf, Bytes};
3
4use crate::error::Error;
5use crate::io::{BufExt, Decode};
6use crate::mysql::protocol::auth::AuthPlugin;
7use crate::mysql::protocol::response::Status;
8use crate::mysql::protocol::Capabilities;
9
10// https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::Handshake
11// https://mariadb.com/kb/en/connection/#initial-handshake-packet
12
13#[derive(Debug)]
14pub(crate) struct Handshake {
15    #[allow(unused)]
16    pub(crate) protocol_version: u8,
17    pub(crate) server_version: String,
18    #[allow(unused)]
19    pub(crate) connection_id: u32,
20    pub(crate) server_capabilities: Capabilities,
21    #[allow(unused)]
22    pub(crate) server_default_collation: u8,
23    #[allow(unused)]
24    pub(crate) status: Status,
25    pub(crate) auth_plugin: Option<AuthPlugin>,
26    pub(crate) auth_plugin_data: Chain<Bytes, Bytes>,
27}
28
29impl Decode<'_> for Handshake {
30    fn decode_with(mut buf: Bytes, _: ()) -> Result<Self, Error> {
31        let protocol_version = buf.get_u8(); // int<1>
32        let server_version = buf.get_str_nul()?; // string<NUL>
33        let connection_id = buf.get_u32_le(); // int<4>
34        let auth_plugin_data_1 = buf.get_bytes(8); // string<8>
35
36        buf.advance(1); // reserved: string<1>
37
38        let capabilities_1 = buf.get_u16_le(); // int<2>
39        let mut capabilities = Capabilities::from_bits_truncate(capabilities_1.into());
40
41        let collation = buf.get_u8(); // int<1>
42        let status = Status::from_bits_truncate(buf.get_u16_le());
43
44        let capabilities_2 = buf.get_u16_le(); // int<2>
45        capabilities |= Capabilities::from_bits_truncate(((capabilities_2 as u32) << 16).into());
46
47        let auth_plugin_data_len = if capabilities.contains(Capabilities::PLUGIN_AUTH) {
48            buf.get_u8()
49        } else {
50            buf.advance(1); // int<1>
51            0
52        };
53
54        buf.advance(6); // reserved: string<6>
55
56        if capabilities.contains(Capabilities::MYSQL) {
57            buf.advance(4); // reserved: string<4>
58        } else {
59            let capabilities_3 = buf.get_u32_le(); // int<4>
60            capabilities |= Capabilities::from_bits_truncate((capabilities_3 as u64) << 32);
61        }
62
63        let auth_plugin_data_2 = if capabilities.contains(Capabilities::SECURE_CONNECTION) {
64            let len = ((auth_plugin_data_len as isize) - 9).max(12) as usize;
65            let v = buf.get_bytes(len);
66            buf.advance(1); // NUL-terminator
67
68            v
69        } else {
70            Bytes::new()
71        };
72
73        let auth_plugin = if capabilities.contains(Capabilities::PLUGIN_AUTH) {
74            Some(buf.get_str_nul()?.parse()?)
75        } else {
76            None
77        };
78
79        Ok(Self {
80            protocol_version,
81            server_version,
82            connection_id,
83            server_default_collation: collation,
84            status,
85            server_capabilities: capabilities,
86            auth_plugin,
87            auth_plugin_data: auth_plugin_data_1.chain(auth_plugin_data_2),
88        })
89    }
90}
91
92#[test]
93fn test_decode_handshake_mysql_8_0_18() {
94    const HANDSHAKE_MYSQL_8_0_18: &[u8] = b"\n8.0.18\x00\x19\x00\x00\x00\x114aB0c\x06g\x00\xff\xff\xff\x02\x00\xff\xc7\x15\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00tL\x03s\x0f[4\rl4. \x00caching_sha2_password\x00";
95
96    let mut p = Handshake::decode(HANDSHAKE_MYSQL_8_0_18.into()).unwrap();
97
98    assert_eq!(p.protocol_version, 10);
99
100    p.server_capabilities.toggle(
101        Capabilities::MYSQL
102            | Capabilities::FOUND_ROWS
103            | Capabilities::LONG_FLAG
104            | Capabilities::CONNECT_WITH_DB
105            | Capabilities::NO_SCHEMA
106            | Capabilities::COMPRESS
107            | Capabilities::ODBC
108            | Capabilities::LOCAL_FILES
109            | Capabilities::IGNORE_SPACE
110            | Capabilities::PROTOCOL_41
111            | Capabilities::INTERACTIVE
112            | Capabilities::SSL
113            | Capabilities::TRANSACTIONS
114            | Capabilities::SECURE_CONNECTION
115            | Capabilities::MULTI_STATEMENTS
116            | Capabilities::MULTI_RESULTS
117            | Capabilities::PS_MULTI_RESULTS
118            | Capabilities::PLUGIN_AUTH
119            | Capabilities::CONNECT_ATTRS
120            | Capabilities::PLUGIN_AUTH_LENENC_DATA
121            | Capabilities::CAN_HANDLE_EXPIRED_PASSWORDS
122            | Capabilities::SESSION_TRACK
123            | Capabilities::DEPRECATE_EOF
124            | Capabilities::ZSTD_COMPRESSION_ALGORITHM
125            | Capabilities::SSL_VERIFY_SERVER_CERT
126            | Capabilities::OPTIONAL_RESULTSET_METADATA
127            | Capabilities::REMEMBER_OPTIONS,
128    );
129
130    assert!(p.server_capabilities.is_empty());
131
132    assert_eq!(p.server_default_collation, 255);
133    assert!(p.status.contains(Status::SERVER_STATUS_AUTOCOMMIT));
134
135    assert!(matches!(
136        p.auth_plugin,
137        Some(AuthPlugin::CachingSha2Password)
138    ));
139
140    assert_eq!(
141        &*p.auth_plugin_data.into_iter().collect::<Vec<_>>(),
142        &[17, 52, 97, 66, 48, 99, 6, 103, 116, 76, 3, 115, 15, 91, 52, 13, 108, 52, 46, 32,]
143    );
144}
145
146#[test]
147fn test_decode_handshake_mariadb_10_4_7() {
148    const HANDSHAKE_MARIA_DB_10_4_7: &[u8] = b"\n5.5.5-10.4.7-MariaDB-1:10.4.7+maria~bionic\x00\x0b\x00\x00\x00t6L\\j\"dS\x00\xfe\xf7\x08\x02\x00\xff\x81\x15\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00U14Oph9\"<H5n\x00mysql_native_password\x00";
149
150    let mut p = Handshake::decode(HANDSHAKE_MARIA_DB_10_4_7.into()).unwrap();
151
152    assert_eq!(p.protocol_version, 10);
153
154    assert_eq!(
155        &*p.server_version,
156        "5.5.5-10.4.7-MariaDB-1:10.4.7+maria~bionic"
157    );
158
159    p.server_capabilities.toggle(
160        Capabilities::FOUND_ROWS
161            | Capabilities::LONG_FLAG
162            | Capabilities::CONNECT_WITH_DB
163            | Capabilities::NO_SCHEMA
164            | Capabilities::COMPRESS
165            | Capabilities::ODBC
166            | Capabilities::LOCAL_FILES
167            | Capabilities::IGNORE_SPACE
168            | Capabilities::PROTOCOL_41
169            | Capabilities::INTERACTIVE
170            | Capabilities::TRANSACTIONS
171            | Capabilities::SECURE_CONNECTION
172            | Capabilities::MULTI_STATEMENTS
173            | Capabilities::MULTI_RESULTS
174            | Capabilities::PS_MULTI_RESULTS
175            | Capabilities::PLUGIN_AUTH
176            | Capabilities::CONNECT_ATTRS
177            | Capabilities::PLUGIN_AUTH_LENENC_DATA
178            | Capabilities::CAN_HANDLE_EXPIRED_PASSWORDS
179            | Capabilities::SESSION_TRACK
180            | Capabilities::DEPRECATE_EOF
181            | Capabilities::REMEMBER_OPTIONS,
182    );
183
184    assert!(p.server_capabilities.is_empty());
185
186    assert_eq!(p.server_default_collation, 8);
187    assert!(p.status.contains(Status::SERVER_STATUS_AUTOCOMMIT));
188    assert!(matches!(
189        p.auth_plugin,
190        Some(AuthPlugin::MySqlNativePassword)
191    ));
192
193    assert_eq!(
194        &*p.auth_plugin_data.into_iter().collect::<Vec<_>>(),
195        &[116, 54, 76, 92, 106, 34, 100, 83, 85, 49, 52, 79, 112, 104, 57, 34, 60, 72, 53, 110,]
196    );
197}