use bytes::{BufMut, Bytes, BytesMut};
use std::{
collections::VecDeque,
env,
io::{Read, Write},
net::{SocketAddr, TcpStream},
};
use crate::{
arc4::*,
blr,
consts::{AuthPluginType, ProtocolVersion, WireOp},
events::*,
srp::*,
util::*,
wire::*,
xsqlda::{parse_xsqlda, xsqlda_to_blr, PrepareInfo, XSqlVar, XSQLDA_DESCRIBE_VARS},
};
use rsfbclient_core::*;
type RustDbHandle = DbHandle;
type RustTrHandle = TrHandle;
type RustStmtHandle = StmtHandle;
fn fetch_batch_size() -> u32 {
env::var("FB_FETCH_BATCH")
.ok()
.and_then(|v| v.parse().ok())
.filter(|&n| n > 0)
.unwrap_or(200)
}
enum FetchOne {
Row(Vec<ParsedColumn>),
BatchEnd,
End,
}
pub struct RustFbClient {
conn: Option<FirebirdWireConnection>,
charset: Charset,
}
#[derive(Default, Clone)]
pub struct RustFbClientAttachmentConfig {
pub host: String,
pub port: u16,
pub db_name: String,
pub user: String,
pub pass: String,
pub role_name: Option<String>,
}
pub struct FirebirdWireConnection {
socket: FbStream,
pub(crate) version: ProtocolVersion,
buff: Box<[u8]>,
pending: Bytes,
lazy_count: u32,
pub(crate) charset: Charset,
pub(crate) auth_plugin: Option<AuthPlugin>,
pub(crate) srp_key: [u8; 32],
event_channel: Option<EventChannel>,
next_event_id: u32,
}
pub struct StmtHandleData {
handle: RustStmtHandle,
xsqlda: Vec<XSqlVar>,
blr: Bytes,
param_count: usize,
prefetched: VecDeque<Vec<Column>>,
cursor_eof: bool,
}
impl RustFbClient {
pub fn new(charset: Charset) -> Self {
Self {
conn: None,
charset,
}
}
}
impl FirebirdClientDbOps for RustFbClient {
type DbHandle = RustDbHandle;
type AttachmentConfig = RustFbClientAttachmentConfig;
fn attach_database(
&mut self,
config: &Self::AttachmentConfig,
dialect: Dialect,
no_db_triggers: bool,
) -> Result<RustDbHandle, FbError> {
let host = config.host.as_str();
let port = config.port;
let db_name = config.db_name.as_str();
let user = config.user.as_str();
let pass = config.pass.as_str();
let role = match &config.role_name {
Some(ro) => Some(ro.as_str()),
None => None,
};
let mut conn = match self.conn.take() {
Some(conn) => conn,
None => FirebirdWireConnection::connect(
host,
port,
db_name,
user,
pass,
self.charset.clone(),
)?,
};
let attach_result =
conn.attach_database(db_name, user, pass, role, dialect, no_db_triggers);
self.conn.replace(conn);
attach_result
}
fn detach_database(&mut self, db_handle: &mut RustDbHandle) -> Result<(), FbError> {
self.conn
.as_mut()
.map(|conn| conn.detach_database(db_handle))
.unwrap_or_else(err_client_not_connected)
}
fn drop_database(&mut self, db_handle: &mut RustDbHandle) -> Result<(), FbError> {
self.conn
.as_mut()
.map(|conn| conn.drop_database(db_handle))
.unwrap_or_else(err_client_not_connected)
}
fn create_database(
&mut self,
config: &Self::AttachmentConfig,
page_size: Option<u32>,
dialect: Dialect,
) -> Result<RustDbHandle, FbError> {
let host = config.host.as_str();
let port = config.port;
let db_name = config.db_name.as_str();
let user = config.user.as_str();
let pass = config.pass.as_str();
let role = match &config.role_name {
Some(ro) => Some(ro.as_str()),
None => None,
};
let mut conn = match self.conn.take() {
Some(conn) => conn,
None => FirebirdWireConnection::connect(
host,
port,
db_name,
user,
pass,
self.charset.clone(),
)?,
};
let attach_result = conn.create_database(db_name, user, pass, page_size, role, dialect);
self.conn.replace(conn);
attach_result
}
}
impl FirebirdClientSqlOps for RustFbClient {
type DbHandle = RustDbHandle;
type TrHandle = RustTrHandle;
type StmtHandle = StmtHandleData;
fn begin_transaction(
&mut self,
db_handle: &mut Self::DbHandle,
confs: TransactionConfiguration,
) -> Result<Self::TrHandle, FbError> {
self.conn
.as_mut()
.map(|conn| conn.begin_transaction(db_handle, confs))
.unwrap_or_else(err_client_not_connected)
}
fn transaction_operation(
&mut self,
tr_handle: &mut Self::TrHandle,
op: TrOp,
) -> Result<(), FbError> {
self.conn
.as_mut()
.map(|conn| conn.transaction_operation(tr_handle, op))
.unwrap_or_else(err_client_not_connected)
}
fn exec_immediate(
&mut self,
_db_handle: &mut Self::DbHandle,
tr_handle: &mut Self::TrHandle,
dialect: Dialect,
sql: &str,
) -> Result<(), FbError> {
self.conn
.as_mut()
.map(|conn| conn.exec_immediate(tr_handle, dialect, sql))
.unwrap_or_else(err_client_not_connected)
}
fn prepare_statement(
&mut self,
db_handle: &mut Self::DbHandle,
tr_handle: &mut Self::TrHandle,
dialect: Dialect,
sql: &str,
) -> Result<(StmtType, Self::StmtHandle), FbError> {
self.conn
.as_mut()
.map(|conn| conn.prepare_statement(db_handle, tr_handle, dialect, sql))
.unwrap_or_else(err_client_not_connected)
}
fn free_statement(
&mut self,
stmt_handle: &mut Self::StmtHandle,
op: FreeStmtOp,
) -> Result<(), FbError> {
self.conn
.as_mut()
.map(|conn| conn.free_statement(stmt_handle, op))
.unwrap_or_else(err_client_not_connected)
}
fn execute(
&mut self,
_db_handle: &mut Self::DbHandle,
tr_handle: &mut Self::TrHandle,
stmt_handle: &mut Self::StmtHandle,
params: Vec<SqlType>,
) -> Result<usize, FbError> {
self.conn
.as_mut()
.map(|conn| conn.execute(tr_handle, stmt_handle, ¶ms))
.unwrap_or_else(err_client_not_connected)
}
fn execute2(
&mut self,
_db_handle: &mut Self::DbHandle,
tr_handle: &mut Self::TrHandle,
stmt_handle: &mut Self::StmtHandle,
params: Vec<SqlType>,
) -> Result<Vec<Column>, FbError> {
self.conn
.as_mut()
.map(|conn| conn.execute2(tr_handle, stmt_handle, ¶ms))
.unwrap_or_else(err_client_not_connected)
}
fn fetch(
&mut self,
_db_handle: &mut Self::DbHandle,
tr_handle: &mut Self::TrHandle,
stmt_handle: &mut Self::StmtHandle,
) -> Result<Option<Vec<Column>>, FbError> {
self.conn
.as_mut()
.map(|conn| conn.fetch(tr_handle, stmt_handle))
.unwrap_or_else(err_client_not_connected)
}
}
impl FirebirdClientDbEvents for RustFbClient {
fn wait_for_event(
&mut self,
db_handle: &mut Self::DbHandle,
name: String,
) -> Result<(), FbError> {
self.conn
.as_mut()
.map(|conn| conn.wait_for_event(db_handle, &name))
.unwrap_or_else(err_client_not_connected)
}
}
fn err_client_not_connected<T>() -> Result<T, FbError> {
Err("Client not connected to the server, call `attach_database` to connect".into())
}
impl FirebirdWireConnection {
pub fn connect(
host: &str,
port: u16,
db_name: &str,
user: &str,
pass: &str,
charset: Charset,
) -> Result<Self, FbError> {
let socket = TcpStream::connect((host, port))?;
let _ = socket.set_nodelay(true);
let username =
env::var("USER").unwrap_or_else(|_| env::var("USERNAME").unwrap_or_default());
let hostname = socket
.local_addr()
.map(|addr| addr.to_string())
.unwrap_or_default();
let mut socket = FbStream::Plain(socket);
let srp_key: [u8; 32] = rand::random();
let req = connect(db_name, user, &username, &hostname, &srp_key);
socket.write_all(&req)?;
socket.flush()?;
let mut buff = vec![0; BUFFER_LENGTH as usize * 2].into_boxed_slice();
let mut pending = Bytes::new();
let ConnectionResponse {
version,
mut auth_plugin,
continue_auth,
} = read_with(&mut socket, &mut buff, &mut pending, &mut 0, |resp, _| {
parse_accept(resp)
})?;
if let Some(auth_plugin) = &mut auth_plugin {
loop {
match auth_plugin.kind {
plugin @ AuthPluginType::Srp => {
let srp = SrpClient::<sha1::Sha1>::new(&srp_key, &SRP_GROUP);
if let Some(data) = auth_plugin.data.clone() {
if continue_auth {
socket = srp_auth(
socket,
&mut buff,
&mut pending,
srp,
plugin,
user,
pass,
&data,
)?;
}
break;
} else {
socket.write_all(&cont_auth(
hex::encode(srp.get_a_pub()).as_bytes(),
plugin,
AuthPluginType::plugin_list(),
&[],
))?;
socket.flush()?;
*auth_plugin = read_with(
&mut socket,
&mut buff,
&mut pending,
&mut 0,
|resp, _| parse_cont_auth(resp),
)?;
}
}
plugin @ AuthPluginType::Srp256 => {
let srp = SrpClient::<sha2::Sha256>::new(&srp_key, &SRP_GROUP);
if let Some(data) = auth_plugin.data.clone() {
if continue_auth {
socket = srp_auth(
socket,
&mut buff,
&mut pending,
srp,
plugin,
user,
pass,
&data,
)?;
}
break;
} else {
socket.write_all(&cont_auth(
hex::encode(srp.get_a_pub()).as_bytes(),
plugin,
AuthPluginType::plugin_list(),
&[],
))?;
socket.flush()?;
*auth_plugin = read_with(
&mut socket,
&mut buff,
&mut pending,
&mut 0,
|resp, _| parse_cont_auth(resp),
)?;
}
}
}
}
}
Ok(Self {
socket,
version,
buff,
pending,
lazy_count: 0,
charset,
auth_plugin: if continue_auth {
None
} else {
auth_plugin
},
srp_key,
event_channel: None,
next_event_id: 1,
})
}
pub fn create_database(
&mut self,
db_name: &str,
user: &str,
pass: &str,
page_size: Option<u32>,
role_name: Option<&str>,
dialect: Dialect,
) -> Result<DbHandle, FbError> {
self.socket.write_all(&create(
db_name,
user,
pass,
self.version,
self.charset.clone(),
page_size,
role_name,
dialect,
self.auth_plugin.as_ref(),
&self.srp_key,
)?)?;
self.socket.flush()?;
let resp = self.read_response()?;
Ok(DbHandle(resp.handle))
}
pub fn attach_database(
&mut self,
db_name: &str,
user: &str,
pass: &str,
role_name: Option<&str>,
dialect: Dialect,
no_db_triggers: bool,
) -> Result<DbHandle, FbError> {
self.socket.write_all(&attach(
db_name,
user,
pass,
self.version,
self.charset.clone(),
role_name,
dialect,
no_db_triggers,
self.auth_plugin.as_ref(),
&self.srp_key,
)?)?;
self.socket.flush()?;
let resp = self.read_response()?;
Ok(DbHandle(resp.handle))
}
pub fn detach_database(&mut self, db_handle: &mut DbHandle) -> Result<(), FbError> {
self.close_event_channel();
self.socket.write_all(&detach(db_handle.0))?;
self.socket.flush()?;
self.read_response()?;
Ok(())
}
pub fn drop_database(&mut self, db_handle: &mut DbHandle) -> Result<(), FbError> {
self.close_event_channel();
self.socket.write_all(&drop_database(db_handle.0))?;
self.socket.flush()?;
self.read_response()?;
Ok(())
}
pub fn wait_for_event(&mut self, db_handle: &mut DbHandle, name: &str) -> Result<(), FbError> {
let name = normalize_event_name(name)?;
self.open_event_channel(db_handle)?;
let event_id = self.new_event_id();
let counters = self.que_events(db_handle, name, 0, event_id)?;
let posted = event_count(&counters, name)?;
let event_id = self.new_event_id();
self.que_events(db_handle, name, posted, event_id)?;
Ok(())
}
fn new_event_id(&mut self) -> u32 {
let event_id = self.next_event_id;
self.next_event_id = self.next_event_id.wrapping_add(1).max(1);
event_id
}
fn que_events(
&mut self,
db_handle: &mut DbHandle,
name: &str,
count: u32,
event_id: u32,
) -> Result<Vec<(String, u32)>, FbError> {
let epb = event_block(&self.charset, [(name, count)])?;
self.socket
.write_all(&que_events(db_handle.0, &epb, event_id))?;
self.socket.flush()?;
self.read_response()?;
let channel = match self.event_channel.as_mut() {
Some(channel) => channel,
None => return Err(FbError::from("The event channel was closed")),
};
match channel.recv_event(event_id) {
Ok(counters) => Ok(counters),
Err(err) => {
self.event_channel = None;
self.cancel_events(db_handle, event_id).ok();
Err(err)
}
}
}
fn cancel_events(&mut self, db_handle: &mut DbHandle, event_id: u32) -> Result<(), FbError> {
self.socket
.write_all(&cancel_events(db_handle.0, event_id))?;
self.socket.flush()?;
self.read_response()?;
Ok(())
}
fn open_event_channel(&mut self, db_handle: &mut DbHandle) -> Result<(), FbError> {
if matches!(&self.event_channel, Some(channel) if channel.db_handle() == db_handle.0) {
return Ok(());
}
self.event_channel = None;
self.socket.write_all(&connect_request(db_handle.0))?;
self.socket.flush()?;
let resp = self.read_response()?;
let port = parse_aux_port(&resp.data)?;
let peer = self.socket.peer_addr()?;
self.event_channel = Some(EventChannel::open(
db_handle.0,
self.charset.clone(),
peer,
port,
)?);
Ok(())
}
fn close_event_channel(&mut self) {
self.event_channel = None;
}
pub fn begin_transaction(
&mut self,
db_handle: &mut DbHandle,
confs: TransactionConfiguration,
) -> Result<TrHandle, FbError> {
let mut tpb = vec![
ibase::isc_tpb_version3 as u8,
confs.isolation.into(),
confs.data_access as u8,
confs.lock_resolution.into(),
];
if let TrLockResolution::Wait(Some(time)) = confs.lock_resolution {
tpb.push(ibase::isc_tpb_lock_timeout as u8);
tpb.push(4 as u8);
tpb.extend_from_slice(&time.to_le_bytes());
}
if let TrIsolationLevel::ReadCommited(rec) = confs.isolation {
tpb.push(rec as u8);
}
self.socket.write_all(&transaction(db_handle.0, &tpb))?;
self.socket.flush()?;
let resp = self.read_response()?;
Ok(TrHandle(resp.handle))
}
pub fn transaction_operation(
&mut self,
tr_handle: &mut TrHandle,
op: TrOp,
) -> Result<(), FbError> {
self.socket
.write_all(&transaction_operation(tr_handle.0, op))?;
self.socket.flush()?;
self.read_response()?;
Ok(())
}
pub fn exec_immediate(
&mut self,
tr_handle: &mut TrHandle,
dialect: Dialect,
sql: &str,
) -> Result<(), FbError> {
self.socket.write_all(&exec_immediate(
tr_handle.0,
dialect as u32,
sql,
&self.charset,
)?)?;
self.socket.flush()?;
self.read_response()?;
Ok(())
}
pub fn prepare_statement(
&mut self,
db_handle: &mut DbHandle,
tr_handle: &mut TrHandle,
dialect: Dialect,
sql: &str,
) -> Result<(StmtType, StmtHandleData), FbError> {
self.socket.write_all(&allocate_statement(db_handle.0))?;
self.socket.write_all(&prepare_statement(
tr_handle.0,
u32::MAX,
dialect as u32,
sql,
&self.charset,
)?)?;
self.socket.flush()?;
let (stmt_handle, mut prepare_data) = read_with(
&mut self.socket,
&mut self.buff,
&mut self.pending,
&mut self.lazy_count,
|resp, lazy_count| {
let op_code = skip_lazy_responses(resp, lazy_count)?;
if op_code != WireOp::Response as u32 {
return err_conn_rejected(op_code);
}
let stmt_handle = StmtHandle(parse_response(resp)?.handle);
let op_code = next_op_code(resp)?;
if op_code != WireOp::Response as u32 {
return err_conn_rejected(op_code);
}
Ok((stmt_handle, parse_response(resp)?.data))
},
)?;
let mut xsqlda = Vec::new();
let PrepareInfo {
stmt_type,
mut param_count,
mut truncated,
} = parse_xsqlda(&mut prepare_data, &mut xsqlda)?;
while truncated {
let next_index = (xsqlda.len() as u16).to_le_bytes();
self.socket.write_all(&info_sql(
stmt_handle.0,
&[
&[
ibase::isc_info_sql_sqlda_start as u8, 2,
next_index[0], next_index[1], ],
&XSQLDA_DESCRIBE_VARS[..], ]
.concat(),
))?;
self.socket.flush()?;
let mut data = self.read_response()?.data;
let parse_resp = parse_xsqlda(&mut data, &mut xsqlda)?;
truncated = parse_resp.truncated;
param_count = parse_resp.param_count;
}
for var in xsqlda.iter_mut() {
var.coerce()?;
}
let blr = xsqlda_to_blr(&xsqlda)?;
Ok((
stmt_type,
StmtHandleData {
handle: stmt_handle,
xsqlda,
blr,
param_count,
prefetched: VecDeque::new(),
cursor_eof: false,
},
))
}
pub fn free_statement(
&mut self,
stmt_handle: &mut StmtHandleData,
op: FreeStmtOp,
) -> Result<(), FbError> {
self.socket
.write_all(&free_statement(stmt_handle.handle.0, op))?;
self.lazy_count += 1;
Ok(())
}
pub fn execute(
&mut self,
tr_handle: &mut TrHandle,
stmt_handle: &mut StmtHandleData,
params: &[SqlType],
) -> Result<usize, FbError> {
if params.len() != stmt_handle.param_count {
return Err(format!(
"Tried to execute a statement that has {} parameters while providing {}",
stmt_handle.param_count,
params.len()
)
.into());
}
stmt_handle.prefetched.clear();
stmt_handle.cursor_eof = false;
let params = blr::params_to_blr(self, tr_handle, params)?;
self.socket.write_all(&execute(
tr_handle.0,
stmt_handle.handle.0,
¶ms.blr,
¶ms.values,
))?;
self.socket.flush()?;
self.read_response()?;
self.socket.write_all(&info_sql(
stmt_handle.handle.0,
&[ibase::isc_info_sql_records as u8], ))?;
self.socket.flush()?;
let mut data = self.read_response()?.data;
parse_info_sql_affected_rows(&mut data)
}
pub fn execute2(
&mut self,
tr_handle: &mut TrHandle,
stmt_handle: &mut StmtHandleData,
params: &[SqlType],
) -> Result<Vec<Column>, FbError> {
if params.len() != stmt_handle.param_count {
return Err(format!(
"Tried to execute a statement that has {} parameters while providing {}",
stmt_handle.param_count,
params.len()
)
.into());
}
stmt_handle.prefetched.clear();
stmt_handle.cursor_eof = false;
let params = blr::params_to_blr(self, tr_handle, params)?;
self.socket.write_all(&execute2(
tr_handle.0,
stmt_handle.handle.0,
¶ms.blr,
¶ms.values,
&stmt_handle.blr,
))?;
self.socket.flush()?;
let version = self.version;
let charset = self.charset.clone();
let xsqlda = &stmt_handle.xsqlda;
let parsed_cols = read_with(
&mut self.socket,
&mut self.buff,
&mut self.pending,
&mut self.lazy_count,
|resp, lazy_count| {
let op_code = skip_lazy_responses(resp, lazy_count)?;
if op_code == WireOp::Response as u32 {
parse_response(resp)?;
}
if op_code != WireOp::SqlResponse as u32 {
return err_conn_rejected(op_code);
}
let parsed_cols = parse_sql_response(resp, xsqlda, version, &charset)?;
parse_response(resp)?;
Ok(parsed_cols)
},
)?;
let mut cols = Vec::with_capacity(parsed_cols.len());
for pc in parsed_cols {
cols.push(pc.into_column(self, tr_handle)?);
}
Ok(cols)
}
pub fn fetch(
&mut self,
tr_handle: &mut TrHandle,
stmt_handle: &mut StmtHandleData,
) -> Result<Option<Vec<Column>>, FbError> {
let count = fetch_batch_size();
let mut empty_batches = 0u32;
while stmt_handle.prefetched.is_empty() && !stmt_handle.cursor_eof {
self.fetch_batch(tr_handle, stmt_handle, count)?;
empty_batches += 1;
if empty_batches > 1000 {
return Err("fetch: too many empty batches without end of cursor".into());
}
}
Ok(stmt_handle.prefetched.pop_front())
}
fn fetch_batch(
&mut self,
tr_handle: &mut TrHandle,
stmt_handle: &mut StmtHandleData,
count: u32,
) -> Result<(), FbError> {
self.socket
.write_all(&fetch(stmt_handle.handle.0, &stmt_handle.blr, count))?;
self.socket.flush()?;
let version = self.version;
let charset = self.charset.clone();
let xsqlda = &stmt_handle.xsqlda;
let mut rows: Vec<Vec<ParsedColumn>> = Vec::new();
let mut cursor_eof = false;
let mut got = 0u32;
loop {
let one = read_with(
&mut self.socket,
&mut self.buff,
&mut self.pending,
&mut self.lazy_count,
|resp, lazy_count| {
parse_one_fetch_response(resp, lazy_count, xsqlda, version, &charset)
},
)?;
match one {
FetchOne::Row(cols) => {
rows.push(cols);
got += 1;
if got > count {
return Err("server sent more rows than requested in op_fetch".into());
}
}
FetchOne::BatchEnd => break,
FetchOne::End => {
cursor_eof = true;
break;
}
}
}
stmt_handle.cursor_eof = cursor_eof;
for parsed in rows {
let mut cols = Vec::with_capacity(parsed.len());
for pc in parsed {
cols.push(pc.into_column(self, tr_handle)?);
}
stmt_handle.prefetched.push_back(cols);
}
Ok(())
}
pub fn create_blob(
&mut self,
tr_handle: &mut TrHandle,
) -> Result<(BlobHandle, BlobId), FbError> {
self.socket.write_all(&create_blob(tr_handle.0))?;
self.socket.flush()?;
let resp = self.read_response()?;
Ok((BlobHandle(resp.handle), BlobId(resp.object_id)))
}
pub fn put_segments(&mut self, blob_handle: BlobHandle, data: &[u8]) -> Result<(), FbError> {
for segment in data.chunks(crate::blr::MAX_DATA_LENGTH) {
self.socket
.write_all(&put_segment(blob_handle.0, segment))?;
self.socket.flush()?;
self.read_response()?;
}
Ok(())
}
pub fn open_blob(
&mut self,
tr_handle: &mut TrHandle,
blob_id: BlobId,
) -> Result<BlobHandle, FbError> {
self.socket.write_all(&open_blob(tr_handle.0, blob_id.0))?;
self.socket.flush()?;
let resp = self.read_response()?;
Ok(BlobHandle(resp.handle))
}
pub fn get_segment(&mut self, blob_handle: BlobHandle) -> Result<(Bytes, bool), FbError> {
self.socket.write_all(&get_segment(blob_handle.0))?;
self.socket.flush()?;
let mut blob_data = BytesMut::with_capacity(256);
let resp = self.read_response()?;
let mut data = resp.data;
loop {
if data.remaining() < 2 {
break;
}
let len = data.get_u16_le()? as usize;
if data.remaining() < len {
return err_invalid_response();
}
blob_data.put_slice(&data[..len]);
data.advance(len)?;
}
Ok((blob_data.freeze(), resp.handle == 2))
}
pub fn close_blob(&mut self, blob_handle: BlobHandle) -> Result<(), FbError> {
self.socket.write_all(&close_blob(blob_handle.0))?;
self.socket.flush()?;
self.read_response()?;
Ok(())
}
fn read_response(&mut self) -> Result<Response, FbError> {
read_response(
&mut self.socket,
&mut self.buff,
&mut self.pending,
&mut self.lazy_count,
)
}
}
fn read_with<T>(
socket: &mut impl Read,
buff: &mut [u8],
pending: &mut Bytes,
lazy_count: &mut u32,
mut parse: impl FnMut(&mut Bytes, &mut u32) -> Result<T, FbError>,
) -> Result<T, FbError> {
loop {
let mut view = pending.clone();
let saved_lazy = *lazy_count;
match parse(&mut view, lazy_count) {
Ok(parsed) => {
*pending = view;
return Ok(parsed);
}
Err(e) if is_incomplete(&e) => {
*lazy_count = saved_lazy;
let len = socket.read(buff)?;
if len == 0 {
return Err("Connection closed by the server".into());
}
let mut next = BytesMut::with_capacity(pending.len() + len);
next.put_slice(pending);
next.put_slice(&buff[..len]);
*pending = next.freeze();
}
Err(e) => {
*pending = view;
return Err(e);
}
}
}
}
fn next_op_code(resp: &mut Bytes) -> Result<u32, FbError> {
loop {
let op_code = resp.get_u32()?;
if op_code != WireOp::Dummy as u32 {
return Ok(op_code);
}
}
}
fn skip_lazy_responses(resp: &mut Bytes, lazy_count: &mut u32) -> Result<u32, FbError> {
let mut op_code = next_op_code(resp)?;
while *lazy_count > 0 {
if op_code != WireOp::Response as u32 {
return err_conn_rejected(op_code);
}
*lazy_count -= 1;
parse_response(resp)?;
op_code = next_op_code(resp)?;
}
Ok(op_code)
}
fn parse_one_fetch_response(
resp: &mut Bytes,
lazy_count: &mut u32,
xsqlda: &[XSqlVar],
version: ProtocolVersion,
charset: &Charset,
) -> Result<FetchOne, FbError> {
let op_code = skip_lazy_responses(resp, lazy_count)?;
if op_code == WireOp::Response as u32 {
parse_response(resp)?;
}
if op_code != WireOp::FetchResponse as u32 {
return Err(format!("unexpected op_code in fetch (op {})", op_code).into());
}
if resp.remaining() < 8 {
return err_invalid_response();
}
let (status, messages) = {
let mut peek = resp.clone();
(peek.get_u32()?, peek.get_u32()?)
};
if status == 100 {
resp.advance(8)?;
return Ok(FetchOne::End);
}
if messages == 0 {
resp.advance(8)?;
return Ok(FetchOne::BatchEnd);
}
match parse_fetch_response(resp, xsqlda, version, charset)? {
Some(parsed) => Ok(FetchOne::Row(parsed)),
None => Ok(FetchOne::End),
}
}
fn read_response(
socket: &mut impl Read,
buff: &mut [u8],
pending: &mut Bytes,
lazy_count: &mut u32,
) -> Result<Response, FbError> {
read_with(socket, buff, pending, lazy_count, |resp, lazy_count| {
let op_code = skip_lazy_responses(resp, lazy_count)?;
if op_code != WireOp::Response as u32 {
return err_conn_rejected(op_code);
}
parse_response(resp)
})
}
pub(crate) fn srp_verifier<D>(
srp: SrpClient<D>,
user: &str,
pass: &str,
data: &SrpAuthData,
) -> Result<SrpClientVerifier<D>, FbError>
where
D: digest::Digest,
{
let private_key = srp_private_key::<sha1::Sha1>(user.as_bytes(), pass.as_bytes(), &data.salt);
let verifier = srp
.process_reply(user.as_bytes(), &data.salt, &private_key, &data.pub_key)
.map_err(|e| FbError::from(format!("Srp error: {}", e)))?;
Ok(verifier)
}
fn srp_auth<D>(
mut socket: FbStream,
buff: &mut [u8],
pending: &mut Bytes,
srp: SrpClient<D>,
plugin: AuthPluginType,
user: &str,
pass: &str,
data: &SrpAuthData,
) -> Result<FbStream, FbError>
where
D: digest::Digest,
{
let verifier = srp_verifier(srp, user, pass, data)?;
let proof = hex::encode(verifier.get_proof());
socket.write_all(&cont_auth(
proof.as_bytes(),
plugin,
AuthPluginType::plugin_list(),
&[],
))?;
socket.flush()?;
read_response(&mut socket, buff, pending, &mut 0)?;
socket.write_all(&crypt("Arc4", "Symmetric"))?;
socket.flush()?;
socket = FbStream::Arc4(Arc4Stream::new(
match socket {
FbStream::Plain(s) => s,
_ => unreachable!("Stream was already encrypted!"),
},
&verifier.get_key(),
buff.len(),
));
read_response(&mut socket, buff, pending, &mut 0)?;
Ok(socket)
}
#[derive(Debug, Clone, Copy)]
pub struct DbHandle(u32);
#[derive(Debug, Clone, Copy)]
pub struct TrHandle(u32);
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub struct StmtHandle(u32);
#[derive(Debug, Clone, Copy)]
pub struct BlobHandle(u32);
#[derive(Debug, Clone, Copy)]
pub struct BlobId(pub(crate) u64);
enum FbStream {
Plain(TcpStream),
Arc4(Arc4Stream<TcpStream>),
}
impl FbStream {
fn peer_addr(&self) -> std::io::Result<SocketAddr> {
match self {
FbStream::Plain(s) => s.peer_addr(),
FbStream::Arc4(s) => s.peer_addr(),
}
}
}
impl Read for FbStream {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
match self {
FbStream::Plain(s) => s.read(buf),
FbStream::Arc4(s) => s.read(buf),
}
}
}
impl Write for FbStream {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
match self {
FbStream::Plain(s) => s.write(buf),
FbStream::Arc4(s) => s.write(buf),
}
}
fn flush(&mut self) -> std::io::Result<()> {
match self {
FbStream::Plain(s) => s.flush(),
FbStream::Arc4(s) => s.flush(),
}
}
}
#[cfg(test)]
mod read_tests {
use super::*;
use rsfbclient_core::charset::UTF_8;
struct Chunked {
data: Vec<u8>,
pos: usize,
chunk: usize,
}
impl Read for Chunked {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let n = self.chunk.min(buf.len()).min(self.data.len() - self.pos);
buf[..n].copy_from_slice(&self.data[self.pos..self.pos + n]);
self.pos += n;
Ok(n)
}
}
fn op_response(handle: u32) -> Vec<u8> {
let mut b = BytesMut::new();
b.put_u32(WireOp::Response as u32);
b.put_u32(handle);
b.put_u64(0); b.put_wire_bytes(&[]); b.put_u32(ibase::isc_arg_end); b.to_vec()
}
fn read_one(data: Vec<u8>, chunk: usize) -> (Result<Response, FbError>, Bytes) {
let mut socket = Chunked {
data,
pos: 0,
chunk,
};
let mut buff = vec![0u8; 2048];
let mut pending = Bytes::new();
let resp = read_response(&mut socket, &mut buff, &mut pending, &mut 0);
(resp, pending)
}
#[test]
fn reassembles_response_split_across_reads() {
let packet = op_response(42);
for chunk in [1, 2, 3, 5, 7, 11, packet.len() - 1] {
let (resp, pending) = read_one(packet.clone(), chunk);
let resp = resp.unwrap_or_else(|e| panic!("chunk {chunk}: {e}"));
assert_eq!(resp.handle, 42, "chunk {chunk}");
assert!(pending.is_empty(), "chunk {chunk}: {} left", pending.len());
}
}
#[test]
fn keeps_surplus_bytes_for_the_next_read() {
let mut data = op_response(1);
data.extend_from_slice(&op_response(2));
let len = data.len();
let mut socket = Chunked {
data,
pos: 0,
chunk: len, };
let mut buff = vec![0u8; 2048];
let mut pending = Bytes::new();
let first = read_response(&mut socket, &mut buff, &mut pending, &mut 0).unwrap();
assert_eq!(first.handle, 1);
assert!(!pending.is_empty(), "second response was dropped");
let second = read_response(&mut socket, &mut buff, &mut pending, &mut 0).unwrap();
assert_eq!(second.handle, 2);
assert!(pending.is_empty());
}
#[test]
fn end_of_cursor_consumes_the_whole_response() {
let mut b = BytesMut::new();
b.put_u32(WireOp::FetchResponse as u32);
b.put_u32(100); b.put_u32(0); let mut resp = b.freeze();
let one = parse_one_fetch_response(&mut resp, &mut 0, &[], ProtocolVersion::V13, &UTF_8)
.unwrap_or_else(|e| panic!("{e}"));
assert!(matches!(one, FetchOne::End));
assert!(
resp.is_empty(),
"{} bytes left for the next operation to trip over",
resp.len()
);
}
}
#[test]
#[ignore]
fn connection_test() {
use rsfbclient_core::charset::UTF_8;
let db_name = "test.fdb";
let user = "SYSDBA";
let pass = "masterkey";
let mut conn =
FirebirdWireConnection::connect("127.0.0.1", 3050, db_name, user, pass, UTF_8).unwrap();
let mut db_handle = conn
.attach_database(db_name, user, pass, None, Dialect::D3, false)
.unwrap();
let mut tr_handle = conn
.begin_transaction(&mut db_handle, TransactionConfiguration::default())
.unwrap();
let (stmt_type, mut stmt_handle) = conn
.prepare_statement(
&mut db_handle,
&mut tr_handle,
Dialect::D3,
"
SELECT
1, 'abcdefghij' as tst, rand(), CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP, -1, -2, -3, -4, -5, 1, 2, 3, 4, 5, 0 as last
FROM RDB$DATABASE where 1 = ?
",
)
.unwrap();
println!("Statement type: {:?}", stmt_type);
let params = match rsfbclient_core::IntoParams::to_params((1,)) {
rsfbclient_core::ParamsType::Positional(params) => params,
_ => unreachable!(),
};
conn.execute(&mut tr_handle, &mut stmt_handle, ¶ms)
.unwrap();
loop {
let resp = conn.fetch(&mut tr_handle, &mut stmt_handle).unwrap();
if resp.is_none() {
break;
}
println!("Fetch Resp: {:#?}", resp);
}
std::thread::sleep(std::time::Duration::from_millis(100));
}