Skip to main content

oracledb_protocol/thin/
connect.rs

1#![forbid(unsafe_code)]
2
3use super::*;
4
5/// Whether the connect data travels inline in the CONNECT packet. Longer
6/// descriptors must be sent in a separate DATA packet right after the CONNECT
7/// packet (reference messages/connect.pyx `ConnectMessage.send`); the caller
8/// owns that follow-up send.
9pub fn connect_data_fits_inline(connect_data: &str) -> bool {
10    connect_data.len() <= TNS_MAX_CONNECT_DATA
11}
12
13pub fn build_connect_packet_payload(connect_data: &str, sdu: u16) -> Result<Vec<u8>> {
14    let connect_bytes = connect_data.as_bytes();
15    let connect_len =
16        u16::try_from(connect_bytes.len()).map_err(|_| ProtocolError::PacketTooLarge {
17            length: connect_bytes.len(),
18        })?;
19
20    let mut writer = TtcWriter::new();
21    writer.write_u16be(TNS_VERSION_DESIRED);
22    writer.write_u16be(TNS_VERSION_MIN);
23    writer.write_u16be(TNS_GSO_DONT_CARE);
24    writer.write_u16be(sdu);
25    writer.write_u16be(sdu);
26    writer.write_u16be(TNS_PROTOCOL_CHARACTERISTICS);
27    writer.write_u16be(0);
28    writer.write_u16be(1);
29    writer.write_u16be(connect_len);
30    writer.write_u16be(74);
31    writer.write_u32be(0);
32    let nsi_flags = TNS_NSI_SUPPORT_SECURITY_RENEG | TNS_NSI_DISABLE_NA;
33    writer.write_u8(nsi_flags);
34    writer.write_u8(nsi_flags);
35    writer.write_u64be(0);
36    writer.write_u64be(0);
37    writer.write_u64be(0);
38    writer.write_u32be(u32::from(sdu));
39    writer.write_u32be(u32::from(sdu));
40    writer.write_u32be(0);
41    writer.write_u32be(0);
42    // Connect data above TNS_MAX_CONNECT_DATA is carried in a separate DATA
43    // packet; the header still advertises the full length either way.
44    if connect_data_fits_inline(connect_data) {
45        writer.write_raw(connect_bytes);
46    }
47    Ok(writer.into_bytes())
48}
49
50pub fn parse_accept_payload(payload: &[u8]) -> Result<AcceptInfo> {
51    let mut reader = TtcReader::new(payload);
52    let protocol_version = reader.read_u16be()?;
53    let protocol_options = reader.read_u16be()?;
54    reader.skip(10)?;
55    let flags1 = reader.read_u8()?;
56    if has_u8_flag(flags1, TNS_NSI_NA_REQUIRED) {
57        return Err(ProtocolError::UnsupportedFeature(
58            "Native Network Encryption and Data Integrity",
59        ));
60    }
61    reader.skip(9)?;
62    let sdu = reader.read_u32be()?;
63    let mut flags2 = 0;
64    if protocol_version >= 318 {
65        reader.skip(5)?;
66        flags2 = reader.read_u32be()?;
67    }
68
69    Ok(AcceptInfo {
70        protocol_version,
71        protocol_options,
72        sdu,
73        supports_fast_auth: has_u32_flag(flags2, TNS_ACCEPT_FLAG_FAST_AUTH),
74        supports_oob_check: has_u32_flag(flags2, TNS_ACCEPT_FLAG_CHECK_OOB),
75        // Reference: Capabilities.supports_oob = protocol_options &
76        // TNS_GSO_CAN_RECV_ATTENTION (capabilities.pyx:121).
77        supports_oob: protocol_options & TNS_GSO_CAN_RECV_ATTENTION != 0,
78        supports_end_of_response: protocol_version >= 319
79            && has_u32_flag(flags2, TNS_ACCEPT_FLAG_HAS_END_OF_RESPONSE),
80    })
81}
82
83pub fn build_fast_auth_phase_one_payload(
84    user: &str,
85    program: &str,
86    machine: &str,
87    osuser: &str,
88    terminal: &str,
89    pid: u32,
90) -> Result<Vec<u8>> {
91    let mut out = Vec::from_hex(FAST_AUTH_PREFIX_HEX)
92        .map_err(|_| ProtocolError::TtcDecode("invalid static fast-auth prefix"))?;
93    append_auth_phase_one(&mut out, user, program, machine, osuser, terminal, pid)?;
94    Ok(out)
95}
96
97// Byte layout of FAST_AUTH_PREFIX_HEX (mirrors reference
98// messages/fast_auth.pyx `FastAuthMessage._write_message`):
99//   [0..4)    fast-auth envelope: msg type 34, version 1, char-conv flags
100//   [4..23)   embedded protocol-negotiation message (msg type 1, version 6,
101//             terminator, driver name string + NUL)
102//   [23..29)  envelope glue: unused server charset/ncharset placeholders +
103//             the pinned ttc field version byte
104//   [29..)    embedded data-types message (msg type 2, UTF8 charsets,
105//             encoding flags, compile/runtime caps, static type table)
106// The classic (non-fast-auth) handshake sends the same two embedded messages
107// as standalone round trips, so pre-23ai servers negotiate byte-identically
108// to the reference implementation.
109const FAST_AUTH_PROTOCOL_MSG_START: usize = 4;
110const FAST_AUTH_PROTOCOL_MSG_END: usize = 23;
111const FAST_AUTH_DATA_TYPES_MSG_START: usize = 29;
112
113fn fast_auth_prefix_slice(start: usize, end: Option<usize>) -> Result<Vec<u8>> {
114    let prefix = Vec::from_hex(FAST_AUTH_PREFIX_HEX)
115        .map_err(|_| ProtocolError::TtcDecode("invalid static fast-auth prefix"))?;
116    let slice = match end {
117        Some(end) => prefix.get(start..end),
118        None => prefix.get(start..),
119    };
120    slice
121        .map(<[u8]>::to_vec)
122        .ok_or(ProtocolError::TtcDecode("fast-auth prefix too short"))
123}
124
125/// Standalone TTC protocol-negotiation message (msg type 1) for the classic
126/// pre-23ai handshake. Byte-identical to the copy embedded in the fast-auth
127/// bundle (reference messages/protocol.pyx `ProtocolMessage._write_message`).
128pub fn build_protocol_negotiation_payload() -> Result<Vec<u8>> {
129    let payload = fast_auth_prefix_slice(
130        FAST_AUTH_PROTOCOL_MSG_START,
131        Some(FAST_AUTH_PROTOCOL_MSG_END),
132    )?;
133    debug_assert_eq!(payload.first(), Some(&TNS_MSG_TYPE_PROTOCOL));
134    Ok(payload)
135}
136
137/// Standalone TTC data-types message (msg type 2) for the classic pre-23ai
138/// handshake. Byte-identical to the copy embedded in the fast-auth bundle
139/// (reference messages/data_types.pyx `DataTypesMessage._write_message`).
140pub fn build_data_types_payload() -> Result<Vec<u8>> {
141    let payload = fast_auth_prefix_slice(FAST_AUTH_DATA_TYPES_MSG_START, None)?;
142    debug_assert_eq!(payload.first(), Some(&TNS_MSG_TYPE_DATA_TYPES));
143    Ok(payload)
144}
145
146/// Standalone auth phase-one function message for the classic pre-23ai
147/// handshake — the same message [`build_fast_auth_phase_one_payload`] appends
148/// after the fast-auth bundle, sent on its own round trip instead.
149pub fn build_auth_phase_one_payload(
150    user: &str,
151    program: &str,
152    machine: &str,
153    osuser: &str,
154    terminal: &str,
155    pid: u32,
156) -> Result<Vec<u8>> {
157    let mut out = Vec::new();
158    append_auth_phase_one(&mut out, user, program, machine, osuser, terminal, pid)?;
159    Ok(out)
160}
161
162/// Fast-auth bundle for **token authentication**: the same static
163/// protocol/data-types prefix as [`build_fast_auth_phase_one_payload`], but with
164/// a phase-two `AUTH_TOKEN` message appended (no verifier round-trip). The caller
165/// sends this once and reads a single auth response.
166pub fn build_fast_auth_token_payload(
167    user: &str,
168    token: &str,
169    driver_name: &str,
170    version_num: u32,
171    connect_string: &str,
172    edition: Option<&str>,
173) -> Result<Vec<u8>> {
174    let mut out = Vec::from_hex(FAST_AUTH_PREFIX_HEX)
175        .map_err(|_| ProtocolError::TtcDecode("invalid static fast-auth prefix"))?;
176    append_auth_phase_two_token(
177        &mut out,
178        user,
179        token,
180        driver_name,
181        version_num,
182        connect_string,
183        edition,
184    )?;
185    Ok(out)
186}
187
188pub fn build_function_payload(function_code: u8, ttc_field_version: u8) -> Vec<u8> {
189    build_function_payload_with_seq(function_code, 1, ttc_field_version)
190}
191
192pub fn build_function_payload_with_seq(
193    function_code: u8,
194    seq_num: u8,
195    ttc_field_version: u8,
196) -> Vec<u8> {
197    build_function_payload_with_seq_and_token(function_code, seq_num, 0, ttc_field_version)
198}
199
200/// Bare function message with an explicit pipeline token (messages/base.pyx
201/// `_write_function_code` writes `ub8 token_num` for field version >= 23.1
202/// ext 1; non-pipelined messages carry 0). On a pre-23.1-ext-1 connection the
203/// token field does not exist on the wire at all; pipelining (nonzero tokens)
204/// only happens on 23ai-negotiated connections, so no token is ever dropped.
205pub fn build_function_payload_with_seq_and_token(
206    function_code: u8,
207    seq_num: u8,
208    token_num: u64,
209    ttc_field_version: u8,
210) -> Vec<u8> {
211    let mut writer = TtcWriter::new();
212    writer.write_function_code_with_seq(function_code, seq_num);
213    if ttc_field_version >= TNS_CCAP_FIELD_VERSION_23_1_EXT_1 {
214        writer.write_ub8(token_num);
215    } else {
216        debug_assert_eq!(
217            token_num, 0,
218            "pipeline tokens require a 23ai-negotiated connection"
219        );
220    }
221    writer.into_bytes()
222}
223
224pub(crate) fn skip_protocol_message(
225    reader: &mut TtcReader<'_>,
226) -> Result<Option<ClientCapabilities>> {
227    let _server_version = reader.read_u8()?;
228    reader.skip(1)?;
229    loop {
230        if reader.read_u8()? == 0 {
231            break;
232        }
233    }
234    let charset_id = reader.read_u16le()?;
235    let _server_flags = reader.read_u8()?;
236    let num_elem = reader.read_u16le()?;
237    reader.skip(usize::from(num_elem) * 5)?;
238    let fdo_len = reader.read_u16be()?;
239    reader.skip(usize::from(fdo_len))?;
240    let compile_caps = reader.read_bytes()?;
241    let runtime_caps = reader.read_bytes()?;
242    let Some(compile_caps) = compile_caps else {
243        return Ok(None);
244    };
245    let server_ttc_field_version = compile_caps
246        .get(TNS_CCAP_FIELD_VERSION)
247        .copied()
248        .unwrap_or_else(|| ClientCapabilities::default().ttc_field_version);
249    // The effective field version is the LOWER of what the server reports and
250    // what this client supports (reference capabilities.pyx
251    // `_adjust_for_server_compile_caps`: "if server < client: client =
252    // server"). Taking the max would over-claim 23ai-era field formats against
253    // pre-23ai servers.
254    let ttc_field_version =
255        server_ttc_field_version.min(ClientCapabilities::default().ttc_field_version);
256    let max_string_size = if runtime_caps
257        .as_deref()
258        .and_then(|caps| caps.get(TNS_RCAP_TTC))
259        .is_some_and(|flags| flags & TNS_RCAP_TTC_32K != 0)
260    {
261        32_767
262    } else {
263        4_000
264    };
265    Ok(Some(ClientCapabilities {
266        ttc_field_version,
267        max_string_size,
268        charset_id,
269    }))
270}
271
272pub(crate) fn skip_data_types_response(reader: &mut TtcReader<'_>) -> Result<()> {
273    loop {
274        let data_type = reader.read_u16be()?;
275        if data_type == 0 {
276            break;
277        }
278        let conv_data_type = reader.read_u16be()?;
279        if conv_data_type != 0 {
280            reader.skip(4)?;
281        }
282    }
283    Ok(())
284}
285
286#[cfg(test)]
287mod tests {
288    use super::*;
289
290    #[test]
291    fn classic_handshake_messages_slice_the_fast_auth_prefix() {
292        let protocol = build_protocol_negotiation_payload().expect("protocol payload");
293        assert_eq!(protocol[0], TNS_MSG_TYPE_PROTOCOL);
294        assert_eq!(protocol[1], 6, "protocol version byte (8.1 and higher)");
295        assert_eq!(protocol[2], 0, "array terminator");
296        assert!(
297            protocol.ends_with(b"python-oracledb\0"),
298            "driver name string with NUL terminator"
299        );
300
301        let data_types = build_data_types_payload().expect("data types payload");
302        assert_eq!(data_types[0], TNS_MSG_TYPE_DATA_TYPES);
303        // UTF8 charset (873) little-endian for charset and ncharset.
304        assert_eq!(&data_types[1..5], &[0x69, 0x03, 0x69, 0x03]);
305
306        // The two standalone messages are exact slices of the fast-auth bundle,
307        // so classic and fast-auth handshakes can never drift apart.
308        let full = Vec::from_hex(FAST_AUTH_PREFIX_HEX).expect("prefix decodes");
309        assert_eq!(full[0], TNS_MSG_TYPE_FAST_AUTH);
310        assert_eq!(
311            &full[FAST_AUTH_PROTOCOL_MSG_START..FAST_AUTH_PROTOCOL_MSG_END],
312            &protocol[..]
313        );
314        assert_eq!(&full[FAST_AUTH_DATA_TYPES_MSG_START..], &data_types[..]);
315    }
316}