Skip to main content

rsfbclient_rust/
client.rs

1//! `FirebirdConnection` implementation for the pure rust firebird client
2
3use bytes::{BufMut, Bytes, BytesMut};
4use std::{
5    collections::VecDeque,
6    env,
7    io::{Read, Write},
8    net::{SocketAddr, TcpStream},
9};
10
11use crate::{
12    arc4::*,
13    blr,
14    consts::{AuthPluginType, ProtocolVersion, WireOp},
15    events::*,
16    srp::*,
17    util::*,
18    wire::*,
19    xsqlda::{parse_xsqlda, xsqlda_to_blr, PrepareInfo, XSqlVar, XSQLDA_DESCRIBE_VARS},
20};
21use rsfbclient_core::*;
22
23type RustDbHandle = DbHandle;
24type RustTrHandle = TrHandle;
25type RustStmtHandle = StmtHandle;
26
27/// How many rows to request per op_fetch (round-trip). Configurable via
28/// FB_FETCH_BATCH; defaults to 200. The crate originally used 1 (one row per round-trip).
29fn fetch_batch_size() -> u32 {
30    env::var("FB_FETCH_BATCH")
31        .ok()
32        .and_then(|v| v.parse().ok())
33        .filter(|&n| n > 0)
34        .unwrap_or(200)
35}
36
37/// Result of parsing ONE op_fetch_response. Blob columns are still unresolved:
38/// fetching them costs extra round-trips that must not be interleaved with the
39/// responses of the running batch (see `fetch_batch`).
40enum FetchOne {
41    /// A row (status=0, messages=1).
42    Row(Vec<ParsedColumn>),
43    /// End of THIS batch (status=0, messages=0): the server ended the op_fetch
44    /// without exhausting the cursor. Re-issuing op_fetch fetches the rest.
45    BatchEnd,
46    /// End of cursor (status=100). Nothing more to read.
47    End,
48}
49
50/// Firebird client implemented in pure rust
51pub struct RustFbClient {
52    conn: Option<FirebirdWireConnection>,
53    charset: Charset,
54}
55
56/// Required configuration for an attachment with the pure rust client
57#[derive(Default, Clone)]
58pub struct RustFbClientAttachmentConfig {
59    pub host: String,
60    pub port: u16,
61    pub db_name: String,
62    pub user: String,
63    pub pass: String,
64    pub role_name: Option<String>,
65}
66
67/// A Connection to a firebird server
68pub struct FirebirdWireConnection {
69    /// Connection socket
70    socket: FbStream,
71
72    /// Wire protocol version
73    pub(crate) version: ProtocolVersion,
74
75    /// Scratch buffer for a single socket read
76    buff: Box<[u8]>,
77
78    /// Bytes received from the socket but not consumed yet.
79    ///
80    /// The wire protocol is a byte stream with no packet framing, so one read()
81    /// can return half a response or several responses at once. Whatever is left
82    /// over after parsing has to survive until the next read — dropping it (as
83    /// the old per-call buffer did) desynchronises the stream and every later
84    /// operation reads garbage as its op code.
85    pending: Bytes,
86
87    /// Lazy responses to read
88    lazy_count: u32,
89
90    pub(crate) charset: Charset,
91
92    /// AuthPlugin data for use on attach when WireCrypt = Disabled
93    pub(crate) auth_plugin: Option<AuthPlugin>,
94    /// Key for the srp auth
95    pub(crate) srp_key: [u8; 32],
96
97    /// Auxiliary connection the server pushes the event notifications on.
98    /// Opened on the first wait, and reused afterwards: firebird keeps a single
99    /// auxiliary port per attachment
100    event_channel: Option<EventChannel>,
101
102    /// Id to use for the next event registration
103    next_event_id: u32,
104}
105
106/// Data to keep track about a prepared statement
107pub struct StmtHandleData {
108    /// Statement handle
109    handle: RustStmtHandle,
110    /// Output xsqlda
111    xsqlda: Vec<XSqlVar>,
112    /// Blr representation of the above
113    blr: Bytes,
114    /// Number of parameters
115    param_count: usize,
116    /// Rows already fetched in a batch but not yet delivered (batch fetch).
117    prefetched: VecDeque<Vec<Column>>,
118    /// Cursor exhausted on the server (do not request more batches).
119    cursor_eof: bool,
120}
121
122impl RustFbClient {
123    ///Construct a new instance of the pure rust client
124    pub fn new(charset: Charset) -> Self {
125        Self {
126            conn: None,
127            charset,
128        }
129    }
130}
131
132impl FirebirdClientDbOps for RustFbClient {
133    type DbHandle = RustDbHandle;
134    type AttachmentConfig = RustFbClientAttachmentConfig;
135
136    fn attach_database(
137        &mut self,
138        config: &Self::AttachmentConfig,
139        dialect: Dialect,
140        no_db_triggers: bool,
141    ) -> Result<RustDbHandle, FbError> {
142        let host = config.host.as_str();
143        let port = config.port;
144        let db_name = config.db_name.as_str();
145        let user = config.user.as_str();
146        let pass = config.pass.as_str();
147        let role = match &config.role_name {
148            Some(ro) => Some(ro.as_str()),
149            None => None,
150        };
151
152        // Take the existing connection, or connects
153        let mut conn = match self.conn.take() {
154            Some(conn) => conn,
155            None => FirebirdWireConnection::connect(
156                host,
157                port,
158                db_name,
159                user,
160                pass,
161                self.charset.clone(),
162            )?,
163        };
164
165        let attach_result =
166            conn.attach_database(db_name, user, pass, role, dialect, no_db_triggers);
167
168        // Put the connection back
169        self.conn.replace(conn);
170
171        attach_result
172    }
173
174    fn detach_database(&mut self, db_handle: &mut RustDbHandle) -> Result<(), FbError> {
175        self.conn
176            .as_mut()
177            .map(|conn| conn.detach_database(db_handle))
178            .unwrap_or_else(err_client_not_connected)
179    }
180
181    fn drop_database(&mut self, db_handle: &mut RustDbHandle) -> Result<(), FbError> {
182        self.conn
183            .as_mut()
184            .map(|conn| conn.drop_database(db_handle))
185            .unwrap_or_else(err_client_not_connected)
186    }
187
188    fn create_database(
189        &mut self,
190        config: &Self::AttachmentConfig,
191        page_size: Option<u32>,
192        dialect: Dialect,
193    ) -> Result<RustDbHandle, FbError> {
194        let host = config.host.as_str();
195        let port = config.port;
196        let db_name = config.db_name.as_str();
197        let user = config.user.as_str();
198        let pass = config.pass.as_str();
199        let role = match &config.role_name {
200            Some(ro) => Some(ro.as_str()),
201            None => None,
202        };
203
204        // Take the existing connection, or connects
205        let mut conn = match self.conn.take() {
206            Some(conn) => conn,
207            None => FirebirdWireConnection::connect(
208                host,
209                port,
210                db_name,
211                user,
212                pass,
213                self.charset.clone(),
214            )?,
215        };
216
217        let attach_result = conn.create_database(db_name, user, pass, page_size, role, dialect);
218
219        // Put the connection back
220        self.conn.replace(conn);
221
222        attach_result
223    }
224}
225
226impl FirebirdClientSqlOps for RustFbClient {
227    type DbHandle = RustDbHandle;
228    type TrHandle = RustTrHandle;
229    type StmtHandle = StmtHandleData;
230
231    fn begin_transaction(
232        &mut self,
233        db_handle: &mut Self::DbHandle,
234        confs: TransactionConfiguration,
235    ) -> Result<Self::TrHandle, FbError> {
236        self.conn
237            .as_mut()
238            .map(|conn| conn.begin_transaction(db_handle, confs))
239            .unwrap_or_else(err_client_not_connected)
240    }
241
242    fn transaction_operation(
243        &mut self,
244        tr_handle: &mut Self::TrHandle,
245        op: TrOp,
246    ) -> Result<(), FbError> {
247        self.conn
248            .as_mut()
249            .map(|conn| conn.transaction_operation(tr_handle, op))
250            .unwrap_or_else(err_client_not_connected)
251    }
252
253    fn exec_immediate(
254        &mut self,
255        _db_handle: &mut Self::DbHandle,
256        tr_handle: &mut Self::TrHandle,
257        dialect: Dialect,
258        sql: &str,
259    ) -> Result<(), FbError> {
260        self.conn
261            .as_mut()
262            .map(|conn| conn.exec_immediate(tr_handle, dialect, sql))
263            .unwrap_or_else(err_client_not_connected)
264    }
265
266    fn prepare_statement(
267        &mut self,
268        db_handle: &mut Self::DbHandle,
269        tr_handle: &mut Self::TrHandle,
270        dialect: Dialect,
271        sql: &str,
272    ) -> Result<(StmtType, Self::StmtHandle), FbError> {
273        self.conn
274            .as_mut()
275            .map(|conn| conn.prepare_statement(db_handle, tr_handle, dialect, sql))
276            .unwrap_or_else(err_client_not_connected)
277    }
278
279    fn free_statement(
280        &mut self,
281        stmt_handle: &mut Self::StmtHandle,
282        op: FreeStmtOp,
283    ) -> Result<(), FbError> {
284        self.conn
285            .as_mut()
286            .map(|conn| conn.free_statement(stmt_handle, op))
287            .unwrap_or_else(err_client_not_connected)
288    }
289
290    fn execute(
291        &mut self,
292        _db_handle: &mut Self::DbHandle,
293        tr_handle: &mut Self::TrHandle,
294        stmt_handle: &mut Self::StmtHandle,
295        params: Vec<SqlType>,
296    ) -> Result<usize, FbError> {
297        self.conn
298            .as_mut()
299            .map(|conn| conn.execute(tr_handle, stmt_handle, &params))
300            .unwrap_or_else(err_client_not_connected)
301    }
302
303    fn execute2(
304        &mut self,
305        _db_handle: &mut Self::DbHandle,
306        tr_handle: &mut Self::TrHandle,
307        stmt_handle: &mut Self::StmtHandle,
308        params: Vec<SqlType>,
309    ) -> Result<Vec<Column>, FbError> {
310        self.conn
311            .as_mut()
312            .map(|conn| conn.execute2(tr_handle, stmt_handle, &params))
313            .unwrap_or_else(err_client_not_connected)
314    }
315
316    fn fetch(
317        &mut self,
318        _db_handle: &mut Self::DbHandle,
319        tr_handle: &mut Self::TrHandle,
320        stmt_handle: &mut Self::StmtHandle,
321    ) -> Result<Option<Vec<Column>>, FbError> {
322        self.conn
323            .as_mut()
324            .map(|conn| conn.fetch(tr_handle, stmt_handle))
325            .unwrap_or_else(err_client_not_connected)
326    }
327}
328
329impl FirebirdClientDbEvents for RustFbClient {
330    fn wait_for_event(
331        &mut self,
332        db_handle: &mut Self::DbHandle,
333        name: String,
334    ) -> Result<(), FbError> {
335        self.conn
336            .as_mut()
337            .map(|conn| conn.wait_for_event(db_handle, &name))
338            .unwrap_or_else(err_client_not_connected)
339    }
340}
341
342fn err_client_not_connected<T>() -> Result<T, FbError> {
343    Err("Client not connected to the server, call `attach_database` to connect".into())
344}
345
346impl FirebirdWireConnection {
347    /// Start a connection to the firebird server
348    pub fn connect(
349        host: &str,
350        port: u16,
351        db_name: &str,
352        user: &str,
353        pass: &str,
354        charset: Charset,
355    ) -> Result<Self, FbError> {
356        let socket = TcpStream::connect((host, port))?;
357        // The wire protocol is request/response with small writes, so Nagle has
358        // nothing to coalesce and every statement waits out a delayed ACK
359        // (~40ms). Measured against Firebird 2.5: SELECT 1 FROM RDB$DATABASE
360        // 44ms -> 0.2ms, a 1000-row select 107ms -> 5.7ms.
361        let _ = socket.set_nodelay(true);
362
363        // System username
364        let username =
365            env::var("USER").unwrap_or_else(|_| env::var("USERNAME").unwrap_or_default());
366        let hostname = socket
367            .local_addr()
368            .map(|addr| addr.to_string())
369            .unwrap_or_default();
370
371        let mut socket = FbStream::Plain(socket);
372
373        // Random key for the srp
374        let srp_key: [u8; 32] = rand::random();
375
376        let req = connect(db_name, user, &username, &hostname, &srp_key);
377        socket.write_all(&req)?;
378        socket.flush()?;
379
380        // May be a bit too much
381        let mut buff = vec![0; BUFFER_LENGTH as usize * 2].into_boxed_slice();
382        let mut pending = Bytes::new();
383
384        let ConnectionResponse {
385            version,
386            mut auth_plugin,
387            continue_auth,
388        } = read_with(&mut socket, &mut buff, &mut pending, &mut 0, |resp, _| {
389            parse_accept(resp)
390        })?;
391
392        if let Some(auth_plugin) = &mut auth_plugin {
393            loop {
394                match auth_plugin.kind {
395                    plugin @ AuthPluginType::Srp => {
396                        let srp = SrpClient::<sha1::Sha1>::new(&srp_key, &SRP_GROUP);
397
398                        if let Some(data) = auth_plugin.data.clone() {
399                            if continue_auth {
400                                // Continue autentication if needed
401                                socket = srp_auth(
402                                    socket,
403                                    &mut buff,
404                                    &mut pending,
405                                    srp,
406                                    plugin,
407                                    user,
408                                    pass,
409                                    &data,
410                                )?;
411                            }
412
413                            // Authentication Ok
414                            break;
415                        } else {
416                            // Server requested a different authentication method than the client specified
417                            // in the initial connection
418
419                            socket.write_all(&cont_auth(
420                                hex::encode(srp.get_a_pub()).as_bytes(),
421                                plugin,
422                                AuthPluginType::plugin_list(),
423                                &[],
424                            ))?;
425                            socket.flush()?;
426
427                            *auth_plugin = read_with(
428                                &mut socket,
429                                &mut buff,
430                                &mut pending,
431                                &mut 0,
432                                |resp, _| parse_cont_auth(resp),
433                            )?;
434                        }
435                    }
436                    plugin @ AuthPluginType::Srp256 => {
437                        let srp = SrpClient::<sha2::Sha256>::new(&srp_key, &SRP_GROUP);
438
439                        if let Some(data) = auth_plugin.data.clone() {
440                            if continue_auth {
441                                // Continue autentication if needed
442                                socket = srp_auth(
443                                    socket,
444                                    &mut buff,
445                                    &mut pending,
446                                    srp,
447                                    plugin,
448                                    user,
449                                    pass,
450                                    &data,
451                                )?;
452                            }
453
454                            // Authentication Ok
455                            break;
456                        } else {
457                            // Server requested a different authentication method than the client specified
458                            // in the initial connection
459
460                            socket.write_all(&cont_auth(
461                                hex::encode(srp.get_a_pub()).as_bytes(),
462                                plugin,
463                                AuthPluginType::plugin_list(),
464                                &[],
465                            ))?;
466                            socket.flush()?;
467
468                            *auth_plugin = read_with(
469                                &mut socket,
470                                &mut buff,
471                                &mut pending,
472                                &mut 0,
473                                |resp, _| parse_cont_auth(resp),
474                            )?;
475                        }
476                    }
477                }
478            }
479        }
480
481        Ok(Self {
482            socket,
483            version,
484            buff,
485            pending,
486            lazy_count: 0,
487            charset,
488            auth_plugin: if continue_auth {
489                // Already authenticated
490                None
491            } else {
492                // Needs to authenticate in attach
493                auth_plugin
494            },
495            srp_key,
496            event_channel: None,
497            next_event_id: 1,
498        })
499    }
500
501    /// Create the database and attach, returning a database handle
502    pub fn create_database(
503        &mut self,
504        db_name: &str,
505        user: &str,
506        pass: &str,
507        page_size: Option<u32>,
508        role_name: Option<&str>,
509        dialect: Dialect,
510    ) -> Result<DbHandle, FbError> {
511        self.socket.write_all(&create(
512            db_name,
513            user,
514            pass,
515            self.version,
516            self.charset.clone(),
517            page_size,
518            role_name,
519            dialect,
520            self.auth_plugin.as_ref(),
521            &self.srp_key,
522        )?)?;
523        self.socket.flush()?;
524
525        let resp = self.read_response()?;
526
527        Ok(DbHandle(resp.handle))
528    }
529
530    /// Connect to a database, returning a database handle
531    pub fn attach_database(
532        &mut self,
533        db_name: &str,
534        user: &str,
535        pass: &str,
536        role_name: Option<&str>,
537        dialect: Dialect,
538        no_db_triggers: bool,
539    ) -> Result<DbHandle, FbError> {
540        self.socket.write_all(&attach(
541            db_name,
542            user,
543            pass,
544            self.version,
545            self.charset.clone(),
546            role_name,
547            dialect,
548            no_db_triggers,
549            self.auth_plugin.as_ref(),
550            &self.srp_key,
551        )?)?;
552        self.socket.flush()?;
553
554        let resp = self.read_response()?;
555
556        Ok(DbHandle(resp.handle))
557    }
558
559    /// Disconnect from the database
560    pub fn detach_database(&mut self, db_handle: &mut DbHandle) -> Result<(), FbError> {
561        // The server drops the auxiliary port along with the attachment
562        self.close_event_channel();
563
564        self.socket.write_all(&detach(db_handle.0))?;
565        self.socket.flush()?;
566
567        self.read_response()?;
568
569        Ok(())
570    }
571
572    /// Drop the database
573    pub fn drop_database(&mut self, db_handle: &mut DbHandle) -> Result<(), FbError> {
574        // The server drops the auxiliary port along with the attachment
575        self.close_event_channel();
576
577        self.socket.write_all(&drop_database(db_handle.0))?;
578        self.socket.flush()?;
579
580        self.read_response()?;
581
582        Ok(())
583    }
584
585    /// Wait until `name` is posted on the database.
586    ///
587    /// Blocks the connection: the notification arrives on the auxiliary
588    /// channel, but registering the interest and acknowledging it both happen
589    /// on this connection.
590    pub fn wait_for_event(&mut self, db_handle: &mut DbHandle, name: &str) -> Result<(), FbError> {
591        let name = normalize_event_name(name)?;
592
593        self.open_event_channel(db_handle)?;
594
595        // Firebird considers an interest satisfied as soon as the counter it
596        // holds for the event has reached the counter of the registration, so
597        // registering with a counter of zero always fires straight away. That
598        // first notification carries the current counter and is a
599        // synchronization, not an event. The native client has to do the very
600        // same thing, hence its two `isc_wait_for_event` calls.
601        let event_id = self.new_event_id();
602        let counters = self.que_events(db_handle, name, 0, event_id)?;
603        let posted = event_count(&counters, name)?;
604
605        // Now that the current counter is known, register again: this time the
606        // server only answers once the event is really posted
607        let event_id = self.new_event_id();
608        self.que_events(db_handle, name, posted, event_id)?;
609
610        Ok(())
611    }
612
613    /// Allocate the id of a new event registration.
614    ///
615    /// Every registration gets its own id, so the notification of a previous
616    /// one can never be taken for the one being waited on. Zero is skipped:
617    /// firebird uses it to mark a registration as already handled.
618    fn new_event_id(&mut self) -> u32 {
619        let event_id = self.next_event_id;
620
621        self.next_event_id = self.next_event_id.wrapping_add(1).max(1);
622
623        event_id
624    }
625
626    /// Register an interest in `name` and block until the server notifies it.
627    ///
628    /// Returns the occurrence counters of the notification.
629    fn que_events(
630        &mut self,
631        db_handle: &mut DbHandle,
632        name: &str,
633        count: u32,
634        event_id: u32,
635    ) -> Result<Vec<(String, u32)>, FbError> {
636        // Encoded with the charset of the connection, like every other string
637        // this connection sends
638        let epb = event_block(&self.charset, [(name, count)])?;
639
640        self.socket
641            .write_all(&que_events(db_handle.0, &epb, event_id))?;
642        self.socket.flush()?;
643
644        // The registration is acknowledged here, the notification itself comes
645        // later on the auxiliary channel
646        self.read_response()?;
647
648        let channel = match self.event_channel.as_mut() {
649            Some(channel) => channel,
650            None => return Err(FbError::from("The event channel was closed")),
651        };
652
653        match channel.recv_event(event_id) {
654            Ok(counters) => Ok(counters),
655
656            Err(err) => {
657                // The notification will never arrive, so release the
658                // registration the server is still holding. Best effort: the
659                // whole connection may be gone.
660                self.event_channel = None;
661                self.cancel_events(db_handle, event_id).ok();
662
663                Err(err)
664            }
665        }
666    }
667
668    /// Cancel a pending event registration
669    fn cancel_events(&mut self, db_handle: &mut DbHandle, event_id: u32) -> Result<(), FbError> {
670        self.socket
671            .write_all(&cancel_events(db_handle.0, event_id))?;
672        self.socket.flush()?;
673
674        self.read_response()?;
675
676        Ok(())
677    }
678
679    /// Open the auxiliary connection the event notifications are pushed on, if
680    /// it is not already open for this database handle
681    fn open_event_channel(&mut self, db_handle: &mut DbHandle) -> Result<(), FbError> {
682        if matches!(&self.event_channel, Some(channel) if channel.db_handle() == db_handle.0) {
683            return Ok(());
684        }
685        self.event_channel = None;
686
687        self.socket.write_all(&connect_request(db_handle.0))?;
688        self.socket.flush()?;
689
690        let resp = self.read_response()?;
691        let port = parse_aux_port(&resp.data)?;
692
693        // The server is listening by the time it answered, so connect now: it
694        // gives up on the auxiliary port after `ConnectionTimeout` seconds
695        let peer = self.socket.peer_addr()?;
696        self.event_channel = Some(EventChannel::open(
697            db_handle.0,
698            self.charset.clone(),
699            peer,
700            port,
701        )?);
702
703        Ok(())
704    }
705
706    /// Close the auxiliary event connection, if any
707    fn close_event_channel(&mut self) {
708        self.event_channel = None;
709    }
710
711    /// Start a new transaction, with the specified transaction parameter buffer
712    pub fn begin_transaction(
713        &mut self,
714        db_handle: &mut DbHandle,
715        confs: TransactionConfiguration,
716    ) -> Result<TrHandle, FbError> {
717        let mut tpb = vec![
718            ibase::isc_tpb_version3 as u8,
719            confs.isolation.into(),
720            confs.data_access as u8,
721            confs.lock_resolution.into(),
722        ];
723        if let TrLockResolution::Wait(Some(time)) = confs.lock_resolution {
724            tpb.push(ibase::isc_tpb_lock_timeout as u8);
725            tpb.push(4 as u8);
726            tpb.extend_from_slice(&time.to_le_bytes());
727        }
728
729        if let TrIsolationLevel::ReadCommited(rec) = confs.isolation {
730            tpb.push(rec as u8);
731        }
732
733        self.socket.write_all(&transaction(db_handle.0, &tpb))?;
734        self.socket.flush()?;
735
736        let resp = self.read_response()?;
737
738        Ok(TrHandle(resp.handle))
739    }
740
741    /// Commit / Rollback a transaction
742    pub fn transaction_operation(
743        &mut self,
744        tr_handle: &mut TrHandle,
745        op: TrOp,
746    ) -> Result<(), FbError> {
747        self.socket
748            .write_all(&transaction_operation(tr_handle.0, op))?;
749        self.socket.flush()?;
750
751        self.read_response()?;
752
753        Ok(())
754    }
755
756    /// Execute a sql immediately, without returning rows
757    pub fn exec_immediate(
758        &mut self,
759        tr_handle: &mut TrHandle,
760        dialect: Dialect,
761        sql: &str,
762    ) -> Result<(), FbError> {
763        self.socket.write_all(&exec_immediate(
764            tr_handle.0,
765            dialect as u32,
766            sql,
767            &self.charset,
768        )?)?;
769        self.socket.flush()?;
770
771        self.read_response()?;
772
773        Ok(())
774    }
775
776    /// Alloc and prepare a statement
777    ///
778    /// Returns the statement type, handle and xsqlda describing the columns
779    pub fn prepare_statement(
780        &mut self,
781        db_handle: &mut DbHandle,
782        tr_handle: &mut TrHandle,
783        dialect: Dialect,
784        sql: &str,
785    ) -> Result<(StmtType, StmtHandleData), FbError> {
786        // Alloc statement
787        self.socket.write_all(&allocate_statement(db_handle.0))?;
788        // Prepare statement
789        self.socket.write_all(&prepare_statement(
790            tr_handle.0,
791            u32::MAX,
792            dialect as u32,
793            sql,
794            &self.charset,
795        )?)?;
796        self.socket.flush()?;
797
798        // Both responses (alloc + prepare) come back in one go. The prepare one
799        // carries the xsqlda and is easily larger than a single read for a wide
800        // table, so it has to be read until it is complete.
801        let (stmt_handle, mut prepare_data) = read_with(
802            &mut self.socket,
803            &mut self.buff,
804            &mut self.pending,
805            &mut self.lazy_count,
806            |resp, lazy_count| {
807                // Alloc resp
808                let op_code = skip_lazy_responses(resp, lazy_count)?;
809                if op_code != WireOp::Response as u32 {
810                    return err_conn_rejected(op_code);
811                }
812                let stmt_handle = StmtHandle(parse_response(resp)?.handle);
813
814                // Prepare resp
815                let op_code = next_op_code(resp)?;
816                if op_code != WireOp::Response as u32 {
817                    return err_conn_rejected(op_code);
818                }
819
820                Ok((stmt_handle, parse_response(resp)?.data))
821            },
822        )?;
823
824        // Parsed outside the retry above: `data` is length-delimited inside the
825        // response, so it is complete by construction here, and parse_xsqlda
826        // appends to `xsqlda` — retrying it would duplicate columns.
827        let mut xsqlda = Vec::new();
828
829        let PrepareInfo {
830            stmt_type,
831            mut param_count,
832            mut truncated,
833        } = parse_xsqlda(&mut prepare_data, &mut xsqlda)?;
834
835        while truncated {
836            // Get more info on the types
837            let next_index = (xsqlda.len() as u16).to_le_bytes();
838
839            self.socket.write_all(&info_sql(
840                stmt_handle.0,
841                &[
842                    &[
843                        ibase::isc_info_sql_sqlda_start as u8, // Describe a xsqlda
844                        2,
845                        next_index[0], // Index, first byte
846                        next_index[1], // Index, second byte
847                    ],
848                    &XSQLDA_DESCRIBE_VARS[..], // Data to be returned
849                ]
850                .concat(),
851            ))?;
852            self.socket.flush()?;
853
854            let mut data = self.read_response()?.data;
855
856            let parse_resp = parse_xsqlda(&mut data, &mut xsqlda)?;
857            truncated = parse_resp.truncated;
858            param_count = parse_resp.param_count;
859        }
860
861        // Coerce the output columns and transform to blr
862        for var in xsqlda.iter_mut() {
863            var.coerce()?;
864        }
865        let blr = xsqlda_to_blr(&xsqlda)?;
866
867        Ok((
868            stmt_type,
869            StmtHandleData {
870                handle: stmt_handle,
871                xsqlda,
872                blr,
873                param_count,
874                prefetched: VecDeque::new(),
875                cursor_eof: false,
876            },
877        ))
878    }
879
880    /// Closes or drops a statement
881    pub fn free_statement(
882        &mut self,
883        stmt_handle: &mut StmtHandleData,
884        op: FreeStmtOp,
885    ) -> Result<(), FbError> {
886        self.socket
887            .write_all(&free_statement(stmt_handle.handle.0, op))?;
888        // Obs.: Lazy response
889
890        self.lazy_count += 1;
891
892        Ok(())
893    }
894
895    /// Execute the prepared statement with parameters
896    pub fn execute(
897        &mut self,
898        tr_handle: &mut TrHandle,
899        stmt_handle: &mut StmtHandleData,
900        params: &[SqlType],
901    ) -> Result<usize, FbError> {
902        if params.len() != stmt_handle.param_count {
903            return Err(format!(
904                "Tried to execute a statement that has {} parameters while providing {}",
905                stmt_handle.param_count,
906                params.len()
907            )
908            .into());
909        }
910
911        // Reopen the cursor: drop prefetched rows and the batch-fetch EOF flag
912        // from the previous execution. Without this, re-executing the same
913        // statement would inherit cursor_eof=true and fetch nothing.
914        stmt_handle.prefetched.clear();
915        stmt_handle.cursor_eof = false;
916
917        // Execute
918        let params = blr::params_to_blr(self, tr_handle, params)?;
919
920        self.socket.write_all(&execute(
921            tr_handle.0,
922            stmt_handle.handle.0,
923            &params.blr,
924            &params.values,
925        ))?;
926        self.socket.flush()?;
927
928        self.read_response()?;
929
930        // Get affected rows
931        self.socket.write_all(&info_sql(
932            stmt_handle.handle.0,
933            &[ibase::isc_info_sql_records as u8], // Request affected rows,
934        ))?;
935        self.socket.flush()?;
936
937        let mut data = self.read_response()?.data;
938
939        parse_info_sql_affected_rows(&mut data)
940    }
941
942    /// Execute the prepared statement with parameters, returning data
943    pub fn execute2(
944        &mut self,
945        tr_handle: &mut TrHandle,
946        stmt_handle: &mut StmtHandleData,
947        params: &[SqlType],
948    ) -> Result<Vec<Column>, FbError> {
949        if params.len() != stmt_handle.param_count {
950            return Err(format!(
951                "Tried to execute a statement that has {} parameters while providing {}",
952                stmt_handle.param_count,
953                params.len()
954            )
955            .into());
956        }
957
958        // Reopen the cursor (same reason as execute): reset the batch-fetch
959        // state from the previous execution.
960        stmt_handle.prefetched.clear();
961        stmt_handle.cursor_eof = false;
962
963        let params = blr::params_to_blr(self, tr_handle, params)?;
964
965        self.socket.write_all(&execute2(
966            tr_handle.0,
967            stmt_handle.handle.0,
968            &params.blr,
969            &params.values,
970            &stmt_handle.blr,
971        ))?;
972        self.socket.flush()?;
973
974        let version = self.version;
975        let charset = self.charset.clone();
976        let xsqlda = &stmt_handle.xsqlda;
977
978        let parsed_cols = read_with(
979            &mut self.socket,
980            &mut self.buff,
981            &mut self.pending,
982            &mut self.lazy_count,
983            |resp, lazy_count| {
984                let op_code = skip_lazy_responses(resp, lazy_count)?;
985
986                if op_code == WireOp::Response as u32 {
987                    // An error ocurred
988                    parse_response(resp)?;
989                }
990
991                if op_code != WireOp::SqlResponse as u32 {
992                    return err_conn_rejected(op_code);
993                }
994
995                let parsed_cols = parse_sql_response(resp, xsqlda, version, &charset)?;
996
997                parse_response(resp)?;
998
999                Ok(parsed_cols)
1000            },
1001        )?;
1002
1003        // Only now, with the response above fully consumed, is it safe to run the
1004        // extra round-trips a blob column needs: issuing them earlier would read
1005        // the blob replies from behind the still-unparsed bytes of this response.
1006        let mut cols = Vec::with_capacity(parsed_cols.len());
1007
1008        for pc in parsed_cols {
1009            cols.push(pc.into_column(self, tr_handle)?);
1010        }
1011
1012        Ok(cols)
1013    }
1014
1015    /// Fetch ONE row. Served from a buffer filled in batches: when the buffer
1016    /// empties, a single op_fetch requests `FB_FETCH_BATCH` rows in one
1017    /// round-trip (it used to be one row per round-trip). Streaming is
1018    /// preserved — rows come out one at a time, memory bounded to one batch.
1019    pub fn fetch(
1020        &mut self,
1021        tr_handle: &mut TrHandle,
1022        stmt_handle: &mut StmtHandleData,
1023    ) -> Result<Option<Vec<Column>>, FbError> {
1024        let count = fetch_batch_size();
1025        let mut empty_batches = 0u32;
1026        while stmt_handle.prefetched.is_empty() && !stmt_handle.cursor_eof {
1027            self.fetch_batch(tr_handle, stmt_handle, count)?;
1028            empty_batches += 1;
1029            // Safety net: a well-behaved server never sends empty batches
1030            // without exhausting the cursor; guards against a hang if it does.
1031            if empty_batches > 1000 {
1032                return Err("fetch: too many empty batches without end of cursor".into());
1033            }
1034        }
1035        Ok(stmt_handle.prefetched.pop_front())
1036    }
1037
1038    /// Requests `count` rows in one op_fetch and reads every op_fetch_response
1039    /// that arrives, filling `stmt_handle.prefetched`.
1040    fn fetch_batch(
1041        &mut self,
1042        tr_handle: &mut TrHandle,
1043        stmt_handle: &mut StmtHandleData,
1044        count: u32,
1045    ) -> Result<(), FbError> {
1046        self.socket
1047            .write_all(&fetch(stmt_handle.handle.0, &stmt_handle.blr, count))?;
1048        self.socket.flush()?;
1049
1050        let version = self.version;
1051        let charset = self.charset.clone();
1052        let xsqlda = &stmt_handle.xsqlda;
1053
1054        // Read the whole batch before touching any blob. Resolving a blob costs
1055        // its own round-trips, and starting one while later rows of this batch
1056        // are still unparsed would make the blob replies queue up behind them.
1057        let mut rows: Vec<Vec<ParsedColumn>> = Vec::new();
1058        let mut cursor_eof = false;
1059        let mut got = 0u32;
1060
1061        loop {
1062            let one = read_with(
1063                &mut self.socket,
1064                &mut self.buff,
1065                &mut self.pending,
1066                &mut self.lazy_count,
1067                |resp, lazy_count| {
1068                    parse_one_fetch_response(resp, lazy_count, xsqlda, version, &charset)
1069                },
1070            )?;
1071
1072            match one {
1073                FetchOne::Row(cols) => {
1074                    rows.push(cols);
1075                    got += 1;
1076                    // Do NOT stop on got>=count: after the rows, the server always
1077                    // sends a terminating op_fetch_response (messages=0 = end of
1078                    // this batch, or status=100 = end of cursor). Let BatchEnd/End
1079                    // end the loop, so the terminator is consumed. Guard against a
1080                    // server sending more rows than requested (should not happen).
1081                    if got > count {
1082                        return Err("server sent more rows than requested in op_fetch".into());
1083                    }
1084                }
1085                // Server ended this op_fetch without exhausting the cursor.
1086                // Deliver what arrived; the next fetch() re-issues op_fetch.
1087                FetchOne::BatchEnd => break,
1088                FetchOne::End => {
1089                    cursor_eof = true;
1090                    break;
1091                }
1092            }
1093        }
1094
1095        stmt_handle.cursor_eof = cursor_eof;
1096
1097        for parsed in rows {
1098            let mut cols = Vec::with_capacity(parsed.len());
1099            for pc in parsed {
1100                cols.push(pc.into_column(self, tr_handle)?);
1101            }
1102            stmt_handle.prefetched.push_back(cols);
1103        }
1104
1105        Ok(())
1106    }
1107
1108    /// Create a new blob, returning the blob handle and id
1109    pub fn create_blob(
1110        &mut self,
1111        tr_handle: &mut TrHandle,
1112    ) -> Result<(BlobHandle, BlobId), FbError> {
1113        self.socket.write_all(&create_blob(tr_handle.0))?;
1114        self.socket.flush()?;
1115
1116        let resp = self.read_response()?;
1117
1118        Ok((BlobHandle(resp.handle), BlobId(resp.object_id)))
1119    }
1120
1121    /// Put blob segments
1122    pub fn put_segments(&mut self, blob_handle: BlobHandle, data: &[u8]) -> Result<(), FbError> {
1123        for segment in data.chunks(crate::blr::MAX_DATA_LENGTH) {
1124            self.socket
1125                .write_all(&put_segment(blob_handle.0, segment))?;
1126            self.socket.flush()?;
1127
1128            self.read_response()?;
1129        }
1130
1131        Ok(())
1132    }
1133
1134    /// Open a blob, returning the blob handle
1135    pub fn open_blob(
1136        &mut self,
1137        tr_handle: &mut TrHandle,
1138        blob_id: BlobId,
1139    ) -> Result<BlobHandle, FbError> {
1140        self.socket.write_all(&open_blob(tr_handle.0, blob_id.0))?;
1141        self.socket.flush()?;
1142
1143        let resp = self.read_response()?;
1144
1145        Ok(BlobHandle(resp.handle))
1146    }
1147
1148    /// Get a blob segment, returns the bytes and true if there is more data
1149    pub fn get_segment(&mut self, blob_handle: BlobHandle) -> Result<(Bytes, bool), FbError> {
1150        self.socket.write_all(&get_segment(blob_handle.0))?;
1151        self.socket.flush()?;
1152
1153        let mut blob_data = BytesMut::with_capacity(256);
1154
1155        let resp = self.read_response()?;
1156        let mut data = resp.data;
1157
1158        loop {
1159            if data.remaining() < 2 {
1160                break;
1161            }
1162            let len = data.get_u16_le()? as usize;
1163            if data.remaining() < len {
1164                return err_invalid_response();
1165            }
1166            blob_data.put_slice(&data[..len]);
1167            data.advance(len)?;
1168        }
1169
1170        Ok((blob_data.freeze(), resp.handle == 2))
1171    }
1172
1173    /// Closes a blob handle
1174    pub fn close_blob(&mut self, blob_handle: BlobHandle) -> Result<(), FbError> {
1175        self.socket.write_all(&close_blob(blob_handle.0))?;
1176        self.socket.flush()?;
1177
1178        self.read_response()?;
1179
1180        Ok(())
1181    }
1182
1183    /// Read a server response
1184    fn read_response(&mut self) -> Result<Response, FbError> {
1185        read_response(
1186            &mut self.socket,
1187            &mut self.buff,
1188            &mut self.pending,
1189            &mut self.lazy_count,
1190        )
1191    }
1192}
1193
1194/// Reads from `socket` until `parse` succeeds against the accumulated bytes,
1195/// then keeps the unconsumed tail in `pending` for the next call.
1196///
1197/// This is the only correct way to read a Firebird response: the protocol has no
1198/// packet framing, so a response is complete exactly when it parses. A single
1199/// read() may return a partial response — the parse then underflows and we read
1200/// more instead of handing a truncated buffer to the caller — or several
1201/// responses at once, in which case the surplus stays in `pending` rather than
1202/// being dropped and leaving the stream misaligned.
1203///
1204/// `parse` may consume from `lazy_count`; a failed attempt is rolled back so the
1205/// retry starts from the same state.
1206fn read_with<T>(
1207    socket: &mut impl Read,
1208    buff: &mut [u8],
1209    pending: &mut Bytes,
1210    lazy_count: &mut u32,
1211    mut parse: impl FnMut(&mut Bytes, &mut u32) -> Result<T, FbError>,
1212) -> Result<T, FbError> {
1213    loop {
1214        // O(1): Bytes shares the underlying allocation
1215        let mut view = pending.clone();
1216        let saved_lazy = *lazy_count;
1217
1218        match parse(&mut view, lazy_count) {
1219            Ok(parsed) => {
1220                // Commit: `view` is what the parse did not consume.
1221                *pending = view;
1222                return Ok(parsed);
1223            }
1224
1225            Err(e) if is_incomplete(&e) => {
1226                // Undo the partial consumption and wait for the rest.
1227                *lazy_count = saved_lazy;
1228
1229                let len = socket.read(buff)?;
1230                if len == 0 {
1231                    return Err("Connection closed by the server".into());
1232                }
1233
1234                let mut next = BytesMut::with_capacity(pending.len() + len);
1235                next.put_slice(pending);
1236                next.put_slice(&buff[..len]);
1237                *pending = next.freeze();
1238            }
1239
1240            // A server-reported error is a fully parsed response, so commit it:
1241            // leaving its bytes in `pending` would make the next operation parse
1242            // them again.
1243            Err(e) => {
1244                *pending = view;
1245                return Err(e);
1246            }
1247        }
1248    }
1249}
1250
1251/// Reads the next op code, skipping `op_dummy` keepalives.
1252fn next_op_code(resp: &mut Bytes) -> Result<u32, FbError> {
1253    loop {
1254        let op_code = resp.get_u32()?;
1255
1256        if op_code != WireOp::Dummy as u32 {
1257            return Ok(op_code);
1258        }
1259    }
1260}
1261
1262/// Consumes the lazy responses pending before the response we actually want.
1263fn skip_lazy_responses(resp: &mut Bytes, lazy_count: &mut u32) -> Result<u32, FbError> {
1264    let mut op_code = next_op_code(resp)?;
1265
1266    while *lazy_count > 0 {
1267        if op_code != WireOp::Response as u32 {
1268            return err_conn_rejected(op_code);
1269        }
1270        *lazy_count -= 1;
1271        parse_response(resp)?;
1272
1273        op_code = next_op_code(resp)?;
1274    }
1275
1276    Ok(op_code)
1277}
1278
1279/// Parses ONE op_fetch_response. Blob columns are returned unresolved — see
1280/// `fetch_batch`.
1281fn parse_one_fetch_response(
1282    resp: &mut Bytes,
1283    lazy_count: &mut u32,
1284    xsqlda: &[XSqlVar],
1285    version: ProtocolVersion,
1286    charset: &Charset,
1287) -> Result<FetchOne, FbError> {
1288    let op_code = skip_lazy_responses(resp, lazy_count)?;
1289
1290    if op_code == WireOp::Response as u32 {
1291        // Error reported by the server
1292        parse_response(resp)?;
1293    }
1294
1295    if op_code != WireOp::FetchResponse as u32 {
1296        return Err(format!("unexpected op_code in fetch (op {})", op_code).into());
1297    }
1298
1299    // Body: [status: u32][messages: u32][null_map][columns...]. Peek both
1300    // without consuming, to tell end-of-cursor (status=100), end-of-batch
1301    // (messages=0) and a row (messages=1) apart before delegating.
1302    if resp.remaining() < 8 {
1303        return err_invalid_response();
1304    }
1305    let (status, messages) = {
1306        let mut peek = resp.clone();
1307        (peek.get_u32()?, peek.get_u32()?)
1308    };
1309
1310    if status == 100 {
1311        // End of cursor. Consume status AND messages: the server always sends
1312        // both, but parse_fetch_response stops after the status, which used to
1313        // leave four bytes in the stream for the next operation to read as its
1314        // op code (op 0 -> "Connection rejected with code 0").
1315        resp.advance(8)?;
1316        return Ok(FetchOne::End);
1317    }
1318
1319    if messages == 0 {
1320        // End of this batch with no row.
1321        resp.advance(8)?;
1322        return Ok(FetchOne::BatchEnd);
1323    }
1324
1325    // A row is present. Delegate to the crate parser (re-reads status+messages+data).
1326    match parse_fetch_response(resp, xsqlda, version, charset)? {
1327        Some(parsed) => Ok(FetchOne::Row(parsed)),
1328        None => Ok(FetchOne::End),
1329    }
1330}
1331
1332/// Read a server response
1333fn read_response(
1334    socket: &mut impl Read,
1335    buff: &mut [u8],
1336    pending: &mut Bytes,
1337    lazy_count: &mut u32,
1338) -> Result<Response, FbError> {
1339    read_with(socket, buff, pending, lazy_count, |resp, lazy_count| {
1340        let op_code = skip_lazy_responses(resp, lazy_count)?;
1341
1342        if op_code != WireOp::Response as u32 {
1343            return err_conn_rejected(op_code);
1344        }
1345
1346        parse_response(resp)
1347    })
1348}
1349
1350pub(crate) fn srp_verifier<D>(
1351    srp: SrpClient<D>,
1352    user: &str,
1353    pass: &str,
1354    data: &SrpAuthData,
1355) -> Result<SrpClientVerifier<D>, FbError>
1356where
1357    D: digest::Digest,
1358{
1359    // Generate a private key with the salt received from the server
1360    let private_key = srp_private_key::<sha1::Sha1>(user.as_bytes(), pass.as_bytes(), &data.salt);
1361
1362    // Generate a verified with the private key above and the server public key received
1363    let verifier = srp
1364        .process_reply(user.as_bytes(), &data.salt, &private_key, &data.pub_key)
1365        .map_err(|e| FbError::from(format!("Srp error: {}", e)))?;
1366
1367    // Generate a proof to send to the server so it can verify the password
1368    Ok(verifier)
1369}
1370
1371/// Performs the srp authentication with the server, returning the encrypted stream
1372fn srp_auth<D>(
1373    mut socket: FbStream,
1374    buff: &mut [u8],
1375    pending: &mut Bytes,
1376    srp: SrpClient<D>,
1377    plugin: AuthPluginType,
1378    user: &str,
1379    pass: &str,
1380    data: &SrpAuthData,
1381) -> Result<FbStream, FbError>
1382where
1383    D: digest::Digest,
1384{
1385    let verifier = srp_verifier(srp, user, pass, data)?;
1386
1387    // Generate a proof to send to the server so it can verify the password
1388    let proof = hex::encode(verifier.get_proof());
1389
1390    // Send proof data
1391    socket.write_all(&cont_auth(
1392        proof.as_bytes(),
1393        plugin,
1394        AuthPluginType::plugin_list(),
1395        &[],
1396    ))?;
1397    socket.flush()?;
1398
1399    read_response(&mut socket, buff, pending, &mut 0)?;
1400
1401    // Enable wire encryption
1402    socket.write_all(&crypt("Arc4", "Symmetric"))?;
1403    socket.flush()?;
1404
1405    socket = FbStream::Arc4(Arc4Stream::new(
1406        match socket {
1407            FbStream::Plain(s) => s,
1408            _ => unreachable!("Stream was already encrypted!"),
1409        },
1410        &verifier.get_key(),
1411        buff.len(),
1412    ));
1413
1414    read_response(&mut socket, buff, pending, &mut 0)?;
1415
1416    Ok(socket)
1417}
1418
1419#[derive(Debug, Clone, Copy)]
1420/// A database handle
1421pub struct DbHandle(u32);
1422
1423#[derive(Debug, Clone, Copy)]
1424/// A transaction handle
1425pub struct TrHandle(u32);
1426
1427#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
1428/// A statement handle
1429pub struct StmtHandle(u32);
1430
1431#[derive(Debug, Clone, Copy)]
1432/// A blob handle
1433pub struct BlobHandle(u32);
1434
1435#[derive(Debug, Clone, Copy)]
1436/// A blob Identificator
1437pub struct BlobId(pub(crate) u64);
1438
1439/// Firebird tcp stream, may be encrypted
1440enum FbStream {
1441    /// Plaintext stream
1442    Plain(TcpStream),
1443
1444    /// Arc4 ecrypted stream
1445    Arc4(Arc4Stream<TcpStream>),
1446}
1447
1448impl FbStream {
1449    /// Address of the server this stream is connected to
1450    fn peer_addr(&self) -> std::io::Result<SocketAddr> {
1451        match self {
1452            FbStream::Plain(s) => s.peer_addr(),
1453            FbStream::Arc4(s) => s.peer_addr(),
1454        }
1455    }
1456}
1457
1458impl Read for FbStream {
1459    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
1460        match self {
1461            FbStream::Plain(s) => s.read(buf),
1462            FbStream::Arc4(s) => s.read(buf),
1463        }
1464    }
1465}
1466
1467impl Write for FbStream {
1468    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
1469        match self {
1470            FbStream::Plain(s) => s.write(buf),
1471            FbStream::Arc4(s) => s.write(buf),
1472        }
1473    }
1474
1475    fn flush(&mut self) -> std::io::Result<()> {
1476        match self {
1477            FbStream::Plain(s) => s.flush(),
1478            FbStream::Arc4(s) => s.flush(),
1479        }
1480    }
1481}
1482
1483#[cfg(test)]
1484mod read_tests {
1485    use super::*;
1486    use rsfbclient_core::charset::UTF_8;
1487
1488    /// A `Read` that hands out at most `chunk` bytes at a time — what a response
1489    /// split across TCP segments looks like to the client.
1490    struct Chunked {
1491        data: Vec<u8>,
1492        pos: usize,
1493        chunk: usize,
1494    }
1495
1496    impl Read for Chunked {
1497        fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
1498            let n = self.chunk.min(buf.len()).min(self.data.len() - self.pos);
1499            buf[..n].copy_from_slice(&self.data[self.pos..self.pos + n]);
1500            self.pos += n;
1501            Ok(n)
1502        }
1503    }
1504
1505    /// One op_response: handle, object_id, empty data, empty status vector.
1506    fn op_response(handle: u32) -> Vec<u8> {
1507        let mut b = BytesMut::new();
1508        b.put_u32(WireOp::Response as u32);
1509        b.put_u32(handle);
1510        b.put_u64(0); // object_id
1511        b.put_wire_bytes(&[]); // data
1512        b.put_u32(ibase::isc_arg_end); // status vector
1513        b.to_vec()
1514    }
1515
1516    fn read_one(data: Vec<u8>, chunk: usize) -> (Result<Response, FbError>, Bytes) {
1517        let mut socket = Chunked {
1518            data,
1519            pos: 0,
1520            chunk,
1521        };
1522        let mut buff = vec![0u8; 2048];
1523        let mut pending = Bytes::new();
1524
1525        let resp = read_response(&mut socket, &mut buff, &mut pending, &mut 0);
1526        (resp, pending)
1527    }
1528
1529    /// A response delivered one byte at a time must be reassembled, not
1530    /// truncated. This is the bug behind "Invalid Xsqlda received from server"
1531    /// and "Invalid server response, missing bytes".
1532    #[test]
1533    fn reassembles_response_split_across_reads() {
1534        let packet = op_response(42);
1535
1536        for chunk in [1, 2, 3, 5, 7, 11, packet.len() - 1] {
1537            let (resp, pending) = read_one(packet.clone(), chunk);
1538            let resp = resp.unwrap_or_else(|e| panic!("chunk {chunk}: {e}"));
1539
1540            assert_eq!(resp.handle, 42, "chunk {chunk}");
1541            assert!(pending.is_empty(), "chunk {chunk}: {} left", pending.len());
1542        }
1543    }
1544
1545    /// Two responses arriving in one read: the second must survive in `pending`.
1546    /// Dropping it is what desynchronised the stream and made the next operation
1547    /// read a random u32 as its op code ("Connection rejected with code ...").
1548    #[test]
1549    fn keeps_surplus_bytes_for_the_next_read() {
1550        let mut data = op_response(1);
1551        data.extend_from_slice(&op_response(2));
1552        let len = data.len();
1553
1554        let mut socket = Chunked {
1555            data,
1556            pos: 0,
1557            chunk: len, // both responses in a single read
1558        };
1559        let mut buff = vec![0u8; 2048];
1560        let mut pending = Bytes::new();
1561
1562        let first = read_response(&mut socket, &mut buff, &mut pending, &mut 0).unwrap();
1563        assert_eq!(first.handle, 1);
1564        assert!(!pending.is_empty(), "second response was dropped");
1565
1566        // The second read must be served from `pending` without touching the
1567        // socket, which is now exhausted.
1568        let second = read_response(&mut socket, &mut buff, &mut pending, &mut 0).unwrap();
1569        assert_eq!(second.handle, 2);
1570        assert!(pending.is_empty());
1571    }
1572
1573    /// End-of-cursor is `[status=100][count]`; consuming only the status left the
1574    /// count behind for the next operation to read as its op code.
1575    #[test]
1576    fn end_of_cursor_consumes_the_whole_response() {
1577        let mut b = BytesMut::new();
1578        b.put_u32(WireOp::FetchResponse as u32);
1579        b.put_u32(100); // status: end of cursor
1580        b.put_u32(0); // count — must be consumed too
1581        let mut resp = b.freeze();
1582
1583        let one = parse_one_fetch_response(&mut resp, &mut 0, &[], ProtocolVersion::V13, &UTF_8)
1584            .unwrap_or_else(|e| panic!("{e}"));
1585
1586        assert!(matches!(one, FetchOne::End));
1587        assert!(
1588            resp.is_empty(),
1589            "{} bytes left for the next operation to trip over",
1590            resp.len()
1591        );
1592    }
1593}
1594
1595#[test]
1596#[ignore]
1597fn connection_test() {
1598    use rsfbclient_core::charset::UTF_8;
1599
1600    let db_name = "test.fdb";
1601    let user = "SYSDBA";
1602    let pass = "masterkey";
1603
1604    let mut conn =
1605        FirebirdWireConnection::connect("127.0.0.1", 3050, db_name, user, pass, UTF_8).unwrap();
1606
1607    let mut db_handle = conn
1608        .attach_database(db_name, user, pass, None, Dialect::D3, false)
1609        .unwrap();
1610
1611    let mut tr_handle = conn
1612        .begin_transaction(&mut db_handle, TransactionConfiguration::default())
1613        .unwrap();
1614
1615    let (stmt_type, mut stmt_handle) = conn
1616        .prepare_statement(
1617            &mut db_handle,
1618            &mut tr_handle,
1619            Dialect::D3,
1620            "
1621            SELECT
1622                1, 'abcdefghij' as tst, rand(), CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP, -1, -2, -3, -4, -5, 1, 2, 3, 4, 5, 0 as last
1623            FROM RDB$DATABASE where 1 = ?
1624            ",
1625            // "
1626            // SELECT cast(1 as bigint), cast('abcdefghij' as varchar(10)) as tst FROM RDB$DATABASE UNION ALL
1627            // SELECT cast(2 as bigint), cast('abcdefgh' as varchar(10)) as tst FROM RDB$DATABASE UNION ALL
1628            // SELECT cast(3 as bigint), cast('abcdef' as varchar(10)) as tst FROM RDB$DATABASE UNION ALL
1629            // SELECT cast(4 as bigint), cast(null as varchar(10)) as tst FROM RDB$DATABASE UNION ALL
1630            // SELECT cast(null as bigint), cast('abcd' as varchar(10)) as tst FROM RDB$DATABASE
1631            // ",
1632        )
1633        .unwrap();
1634
1635    println!("Statement type: {:?}", stmt_type);
1636
1637    let params = match rsfbclient_core::IntoParams::to_params((1,)) {
1638        rsfbclient_core::ParamsType::Positional(params) => params,
1639        _ => unreachable!(),
1640    };
1641
1642    conn.execute(&mut tr_handle, &mut stmt_handle, &params)
1643        .unwrap();
1644
1645    loop {
1646        let resp = conn.fetch(&mut tr_handle, &mut stmt_handle).unwrap();
1647
1648        if resp.is_none() {
1649            break;
1650        }
1651        println!("Fetch Resp: {:#?}", resp);
1652    }
1653
1654    std::thread::sleep(std::time::Duration::from_millis(100));
1655}