zero_mysql/protocol/
response.rs

1use crate::constant::ServerStatusFlags;
2use crate::error::{Error, Result, eyre};
3use crate::protocol::primitive::*;
4use zerocopy::byteorder::little_endian::U16 as U16LE;
5use zerocopy::{FromBytes, Immutable, KnownLayout};
6
7/// The payload part of OK packet
8#[derive(Debug, Clone, Copy)]
9pub struct OkPayloadBytes<'a>(pub &'a [u8]);
10
11impl<'a> OkPayloadBytes<'a> {
12    pub fn assert_eof(&self) -> Result<()> {
13        if self.0[0] == 0xFE {
14            Ok(())
15        } else {
16            Err(Error::LibraryBug(eyre!(
17                "expected EOF packet header 0xFE, got 0x{:02X}",
18                self.0[0]
19            )))
20        }
21    }
22
23    pub fn bytes(&self) -> &[u8] {
24        self.0
25    }
26}
27
28/// The OK packet parsed from OkPayloadBytes
29#[derive(Debug, Clone)]
30pub struct OkPayload {
31    pub affected_rows: u64,
32    pub last_insert_id: u64,
33    pub status_flags: ServerStatusFlags,
34    pub warnings: u16,
35    // pub info: String, // SERVER_SESSION_STATE_CHANGED
36    // pub session_state_info: String, // SERVER_SESSION_STATE_CHANGED
37}
38
39impl TryFrom<OkPayloadBytes<'_>> for OkPayload {
40    type Error = Error;
41
42    fn try_from(bytes: OkPayloadBytes<'_>) -> Result<Self> {
43        let (header, data) = read_int_1(bytes.bytes())?;
44        if header != 0x00 && header != 0xFE {
45            return Err(Error::LibraryBug(eyre!(
46                "expected OK/EOF packet header 0x00 or 0xFE, got 0x{:02X}",
47                header
48            )));
49        }
50
51        let (affected_rows, data) = read_int_lenenc(data)?;
52        let (last_insert_id, data) = read_int_lenenc(data)?;
53        let (status_flags, data) = read_int_2(data)?;
54        let (warnings, _data) = read_int_2(data)?;
55
56        // TODO: Supports SERVER_SESSION_STATE_CHANGED
57
58        Ok(OkPayload {
59            affected_rows,
60            last_insert_id,
61            status_flags: ServerStatusFlags::from_bits_truncate(status_flags),
62            warnings,
63        })
64    }
65}
66
67#[derive(Debug)]
68pub struct ErrPayloadBytes<'a>(pub &'a [u8]);
69
70/// The ERR packet parsed from ErrPayloadBytes
71#[derive(Debug, Clone, thiserror::Error)]
72#[error("ERROR {} ({}): {}", self.error_code, self.sql_state, self.message)]
73pub struct ErrPayload {
74    pub error_code: u16,
75    pub sql_state: String,
76    pub message: String,
77}
78
79impl TryFrom<ErrPayloadBytes<'_>> for ErrPayload {
80    type Error = Error;
81
82    fn try_from(bytes: ErrPayloadBytes<'_>) -> Result<Self> {
83        let (header, data) = read_int_1(bytes.0)?;
84        debug_assert_eq!(header, 0xFF);
85
86        let (error_code, data) = read_int_2(data)?;
87
88        // marker is '#'
89        let (_sql_state_marker, data) = read_string_fix(data, 1)?;
90        let (sql_state, data) = read_string_fix(data, 5)?;
91
92        Ok(ErrPayload {
93            error_code,
94            sql_state: String::from_utf8_lossy(sql_state).to_string(),
95            message: String::from_utf8_lossy(data).to_string(), // string<EOF>
96        })
97    }
98}
99
100#[repr(C, packed)]
101#[derive(Debug, Clone, Copy, FromBytes, KnownLayout, Immutable)]
102pub struct EofPacket {
103    warnings: U16LE,
104    status_flags: U16LE,
105}
106
107impl EofPacket {
108    pub fn warnings(&self) -> u16 {
109        self.warnings.get()
110    }
111    /// Get status flags as ServerStatusFlags
112    pub fn status_flags(&self) -> ServerStatusFlags {
113        ServerStatusFlags::from_bits_truncate(self.status_flags.get())
114    }
115}
116
117/// Read EOF packet (header byte 0xFE, length < 9) - zero-copy
118pub fn read_eof_packet(payload: &[u8]) -> Result<&EofPacket> {
119    let (header, data) = read_int_1(payload)?;
120    if header != 0xFE {
121        return Err(Error::LibraryBug(eyre!(
122            "expected EOF packet header 0xFE, got 0x{:02X}",
123            header
124        )));
125    }
126
127    // EofPacket is 4 bytes (2 + 2)
128    if data.len() < 4 {
129        return Err(Error::LibraryBug(eyre!(
130            "EOF packet data too short: {} < 4",
131            data.len()
132        )));
133    }
134
135    // Zero-copy cast using zerocopy
136    Ok(EofPacket::ref_from_bytes(&data[..4])?)
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142
143    #[test]
144    fn eof_packet_has_alignment_of_1() {
145        assert_eq!(std::mem::align_of::<EofPacket>(), 1);
146    }
147}