use std::time::Duration;
use crate::session::{ConnectParams, TlsSession, connect};
#[cxx::bridge]
mod ffi {
struct TlsConnect {
host: String,
port: u16,
secure: bool,
sni: String,
ca_pem: Vec<u8>,
use_native_roots: bool,
skip_verification: bool,
connect_timeout_ms: u64,
recv_timeout_ms: u64,
send_timeout_ms: u64,
tcp_nodelay: bool,
tcp_keepalive: bool,
keepalive_idle_secs: u64,
keepalive_interval_secs: u64,
keepalive_count: u32,
}
struct NativeOpen {
host: String,
port: u16,
user: String,
password: String,
database: String,
secure: bool,
sni: String,
ca_pem: Vec<u8>,
use_native_roots: bool,
skip_verification: bool,
connect_timeout_ms: u64,
recv_timeout_ms: u64,
send_timeout_ms: u64,
tcp_nodelay: bool,
tcp_keepalive: bool,
keepalive_idle_secs: u64,
keepalive_interval_secs: u64,
keepalive_count: u32,
send_retries: u32,
retry_timeout_secs: u64,
ping_before_query: bool,
extra_hosts: Vec<String>,
extra_ports: Vec<u16>,
compression: u8,
}
struct EchoOptions {
host: String,
port: u16,
secure: bool,
sni: String,
ca_pem: Vec<u8>,
use_native_roots: bool,
skip_verification: bool,
connect_timeout_ms: u64,
recv_timeout_ms: u64,
send_timeout_ms: u64,
tcp_nodelay: bool,
tcp_keepalive: bool,
keepalive_idle_secs: u64,
keepalive_interval_secs: u64,
keepalive_count: u32,
payload: Vec<u8>,
}
extern "Rust" {
type TlsSession;
fn tls_connect(request: &TlsConnect) -> Result<Box<TlsSession>>;
fn tls_read(session: &TlsSession, buf: &mut [u8]) -> Result<usize>;
fn tls_write(session: &TlsSession, buf: &[u8]) -> Result<usize>;
fn tls_flush(session: &TlsSession) -> Result<()>;
}
unsafe extern "C++" {
include!("native_client.h");
include!("rustls_socket.h");
#[namespace = "chcxx"]
type Client = crate::ffi::Client;
#[namespace = "chcxx"]
fn new_native_client(opts: &NativeOpen) -> Result<UniquePtr<Client>>;
fn factory_echo(opts: &EchoOptions) -> Result<Vec<u8>>;
}
}
type BridgeError = Box<dyn std::error::Error + Send + Sync>;
#[derive(Debug)]
struct BridgeIoError(std::io::Error);
impl std::fmt::Display for BridgeIoError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use std::io::ErrorKind;
let prefix = match self.0.kind() {
ErrorKind::InvalidInput | ErrorKind::InvalidData | ErrorKind::PermissionDenied => {
"fatal"
}
_ => "retriable",
};
write!(formatter, "{prefix}: {}", self.0)
}
}
impl std::error::Error for BridgeIoError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.0)
}
}
fn bridge_io(err: std::io::Error) -> BridgeError {
Box::new(BridgeIoError(err))
}
fn tls_connect(request: &ffi::TlsConnect) -> Result<Box<TlsSession>, BridgeError> {
let params = ConnectParams {
host: &request.host,
port: request.port,
secure: request.secure,
sni: &request.sni,
ca_pem: &request.ca_pem,
use_native_roots: request.use_native_roots,
skip_verification: request.skip_verification,
connect_timeout: Duration::from_millis(request.connect_timeout_ms),
read_timeout: Duration::from_millis(request.recv_timeout_ms),
write_timeout: Duration::from_millis(request.send_timeout_ms),
tcp_nodelay: request.tcp_nodelay,
tcp_keepalive: request.tcp_keepalive,
keepalive_idle: Duration::from_secs(request.keepalive_idle_secs),
keepalive_interval: Duration::from_secs(request.keepalive_interval_secs),
keepalive_count: request.keepalive_count,
};
connect(¶ms).map(Box::new).map_err(bridge_io)
}
fn tls_read(session: &TlsSession, buf: &mut [u8]) -> Result<usize, BridgeError> {
session.read(buf).map_err(bridge_io)
}
fn tls_write(session: &TlsSession, buf: &[u8]) -> Result<usize, BridgeError> {
session.write(buf).map_err(bridge_io)
}
fn tls_flush(session: &TlsSession) -> Result<(), BridgeError> {
session.flush().map_err(bridge_io)
}
pub struct NativeOptions {
pub host: String,
pub port: u16,
pub user: String,
pub password: String,
pub database: String,
pub secure: bool,
pub sni: String,
pub ca_pem: Vec<u8>,
pub use_native_roots: bool,
pub skip_verification: bool,
pub connect_timeout_ms: u64,
pub recv_timeout_ms: u64,
pub send_timeout_ms: u64,
pub tcp_nodelay: bool,
pub tcp_keepalive: bool,
pub keepalive_idle_secs: u64,
pub keepalive_interval_secs: u64,
pub keepalive_count: u32,
pub send_retries: u32,
pub retry_timeout_secs: u64,
pub ping_before_query: bool,
pub extra_hosts: Vec<String>,
pub extra_ports: Vec<u16>,
pub compression: u8,
}
impl Default for NativeOptions {
fn default() -> Self {
Self {
host: String::new(),
port: 9000,
user: "default".to_owned(),
password: String::new(),
database: "default".to_owned(),
secure: false,
sni: String::new(),
ca_pem: Vec::new(),
use_native_roots: true,
skip_verification: false,
connect_timeout_ms: 5_000,
recv_timeout_ms: 0,
send_timeout_ms: 0,
tcp_nodelay: true,
tcp_keepalive: false,
keepalive_idle_secs: 60,
keepalive_interval_secs: 5,
keepalive_count: 3,
send_retries: 0,
retry_timeout_secs: 0,
ping_before_query: false,
extra_hosts: Vec::new(),
extra_ports: Vec::new(),
compression: 0,
}
}
}
pub fn factory_echo(opts: &EchoParams) -> Result<Vec<u8>, String> {
ffi::factory_echo(&opts.to_ffi()).map_err(|err| err.to_string())
}
pub struct EchoParams {
pub host: String,
pub port: u16,
pub secure: bool,
pub sni: String,
pub ca_pem: Vec<u8>,
pub use_native_roots: bool,
pub skip_verification: bool,
pub connect_timeout_ms: u64,
pub recv_timeout_ms: u64,
pub send_timeout_ms: u64,
pub tcp_nodelay: bool,
pub tcp_keepalive: bool,
pub keepalive_idle_secs: u64,
pub keepalive_interval_secs: u64,
pub keepalive_count: u32,
pub payload: Vec<u8>,
}
impl EchoParams {
fn to_ffi(&self) -> ffi::EchoOptions {
ffi::EchoOptions {
host: self.host.clone(),
port: self.port,
secure: self.secure,
sni: self.sni.clone(),
ca_pem: self.ca_pem.clone(),
use_native_roots: self.use_native_roots,
skip_verification: self.skip_verification,
connect_timeout_ms: self.connect_timeout_ms,
recv_timeout_ms: self.recv_timeout_ms,
send_timeout_ms: self.send_timeout_ms,
tcp_nodelay: self.tcp_nodelay,
tcp_keepalive: self.tcp_keepalive,
keepalive_idle_secs: self.keepalive_idle_secs,
keepalive_interval_secs: self.keepalive_interval_secs,
keepalive_count: self.keepalive_count,
payload: self.payload.clone(),
}
}
}
impl NativeOptions {
fn to_ffi(&self) -> ffi::NativeOpen {
ffi::NativeOpen {
host: self.host.clone(),
port: self.port,
user: self.user.clone(),
password: self.password.clone(),
database: self.database.clone(),
secure: self.secure,
sni: self.sni.clone(),
ca_pem: self.ca_pem.clone(),
use_native_roots: self.use_native_roots,
skip_verification: self.skip_verification,
connect_timeout_ms: self.connect_timeout_ms,
recv_timeout_ms: self.recv_timeout_ms,
send_timeout_ms: self.send_timeout_ms,
tcp_nodelay: self.tcp_nodelay,
tcp_keepalive: self.tcp_keepalive,
keepalive_idle_secs: self.keepalive_idle_secs,
keepalive_interval_secs: self.keepalive_interval_secs,
keepalive_count: self.keepalive_count,
send_retries: self.send_retries,
retry_timeout_secs: self.retry_timeout_secs,
ping_before_query: self.ping_before_query,
extra_hosts: self.extra_hosts.clone(),
extra_ports: self.extra_ports.clone(),
compression: self.compression,
}
}
}
pub fn new_native_client(
opts: &NativeOptions,
) -> Result<cxx::UniquePtr<crate::ffi::Client>, String> {
ffi::new_native_client(&opts.to_ffi()).map_err(|err| err.to_string())
}