oracle-tns
Oracle Database Driver

Zero Instant Client.
Pure Rust. Oracle 12c+.

oracle-tns is a pure Rust implementation of the Oracle TNS wire protocol. No Oracle Instant Client, no C bindings, no shared libraries. A single static-musl binary that speaks TNS natively — O3LOGON, O5LOGON, queries, PL/SQL, stored procedures, REF CURSORs.

This driver connects to Oracle 12c R1 with 10G/11G password verifiers — something python-oracledb, node-oracledb, oracle-nio, and kalliope all fail to do (DPY-3015).
~5.5 MB
static musl binary
13ms
avg latency over VPN
5
fuzz targets
48M+
fuzz executions

Quickstart

Connect to Oracle
in three steps.

1. Add dependency
cargo add oracle-tns
2. Configure connection
use oracle_tns::{ConnectConfig, OracleSession};

let config = ConnectConfig {
    host: "db.example.com".to_string(),
    port: 1521,
    service_name: "MYDB".to_string(),
    username: "myuser".to_string(),
    password: "mypassword".to_string(),
};
3. Query
let mut session = OracleSession::connect(&config).await?;

let rows = session.query(
    "SELECT id, name, email FROM users WHERE department = :1",
    &[BindParam::varchar("Engineering")],
).await?;

for row in rows {
    let id: i64 = row.get("ID")?;
    let name: String = row.get("NAME")?;
    println!("{id}: {name}");
}

session.close().await?;

Features

Full Oracle protocol
in pure Rust.

ConnectTNS handshake, O3LOGON (0x939) + O5LOGON auth, service name resolution
QuerySELECT with full row decoding — VARCHAR2, NUMBER, DATE, RAW, NULL
Bind parametersPositional bind variables (:1, :2) — VARCHAR, INTEGER types
PaginationOFFSET/FETCH NEXT support for Oracle 12c+ row limiting
PL/SQL blocksExecute anonymous PL/SQL blocks with bind variables
Stored proceduresCALL with IN and OUT parameters
OUT parametersRead output values from stored procedure calls
REF CURSORBind OUT cursor, fetch result set with column metadata
DMLINSERT, UPDATE, DELETE, CREATE TABLE, DROP TABLE
Connection poolAsync pool with configurable min/max connections and idle timeout
PingLightweight connection health check over TNS

Comparison

oracle-tns vs the rest

oracle-tnspython-oracledbnode-oracledbInstant Client
Binary size~5.5 MB (static musl)~50 MB + Python~30 MB + Node.js~250 MB
Instant Client requiredNoThin: No, Thick: YesThin: No, Thick: YesYes (is it)
Oracle 12c R1 supportYesFails (DPY-3015)FailsYes
10G/11G verifierYes (O3LOGON)NoNoYes
AsyncYes (tokio)Yes (asyncio)Yes (promises)No
Connection poolingYesYesYesYes (OCI)
Fuzz testedYes (48M+ execs)NoNoNo

Security

Fuzz-tested.
48 million executions.

Every parser in oracle-tns is fuzz-tested with cargo-fuzz. Five targets covering TNS packet decoding, number parsing, date decoding, codec operations, and connection packet handling. Five bugs found and fixed before they reached production.

BugTargetImpactStatus
decode_number overflowfuzz_numberPanic on malformed NUMBER bytesFixed
AcceptPacket boundsfuzz_tns_packetOut-of-bounds read on truncated packetFixed
codec OOMfuzz_codecAllocation bomb via crafted length prefix (DoS vector)Fixed
decode_date overflowfuzz_datePanic on invalid date componentsFixed
slice index panicfuzz_codecIndex out of bounds on partial CLR chunkFixed
5
fuzz targets
48M+
total executions
5
bugs found
0
open issues

Wire Protocol

TNS from the wire up.

TNS Packet Framing
Every Oracle message is wrapped in a TNS packet: 8-byte header with packet length, checksum, type, and flags. Types include Connect (1), Accept (2), Data (6), Marker (12). The driver handles packet reassembly, SDU negotiation, and chunked reads.
Variable-Length Integers
Oracle uses UB2 (2-byte) and UB4 (4-byte) unsigned integers in big-endian format for lengths and offsets. The driver reads these from the wire and uses them to parse field boundaries in data packets.
CLR Chunked Encoding
Column data is encoded in CLR (Column Length Representation) format: a length byte followed by data bytes. For values longer than 254 bytes, a chunked encoding is used with 0xFE prefix and multiple length-prefixed segments.
O3LOGON / O5LOGON Auth
O5LOGON uses a Diffie-Hellman key exchange with SHA-1. O3LOGON (verifier 0x939) is required for Oracle 12c R1 with legacy 10G/11G password verifiers — this is the auth that every other thin driver fails to implement.

API Reference

Key types and functions.

ConnectConfig
pub struct ConnectConfig {
    pub host: String,
    pub port: u16,
    pub service_name: String,
    pub username: String,
    pub password: String,
}
OracleSession
impl OracleSession {
    pub async fn connect(config: &ConnectConfig) -> Result<Self>;
    pub async fn query(&mut self, sql: &str, params: &[BindParam]) -> Result<Vec<Row>>;
    pub async fn execute(&mut self, sql: &str, params: &[BindParam]) -> Result<u64>;
    pub async fn call(&mut self, sql: &str, params: &mut [BindParam]) -> Result<()>;
    pub async fn ping(&mut self) -> Result<()>;
    pub async fn close(self) -> Result<()>;
}
BindParam
impl BindParam {
    pub fn varchar(value: &str) -> Self;
    pub fn integer(value: i64) -> Self;
    pub fn out_varchar() -> Self;
    pub fn out_cursor() -> Self;
}
Cursor + OracleValue
impl Cursor {
    pub fn fetch_all(&self) -> &[Row];
    pub fn columns(&self) -> &[ColumnInfo];
}

pub enum OracleValue {
    Varchar(String),
    Number(f64),
    Integer(i64),
    Date(NaiveDateTime),
    Raw(Vec<u8>),
    Null,
}

Known Limitations

What's not done yet.

CLOB/BLOB streaming
Large object types are not yet supported. Values over the SDU size will need streaming reads — implementation pending.
TIMESTAMP WITH TIME ZONE
Basic DATE and TIMESTAMP work. TIMESTAMP WITH TIME ZONE and TIMESTAMP WITH LOCAL TIME ZONE parsing is not yet implemented.
TCP keepalive
Long-lived connections over unreliable networks may drop silently. TCP keepalive configuration is pending.