#![forbid(unsafe_code)]
use super::*;
pub fn connect_data_fits_inline(connect_data: &str) -> bool {
connect_data.len() <= TNS_MAX_CONNECT_DATA
}
pub fn build_connect_packet_payload(connect_data: &str, sdu: u16) -> Result<Vec<u8>> {
let connect_bytes = connect_data.as_bytes();
let connect_len =
u16::try_from(connect_bytes.len()).map_err(|_| ProtocolError::PacketTooLarge {
length: connect_bytes.len(),
})?;
let mut writer = TtcWriter::new();
writer.write_u16be(TNS_VERSION_DESIRED);
writer.write_u16be(TNS_VERSION_MIN);
writer.write_u16be(TNS_GSO_DONT_CARE);
writer.write_u16be(sdu);
writer.write_u16be(sdu);
writer.write_u16be(TNS_PROTOCOL_CHARACTERISTICS);
writer.write_u16be(0);
writer.write_u16be(1);
writer.write_u16be(connect_len);
writer.write_u16be(74);
writer.write_u32be(0);
let nsi_flags = TNS_NSI_SUPPORT_SECURITY_RENEG | TNS_NSI_DISABLE_NA;
writer.write_u8(nsi_flags);
writer.write_u8(nsi_flags);
writer.write_u64be(0);
writer.write_u64be(0);
writer.write_u64be(0);
writer.write_u32be(u32::from(sdu));
writer.write_u32be(u32::from(sdu));
writer.write_u32be(0);
writer.write_u32be(0);
if connect_data_fits_inline(connect_data) {
writer.write_raw(connect_bytes);
}
Ok(writer.into_bytes())
}
pub fn parse_accept_payload(payload: &[u8]) -> Result<AcceptInfo> {
let mut reader = TtcReader::new(payload);
let protocol_version = reader.read_u16be()?;
if protocol_version < TNS_VERSION_MIN_ACCEPTED {
return Err(ProtocolError::UnsupportedVersion {
version: protocol_version,
minimum: TNS_VERSION_MIN_ACCEPTED,
});
}
let protocol_options = reader.read_u16be()?;
reader.skip(10)?;
let flags1 = reader.read_u8()?;
if has_u8_flag(flags1, TNS_NSI_NA_REQUIRED) {
return Err(ProtocolError::UnsupportedFeature(
"Native Network Encryption and Data Integrity",
));
}
reader.skip(9)?;
let sdu = reader.read_u32be()?;
let mut flags2 = 0;
if protocol_version >= 318 {
reader.skip(5)?;
flags2 = reader.read_u32be()?;
}
Ok(AcceptInfo {
protocol_version,
protocol_options,
sdu,
supports_fast_auth: has_u32_flag(flags2, TNS_ACCEPT_FLAG_FAST_AUTH),
supports_oob_check: has_u32_flag(flags2, TNS_ACCEPT_FLAG_CHECK_OOB),
supports_oob: protocol_options & TNS_GSO_CAN_RECV_ATTENTION != 0,
supports_end_of_response: protocol_version >= 319
&& has_u32_flag(flags2, TNS_ACCEPT_FLAG_HAS_END_OF_RESPONSE),
})
}
pub fn build_fast_auth_phase_one_payload(
user: &str,
program: &str,
machine: &str,
osuser: &str,
terminal: &str,
pid: u32,
) -> Result<Vec<u8>> {
let mut out = Vec::from_hex(FAST_AUTH_PREFIX_HEX)
.map_err(|_| ProtocolError::TtcDecode("invalid static fast-auth prefix"))?;
append_auth_phase_one(&mut out, user, program, machine, osuser, terminal, pid)?;
Ok(out)
}
const FAST_AUTH_PROTOCOL_MSG_START: usize = 4;
const FAST_AUTH_PROTOCOL_MSG_END: usize = 23;
const FAST_AUTH_DATA_TYPES_MSG_START: usize = 29;
fn fast_auth_prefix_slice(start: usize, end: Option<usize>) -> Result<Vec<u8>> {
let prefix = Vec::from_hex(FAST_AUTH_PREFIX_HEX)
.map_err(|_| ProtocolError::TtcDecode("invalid static fast-auth prefix"))?;
let slice = match end {
Some(end) => prefix.get(start..end),
None => prefix.get(start..),
};
slice
.map(<[u8]>::to_vec)
.ok_or(ProtocolError::TtcDecode("fast-auth prefix too short"))
}
pub fn build_protocol_negotiation_payload() -> Result<Vec<u8>> {
let payload = fast_auth_prefix_slice(
FAST_AUTH_PROTOCOL_MSG_START,
Some(FAST_AUTH_PROTOCOL_MSG_END),
)?;
debug_assert_eq!(payload.first(), Some(&TNS_MSG_TYPE_PROTOCOL));
Ok(payload)
}
pub fn build_data_types_payload() -> Result<Vec<u8>> {
let payload = fast_auth_prefix_slice(FAST_AUTH_DATA_TYPES_MSG_START, None)?;
debug_assert_eq!(payload.first(), Some(&TNS_MSG_TYPE_DATA_TYPES));
Ok(payload)
}
pub fn build_auth_phase_one_payload(
user: &str,
program: &str,
machine: &str,
osuser: &str,
terminal: &str,
pid: u32,
) -> Result<Vec<u8>> {
let mut out = Vec::new();
append_auth_phase_one(&mut out, user, program, machine, osuser, terminal, pid)?;
Ok(out)
}
pub fn build_fast_auth_token_payload(
user: &str,
token: &str,
driver_name: &str,
version_num: u32,
connect_string: &str,
edition: Option<&str>,
) -> Result<Vec<u8>> {
build_fast_auth_token_payload_with_pop(
user,
token,
driver_name,
version_num,
connect_string,
edition,
None,
)
}
pub fn build_fast_auth_token_payload_with_pop(
user: &str,
token: &str,
driver_name: &str,
version_num: u32,
connect_string: &str,
edition: Option<&str>,
pop: Option<TokenPop<'_>>,
) -> Result<Vec<u8>> {
build_fast_auth_token_payload_with_pop_and_proxy(
user,
token,
driver_name,
version_num,
connect_string,
edition,
pop,
None,
)
}
#[allow(clippy::too_many_arguments)] pub fn build_fast_auth_token_payload_with_pop_and_proxy(
user: &str,
token: &str,
driver_name: &str,
version_num: u32,
connect_string: &str,
edition: Option<&str>,
pop: Option<TokenPop<'_>>,
proxy_user: Option<&str>,
) -> Result<Vec<u8>> {
let mut out = Vec::from_hex(FAST_AUTH_PREFIX_HEX)
.map_err(|_| ProtocolError::TtcDecode("invalid static fast-auth prefix"))?;
append_auth_phase_two_token_with_pop_and_proxy(
&mut out,
user,
token,
driver_name,
version_num,
connect_string,
edition,
pop,
proxy_user,
)?;
Ok(out)
}
pub fn build_auth_phase_two_token_payload(
user: &str,
token: &str,
driver_name: &str,
version_num: u32,
connect_string: &str,
edition: Option<&str>,
) -> Result<Vec<u8>> {
build_auth_phase_two_token_payload_with_pop(
user,
token,
driver_name,
version_num,
connect_string,
edition,
None,
)
}
pub fn build_auth_phase_two_token_payload_with_pop(
user: &str,
token: &str,
driver_name: &str,
version_num: u32,
connect_string: &str,
edition: Option<&str>,
pop: Option<TokenPop<'_>>,
) -> Result<Vec<u8>> {
build_auth_phase_two_token_payload_with_pop_and_proxy(
user,
token,
driver_name,
version_num,
connect_string,
edition,
pop,
None,
)
}
#[allow(clippy::too_many_arguments)] pub fn build_auth_phase_two_token_payload_with_pop_and_proxy(
user: &str,
token: &str,
driver_name: &str,
version_num: u32,
connect_string: &str,
edition: Option<&str>,
pop: Option<TokenPop<'_>>,
proxy_user: Option<&str>,
) -> Result<Vec<u8>> {
let mut out = Vec::new();
append_auth_phase_two_token_with_pop_and_proxy(
&mut out,
user,
token,
driver_name,
version_num,
connect_string,
edition,
pop,
proxy_user,
)?;
Ok(out)
}
pub fn build_function_payload(function_code: u8, ttc_field_version: u8) -> Vec<u8> {
build_function_payload_with_seq(function_code, 1, ttc_field_version)
}
pub fn build_function_payload_with_seq(
function_code: u8,
seq_num: u8,
ttc_field_version: u8,
) -> Vec<u8> {
build_function_payload_with_seq_and_token(function_code, seq_num, 0, ttc_field_version)
}
pub fn build_function_payload_with_seq_and_token(
function_code: u8,
seq_num: u8,
token_num: u64,
ttc_field_version: u8,
) -> Vec<u8> {
let mut writer = TtcWriter::new();
writer.write_function_code_with_seq(function_code, seq_num);
if version_gates::writes_pipeline_token(ttc_field_version) {
writer.write_ub8(token_num);
} else {
debug_assert_eq!(
token_num, 0,
"pipeline tokens require a 23ai-negotiated connection"
);
}
writer.into_bytes()
}
pub(crate) fn skip_protocol_message(
reader: &mut TtcReader<'_>,
) -> Result<Option<ClientCapabilities>> {
let _server_version = reader.read_u8()?;
reader.skip(1)?;
loop {
if reader.read_u8()? == 0 {
break;
}
}
let charset_id = reader.read_u16le()?;
let _server_flags = reader.read_u8()?;
let num_elem = reader.read_u16le()?;
reader.skip(usize::from(num_elem) * 5)?;
let fdo_len = reader.read_u16be()?;
reader.skip(usize::from(fdo_len))?;
let compile_caps = reader.read_bytes()?;
let runtime_caps = reader.read_bytes()?;
let Some(compile_caps) = compile_caps else {
return Ok(None);
};
let server_ttc_field_version = compile_caps
.get(TNS_CCAP_FIELD_VERSION)
.copied()
.unwrap_or_else(|| ClientCapabilities::default().ttc_field_version);
let ttc_field_version =
server_ttc_field_version.min(ClientCapabilities::default().ttc_field_version);
let max_string_size = if runtime_caps
.as_deref()
.and_then(|caps| caps.get(TNS_RCAP_TTC))
.is_some_and(|flags| flags & TNS_RCAP_TTC_32K != 0)
{
32_767
} else {
4_000
};
Ok(Some(ClientCapabilities {
ttc_field_version,
max_string_size,
charset_id,
}))
}
pub(crate) fn skip_data_types_response(reader: &mut TtcReader<'_>) -> Result<()> {
loop {
let data_type = reader.read_u16be()?;
if data_type == 0 {
break;
}
let conv_data_type = reader.read_u16be()?;
if conv_data_type != 0 {
reader.skip(4)?;
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn classic_handshake_messages_slice_the_fast_auth_prefix() {
let protocol = build_protocol_negotiation_payload().expect("protocol payload");
assert_eq!(protocol[0], TNS_MSG_TYPE_PROTOCOL);
assert_eq!(protocol[1], 6, "protocol version byte (8.1 and higher)");
assert_eq!(protocol[2], 0, "array terminator");
assert!(
protocol.ends_with(b"python-oracledb\0"),
"driver name string with NUL terminator"
);
let data_types = build_data_types_payload().expect("data types payload");
assert_eq!(data_types[0], TNS_MSG_TYPE_DATA_TYPES);
assert_eq!(&data_types[1..5], &[0x69, 0x03, 0x69, 0x03]);
let full = Vec::from_hex(FAST_AUTH_PREFIX_HEX).expect("prefix decodes");
assert_eq!(full[0], TNS_MSG_TYPE_FAST_AUTH);
assert_eq!(
&full[FAST_AUTH_PROTOCOL_MSG_START..FAST_AUTH_PROTOCOL_MSG_END],
&protocol[..]
);
assert_eq!(&full[FAST_AUTH_DATA_TYPES_MSG_START..], &data_types[..]);
}
#[test]
fn classic_token_payload_is_the_fast_auth_phase_two_suffix() {
let classic = build_auth_phase_two_token_payload(
"scott",
"token-secret",
"rust-oracledb",
4_000_000_000,
"db.example.com/service",
Some("MY_EDITION"),
)
.expect("classic token payload");
let fast = build_fast_auth_token_payload(
"scott",
"token-secret",
"rust-oracledb",
4_000_000_000,
"db.example.com/service",
Some("MY_EDITION"),
)
.expect("fast token payload");
let prefix = Vec::from_hex(FAST_AUTH_PREFIX_HEX).expect("prefix decodes");
assert_eq!(&fast[..prefix.len()], prefix.as_slice());
assert_eq!(&fast[prefix.len()..], classic.as_slice());
assert_eq!(classic[0], TNS_MSG_TYPE_FUNCTION);
assert_eq!(classic[1], TNS_FUNC_AUTH_PHASE_TWO);
assert_eq!(
classic[2], 1,
"standalone phase two is the first TTC function"
);
}
fn accept_bytes(version: u16, options: u16, flags2: u32) -> Vec<u8> {
let mut w = TtcWriter::new();
w.write_u16be(version); w.write_u16be(options); w.write_raw(&[0u8; 10]); w.write_u8(0); w.write_raw(&[0u8; 9]); w.write_u32be(8192); w.write_raw(&[0u8; 5]); w.write_u32be(flags2); w.into_bytes()
}
#[test]
fn accept_parsing_gates_capabilities_on_protocol_version() {
assert!(
!parse_accept_payload(&accept_bytes(319, 0, 0))
.unwrap()
.supports_oob,
"no CAN_RECV_ATTENTION bit => supports_oob false"
);
assert!(
parse_accept_payload(&accept_bytes(319, TNS_GSO_CAN_RECV_ATTENTION, 0))
.unwrap()
.supports_oob,
"CAN_RECV_ATTENTION bit => supports_oob true"
);
let flags2 = TNS_ACCEPT_FLAG_FAST_AUTH | TNS_ACCEPT_FLAG_CHECK_OOB;
assert!(
!parse_accept_payload(&accept_bytes(317, 0, flags2))
.unwrap()
.supports_fast_auth,
"below 318 flags2 is not read"
);
let at_318 = parse_accept_payload(&accept_bytes(318, 0, flags2)).unwrap();
assert!(at_318.supports_fast_auth, "at 318 flags2 is read");
assert!(
at_318.supports_oob_check,
"at 318 the CHECK_OOB flag is read"
);
assert!(
!parse_accept_payload(&accept_bytes(318, 0, TNS_ACCEPT_FLAG_HAS_END_OF_RESPONSE))
.unwrap()
.supports_end_of_response,
"318 < 319: no end-of-response even with the flag"
);
assert!(
parse_accept_payload(&accept_bytes(319, 0, TNS_ACCEPT_FLAG_HAS_END_OF_RESPONSE))
.unwrap()
.supports_end_of_response,
"319 + flag: end-of-response negotiated"
);
assert!(
!parse_accept_payload(&accept_bytes(319, 0, 0))
.unwrap()
.supports_end_of_response,
"319 without the flag: no end-of-response"
);
}
fn protocol_info_with_caps(field_version: u8, runtime_ttc: u8) -> Vec<u8> {
let mut writer = TtcWriter::new();
writer.write_u8(0); writer.write_u8(0); writer.write_u8(0); writer.write_u16le(873); writer.write_u8(0); writer.write_u16le(0); writer.write_u16be(0);
let mut compile_caps = vec![0; TNS_CCAP_FIELD_VERSION + 1];
compile_caps[TNS_CCAP_FIELD_VERSION] = field_version;
writer
.write_bytes_with_length(&compile_caps)
.expect("short compile caps");
let mut runtime_caps = vec![0; TNS_RCAP_TTC + 1];
runtime_caps[TNS_RCAP_TTC] = runtime_ttc;
writer
.write_bytes_with_length(&runtime_caps)
.expect("short runtime caps");
writer.into_bytes()
}
#[test]
fn nineteen_c_caps_profile_derives_the_reference_19c_mask() {
let bytes = protocol_info_with_caps(TNS_CCAP_FIELD_VERSION_19_1_EXT_1, 0);
let caps = skip_protocol_message(&mut TtcReader::new(&bytes))
.expect("19c profile decodes")
.expect("compile caps present");
assert_eq!(
caps.ttc_field_version, TNS_CCAP_FIELD_VERSION_19_1_EXT_1,
"server field version caps the client at the 19c profile"
);
assert_eq!(caps.max_string_size, 4_000);
assert_eq!(caps.charset_id, 873);
}
}