Skip to main content

firebird_wire/wire/
xdr.rs

1//! Codificação/decodificação XDR (RFC 4506) conforme usada pelo protocolo de comunicação (wire protocol) do Firebird.
2//!
3//! Tudo é big-endian e preenchido (padding) até um limite de 4 bytes. Os inteiros são
4//! de 32 bits no wire mesmo quando o valor lógico é menor. Este módulo
5//! fornece auxiliares [`XdrWriter`]/[`XdrReader`] em memória mais construtores de clumplet
6//! para os vários buffers de parâmetros (DPB/TPB/SPB/BPB/batch PB).
7
8#![allow(missing_docs)]
9
10use crate::error::{Error, Result};
11
12/// Arredonda `n` para cima até o próximo múltiplo de 4.
13#[inline]
14pub const fn pad4(n: usize) -> usize {
15    (n + 3) & !3
16}
17
18/// Constrói um fluxo (stream) de bytes XDR em memória.
19#[derive(Debug, Default, Clone)]
20pub struct XdrWriter {
21    buf: Vec<u8>,
22}
23
24impl XdrWriter {
25    #[inline]
26    pub fn new() -> Self {
27        Self {
28            buf: Vec::with_capacity(64),
29        }
30    }
31
32    #[inline]
33    pub fn with_capacity(cap: usize) -> Self {
34        Self {
35            buf: Vec::with_capacity(cap),
36        }
37    }
38
39    #[inline]
40    pub fn len(&self) -> usize {
41        self.buf.len()
42    }
43
44    #[inline]
45    pub fn is_empty(&self) -> bool {
46        self.buf.is_empty()
47    }
48
49    #[inline]
50    pub fn as_slice(&self) -> &[u8] {
51        &self.buf
52    }
53
54    #[inline]
55    pub fn into_vec(self) -> Vec<u8> {
56        self.buf
57    }
58
59    /// Anexa um inteiro big-endian de 32 bits.
60    #[inline]
61    pub fn put_i32(&mut self, v: i32) -> &mut Self {
62        self.buf.extend_from_slice(&v.to_be_bytes());
63        self
64    }
65
66    #[inline]
67    pub fn put_u32(&mut self, v: u32) -> &mut Self {
68        self.buf.extend_from_slice(&v.to_be_bytes());
69        self
70    }
71
72    /// Anexa um inteiro big-endian de 64 bits (duas palavras XDR).
73    #[inline]
74    pub fn put_i64(&mut self, v: i64) -> &mut Self {
75        self.buf.extend_from_slice(&v.to_be_bytes());
76        self
77    }
78
79    #[inline]
80    pub fn put_f64(&mut self, v: f64) -> &mut Self {
81        self.buf.extend_from_slice(&v.to_be_bytes());
82        self
83    }
84
85    /// Anexa bytes brutos sem prefixo de comprimento e sem preenchimento (padding).
86    #[inline]
87    pub fn put_raw(&mut self, bytes: &[u8]) -> &mut Self {
88        self.buf.extend_from_slice(bytes);
89        self
90    }
91
92    /// Preenche (padding) o buffer com bytes zero até o próximo limite de 4 bytes.
93    #[inline]
94    pub fn align(&mut self) -> &mut Self {
95        while !self.buf.len().is_multiple_of(4) {
96            self.buf.push(0);
97        }
98        self
99    }
100
101    /// Anexa um opaco/string XDR: comprimento de 4 bytes, os dados, depois preenchimento (padding) com zeros
102    /// até um limite de 4 bytes. Este é o formato `cstring`/`buffer` usado para DPBs,
103    /// texto SQL, blobs de mensagem, etc.
104    pub fn put_bytes(&mut self, data: &[u8]) -> &mut Self {
105        self.put_i32(data.len() as i32);
106        self.buf.extend_from_slice(data);
107        self.align();
108        self
109    }
110
111    /// Conveniência para [`Self::put_bytes`] sobre um slice de string.
112    #[inline]
113    pub fn put_str(&mut self, s: &str) -> &mut Self {
114        self.put_bytes(s.as_bytes())
115    }
116}
117
118/// Lê um fluxo (stream) de bytes XDR produzido pelo servidor.
119#[derive(Debug, Clone)]
120pub struct XdrReader<'a> {
121    buf: &'a [u8],
122    pos: usize,
123}
124
125impl<'a> XdrReader<'a> {
126    #[inline]
127    pub fn new(buf: &'a [u8]) -> Self {
128        Self { buf, pos: 0 }
129    }
130
131    #[inline]
132    pub fn position(&self) -> usize {
133        self.pos
134    }
135
136    #[inline]
137    pub fn remaining(&self) -> usize {
138        self.buf.len() - self.pos
139    }
140
141    #[inline]
142    pub fn is_empty(&self) -> bool {
143        self.remaining() == 0
144    }
145
146    fn need(&self, n: usize) -> Result<()> {
147        if self.remaining() < n {
148            return Err(Error::protocol(format!(
149                "short XDR read: need {n} bytes, have {}",
150                self.remaining()
151            )));
152        }
153        Ok(())
154    }
155
156    #[inline]
157    pub fn get_i32(&mut self) -> Result<i32> {
158        self.need(4)?;
159        let v = i32::from_be_bytes(self.buf[self.pos..self.pos + 4].try_into().unwrap());
160        self.pos += 4;
161        Ok(v)
162    }
163
164    #[inline]
165    pub fn get_u32(&mut self) -> Result<u32> {
166        Ok(self.get_i32()? as u32)
167    }
168
169    #[inline]
170    pub fn get_i64(&mut self) -> Result<i64> {
171        self.need(8)?;
172        let v = i64::from_be_bytes(self.buf[self.pos..self.pos + 8].try_into().unwrap());
173        self.pos += 8;
174        Ok(v)
175    }
176
177    #[inline]
178    pub fn get_f64(&mut self) -> Result<f64> {
179        Ok(f64::from_bits(self.get_i64()? as u64))
180    }
181
182    /// Lê `n` bytes brutos sem preenchimento (padding).
183    pub fn get_raw(&mut self, n: usize) -> Result<&'a [u8]> {
184        self.need(n)?;
185        let s = &self.buf[self.pos..self.pos + n];
186        self.pos += n;
187        Ok(s)
188    }
189
190    /// Pula o preenchimento (padding) até o próximo limite de 4 bytes, relativo ao início do buffer.
191    #[inline]
192    pub fn align(&mut self) -> Result<()> {
193        let pad = pad4(self.pos) - self.pos;
194        if pad > 0 {
195            self.need(pad)?;
196            self.pos += pad;
197        }
198        Ok(())
199    }
200
201    /// Lê um buffer opaco com prefixo de comprimento, alinhado em 4 bytes (espelho de
202    /// [`XdrWriter::put_bytes`]).
203    pub fn get_bytes(&mut self) -> Result<&'a [u8]> {
204        let len = self.get_i32()? as usize;
205        let data = self.get_raw(len)?;
206        self.align()?;
207        Ok(data)
208    }
209
210    /// Como [`Self::get_bytes`] mas retorna uma cópia própria.
211    pub fn get_bytes_owned(&mut self) -> Result<Vec<u8>> {
212        Ok(self.get_bytes()?.to_vec())
213    }
214}
215
216// ---------------------------------------------------------------------------
217// Construtores de buffer de parâmetros (clumplet)
218// ---------------------------------------------------------------------------
219
220/// Um buffer de parâmetros (DPB/TPB/SPB/BPB) construído como uma sequência de clumplets.
221///
222/// Os clumplets "tradicionais" são `tag(1) + length(1) + value`. A versão 2 do DPB
223/// do Firebird, em vez disso, usa comprimentos de 4 bytes; este construtor segue a forma clássica
224/// de 1 byte que todo servidor ainda aceita para os itens que emitimos.
225#[derive(Debug, Clone)]
226pub struct ParameterBuffer {
227    buf: Vec<u8>,
228}
229
230impl ParameterBuffer {
231    /// Inicia um buffer com o byte de versão fornecido (ex.: `DPB_VERSION1`).
232    pub fn new(version: u8) -> Self {
233        Self { buf: vec![version] }
234    }
235
236    /// Inicia um buffer sem byte de versão inicial (batch PB e alguns SPBs).
237    pub fn raw() -> Self {
238        Self { buf: Vec::new() }
239    }
240
241    #[inline]
242    pub fn is_empty(&self) -> bool {
243        // Um byte de versão sozinho conta como "sem parâmetros".
244        self.buf.len() <= 1
245    }
246
247    /// Uma tag isolada sem valor.
248    pub fn tag(&mut self, tag: u8) -> &mut Self {
249        self.buf.push(tag);
250        self
251    }
252
253    /// `tag + len + bytes`, comprimento codificado como um único byte (valor <= 255).
254    pub fn bytes(&mut self, tag: u8, value: &[u8]) -> &mut Self {
255        debug_assert!(value.len() <= u8::MAX as usize, "clumplet value too long");
256        self.buf.push(tag);
257        self.buf.push(value.len() as u8);
258        self.buf.extend_from_slice(value);
259        self
260    }
261
262    #[inline]
263    pub fn string(&mut self, tag: u8, value: &str) -> &mut Self {
264        self.bytes(tag, value.as_bytes())
265    }
266
267    /// Um clumplet cujo valor é um inteiro little-endian de largura mínima.
268    /// Os inteiros do buffer de parâmetros do Firebird são little-endian (diferente do quadro
269    /// XDR), codificados com o menor número de bytes que cabem.
270    pub fn int(&mut self, tag: u8, value: i32) -> &mut Self {
271        let le = value.to_le_bytes();
272        // Número de bytes significativos (pelo menos 1).
273        let mut n = 4;
274        while n > 1 && le[n - 1] == 0 {
275            n -= 1;
276        }
277        self.bytes(tag, &le[..n])
278    }
279
280    /// Um clumplet que carrega um valor `u32` little-endian de largura fixa.
281    pub fn int_u32(&mut self, tag: u8, value: u32) -> &mut Self {
282        self.bytes(tag, &value.to_le_bytes())
283    }
284
285    /// Um clumplet que carrega um valor `u64` little-endian de largura fixa (batch PB).
286    pub fn int_u64(&mut self, tag: u8, value: u64) -> &mut Self {
287        self.bytes(tag, &value.to_le_bytes())
288    }
289
290    /// `tag + len + bytes` usando um comprimento little-endian de 4 bytes, conforme exigido
291    /// pelos itens de comprimento variável do batch parameter buffer.
292    pub fn bytes_be_len4(&mut self, tag: u8, value: &[u8]) -> &mut Self {
293        self.buf.push(tag);
294        self.buf
295            .extend_from_slice(&(value.len() as u32).to_le_bytes());
296        self.buf.extend_from_slice(value);
297        self
298    }
299
300    #[inline]
301    pub fn as_slice(&self) -> &[u8] {
302        &self.buf
303    }
304
305    #[inline]
306    pub fn into_vec(self) -> Vec<u8> {
307        self.buf
308    }
309}
310
311/// Decodifica um inteiro little-endian de até 8 bytes (valor de item de info /
312/// buffer de parâmetros).
313pub fn read_le_int(bytes: &[u8]) -> i64 {
314    let mut v: i64 = 0;
315    for (i, &b) in bytes.iter().enumerate().take(8) {
316        v |= (b as i64) << (8 * i);
317    }
318    v
319}
320
321/// Decodifica um inteiro little-endian de até 8 bytes, estendendo o sinal a partir de sua
322/// largura. Usado para campos que podem ser negativos (ex.: o `scale` de uma coluna).
323pub fn read_le_int_signed(bytes: &[u8]) -> i64 {
324    let v = read_le_int(bytes);
325    let width = bytes.len().min(8);
326    if width == 0 || width == 8 {
327        return v;
328    }
329    let bits = width * 8;
330    let sign = 1i64 << (bits - 1);
331    if v & sign != 0 {
332        v | !((1i64 << bits) - 1) // define todos os bits altos acima da largura do valor
333    } else {
334        v
335    }
336}
337
338#[cfg(test)]
339mod tests {
340    use super::*;
341
342    #[test]
343    fn roundtrip_ints_and_bytes() {
344        let mut w = XdrWriter::new();
345        w.put_i32(-7)
346            .put_i64(1 << 40)
347            .put_bytes(b"hello")
348            .put_str("hi");
349        let bytes = w.into_vec();
350
351        let mut r = XdrReader::new(&bytes);
352        assert_eq!(r.get_i32().unwrap(), -7);
353        assert_eq!(r.get_i64().unwrap(), 1 << 40);
354        assert_eq!(r.get_bytes().unwrap(), b"hello");
355        assert_eq!(r.get_bytes().unwrap(), b"hi");
356        assert!(r.is_empty());
357    }
358
359    #[test]
360    fn put_bytes_is_padded() {
361        let mut w = XdrWriter::new();
362        w.put_bytes(b"abc"); // 4 (len) + 3 (dados) + 1 (preenchimento) = 8
363        assert_eq!(w.len(), 8);
364        assert_eq!(&w.as_slice()[4..7], b"abc");
365        assert_eq!(w.as_slice()[7], 0);
366    }
367
368    #[test]
369    fn clumplet_minimal_int_width() {
370        let mut pb = ParameterBuffer::new(crate::wire::consts::DPB_VERSION1);
371        pb.int(crate::wire::consts::dpb::SQL_DIALECT, 3);
372        // version(1) + tag(1) + len(1) + valor de 1 byte
373        assert_eq!(pb.as_slice(), &[1, 63, 1, 3]);
374    }
375
376    #[test]
377    fn le_int_decode() {
378        assert_eq!(read_le_int(&[0x10, 0x27]), 10000);
379        assert_eq!(read_le_int(&[0xff]), 255);
380    }
381}