Skip to main content

rustlavel_db/mysql/
protocol.rs

1//! The MySQL client/server protocol, written directly on the wire format.
2//!
3//! Every exchange is framed the same way: a 3-byte little-endian payload
4//! length, a 1-byte sequence id, then the payload. The sequence id restarts at
5//! zero for each command and must increase by one for every packet either side
6//! sends, which is how both ends notice a lost or reordered packet.
7//!
8//! Everything here is little-endian — the opposite of PostgreSQL — and lengths
9//! are usually *length-encoded integers*, a variable-width form that spends one
10//! byte on the small numbers that dominate a result set.
11//!
12//! Client packets are built into a [`Buffer`]; server packets are parsed by the
13//! `parse_*` functions and by [`Packet::parse`].
14
15use crate::mysql::types;
16use crate::value::Value;
17use rustlavel_core::{Error, Result};
18
19/// The largest payload one frame can carry: 2^24 - 1.
20///
21/// A longer payload is split across frames, and the split is only detectable by
22/// a frame that is exactly this long, so a payload of exactly 16 MiB is
23/// followed by an empty frame.
24pub const MAX_PAYLOAD: usize = 0xFF_FF_FF;
25
26/// The largest payload we tell the server we can receive.
27pub const MAX_PACKET_SIZE: u32 = 1 << 24;
28
29/// `utf8mb4_general_ci` — the framework speaks UTF-8, and `utf8mb3` cannot hold
30/// an emoji, which is the kind of bug that only shows up in production.
31pub const CHARSET_UTF8MB4: u8 = 45;
32
33/// The `binary` collation id. A column carrying it holds bytes, not text.
34pub const CHARSET_BINARY: u16 = 63;
35
36// --- Capability flags ---
37//
38// Negotiated in the handshake: the server advertises what it supports, the
39// client answers with the subset it wants, and the intersection governs the
40// shape of every packet afterwards.
41
42pub const CLIENT_LONG_PASSWORD: u32 = 0x0000_0001;
43pub const CLIENT_FOUND_ROWS: u32 = 0x0000_0002;
44pub const CLIENT_LONG_FLAG: u32 = 0x0000_0004;
45pub const CLIENT_CONNECT_WITH_DB: u32 = 0x0000_0008;
46pub const CLIENT_LOCAL_FILES: u32 = 0x0000_0080;
47pub const CLIENT_PROTOCOL_41: u32 = 0x0000_0200;
48pub const CLIENT_SSL: u32 = 0x0000_0800;
49pub const CLIENT_TRANSACTIONS: u32 = 0x0000_2000;
50pub const CLIENT_SECURE_CONNECTION: u32 = 0x0000_8000;
51pub const CLIENT_MULTI_STATEMENTS: u32 = 0x0001_0000;
52pub const CLIENT_MULTI_RESULTS: u32 = 0x0002_0000;
53pub const CLIENT_PS_MULTI_RESULTS: u32 = 0x0004_0000;
54pub const CLIENT_PLUGIN_AUTH: u32 = 0x0008_0000;
55pub const CLIENT_CONNECT_ATTRS: u32 = 0x0010_0000;
56pub const CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA: u32 = 0x0020_0000;
57pub const CLIENT_SESSION_TRACK: u32 = 0x0080_0000;
58pub const CLIENT_DEPRECATE_EOF: u32 = 0x0100_0000;
59
60/// What this driver asks for.
61///
62/// `CLIENT_MULTI_STATEMENTS` is deliberately absent: with it the server would
63/// accept `a; b` in one `COM_QUERY`, which turns any missed escape anywhere in
64/// the stack into a second statement. Leaving it off means the server itself
65/// refuses, rather than the framework having to.
66///
67/// `CLIENT_LOCAL_FILES` is absent for the same reason — it lets the *server*
68/// ask the client to upload a local file, and a compromised or hostile server
69/// should not be able to read the application's disk.
70pub const CLIENT_CAPABILITIES: u32 = CLIENT_LONG_PASSWORD
71    | CLIENT_LONG_FLAG
72    | CLIENT_PROTOCOL_41
73    | CLIENT_TRANSACTIONS
74    | CLIENT_SECURE_CONNECTION
75    | CLIENT_MULTI_RESULTS
76    | CLIENT_PS_MULTI_RESULTS
77    | CLIENT_PLUGIN_AUTH
78    | CLIENT_CONNECT_ATTRS
79    | CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA
80    | CLIENT_SESSION_TRACK;
81
82// --- Server status flags ---
83
84/// Set while a transaction is open — the only way to know without tracking
85/// `begin`/`commit` ourselves, and therefore the only way that stays right when
86/// a statement implicitly commits.
87pub const SERVER_STATUS_IN_TRANS: u16 = 0x0001;
88pub const SERVER_STATUS_AUTOCOMMIT: u16 = 0x0002;
89pub const SERVER_MORE_RESULTS_EXISTS: u16 = 0x0008;
90
91// --- Column flags ---
92
93pub const NOT_NULL_FLAG: u16 = 0x0001;
94pub const UNSIGNED_FLAG: u16 = 0x0020;
95pub const BINARY_FLAG: u16 = 0x0080;
96
97// --- Commands ---
98
99pub const COM_QUIT: u8 = 0x01;
100pub const COM_QUERY: u8 = 0x03;
101pub const COM_PING: u8 = 0x0E;
102pub const COM_STMT_PREPARE: u8 = 0x16;
103pub const COM_STMT_EXECUTE: u8 = 0x17;
104pub const COM_STMT_CLOSE: u8 = 0x19;
105
106/// `COM_STMT_EXECUTE` without a server-side cursor: every row comes back at
107/// once, which is what the driver's `Vec<Row>` result wants anyway.
108pub const CURSOR_TYPE_NO_CURSOR: u8 = 0x00;
109
110/// A client packet payload under construction.
111///
112/// Holds only the payload; framing is added by [`frame`] when the connection
113/// knows which sequence id the packet must carry.
114#[derive(Default)]
115pub struct Buffer {
116    bytes: Vec<u8>,
117}
118
119impl Buffer {
120    pub fn new() -> Self {
121        Buffer::default()
122    }
123
124    pub fn into_bytes(self) -> Vec<u8> {
125        self.bytes
126    }
127
128    pub fn len(&self) -> usize {
129        self.bytes.len()
130    }
131
132    pub fn is_empty(&self) -> bool {
133        self.bytes.is_empty()
134    }
135
136    pub fn u8(&mut self, value: u8) -> &mut Self {
137        self.bytes.push(value);
138        self
139    }
140
141    pub fn u16(&mut self, value: u16) -> &mut Self {
142        self.bytes.extend_from_slice(&value.to_le_bytes());
143        self
144    }
145
146    pub fn u32(&mut self, value: u32) -> &mut Self {
147        self.bytes.extend_from_slice(&value.to_le_bytes());
148        self
149    }
150
151    pub fn u64(&mut self, value: u64) -> &mut Self {
152        self.bytes.extend_from_slice(&value.to_le_bytes());
153        self
154    }
155
156    pub fn raw(&mut self, value: &[u8]) -> &mut Self {
157        self.bytes.extend_from_slice(value);
158        self
159    }
160
161    /// A NUL-terminated string.
162    pub fn cstr(&mut self, value: &str) -> &mut Self {
163        // A NUL inside the value would terminate the field early and shift
164        // every field after it, so it is dropped rather than trusted.
165        self.bytes.extend(value.bytes().filter(|byte| *byte != 0));
166        self.bytes.push(0);
167        self
168    }
169
170    /// A length-encoded integer: one byte for the small values that dominate a
171    /// result set, and a marker byte plus 2, 3 or 8 bytes for the rest.
172    pub fn lenenc_int(&mut self, value: u64) -> &mut Self {
173        match value {
174            // 0xFB and 0xFF are reserved as markers, so the one-byte form stops
175            // just below them.
176            0..=0xFA => self.bytes.push(value as u8),
177            0xFB..=0xFFFF => {
178                self.bytes.push(0xFC);
179                self.bytes.extend_from_slice(&(value as u16).to_le_bytes());
180            }
181            0x1_0000..=0xFF_FFFF => {
182                self.bytes.push(0xFD);
183                self.bytes.extend_from_slice(&(value as u32).to_le_bytes()[..3]);
184            }
185            _ => {
186                self.bytes.push(0xFE);
187                self.bytes.extend_from_slice(&value.to_le_bytes());
188            }
189        }
190        self
191    }
192
193    /// A length-encoded byte string.
194    pub fn lenenc_bytes(&mut self, value: &[u8]) -> &mut Self {
195        self.lenenc_int(value.len() as u64);
196        self.bytes.extend_from_slice(value);
197        self
198    }
199
200    /// The `SSLRequest` packet: the first 32 bytes of a handshake response, and
201    /// nothing else.
202    ///
203    /// It is deliberately the same prefix as [`Buffer::handshake_response`],
204    /// because that is what the protocol says it is — the server reads this
205    /// much, sees `CLIENT_SSL`, and starts a TLS handshake instead of reading
206    /// on. The credentials then go inside the tunnel, in a second packet that
207    /// repeats these 32 bytes and continues past them.
208    pub fn ssl_request(&mut self, capabilities: u32) -> &mut Self {
209        self.u32(capabilities | CLIENT_SSL);
210        self.u32(MAX_PACKET_SIZE);
211        self.u8(CHARSET_UTF8MB4);
212        self.raw(&[0u8; 23]);
213        self
214    }
215
216    /// The reply to the server's handshake: capabilities, then credentials.
217    pub fn handshake_response(
218        &mut self,
219        capabilities: u32,
220        user: &str,
221        auth_response: &[u8],
222        database: Option<&str>,
223        plugin: &str,
224        attributes: &[(&str, &str)],
225    ) -> &mut Self {
226        self.u32(capabilities);
227        self.u32(MAX_PACKET_SIZE);
228        self.u8(CHARSET_UTF8MB4);
229        self.raw(&[0u8; 23]);
230        self.cstr(user);
231
232        // Length-encoded rather than a single length byte, so an auth response
233        // longer than 255 bytes (the full caching_sha2 exchange) still fits.
234        if capabilities & CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA != 0 {
235            self.lenenc_bytes(auth_response);
236        } else {
237            self.u8(auth_response.len() as u8);
238            self.raw(auth_response);
239        }
240
241        if capabilities & CLIENT_CONNECT_WITH_DB != 0 {
242            self.cstr(database.unwrap_or(""));
243        }
244        if capabilities & CLIENT_PLUGIN_AUTH != 0 {
245            self.cstr(plugin);
246        }
247        if capabilities & CLIENT_CONNECT_ATTRS != 0 {
248            let mut pairs = Buffer::new();
249            for (key, value) in attributes {
250                pairs.lenenc_bytes(key.as_bytes());
251                pairs.lenenc_bytes(value.as_bytes());
252            }
253            let pairs = pairs.into_bytes();
254            self.lenenc_int(pairs.len() as u64);
255            self.raw(&pairs);
256        }
257        self
258    }
259
260    /// The reply to an `AuthSwitchRequest`, or the extra data a plugin needs.
261    ///
262    /// It is a bare payload with no command byte: the server already knows what
263    /// it asked for.
264    pub fn auth_response(&mut self, data: &[u8]) -> &mut Self {
265        self.raw(data)
266    }
267
268    /// A statement executed as text. No parameters, so it is reserved for DDL
269    /// and transaction control.
270    pub fn com_query(&mut self, sql: &str) -> &mut Self {
271        self.u8(COM_QUERY);
272        self.raw(sql.as_bytes())
273    }
274
275    /// Ask the server whether it is still there. Used to check a pooled
276    /// connection without the cost of a round trip through the parser.
277    pub fn com_ping(&mut self) -> &mut Self {
278        self.u8(COM_PING)
279    }
280
281    pub fn com_quit(&mut self) -> &mut Self {
282        self.u8(COM_QUIT)
283    }
284
285    /// Ask the server to parse a statement and hand back a handle for it.
286    pub fn com_stmt_prepare(&mut self, sql: &str) -> &mut Self {
287        self.u8(COM_STMT_PREPARE);
288        self.raw(sql.as_bytes())
289    }
290
291    /// Run a prepared statement with values bound out of band.
292    ///
293    /// The values travel as typed binary in their own section of the packet and
294    /// are never spliced into the statement text, which is what makes injection
295    /// structurally impossible rather than a matter of remembering to escape.
296    pub fn com_stmt_execute(&mut self, statement_id: u32, params: &[Value]) -> &mut Self {
297        self.u8(COM_STMT_EXECUTE);
298        self.u32(statement_id);
299        self.u8(CURSOR_TYPE_NO_CURSOR);
300        // Iteration count is always 1; the protocol reserves the field but the
301        // server rejects any other value.
302        self.u32(1);
303
304        if params.is_empty() {
305            return self;
306        }
307
308        // One bit per parameter, least significant bit first, saying which are
309        // NULL. A NULL contributes a bit and nothing else — it has no value
310        // section at all.
311        let mut null_bitmap = vec![0u8; params.len().div_ceil(8)];
312        for (index, param) in params.iter().enumerate() {
313            if param.is_null() {
314                null_bitmap[index / 8] |= 1 << (index % 8);
315            }
316        }
317        self.raw(&null_bitmap);
318
319        // "New parameters bound": the types that follow are authoritative. The
320        // driver never reuses a statement handle across differently typed
321        // arguments, so this is always 1.
322        self.u8(1);
323        for param in params {
324            let (column_type, unsigned) = types::bind_type(param);
325            self.u8(column_type);
326            self.u8(if unsigned { 0x80 } else { 0x00 });
327        }
328        for param in params {
329            types::encode_bind(param, &mut self.bytes);
330        }
331        self
332    }
333
334    /// Release a prepared statement's server-side resources.
335    ///
336    /// The server sends nothing back, so a connection that skips this leaks a
337    /// handle for the life of the session.
338    pub fn com_stmt_close(&mut self, statement_id: u32) -> &mut Self {
339        self.u8(COM_STMT_CLOSE);
340        self.u32(statement_id)
341    }
342}
343
344/// Wrap a payload in one or more frames, starting at `sequence`.
345///
346/// A payload of `MAX_PAYLOAD` or more is split, and a payload that is an exact
347/// multiple of `MAX_PAYLOAD` gets a trailing empty frame — without it the
348/// server would wait forever for a continuation that never comes.
349pub fn frame(payload: &[u8], sequence: u8) -> (Vec<u8>, u8) {
350    let mut out = Vec::with_capacity(payload.len() + 4);
351    let mut sequence = sequence;
352    let mut rest = payload;
353
354    loop {
355        let take = rest.len().min(MAX_PAYLOAD);
356        out.extend_from_slice(&(take as u32).to_le_bytes()[..3]);
357        out.push(sequence);
358        out.extend_from_slice(&rest[..take]);
359        sequence = sequence.wrapping_add(1);
360        rest = &rest[take..];
361
362        if take < MAX_PAYLOAD {
363            break;
364        }
365    }
366
367    (out, sequence)
368}
369
370/// One column's metadata, from a `ColumnDefinition41` packet.
371#[derive(Debug, Clone, Default)]
372pub struct Column {
373    /// The name as the result set presents it, which is the alias when there is
374    /// one — that is what `row.get("total")` has to match.
375    pub name: String,
376    pub original_name: String,
377    pub table: String,
378    pub charset: u16,
379    pub length: u32,
380    pub column_type: u8,
381    pub flags: u16,
382    pub decimals: u8,
383}
384
385impl Column {
386    /// Whether the column's values are bytes rather than text.
387    ///
388    /// MySQL distinguishes `blob` from `text` only by collation, so a `blob`
389    /// and a `text` column arrive with the same type byte and are told apart
390    /// here.
391    pub fn is_binary(&self) -> bool {
392        self.charset == CHARSET_BINARY
393    }
394
395    pub fn is_unsigned(&self) -> bool {
396        self.flags & UNSIGNED_FLAG != 0
397    }
398}
399
400/// The server's opening `Handshake` packet, protocol version 10.
401#[derive(Debug, Clone, Default)]
402pub struct Handshake {
403    pub server_version: String,
404    pub connection_id: u32,
405    /// The 20-byte nonce every password plugin salts its digest with.
406    pub scramble: Vec<u8>,
407    pub capabilities: u32,
408    pub charset: u8,
409    pub status: u16,
410    pub auth_plugin: String,
411}
412
413/// An `OK` packet: what the server sends when a statement produced no rows.
414#[derive(Debug, Clone, Default)]
415pub struct OkPacket {
416    pub affected_rows: u64,
417    /// The key an `auto_increment` column generated, or 0 when none did.
418    ///
419    /// This is the whole reason `QueryResult::last_insert_id` exists: MySQL
420    /// reports the key here rather than as a row.
421    pub last_insert_id: u64,
422    pub status: u16,
423    pub warnings: u16,
424    pub info: String,
425}
426
427/// An `EOF` packet, marking the end of a section of a result set.
428#[derive(Debug, Clone, Default)]
429pub struct EofPacket {
430    pub warnings: u16,
431    pub status: u16,
432}
433
434/// An error the server reported, carrying the code people search for.
435#[derive(Debug, Clone, Default)]
436pub struct ServerError {
437    pub code: u16,
438    /// The five-character SQLSTATE, when the server sent one. Absent only from
439    /// pre-4.1 servers and from a handshake that failed before negotiation.
440    pub sql_state: String,
441    pub message: String,
442}
443
444impl ServerError {
445    /// Render for a human, naming the code, the SQL state and the statement.
446    ///
447    /// The code is the part that is searchable and the statement is the part
448    /// that says where to look, so both are always present when known.
449    pub fn into_error(self, sql: Option<&str>) -> Error {
450        let mut text = if self.sql_state.is_empty() {
451            format!("MySQL error {}: {}", self.code, self.message)
452        } else {
453            format!("MySQL error {} ({}): {}", self.code, self.sql_state, self.message)
454        };
455
456        if let Some(sql) = sql {
457            text.push_str(&format!("\n  SQL: {sql}"));
458        }
459        Error::msg(text)
460    }
461}
462
463/// The server's answer to `COM_STMT_PREPARE`.
464#[derive(Debug, Clone, Default)]
465pub struct PrepareOk {
466    pub statement_id: u32,
467    pub columns: u16,
468    pub params: u16,
469    pub warnings: u16,
470}
471
472/// A packet whose kind can be told from its first byte.
473///
474/// Only unambiguous in reply to a command. Inside a result set a leading `0x00`
475/// is a binary row and a leading `0xFE` is a length-encoded integer, so rows
476/// are parsed by the code that knows it asked for them.
477#[derive(Debug)]
478pub enum Packet {
479    Ok(OkPacket),
480    Err(ServerError),
481    Eof(EofPacket),
482    /// The server wants a different authentication plugin than the one the
483    /// handshake named.
484    AuthSwitch { plugin: String, data: Vec<u8> },
485    /// Plugin-specific data mid-authentication, such as caching_sha2's verdict.
486    AuthMoreData(Vec<u8>),
487    /// A payload the caller has to interpret itself — a result-set header.
488    Other(Vec<u8>),
489}
490
491impl Packet {
492    /// Classify one payload received in reply to a command.
493    pub fn parse(payload: &[u8]) -> Result<Packet> {
494        match payload.first() {
495            None => Err(Error::Protocol("the server sent an empty packet".into())),
496            Some(0x00) => Ok(Packet::Ok(parse_ok(payload)?)),
497            Some(0xFF) => Ok(Packet::Err(parse_err(payload)?)),
498            Some(0x01) => Ok(Packet::AuthMoreData(payload[1..].to_vec())),
499            // `0xFE` is EOF only in a short packet; in a longer one it is the
500            // marker of an 8-byte length-encoded integer, or an auth switch.
501            Some(0xFE) if payload.len() < 9 => Ok(Packet::Eof(parse_eof(payload)?)),
502            Some(0xFE) => {
503                let mut reader = Reader::new(&payload[1..]);
504                Ok(Packet::AuthSwitch {
505                    plugin: reader.cstr()?,
506                    data: trim_trailing_nul(reader.rest()).to_vec(),
507                })
508            }
509            Some(_) => Ok(Packet::Other(payload.to_vec())),
510        }
511    }
512}
513
514/// Whether a payload is an `ERR` packet. Checked before anything else, because
515/// an error can arrive in place of any expected packet.
516pub fn is_err(payload: &[u8]) -> bool {
517    payload.first() == Some(&0xFF)
518}
519
520/// Whether a payload is an `EOF` packet rather than a row.
521pub fn is_eof(payload: &[u8]) -> bool {
522    payload.first() == Some(&0xFE) && payload.len() < 9
523}
524
525/// Parse the server's opening handshake.
526pub fn parse_handshake(payload: &[u8]) -> Result<Handshake> {
527    let mut reader = Reader::new(payload);
528
529    let version = reader.u8()?;
530    if version != 10 {
531        return Err(Error::Protocol(format!(
532            "the server speaks handshake protocol {version}; this driver implements version 10. \
533             MySQL 4.0 and older are not supported."
534        )));
535    }
536
537    let mut handshake = Handshake {
538        server_version: reader.cstr()?,
539        connection_id: reader.u32()?,
540        ..Handshake::default()
541    };
542
543    // The nonce arrives in two pieces with unrelated bytes between them, a
544    // legacy of the field having been extended in place.
545    handshake.scramble.extend_from_slice(reader.take(8)?);
546    reader.skip(1)?; // filler
547
548    let lower = reader.u16()? as u32;
549    handshake.capabilities = lower;
550
551    // A minimal server stops here; everything after is optional.
552    if reader.is_empty() {
553        return Ok(handshake);
554    }
555
556    handshake.charset = reader.u8()?;
557    handshake.status = reader.u16()?;
558    handshake.capabilities |= (reader.u16()? as u32) << 16;
559
560    let scramble_length = reader.u8()?;
561    reader.skip(10)?; // reserved
562
563    if handshake.capabilities & CLIENT_SECURE_CONNECTION != 0 {
564        // The field is at least 13 bytes whatever the plugin says it needs, and
565        // the last of those is a NUL that is not part of the nonce.
566        let rest = (scramble_length as usize).saturating_sub(8).max(13);
567        let part = reader.take(rest.min(reader.remaining()))?;
568        handshake.scramble.extend_from_slice(trim_trailing_nul(part));
569    }
570
571    if handshake.capabilities & CLIENT_PLUGIN_AUTH != 0 && !reader.is_empty() {
572        handshake.auth_plugin = reader.cstr().unwrap_or_default();
573    }
574
575    Ok(handshake)
576}
577
578/// Parse an `OK` packet.
579pub fn parse_ok(payload: &[u8]) -> Result<OkPacket> {
580    let mut reader = Reader::new(payload);
581    reader.skip(1)?; // the 0x00 header
582
583    Ok(OkPacket {
584        affected_rows: reader.lenenc_int()?,
585        last_insert_id: reader.lenenc_int()?,
586        status: reader.u16().unwrap_or(0),
587        warnings: reader.u16().unwrap_or(0),
588        info: String::from_utf8_lossy(reader.rest()).into_owned(),
589    })
590}
591
592/// Parse an `ERR` packet, including its SQL state.
593pub fn parse_err(payload: &[u8]) -> Result<ServerError> {
594    let mut reader = Reader::new(payload);
595    reader.skip(1)?; // the 0xFF header
596
597    let code = reader.u16()?;
598
599    // A `#` marks the SQLSTATE field. Its absence means a pre-4.1 server, or an
600    // error raised before capabilities were negotiated.
601    let sql_state = if reader.peek() == Some(b'#') {
602        reader.skip(1)?;
603        String::from_utf8_lossy(reader.take(5)?).into_owned()
604    } else {
605        String::new()
606    };
607
608    Ok(ServerError {
609        code,
610        sql_state,
611        message: String::from_utf8_lossy(reader.rest()).into_owned(),
612    })
613}
614
615/// Parse an `EOF` packet.
616pub fn parse_eof(payload: &[u8]) -> Result<EofPacket> {
617    let mut reader = Reader::new(payload);
618    reader.skip(1)?; // the 0xFE header
619
620    Ok(EofPacket {
621        warnings: reader.u16().unwrap_or(0),
622        status: reader.u16().unwrap_or(0),
623    })
624}
625
626/// Parse a `ColumnDefinition41` packet.
627pub fn parse_column(payload: &[u8]) -> Result<Column> {
628    let mut reader = Reader::new(payload);
629
630    reader.lenenc_bytes()?; // catalog, always "def"
631    reader.lenenc_bytes()?; // schema
632    let table = String::from_utf8_lossy(reader.lenenc_bytes()?).into_owned();
633    reader.lenenc_bytes()?; // original table
634    let name = String::from_utf8_lossy(reader.lenenc_bytes()?).into_owned();
635    let original_name = String::from_utf8_lossy(reader.lenenc_bytes()?).into_owned();
636
637    reader.lenenc_int()?; // length of the fixed-length section that follows
638
639    Ok(Column {
640        name,
641        original_name,
642        table,
643        charset: reader.u16()?,
644        length: reader.u32()?,
645        column_type: reader.u8()?,
646        flags: reader.u16()?,
647        decimals: reader.u8()?,
648    })
649}
650
651/// Parse the response to `COM_STMT_PREPARE`.
652pub fn parse_prepare_ok(payload: &[u8]) -> Result<PrepareOk> {
653    let mut reader = Reader::new(payload);
654    reader.skip(1)?; // the 0x00 status byte
655
656    let statement_id = reader.u32()?;
657    let columns = reader.u16()?;
658    let params = reader.u16()?;
659    reader.skip(1).ok(); // reserved filler
660
661    Ok(PrepareOk {
662        statement_id,
663        columns,
664        params,
665        warnings: reader.u16().unwrap_or(0),
666    })
667}
668
669/// Split a text-protocol row into its columns.
670///
671/// Every value is a length-encoded string, and `0xFB` in place of a length is
672/// how NULL is spelled — which is why an empty string and a NULL are still
673/// distinguishable.
674pub fn parse_text_row(payload: &[u8], columns: usize) -> Result<Vec<Option<Vec<u8>>>> {
675    let mut reader = Reader::new(payload);
676    let mut values = Vec::with_capacity(columns);
677
678    for _ in 0..columns {
679        values.push(reader.lenenc_bytes_or_null()?.map(<[u8]>::to_vec));
680    }
681
682    Ok(values)
683}
684
685/// Drop a single trailing NUL, which several fields carry as a terminator that
686/// is not part of the value.
687fn trim_trailing_nul(bytes: &[u8]) -> &[u8] {
688    match bytes.last() {
689        Some(0) => &bytes[..bytes.len() - 1],
690        _ => bytes,
691    }
692}
693
694/// A cursor over a packet payload.
695pub struct Reader<'a> {
696    bytes: &'a [u8],
697    position: usize,
698}
699
700impl<'a> Reader<'a> {
701    pub fn new(bytes: &'a [u8]) -> Self {
702        Reader { bytes, position: 0 }
703    }
704
705    pub fn is_empty(&self) -> bool {
706        self.position >= self.bytes.len()
707    }
708
709    pub fn remaining(&self) -> usize {
710        self.bytes.len().saturating_sub(self.position)
711    }
712
713    pub fn peek(&self) -> Option<u8> {
714        self.bytes.get(self.position).copied()
715    }
716
717    pub fn take(&mut self, count: usize) -> Result<&'a [u8]> {
718        let end = self.position.checked_add(count).ok_or_else(truncated)?;
719        if end > self.bytes.len() {
720            return Err(truncated());
721        }
722        let slice = &self.bytes[self.position..end];
723        self.position = end;
724        Ok(slice)
725    }
726
727    pub fn skip(&mut self, count: usize) -> Result<()> {
728        self.take(count).map(|_| ())
729    }
730
731    pub fn u8(&mut self) -> Result<u8> {
732        Ok(self.take(1)?[0])
733    }
734
735    pub fn u16(&mut self) -> Result<u16> {
736        Ok(u16::from_le_bytes(self.take(2)?.try_into().expect("2 bytes")))
737    }
738
739    pub fn u32(&mut self) -> Result<u32> {
740        Ok(u32::from_le_bytes(self.take(4)?.try_into().expect("4 bytes")))
741    }
742
743    pub fn u64(&mut self) -> Result<u64> {
744        Ok(u64::from_le_bytes(self.take(8)?.try_into().expect("8 bytes")))
745    }
746
747    /// A length-encoded integer. `0xFB` (NULL) is an error here; the callers
748    /// that allow it use [`Reader::lenenc_bytes_or_null`].
749    pub fn lenenc_int(&mut self) -> Result<u64> {
750        match self.u8()? {
751            marker @ 0..=0xFA => Ok(marker as u64),
752            0xFB => Err(Error::Protocol("a NULL where a length was expected".into())),
753            0xFC => Ok(self.u16()? as u64),
754            0xFD => {
755                let bytes = self.take(3)?;
756                Ok(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], 0]) as u64)
757            }
758            _ => self.u64(),
759        }
760    }
761
762    pub fn lenenc_bytes(&mut self) -> Result<&'a [u8]> {
763        let length = self.lenenc_int()? as usize;
764        self.take(length)
765    }
766
767    pub fn lenenc_bytes_or_null(&mut self) -> Result<Option<&'a [u8]>> {
768        if self.peek() == Some(0xFB) {
769            self.position += 1;
770            return Ok(None);
771        }
772        self.lenenc_bytes().map(Some)
773    }
774
775    pub fn cstr(&mut self) -> Result<String> {
776        let start = self.position;
777        while self.position < self.bytes.len() && self.bytes[self.position] != 0 {
778            self.position += 1;
779        }
780        if self.position >= self.bytes.len() {
781            return Err(Error::Protocol("unterminated string from the server".into()));
782        }
783        let text = String::from_utf8_lossy(&self.bytes[start..self.position]).into_owned();
784        self.position += 1;
785        Ok(text)
786    }
787
788    pub fn rest(&mut self) -> &'a [u8] {
789        let slice = &self.bytes[self.position.min(self.bytes.len())..];
790        self.position = self.bytes.len();
791        slice
792    }
793}
794
795fn truncated() -> Error {
796    Error::Protocol("truncated packet from the server".into())
797}
798
799#[cfg(test)]
800mod tests {
801    use super::*;
802
803    #[test]
804    fn a_frame_carries_a_little_endian_length_and_a_sequence_id() {
805        let (bytes, next) = frame(b"select 1", 0);
806
807        assert_eq!(&bytes[..3], &[8, 0, 0]);
808        assert_eq!(bytes[3], 0);
809        assert_eq!(&bytes[4..], b"select 1");
810        assert_eq!(next, 1);
811    }
812
813    #[test]
814    fn a_payload_longer_than_one_frame_is_split_and_terminated() {
815        let payload = vec![0x41u8; MAX_PAYLOAD + 5];
816        let (bytes, next) = frame(&payload, 3);
817
818        // Two frames: a full one, then the remainder.
819        assert_eq!(&bytes[..3], &[0xFF, 0xFF, 0xFF]);
820        assert_eq!(bytes[3], 3);
821        let second = &bytes[4 + MAX_PAYLOAD..];
822        assert_eq!(&second[..3], &[5, 0, 0]);
823        assert_eq!(second[3], 4);
824        assert_eq!(next, 5);
825    }
826
827    #[test]
828    fn a_length_encoded_integer_uses_each_of_its_four_widths() {
829        // One byte, up to the first reserved marker.
830        assert_eq!(Buffer::new().lenenc_int(250).clone_bytes(), vec![250]);
831        // Two bytes behind 0xFC.
832        assert_eq!(Buffer::new().lenenc_int(251).clone_bytes(), vec![0xFC, 251, 0]);
833        assert_eq!(Buffer::new().lenenc_int(65_535).clone_bytes(), vec![0xFC, 0xFF, 0xFF]);
834        // Three bytes behind 0xFD.
835        assert_eq!(Buffer::new().lenenc_int(65_536).clone_bytes(), vec![0xFD, 0, 0, 1]);
836        assert_eq!(
837            Buffer::new().lenenc_int(16_777_215).clone_bytes(),
838            vec![0xFD, 0xFF, 0xFF, 0xFF]
839        );
840        // Eight bytes behind 0xFE.
841        assert_eq!(
842            Buffer::new().lenenc_int(16_777_216).clone_bytes(),
843            vec![0xFE, 0, 0, 0, 1, 0, 0, 0, 0]
844        );
845    }
846
847    #[test]
848    fn a_length_encoded_integer_survives_a_round_trip_at_every_width() {
849        for value in [0u64, 1, 250, 251, 65_535, 65_536, 16_777_215, 16_777_216, u64::MAX] {
850            let bytes = Buffer::new().lenenc_int(value).clone_bytes();
851            let decoded = Reader::new(&bytes).lenenc_int().unwrap();
852            assert_eq!(decoded, value, "for {value}");
853        }
854    }
855
856    #[test]
857    fn a_length_encoded_string_carries_its_own_length() {
858        let bytes = Buffer::new().lenenc_bytes(b"ada").clone_bytes();
859        assert_eq!(bytes, b"\x03ada");
860
861        let mut reader = Reader::new(&bytes);
862        assert_eq!(reader.lenenc_bytes().unwrap(), b"ada");
863    }
864
865    #[test]
866    fn parses_a_handshake_v10() {
867        let mut payload = vec![10u8];
868        payload.extend_from_slice(b"8.0.36\0");
869        payload.extend_from_slice(&7u32.to_le_bytes());
870        payload.extend_from_slice(b"12345678"); // scramble, part one
871        payload.push(0); // filler
872        payload.extend_from_slice(&((CLIENT_CAPABILITIES & 0xFFFF) as u16).to_le_bytes());
873        payload.push(CHARSET_UTF8MB4);
874        payload.extend_from_slice(&SERVER_STATUS_AUTOCOMMIT.to_le_bytes());
875        payload.extend_from_slice(&((CLIENT_CAPABILITIES >> 16) as u16).to_le_bytes());
876        payload.push(21); // total scramble length, including its NUL
877        payload.extend_from_slice(&[0u8; 10]);
878        payload.extend_from_slice(b"abcdefghijkl\0"); // scramble, part two
879        payload.extend_from_slice(b"caching_sha2_password\0");
880
881        let handshake = parse_handshake(&payload).unwrap();
882
883        assert_eq!(handshake.server_version, "8.0.36");
884        assert_eq!(handshake.connection_id, 7);
885        assert_eq!(handshake.scramble, b"12345678abcdefghijkl");
886        assert_eq!(handshake.auth_plugin, "caching_sha2_password");
887        assert!(handshake.capabilities & CLIENT_PLUGIN_AUTH != 0);
888    }
889
890    #[test]
891    fn refuses_a_handshake_from_a_server_too_old_to_talk_to() {
892        let error = parse_handshake(&[9, 0]).unwrap_err().to_string();
893        assert!(error.contains("version 10"), "{error}");
894    }
895
896    #[test]
897    fn parses_an_ok_packet_including_the_generated_key() {
898        // 0x00, affected=1, last insert id=42, status, warnings.
899        let payload = [0x00, 0x01, 0x2A, 0x02, 0x00, 0x00, 0x00];
900
901        let ok = parse_ok(&payload).unwrap();
902        assert_eq!(ok.affected_rows, 1);
903        assert_eq!(ok.last_insert_id, 42);
904        assert_eq!(ok.status, SERVER_STATUS_AUTOCOMMIT);
905    }
906
907    #[test]
908    fn an_error_packet_becomes_an_error_that_names_the_sql_state() {
909        let mut payload = vec![0xFF];
910        payload.extend_from_slice(&1146u16.to_le_bytes());
911        payload.push(b'#');
912        payload.extend_from_slice(b"42S02");
913        payload.extend_from_slice(b"Table 'blog.nope' doesn't exist");
914
915        let error = parse_err(&payload).unwrap();
916        assert_eq!(error.code, 1146);
917        assert_eq!(error.sql_state, "42S02");
918
919        let rendered = error.into_error(Some("select * from nope")).to_string();
920        assert!(rendered.contains("1146"), "{rendered}");
921        assert!(rendered.contains("42S02"), "{rendered}");
922        assert!(rendered.contains("SQL: select * from nope"), "{rendered}");
923    }
924
925    #[test]
926    fn an_error_without_a_sql_state_still_reports_its_code() {
927        let mut payload = vec![0xFF];
928        payload.extend_from_slice(&1045u16.to_le_bytes());
929        payload.extend_from_slice(b"Access denied");
930
931        let error = parse_err(&payload).unwrap();
932        assert_eq!(error.code, 1045);
933        assert!(error.sql_state.is_empty());
934        assert!(error.into_error(None).to_string().contains("1045"));
935    }
936
937    #[test]
938    fn an_eof_packet_is_told_from_a_row_by_its_length() {
939        let eof = [0xFE, 0x00, 0x00, 0x02, 0x00];
940        assert!(is_eof(&eof));
941        assert_eq!(parse_eof(&eof).unwrap().status, SERVER_STATUS_AUTOCOMMIT);
942
943        // The same first byte in a longer packet is a length marker, not an EOF.
944        let row = [0xFE, 1, 2, 3, 4, 5, 6, 7, 8, 9];
945        assert!(!is_eof(&row));
946    }
947
948    #[test]
949    fn parses_a_column_definition() {
950        let mut payload = Buffer::new();
951        payload.lenenc_bytes(b"def");
952        payload.lenenc_bytes(b"blog");
953        payload.lenenc_bytes(b"users");
954        payload.lenenc_bytes(b"users");
955        payload.lenenc_bytes(b"total");
956        payload.lenenc_bytes(b"id");
957        payload.lenenc_int(0x0C);
958        payload.u16(CHARSET_BINARY);
959        payload.u32(20);
960        payload.u8(types::LONGLONG);
961        payload.u16(UNSIGNED_FLAG | NOT_NULL_FLAG);
962        payload.u8(0);
963        payload.u16(0);
964
965        let column = parse_column(&payload.clone_bytes()).unwrap();
966
967        // The alias, not the underlying column, because that is what a caller
968        // asks for by name.
969        assert_eq!(column.name, "total");
970        assert_eq!(column.original_name, "id");
971        assert_eq!(column.table, "users");
972        assert_eq!(column.column_type, types::LONGLONG);
973        assert!(column.is_unsigned());
974        assert!(column.is_binary());
975    }
976
977    #[test]
978    fn parses_a_prepare_response() {
979        let mut payload = vec![0x00];
980        payload.extend_from_slice(&9u32.to_le_bytes());
981        payload.extend_from_slice(&3u16.to_le_bytes());
982        payload.extend_from_slice(&2u16.to_le_bytes());
983        payload.push(0);
984        payload.extend_from_slice(&0u16.to_le_bytes());
985
986        let prepared = parse_prepare_ok(&payload).unwrap();
987        assert_eq!(prepared.statement_id, 9);
988        assert_eq!(prepared.columns, 3);
989        assert_eq!(prepared.params, 2);
990    }
991
992    #[test]
993    fn a_text_row_distinguishes_null_from_the_empty_string() {
994        let payload = [0x03, b'a', b'd', b'a', 0xFB, 0x00];
995
996        let values = parse_text_row(&payload, 3).unwrap();
997        assert_eq!(values[0].as_deref(), Some(&b"ada"[..]));
998        assert_eq!(values[1], None);
999        assert_eq!(values[2].as_deref(), Some(&b""[..]));
1000    }
1001
1002    #[test]
1003    fn an_ssl_request_is_exactly_thirty_two_bytes() {
1004        // The server reads a fixed 32 bytes and then starts the TLS handshake.
1005        // One byte too many or too few and it is reading TLS as MySQL, which
1006        // shows up as a connection that closes with no error at all.
1007        let mut buffer = Buffer::new();
1008        buffer.ssl_request(CLIENT_PROTOCOL_41);
1009        let bytes = buffer.into_bytes();
1010
1011        assert_eq!(bytes.len(), 32);
1012        assert_eq!(&bytes[9..32], &[0u8; 23], "the 23-byte filler must be zeroed");
1013    }
1014
1015    #[test]
1016    fn an_ssl_request_sets_the_ssl_flag_whatever_it_was_given() {
1017        let mut buffer = Buffer::new();
1018        buffer.ssl_request(CLIENT_PROTOCOL_41);
1019        let flags = u32::from_le_bytes(buffer.into_bytes()[0..4].try_into().unwrap());
1020
1021        assert!(flags & CLIENT_SSL != 0, "without this the server never starts a handshake");
1022        assert!(flags & CLIENT_PROTOCOL_41 != 0, "and it must not drop what it was given");
1023    }
1024
1025    #[test]
1026    fn an_ssl_request_is_the_prefix_of_the_handshake_response() {
1027        // The protocol defines it that way, and the second packet repeats these
1028        // bytes inside the tunnel. If they ever disagree, the server sees two
1029        // different sets of capabilities and rejects the login.
1030        let mut request = Buffer::new();
1031        request.ssl_request(CLIENT_PROTOCOL_41 | CLIENT_SSL);
1032
1033        let mut full = Buffer::new();
1034        full.handshake_response(
1035            CLIENT_PROTOCOL_41 | CLIENT_SSL,
1036            "someone",
1037            b"digest",
1038            None,
1039            "caching_sha2_password",
1040            &[],
1041        );
1042
1043        assert_eq!(request.into_bytes(), full.into_bytes()[..32]);
1044    }
1045
1046    #[test]
1047    fn a_handshake_response_names_the_plugin_and_the_database() {
1048        let bytes = Buffer::new()
1049            .handshake_response(
1050                CLIENT_CAPABILITIES | CLIENT_CONNECT_WITH_DB,
1051                "ada",
1052                &[0xAA; 20],
1053                Some("blog"),
1054                "mysql_native_password",
1055                &[("program_name", "rustlavel")],
1056            )
1057            .clone_bytes();
1058
1059        let capabilities = u32::from_le_bytes(bytes[0..4].try_into().unwrap());
1060        assert!(capabilities & CLIENT_CONNECT_WITH_DB != 0);
1061        assert_eq!(u32::from_le_bytes(bytes[4..8].try_into().unwrap()), MAX_PACKET_SIZE);
1062        assert_eq!(bytes[8], CHARSET_UTF8MB4);
1063        assert_eq!(&bytes[9..32], &[0u8; 23]);
1064        assert_eq!(&bytes[32..36], b"ada\0");
1065        // Length-encoded auth response: 20 bytes of scramble.
1066        assert_eq!(bytes[36], 20);
1067        assert!(bytes.windows(5).any(|w| w == b"blog\0"));
1068        assert!(bytes.windows(22).any(|w| w == b"mysql_native_password\0"));
1069        assert!(bytes.windows(12).any(|w| w == b"program_name"));
1070    }
1071
1072    #[test]
1073    fn a_nul_in_a_username_cannot_truncate_the_field() {
1074        let bytes = Buffer::new().cstr("ada\0admin").clone_bytes();
1075        assert_eq!(bytes, b"adaadmin\0");
1076    }
1077
1078    #[test]
1079    fn execute_marks_null_parameters_in_a_bitmap_and_sends_no_value_for_them() {
1080        let bytes = Buffer::new()
1081            .com_stmt_execute(7, &[Value::Int(1), Value::Null, Value::Int(2)])
1082            .clone_bytes();
1083
1084        assert_eq!(bytes[0], COM_STMT_EXECUTE);
1085        assert_eq!(u32::from_le_bytes(bytes[1..5].try_into().unwrap()), 7);
1086        assert_eq!(bytes[5], CURSOR_TYPE_NO_CURSOR);
1087        assert_eq!(u32::from_le_bytes(bytes[6..10].try_into().unwrap()), 1);
1088
1089        // Three parameters fit in one bitmap byte; only the second is NULL.
1090        assert_eq!(bytes[10], 0b0000_0010);
1091        assert_eq!(bytes[11], 1, "new parameters are bound");
1092
1093        // Three type pairs, then two 8-byte values — the NULL contributes none.
1094        assert_eq!(&bytes[12..18], &[types::LONGLONG, 0, types::NULL, 0, types::LONGLONG, 0]);
1095        assert_eq!(bytes.len(), 18 + 16);
1096    }
1097
1098    #[test]
1099    fn execute_without_parameters_stops_before_the_bitmap() {
1100        let bytes = Buffer::new().com_stmt_execute(7, &[]).clone_bytes();
1101        assert_eq!(bytes.len(), 10);
1102    }
1103
1104    #[test]
1105    fn builds_the_small_commands() {
1106        assert_eq!(Buffer::new().com_query("select 1").clone_bytes(), b"\x03select 1");
1107        assert_eq!(Buffer::new().com_ping().clone_bytes(), vec![COM_PING]);
1108        assert_eq!(Buffer::new().com_quit().clone_bytes(), vec![COM_QUIT]);
1109        assert_eq!(
1110            Buffer::new().com_stmt_prepare("select ?").clone_bytes(),
1111            b"\x16select ?"
1112        );
1113        assert_eq!(
1114            Buffer::new().com_stmt_close(5).clone_bytes(),
1115            vec![COM_STMT_CLOSE, 5, 0, 0, 0]
1116        );
1117    }
1118
1119    #[test]
1120    fn a_truncated_packet_is_a_protocol_error_rather_than_a_panic() {
1121        assert!(parse_column(&[0x03, b'd']).is_err());
1122        assert!(parse_handshake(&[10]).is_err());
1123        assert!(Packet::parse(&[]).is_err());
1124        assert!(Reader::new(&[0xFC, 1]).lenenc_int().is_err());
1125    }
1126
1127    #[test]
1128    fn classifies_the_packets_a_command_can_be_answered_with() {
1129        assert!(matches!(Packet::parse(&[0x00, 0, 0, 2, 0, 0, 0]).unwrap(), Packet::Ok(_)));
1130        assert!(matches!(Packet::parse(&[0xFF, 0x15, 0x04]).unwrap(), Packet::Err(_)));
1131        assert!(matches!(Packet::parse(&[0xFE, 0, 0, 2, 0]).unwrap(), Packet::Eof(_)));
1132        assert!(matches!(Packet::parse(&[0x01, 3]).unwrap(), Packet::AuthMoreData(data) if data == [3]));
1133
1134        let mut switch = vec![0xFE];
1135        switch.extend_from_slice(b"mysql_native_password\0");
1136        switch.extend_from_slice(b"0123456789abcdefghij\0");
1137        match Packet::parse(&switch).unwrap() {
1138            Packet::AuthSwitch { plugin, data } => {
1139                assert_eq!(plugin, "mysql_native_password");
1140                assert_eq!(data, b"0123456789abcdefghij");
1141            }
1142            other => panic!("expected an auth switch, got {other:?}"),
1143        }
1144    }
1145
1146    #[test]
1147    fn the_negotiated_capabilities_leave_out_the_dangerous_ones() {
1148        // Multi-statement would let one `COM_QUERY` carry two statements, and
1149        // LOAD DATA LOCAL would let the server read the client's disk.
1150        assert_eq!(CLIENT_CAPABILITIES & CLIENT_MULTI_STATEMENTS, 0);
1151        assert_eq!(CLIENT_CAPABILITIES & CLIENT_LOCAL_FILES, 0);
1152        // The ones the driver relies on are present.
1153        assert_ne!(CLIENT_CAPABILITIES & CLIENT_PROTOCOL_41, 0);
1154        assert_ne!(CLIENT_CAPABILITIES & CLIENT_PLUGIN_AUTH, 0);
1155        assert_ne!(CLIENT_CAPABILITIES & CLIENT_TRANSACTIONS, 0);
1156        // EOF packets are kept, so the result-set reader has one shape.
1157        assert_eq!(CLIENT_CAPABILITIES & CLIENT_DEPRECATE_EOF, 0);
1158        assert_eq!(CLIENT_CAPABILITIES & CLIENT_SSL, 0);
1159    }
1160
1161    impl Buffer {
1162        fn clone_bytes(&self) -> Vec<u8> {
1163            self.bytes.clone()
1164        }
1165    }
1166}