1#![forbid(unsafe_code)]
2
3pub mod capabilities;
4pub mod crypto;
5pub mod dpl;
6pub mod net;
7pub mod oson;
8pub mod packet;
9pub mod sql;
10pub mod thin;
11pub mod tls;
12pub mod vector;
13pub mod wire;
14
15use std::borrow::Cow;
16
17pub const PYTHON_ORACLEDB_REFERENCE_TAG: &str = "v4.0.1";
18pub const PYTHON_ORACLEDB_REFERENCE_COMMIT: &str = "3daef052904e41668bb862e6fa40f43c22a81beb";
19pub const TNS_VERSION_MIN: u16 = 300;
20pub const TNS_VERSION_DESIRED: u16 = 319;
21
22#[derive(Debug, thiserror::Error)]
23pub enum ProtocolError {
24 #[error("truncated packet header: got {got} bytes")]
25 TruncatedHeader { got: usize },
26 #[error("invalid packet length {length}; expected at least {minimum}")]
27 InvalidPacketLength { length: usize, minimum: usize },
28 #[error("packet length {declared} exceeds available bytes {available}")]
29 IncompletePacket { declared: usize, available: usize },
30 #[error("packet length {length} exceeds TNS two-byte length field")]
31 PacketTooLarge { length: usize },
32 #[error("unsupported TNS version {version}")]
33 UnsupportedVersion { version: u16 },
34 #[error("invalid client identity field {field}: {reason}")]
35 InvalidClientIdentity {
36 field: &'static str,
37 reason: Cow<'static, str>,
38 },
39 #[error("invalid connect descriptor: {0}")]
40 InvalidConnectDescriptor(String),
41 #[error("TTC decode failed: {0}")]
42 TtcDecode(&'static str),
43 #[error("unknown TTC message type {message_type} at position {position}")]
44 UnknownMessageType { message_type: u8, position: usize },
45 #[error("server returned Oracle error: {0}")]
46 ServerError(String),
47 #[error("server returned Oracle error: {message}")]
48 ServerErrorWithRowCount { message: String, row_count: u64 },
49 #[error("server returned Oracle error: {}", .0.message)]
50 ServerErrorInfo(Box<ServerErrorDetails>),
51 #[error("unsupported feature: {0}")]
52 UnsupportedFeature(&'static str),
53 #[error("missing authentication parameter {key}")]
54 MissingAuthParameter { key: &'static str },
55 #[error("unsupported password verifier type {verifier_type:#x}")]
56 UnsupportedVerifier { verifier_type: u32 },
57 #[error("invalid AES key length")]
58 InvalidAesKey,
59 #[error("invalid server authentication response")]
60 InvalidServerResponse,
61 #[error(
65 "DPY-8000: value of size {actual_size} exeeds maximum allowed size of \
66 {max_size} for column \"{column_name}\" of row {row_num}"
67 )]
68 ValueTooLarge {
69 actual_size: usize,
70 max_size: u32,
71 column_name: String,
72 row_num: u64,
73 },
74 #[error("DPY-8001: value for column \"{column_name}\" may not be null on row {row_num}")]
75 NullsNotAllowed { column_name: String, row_num: u64 },
76 #[error("DPY-4041: the maximum size of a Direct Path load has been exceeded")]
77 DirectPathLoadTooMuchData,
78 #[error("not implemented: {0}")]
79 NotImplemented(&'static str),
80 #[error("DPY-5004: input data is not in the OSON format: {0}")]
86 OsonNotEncoded(&'static str),
87 #[error("DPY-5006: invalid OSON data: {0}")]
88 OsonInvalid(&'static str),
89 #[error("DPY-3007: the data type {0} is not supported")]
92 OsonTypeNotSupported(&'static str),
93}
94
95pub type Result<T> = std::result::Result<T, ProtocolError>;
96
97#[derive(Clone, Debug, Default, Eq, PartialEq)]
100pub struct ServerErrorDetails {
101 pub message: String,
102 pub code: u32,
104 pub pos: i32,
106 pub row_count: u64,
108 pub rowid: Option<String>,
110 pub array_dml_row_counts: Option<Vec<u64>>,
113}
114
115#[derive(Clone, Debug, Eq, PartialEq)]
116pub struct ClientIdentity {
117 pub program: String,
118 pub machine: String,
119 pub osuser: String,
120 pub terminal: String,
121 pub driver_name: String,
122}
123
124impl ClientIdentity {
125 pub fn new(
126 program: impl Into<String>,
127 machine: impl Into<String>,
128 osuser: impl Into<String>,
129 terminal: impl Into<String>,
130 driver_name: impl Into<String>,
131 ) -> Result<Self> {
132 Ok(Self {
133 program: sanitize_identity_field("program", program.into())?,
134 machine: sanitize_identity_field("machine", machine.into())?,
135 osuser: sanitize_identity_field("osuser", osuser.into())?,
136 terminal: sanitize_identity_field("terminal", terminal.into())?,
137 driver_name: sanitize_identity_field("driver_name", driver_name.into())?,
138 })
139 }
140}
141
142fn sanitize_identity_field(field: &'static str, value: String) -> Result<String> {
143 let trimmed = value.trim();
144 if trimmed.is_empty() {
145 return Err(ProtocolError::InvalidClientIdentity {
146 field,
147 reason: Cow::Borrowed("value must not be empty"),
148 });
149 }
150
151 let mut out = String::with_capacity(trimmed.len().min(30));
152 for ch in trimmed.chars() {
153 if ch.is_control() {
154 return Err(ProtocolError::InvalidClientIdentity {
155 field,
156 reason: Cow::Borrowed("control characters are not allowed"),
157 });
158 }
159 if out.len() + ch.len_utf8() > 30 {
160 break;
161 }
162 out.push(ch);
163 }
164 Ok(out)
165}
166
167#[cfg(fuzzing)]
177pub mod fuzz_api {
178 use crate::wire::TtcReader;
179 use crate::Result;
180
181 pub fn fuzz_parse_server_error_info(data: &[u8]) -> Result<()> {
185 let (ttc_field_version, rest) = data.split_first().map_or((24u8, data), |(v, r)| (*v, r));
186 let mut reader = TtcReader::new(rest);
187 crate::thin::parse_server_error_info(&mut reader, ttc_field_version).map(|_| ())
188 }
189
190 pub fn fuzz_skip_server_side_piggyback(data: &[u8]) -> Result<()> {
192 let mut reader = TtcReader::new(data);
193 crate::thin::skip_server_side_piggyback(&mut reader).map(|_| ())
194 }
195
196 pub fn fuzz_scalar_codecs(data: &[u8]) {
200 let _ = crate::thin::decode_number_value(data);
201 let _ = crate::thin::decode_datetime_value(data);
202 let _ = crate::thin::decode_interval_ds(data);
203 let _ = crate::thin::decode_interval_ym(data);
204 let _ = crate::thin::decode_binary_float(data);
205 let _ = crate::thin::decode_binary_double(data);
206 }
207
208 pub fn fuzz_aq_responses(data: &[u8]) {
215 use crate::thin::aq::{
216 parse_aq_array_response, parse_aq_deq_response, parse_aq_enq_response, AqPayloadKind,
217 };
218 let (selector, payload) = data.split_first().map_or((0u8, data), |(v, r)| (*v, r));
219 let caps = crate::thin::ClientCapabilities {
220 ttc_field_version: 24 - (selector & 0x07),
221 ..crate::thin::ClientCapabilities::default()
222 };
223 let kind = match (selector >> 3) % 3 {
224 0 => AqPayloadKind::Raw,
225 1 => AqPayloadKind::Json,
226 _ => AqPayloadKind::Object,
227 };
228 let _ = parse_aq_enq_response(payload, caps);
229 let _ = parse_aq_deq_response(payload, caps, &kind);
230 let operation = i32::from(selector >> 6);
233 let props_count = u32::from(selector & 0x0f);
234 let _ = parse_aq_array_response(payload, caps, operation, props_count, &kind);
235 }
236
237 pub fn fuzz_subscr_responses(data: &[u8]) {
242 use crate::thin::{
243 parse_notification_stream, parse_subscribe_response, ClientCapabilities,
244 };
245 let (selector, payload) = data.split_first().map_or((0u8, data), |(v, r)| (*v, r));
246 let caps = ClientCapabilities {
247 ttc_field_version: 24 - (selector & 0x07),
248 ..ClientCapabilities::default()
249 };
250 let _ = parse_subscribe_response(payload, caps);
251 let namespace = u32::from(selector >> 4);
252 let public_qos = u32::from((selector >> 2) & 0x03);
253 let _ = parse_notification_stream(payload, namespace, public_qos, None);
254 let _ = parse_notification_stream(payload, namespace, public_qos, Some("FUZZDB"));
255 }
256
257 pub fn fuzz_connect_string(input: &str) {
270 let _ = crate::net::connectstring::parse(input);
271 let _ = crate::net::connectstring::tnsnames::fuzz_parse_file(input);
272 }
273}
274
275#[cfg(test)]
276mod tests {
277 use super::*;
278
279 #[test]
280 fn identity_fields_are_trimmed_and_bounded() {
281 let identity = ClientIdentity::new(
282 " program-name-longer-than-thirty-bytes ",
283 "machine",
284 "user",
285 "terminal",
286 "driver",
287 )
288 .expect("valid identity fields should sanitize");
289
290 assert_eq!(identity.program, "program-name-longer-than-thirt");
291 assert_eq!(identity.machine, "machine");
292 }
293
294 #[test]
295 fn identity_rejects_empty_fields() {
296 let err = ClientIdentity::new("", "machine", "user", "terminal", "driver")
297 .expect_err("empty program should be rejected");
298 assert!(matches!(
299 err,
300 ProtocolError::InvalidClientIdentity {
301 field: "program",
302 ..
303 }
304 ));
305 }
306}