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-tns2. 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.
| Connect | TNS handshake, O3LOGON (0x939) + O5LOGON auth, service name resolution |
| Query | SELECT with full row decoding — VARCHAR2, NUMBER, DATE, RAW, NULL |
| Bind parameters | Positional bind variables (:1, :2) — VARCHAR, INTEGER types |
| Pagination | OFFSET/FETCH NEXT support for Oracle 12c+ row limiting |
| PL/SQL blocks | Execute anonymous PL/SQL blocks with bind variables |
| Stored procedures | CALL with IN and OUT parameters |
| OUT parameters | Read output values from stored procedure calls |
| REF CURSOR | Bind OUT cursor, fetch result set with column metadata |
| DML | INSERT, UPDATE, DELETE, CREATE TABLE, DROP TABLE |
| Connection pool | Async pool with configurable min/max connections and idle timeout |
| Ping | Lightweight connection health check over TNS |
Comparison
oracle-tns vs the rest
| oracle-tns | python-oracledb | node-oracledb | Instant Client | |
| Binary size | ~5.5 MB (static musl) | ~50 MB + Python | ~30 MB + Node.js | ~250 MB |
| Instant Client required | No | Thin: No, Thick: Yes | Thin: No, Thick: Yes | Yes (is it) |
| Oracle 12c R1 support | Yes | Fails (DPY-3015) | Fails | Yes |
| 10G/11G verifier | Yes (O3LOGON) | No | No | Yes |
| Async | Yes (tokio) | Yes (asyncio) | Yes (promises) | No |
| Connection pooling | Yes | Yes | Yes | Yes (OCI) |
| Fuzz tested | Yes (48M+ execs) | No | No | No |
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.
| Bug | Target | Impact | Status |
| decode_number overflow | fuzz_number | Panic on malformed NUMBER bytes | Fixed |
| AcceptPacket bounds | fuzz_tns_packet | Out-of-bounds read on truncated packet | Fixed |
| codec OOM | fuzz_codec | Allocation bomb via crafted length prefix (DoS vector) | Fixed |
| decode_date overflow | fuzz_date | Panic on invalid date components | Fixed |
| slice index panic | fuzz_codec | Index out of bounds on partial CLR chunk | Fixed |
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.