Skip to main content

rupg_types/
binary.rs

1//! The binary format of the types in this crate.
2//!
3//! The send side needs no code: each value is its big-endian bytes, `bool` and `"char"` are one byte, and `uuid`, `bytea` and the string types are their bytes. The receive side reads a `Bind` parameter as the receive functions of PostgreSQL read their `StringInfo`, with the same errors when the value is too short. `exec_bind_message` in `src/backend/tcop/postgres.c` then checks that the function read the whole value, which is [`Recv::finish`].
4//!
5//! Lifted from `crates/rudb-pgtypes/src/binary.rs` of tamnd/rudb at f5f7065a (spec/04 section 4.9).
6
7use rupg_common::SqlState;
8
9use crate::error::TypeError;
10use crate::scalar::NAME_MAX_BYTES;
11
12/// A binary parameter value and how much of it a receive function has read.
13#[derive(Debug, Clone)]
14pub struct Recv<'a> {
15    data: &'a [u8],
16    at: usize,
17}
18
19impl<'a> Recv<'a> {
20    pub fn new(data: &'a [u8]) -> Recv<'a> {
21        Recv { data, at: 0 }
22    }
23
24    /// `pq_copymsgbytes` and `pq_getmsgbytes`.
25    pub fn bytes(&mut self, len: usize) -> Result<&'a [u8], TypeError> {
26        let Some(bytes) = self.data.get(self.at..self.at.saturating_add(len)) else {
27            return Err(TypeError::new(
28                SqlState::PROTOCOL_VIOLATION,
29                "insufficient data left in message".to_string(),
30            ));
31        };
32        self.at += len;
33        Ok(bytes)
34    }
35
36    fn array<const N: usize>(&mut self) -> Result<[u8; N], TypeError> {
37        let mut out = [0; N];
38        out.copy_from_slice(self.bytes(N)?);
39        Ok(out)
40    }
41
42    /// The number of bytes that are not read.
43    pub fn remaining(&self) -> usize {
44        self.data.len() - self.at
45    }
46
47    /// `pq_getmsgbyte`, which has its own text for an empty value.
48    pub fn byte(&mut self) -> Result<u8, TypeError> {
49        let Some(&byte) = self.data.get(self.at) else {
50            return Err(TypeError::new(
51                SqlState::PROTOCOL_VIOLATION,
52                "no data left in message".to_string(),
53            ));
54        };
55        self.at += 1;
56        Ok(byte)
57    }
58
59    /// The rest of the value, as `textrecv` and `bytearecv` read it.
60    pub fn rest(&mut self) -> &'a [u8] {
61        let rest = &self.data[self.at..];
62        self.at = self.data.len();
63        rest
64    }
65
66    /// The rest of the value as a string in the server encoding, as `pq_getmsgtext` reads it for `textrecv`, `varcharrecv`, `json_recv` and the other string types. The client encoding is UTF-8, so the check is the check of `pg_verify_mbstr`: a zero byte or a byte sequence that is not UTF-8 is an error that shows the bytes of the bad character.
67    pub fn text(&mut self) -> Result<&'a str, TypeError> {
68        utf8(self.rest())
69    }
70
71    /// `pq_getmsgstring`: the bytes up to a zero byte, which the function reads too, as a string in the server encoding with the check of [`Recv::text`].
72    pub fn cstring(&mut self) -> Result<&'a str, TypeError> {
73        let rest = &self.data[self.at..];
74        let Some(len) = rest.iter().position(|&b| b == 0) else {
75            return Err(TypeError::new(
76                SqlState::PROTOCOL_VIOLATION,
77                "invalid string in message".to_string(),
78            ));
79        };
80        self.at += len + 1;
81        utf8(&rest[..len])
82    }
83
84    /// `boolrecv`: any byte other than 0 is true.
85    pub fn bool(&mut self) -> Result<bool, TypeError> {
86        Ok(self.byte()? != 0)
87    }
88
89    pub fn i16(&mut self) -> Result<i16, TypeError> {
90        self.array().map(i16::from_be_bytes)
91    }
92
93    pub fn i32(&mut self) -> Result<i32, TypeError> {
94        self.array().map(i32::from_be_bytes)
95    }
96
97    pub fn i64(&mut self) -> Result<i64, TypeError> {
98        self.array().map(i64::from_be_bytes)
99    }
100
101    /// `oidrecv`.
102    pub fn u32(&mut self) -> Result<u32, TypeError> {
103        self.array().map(u32::from_be_bytes)
104    }
105
106    pub fn f32(&mut self) -> Result<f32, TypeError> {
107        self.array().map(f32::from_be_bytes)
108    }
109
110    pub fn f64(&mut self) -> Result<f64, TypeError> {
111        self.array().map(f64::from_be_bytes)
112    }
113
114    pub fn uuid(&mut self) -> Result<[u8; 16], TypeError> {
115        self.array()
116    }
117
118    /// The check after the receive function: parameter `number`, from 1, must have no bytes left. A field of `COPY ... FROM` has the number 0.
119    pub fn finish(self, number: usize) -> Result<(), TypeError> {
120        if self.at == self.data.len() {
121            return Ok(());
122        }
123        // `COPY ... FROM` with the binary format gives no parameter number.
124        let message = if number == 0 {
125            "incorrect binary data format".to_string()
126        } else {
127            format!("incorrect binary data format in bind parameter {number}")
128        };
129        Err(TypeError::new(SqlState::INVALID_BINARY_REPRESENTATION, message))
130    }
131}
132
133/// The check of `pg_verify_mbstr` for a string from the client: a zero byte or a byte sequence that is not UTF-8 is an error that shows the bytes of the bad character.
134fn utf8(rest: &[u8]) -> Result<&str, TypeError> {
135    let valid = match std::str::from_utf8(rest) {
136        Ok(text) if !text.contains('\0') => return Ok(text),
137        Ok(text) => text.len(),
138        Err(error) => error.valid_up_to(),
139    };
140    let bad = rest[..valid].iter().position(|&b| b == 0).unwrap_or(valid);
141    // pg_encoding_mblen_or_incomplete gives the length from the first byte.
142    let len = match rest[bad] {
143        b if b & 0x80 == 0 => 1,
144        b if b & 0xe0 == 0xc0 => 2,
145        b if b & 0xf0 == 0xe0 => 3,
146        b if b & 0xf8 == 0xf0 => 4,
147        _ => 1,
148    };
149    let bytes: Vec<String> = rest[bad..].iter().take(len).map(|b| format!("0x{b:02x}")).collect();
150    Err(TypeError::new(
151        SqlState::CHARACTER_NOT_IN_REPERTOIRE,
152        format!("invalid byte sequence for encoding \"UTF8\": {}", bytes.join(" ")),
153    ))
154}
155
156/// `namerecv`: unlike the text input, the binary input refuses a name that is too long. The caller checks the encoding first, as `pq_getmsgtext` does.
157pub fn name_recv(text: &str) -> Result<&str, TypeError> {
158    if text.len() <= NAME_MAX_BYTES {
159        return Ok(text);
160    }
161    let mut error = TypeError::new(SqlState::NAME_TOO_LONG, "identifier too long".to_string());
162    error.detail = Some(format!("Identifier must be less than {} characters.", NAME_MAX_BYTES + 1));
163    Err(error)
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169
170    #[test]
171    fn a_value_must_have_the_size_of_its_type() {
172        let mut recv = Recv::new(&[0, 0, 0, 42]);
173        assert_eq!(recv.i32(), Ok(42));
174        assert_eq!(recv.finish(1), Ok(()));
175
176        let mut recv = Recv::new(&[0, 0, 42]);
177        let short = recv.i32().unwrap_err();
178        assert_eq!(
179            (short.sqlstate.as_str(), short.message.as_str()),
180            ("08P01", "insufficient data left in message")
181        );
182
183        let mut recv = Recv::new(&[0, 0, 0, 0, 42]);
184        assert_eq!(recv.i32(), Ok(0));
185        let long = recv.finish(3).unwrap_err();
186        assert_eq!(
187            (long.sqlstate.as_str(), long.message.as_str()),
188            ("22P03", "incorrect binary data format in bind parameter 3")
189        );
190
191        assert_eq!(Recv::new(&[]).bool().unwrap_err().message, "no data left in message");
192        assert_eq!(Recv::new(&[7]).bool(), Ok(true));
193        assert_eq!(Recv::new(&1.5f64.to_be_bytes()).f64(), Ok(1.5));
194        assert_eq!(Recv::new(&[0xff; 4]).u32(), Ok(u32::MAX));
195        let mut recv = Recv::new(b"abc");
196        assert_eq!((recv.rest(), recv.finish(1)), (&b"abc"[..], Ok(())));
197    }
198
199    #[test]
200    fn a_string_must_be_utf8_with_no_zero_byte() {
201        assert_eq!(Recv::new("aé".as_bytes()).text(), Ok("aé"));
202        let message = |bytes: &[u8]| Recv::new(bytes).text().unwrap_err().message;
203        assert_eq!(message(b"a\0b"), "invalid byte sequence for encoding \"UTF8\": 0x00");
204        assert_eq!(message(b"a\xc3("), "invalid byte sequence for encoding \"UTF8\": 0xc3 0x28");
205        assert_eq!(message(b"\xe2\x82"), "invalid byte sequence for encoding \"UTF8\": 0xe2 0x82");
206        assert_eq!(message(b"\xff"), "invalid byte sequence for encoding \"UTF8\": 0xff");
207        assert_eq!(Recv::new(b"\xed\xa0\x80").text().unwrap_err().sqlstate.as_str(), "22021");
208    }
209
210    #[test]
211    fn a_long_name_is_an_error_in_binary() {
212        assert_eq!(name_recv(&"n".repeat(63)).map(str::len), Ok(63));
213        let error = name_recv(&"n".repeat(64)).unwrap_err();
214        assert_eq!(
215            (error.sqlstate.as_str(), error.message.as_str()),
216            ("42622", "identifier too long")
217        );
218        assert_eq!(error.detail.as_deref(), Some("Identifier must be less than 64 characters."));
219    }
220}