Skip to main content

zero_postgres/conversion/
bytes.rs

1//! Byte type implementations (`&[u8]`, `Vec<u8>`).
2
3use crate::error::{Error, Result};
4use crate::protocol::types::{Oid, oid};
5
6use super::{FromWireValue, ToWireValue};
7
8impl<'a> FromWireValue<'a> for &'a [u8] {
9    fn from_text(oid: Oid, bytes: &'a [u8]) -> Result<Self> {
10        if oid != oid::BYTEA {
11            return Err(Error::Decode(format!("cannot decode oid {} as bytes", oid)));
12        }
13        // Text format for bytea is hex-encoded: \x followed by hex digits
14        // For simplicity, we just return the raw bytes (caller can decode if needed)
15        Ok(bytes)
16    }
17
18    fn from_binary(oid: Oid, bytes: &'a [u8]) -> Result<Self> {
19        if oid != oid::BYTEA {
20            return Err(Error::Decode(format!("cannot decode oid {} as bytes", oid)));
21        }
22        Ok(bytes)
23    }
24}
25
26impl FromWireValue<'_> for Vec<u8> {
27    fn from_text(oid: Oid, bytes: &[u8]) -> Result<Self> {
28        if oid != oid::BYTEA {
29            return Err(Error::Decode(format!(
30                "cannot decode oid {} as Vec<u8>",
31                oid
32            )));
33        }
34        // Text format for bytea is hex-encoded: \xDEADBEEF
35        if bytes.starts_with(b"\\x") {
36            decode_hex(&bytes[2..])
37        } else {
38            // Fallback: return raw bytes
39            Ok(bytes.to_vec())
40        }
41    }
42
43    fn from_binary(oid: Oid, bytes: &[u8]) -> Result<Self> {
44        if oid != oid::BYTEA {
45            return Err(Error::Decode(format!(
46                "cannot decode oid {} as Vec<u8>",
47                oid
48            )));
49        }
50        Ok(bytes.to_vec())
51    }
52}
53
54impl ToWireValue for [u8] {
55    fn natural_oid(&self) -> Oid {
56        oid::BYTEA
57    }
58
59    fn encode(&self, target_oid: Oid, buf: &mut Vec<u8>) -> Result<()> {
60        match target_oid {
61            oid::BYTEA => {
62                buf.extend_from_slice(&(self.len() as i32).to_be_bytes());
63                buf.extend_from_slice(self);
64                Ok(())
65            }
66            _ => Err(Error::type_mismatch(self.natural_oid(), target_oid)),
67        }
68    }
69}
70
71impl ToWireValue for Vec<u8> {
72    fn natural_oid(&self) -> Oid {
73        oid::BYTEA
74    }
75
76    fn encode(&self, target_oid: Oid, buf: &mut Vec<u8>) -> Result<()> {
77        self.as_slice().encode(target_oid, buf)
78    }
79}
80
81/// Nibble lookup table: ASCII byte → 0x00–0x0F for valid hex, 0xFF for invalid.
82static HEX_LOOKUP: [u8; 256] = {
83    let mut table = [0xFF_u8; 256];
84    let mut i: usize = 0;
85    while i < 256 {
86        table[i] = match i as u8 {
87            b'0'..=b'9' => i as u8 - b'0',
88            b'a'..=b'f' => i as u8 - b'a' + 10,
89            b'A'..=b'F' => i as u8 - b'A' + 10,
90            _ => 0xFF,
91        };
92        i += 1;
93    }
94    table
95};
96
97/// Decode hex string to bytes
98fn decode_hex(hex: &[u8]) -> Result<Vec<u8>> {
99    if !hex.len().is_multiple_of(2) {
100        return Err(Error::Decode("invalid hex length".into()));
101    }
102
103    let mut result = Vec::with_capacity(hex.len() >> 1);
104    for pair in hex.chunks_exact(2) {
105        let &[hi, lo] = pair else {
106            return Err(Error::Decode("invalid hex length".into()));
107        };
108        let high = HEX_LOOKUP[hi as usize];
109        let low = HEX_LOOKUP[lo as usize];
110        if (high | low) > 0x0F {
111            return Err(Error::Decode("invalid hex digit".into()));
112        }
113        result.push((high << 4) | low);
114    }
115    Ok(result)
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    #[test]
123    fn bytea_hex() {
124        assert_eq!(
125            Vec::<u8>::from_text(oid::BYTEA, b"\\xDEADBEEF").unwrap(),
126            vec![0xDE, 0xAD, 0xBE, 0xEF]
127        );
128    }
129}