1#![forbid(unsafe_code)]
2
3use super::*;
4
5pub 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 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 if protocol_version < TNS_VERSION_MIN_ACCEPTED {
60 return Err(ProtocolError::UnsupportedVersion {
61 version: protocol_version,
62 minimum: TNS_VERSION_MIN_ACCEPTED,
63 });
64 }
65 let protocol_options = reader.read_u16be()?;
66 reader.skip(10)?;
67 let flags1 = reader.read_u8()?;
68 if has_u8_flag(flags1, TNS_NSI_NA_REQUIRED) {
69 return Err(ProtocolError::UnsupportedFeature(
70 "Native Network Encryption and Data Integrity",
71 ));
72 }
73 reader.skip(9)?;
74 let sdu = reader.read_u32be()?;
75 let mut flags2 = 0;
76 if protocol_version >= 318 {
77 reader.skip(5)?;
78 flags2 = reader.read_u32be()?;
79 }
80
81 Ok(AcceptInfo {
82 protocol_version,
83 protocol_options,
84 sdu,
85 supports_fast_auth: has_u32_flag(flags2, TNS_ACCEPT_FLAG_FAST_AUTH),
86 supports_oob_check: has_u32_flag(flags2, TNS_ACCEPT_FLAG_CHECK_OOB),
87 supports_oob: protocol_options & TNS_GSO_CAN_RECV_ATTENTION != 0,
90 supports_end_of_response: protocol_version >= 319
91 && has_u32_flag(flags2, TNS_ACCEPT_FLAG_HAS_END_OF_RESPONSE),
92 })
93}
94
95pub fn build_fast_auth_phase_one_payload(
96 user: &str,
97 program: &str,
98 machine: &str,
99 osuser: &str,
100 terminal: &str,
101 pid: u32,
102) -> Result<Vec<u8>> {
103 let mut out = Vec::from_hex(FAST_AUTH_PREFIX_HEX)
104 .map_err(|_| ProtocolError::TtcDecode("invalid static fast-auth prefix"))?;
105 append_auth_phase_one(&mut out, user, program, machine, osuser, terminal, pid)?;
106 Ok(out)
107}
108
109const FAST_AUTH_PROTOCOL_MSG_START: usize = 4;
122const FAST_AUTH_PROTOCOL_MSG_END: usize = 23;
123const FAST_AUTH_DATA_TYPES_MSG_START: usize = 29;
124
125fn fast_auth_prefix_slice(start: usize, end: Option<usize>) -> Result<Vec<u8>> {
126 let prefix = Vec::from_hex(FAST_AUTH_PREFIX_HEX)
127 .map_err(|_| ProtocolError::TtcDecode("invalid static fast-auth prefix"))?;
128 let slice = match end {
129 Some(end) => prefix.get(start..end),
130 None => prefix.get(start..),
131 };
132 slice
133 .map(<[u8]>::to_vec)
134 .ok_or(ProtocolError::TtcDecode("fast-auth prefix too short"))
135}
136
137pub fn build_protocol_negotiation_payload() -> Result<Vec<u8>> {
141 let payload = fast_auth_prefix_slice(
142 FAST_AUTH_PROTOCOL_MSG_START,
143 Some(FAST_AUTH_PROTOCOL_MSG_END),
144 )?;
145 debug_assert_eq!(payload.first(), Some(&TNS_MSG_TYPE_PROTOCOL));
146 Ok(payload)
147}
148
149pub fn build_data_types_payload() -> Result<Vec<u8>> {
153 let payload = fast_auth_prefix_slice(FAST_AUTH_DATA_TYPES_MSG_START, None)?;
154 debug_assert_eq!(payload.first(), Some(&TNS_MSG_TYPE_DATA_TYPES));
155 Ok(payload)
156}
157
158pub fn build_auth_phase_one_payload(
162 user: &str,
163 program: &str,
164 machine: &str,
165 osuser: &str,
166 terminal: &str,
167 pid: u32,
168) -> Result<Vec<u8>> {
169 let mut out = Vec::new();
170 append_auth_phase_one(&mut out, user, program, machine, osuser, terminal, pid)?;
171 Ok(out)
172}
173
174pub fn build_fast_auth_token_payload(
179 user: &str,
180 token: &str,
181 driver_name: &str,
182 version_num: u32,
183 connect_string: &str,
184 edition: Option<&str>,
185) -> Result<Vec<u8>> {
186 build_fast_auth_token_payload_with_pop(
187 user,
188 token,
189 driver_name,
190 version_num,
191 connect_string,
192 edition,
193 None,
194 )
195}
196
197pub fn build_fast_auth_token_payload_with_pop(
202 user: &str,
203 token: &str,
204 driver_name: &str,
205 version_num: u32,
206 connect_string: &str,
207 edition: Option<&str>,
208 pop: Option<TokenPop<'_>>,
209) -> Result<Vec<u8>> {
210 build_fast_auth_token_payload_with_pop_and_proxy(
211 user,
212 token,
213 driver_name,
214 version_num,
215 connect_string,
216 edition,
217 pop,
218 None,
219 )
220}
221
222#[allow(clippy::too_many_arguments)] pub fn build_fast_auth_token_payload_with_pop_and_proxy(
226 user: &str,
227 token: &str,
228 driver_name: &str,
229 version_num: u32,
230 connect_string: &str,
231 edition: Option<&str>,
232 pop: Option<TokenPop<'_>>,
233 proxy_user: Option<&str>,
234) -> Result<Vec<u8>> {
235 let mut out = Vec::from_hex(FAST_AUTH_PREFIX_HEX)
236 .map_err(|_| ProtocolError::TtcDecode("invalid static fast-auth prefix"))?;
237 append_auth_phase_two_token_with_pop_and_proxy(
238 &mut out,
239 user,
240 token,
241 driver_name,
242 version_num,
243 connect_string,
244 edition,
245 pop,
246 proxy_user,
247 )?;
248 Ok(out)
249}
250
251pub fn build_auth_phase_two_token_payload(
258 user: &str,
259 token: &str,
260 driver_name: &str,
261 version_num: u32,
262 connect_string: &str,
263 edition: Option<&str>,
264) -> Result<Vec<u8>> {
265 build_auth_phase_two_token_payload_with_pop(
266 user,
267 token,
268 driver_name,
269 version_num,
270 connect_string,
271 edition,
272 None,
273 )
274}
275
276pub fn build_auth_phase_two_token_payload_with_pop(
281 user: &str,
282 token: &str,
283 driver_name: &str,
284 version_num: u32,
285 connect_string: &str,
286 edition: Option<&str>,
287 pop: Option<TokenPop<'_>>,
288) -> Result<Vec<u8>> {
289 build_auth_phase_two_token_payload_with_pop_and_proxy(
290 user,
291 token,
292 driver_name,
293 version_num,
294 connect_string,
295 edition,
296 pop,
297 None,
298 )
299}
300
301#[allow(clippy::too_many_arguments)] pub fn build_auth_phase_two_token_payload_with_pop_and_proxy(
305 user: &str,
306 token: &str,
307 driver_name: &str,
308 version_num: u32,
309 connect_string: &str,
310 edition: Option<&str>,
311 pop: Option<TokenPop<'_>>,
312 proxy_user: Option<&str>,
313) -> Result<Vec<u8>> {
314 let mut out = Vec::new();
315 append_auth_phase_two_token_with_pop_and_proxy(
316 &mut out,
317 user,
318 token,
319 driver_name,
320 version_num,
321 connect_string,
322 edition,
323 pop,
324 proxy_user,
325 )?;
326 Ok(out)
327}
328
329pub fn build_function_payload(function_code: u8, ttc_field_version: u8) -> Vec<u8> {
330 build_function_payload_with_seq(function_code, 1, ttc_field_version)
331}
332
333pub fn build_function_payload_with_seq(
334 function_code: u8,
335 seq_num: u8,
336 ttc_field_version: u8,
337) -> Vec<u8> {
338 build_function_payload_with_seq_and_token(function_code, seq_num, 0, ttc_field_version)
339}
340
341pub fn build_function_payload_with_seq_and_token(
347 function_code: u8,
348 seq_num: u8,
349 token_num: u64,
350 ttc_field_version: u8,
351) -> Vec<u8> {
352 let mut writer = TtcWriter::new();
353 writer.write_function_code_with_seq(function_code, seq_num);
354 if version_gates::writes_pipeline_token(ttc_field_version) {
355 writer.write_ub8(token_num);
356 } else {
357 debug_assert_eq!(
358 token_num, 0,
359 "pipeline tokens require a 23ai-negotiated connection"
360 );
361 }
362 writer.into_bytes()
363}
364
365pub(crate) fn skip_protocol_message(
366 reader: &mut TtcReader<'_>,
367) -> Result<Option<ClientCapabilities>> {
368 let _server_version = reader.read_u8()?;
369 reader.skip(1)?;
370 loop {
371 if reader.read_u8()? == 0 {
372 break;
373 }
374 }
375 let charset_id = reader.read_u16le()?;
376 let _server_flags = reader.read_u8()?;
377 let num_elem = reader.read_u16le()?;
378 reader.skip(usize::from(num_elem) * 5)?;
379 let fdo_len = reader.read_u16be()?;
380 reader.skip(usize::from(fdo_len))?;
381 let compile_caps = reader.read_bytes()?;
382 let runtime_caps = reader.read_bytes()?;
383 let Some(compile_caps) = compile_caps else {
384 return Ok(None);
385 };
386 let server_ttc_field_version = compile_caps
387 .get(TNS_CCAP_FIELD_VERSION)
388 .copied()
389 .unwrap_or_else(|| ClientCapabilities::default().ttc_field_version);
390 let ttc_field_version =
396 server_ttc_field_version.min(ClientCapabilities::default().ttc_field_version);
397 let max_string_size = if runtime_caps
398 .as_deref()
399 .and_then(|caps| caps.get(TNS_RCAP_TTC))
400 .is_some_and(|flags| flags & TNS_RCAP_TTC_32K != 0)
401 {
402 32_767
403 } else {
404 4_000
405 };
406 Ok(Some(ClientCapabilities {
407 ttc_field_version,
408 max_string_size,
409 charset_id,
410 }))
411}
412
413pub(crate) fn skip_data_types_response(reader: &mut TtcReader<'_>) -> Result<()> {
414 loop {
415 let data_type = reader.read_u16be()?;
416 if data_type == 0 {
417 break;
418 }
419 let conv_data_type = reader.read_u16be()?;
420 if conv_data_type != 0 {
421 reader.skip(4)?;
422 }
423 }
424 Ok(())
425}
426
427#[cfg(test)]
428mod tests {
429 use super::*;
430
431 #[test]
432 fn classic_handshake_messages_slice_the_fast_auth_prefix() {
433 let protocol = build_protocol_negotiation_payload().expect("protocol payload");
434 assert_eq!(protocol[0], TNS_MSG_TYPE_PROTOCOL);
435 assert_eq!(protocol[1], 6, "protocol version byte (8.1 and higher)");
436 assert_eq!(protocol[2], 0, "array terminator");
437 assert!(
438 protocol.ends_with(b"python-oracledb\0"),
439 "driver name string with NUL terminator"
440 );
441
442 let data_types = build_data_types_payload().expect("data types payload");
443 assert_eq!(data_types[0], TNS_MSG_TYPE_DATA_TYPES);
444 assert_eq!(&data_types[1..5], &[0x69, 0x03, 0x69, 0x03]);
446
447 let full = Vec::from_hex(FAST_AUTH_PREFIX_HEX).expect("prefix decodes");
450 assert_eq!(full[0], TNS_MSG_TYPE_FAST_AUTH);
451 assert_eq!(
452 &full[FAST_AUTH_PROTOCOL_MSG_START..FAST_AUTH_PROTOCOL_MSG_END],
453 &protocol[..]
454 );
455 assert_eq!(&full[FAST_AUTH_DATA_TYPES_MSG_START..], &data_types[..]);
456 }
457
458 #[test]
459 fn classic_token_payload_is_the_fast_auth_phase_two_suffix() {
460 let classic = build_auth_phase_two_token_payload(
461 "scott",
462 "token-secret",
463 "rust-oracledb",
464 4_000_000_000,
465 "db.example.com/service",
466 Some("MY_EDITION"),
467 )
468 .expect("classic token payload");
469 let fast = build_fast_auth_token_payload(
470 "scott",
471 "token-secret",
472 "rust-oracledb",
473 4_000_000_000,
474 "db.example.com/service",
475 Some("MY_EDITION"),
476 )
477 .expect("fast token payload");
478 let prefix = Vec::from_hex(FAST_AUTH_PREFIX_HEX).expect("prefix decodes");
479
480 assert_eq!(&fast[..prefix.len()], prefix.as_slice());
481 assert_eq!(&fast[prefix.len()..], classic.as_slice());
482 assert_eq!(classic[0], TNS_MSG_TYPE_FUNCTION);
483 assert_eq!(classic[1], TNS_FUNC_AUTH_PHASE_TWO);
484 assert_eq!(
485 classic[2], 1,
486 "standalone phase two is the first TTC function"
487 );
488 }
489
490 fn accept_bytes(version: u16, options: u16, flags2: u32) -> Vec<u8> {
502 let mut w = TtcWriter::new();
503 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()
512 }
513
514 #[test]
515 fn accept_parsing_gates_capabilities_on_protocol_version() {
516 assert!(
519 !parse_accept_payload(&accept_bytes(319, 0, 0))
520 .unwrap()
521 .supports_oob,
522 "no CAN_RECV_ATTENTION bit => supports_oob false"
523 );
524 assert!(
525 parse_accept_payload(&accept_bytes(319, TNS_GSO_CAN_RECV_ATTENTION, 0))
526 .unwrap()
527 .supports_oob,
528 "CAN_RECV_ATTENTION bit => supports_oob true"
529 );
530
531 let flags2 = TNS_ACCEPT_FLAG_FAST_AUTH | TNS_ACCEPT_FLAG_CHECK_OOB;
535 assert!(
536 !parse_accept_payload(&accept_bytes(317, 0, flags2))
537 .unwrap()
538 .supports_fast_auth,
539 "below 318 flags2 is not read"
540 );
541 let at_318 = parse_accept_payload(&accept_bytes(318, 0, flags2)).unwrap();
542 assert!(at_318.supports_fast_auth, "at 318 flags2 is read");
543 assert!(
544 at_318.supports_oob_check,
545 "at 318 the CHECK_OOB flag is read"
546 );
547
548 assert!(
552 !parse_accept_payload(&accept_bytes(318, 0, TNS_ACCEPT_FLAG_HAS_END_OF_RESPONSE))
553 .unwrap()
554 .supports_end_of_response,
555 "318 < 319: no end-of-response even with the flag"
556 );
557 assert!(
558 parse_accept_payload(&accept_bytes(319, 0, TNS_ACCEPT_FLAG_HAS_END_OF_RESPONSE))
559 .unwrap()
560 .supports_end_of_response,
561 "319 + flag: end-of-response negotiated"
562 );
563 assert!(
564 !parse_accept_payload(&accept_bytes(319, 0, 0))
565 .unwrap()
566 .supports_end_of_response,
567 "319 without the flag: no end-of-response"
568 );
569 }
570
571 fn protocol_info_with_caps(field_version: u8, runtime_ttc: u8) -> Vec<u8> {
575 let mut writer = TtcWriter::new();
576 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];
585 compile_caps[TNS_CCAP_FIELD_VERSION] = field_version;
586 writer
587 .write_bytes_with_length(&compile_caps)
588 .expect("short compile caps");
589
590 let mut runtime_caps = vec![0; TNS_RCAP_TTC + 1];
591 runtime_caps[TNS_RCAP_TTC] = runtime_ttc;
592 writer
593 .write_bytes_with_length(&runtime_caps)
594 .expect("short runtime caps");
595 writer.into_bytes()
596 }
597
598 #[test]
599 fn nineteen_c_caps_profile_derives_the_reference_19c_mask() {
600 let bytes = protocol_info_with_caps(TNS_CCAP_FIELD_VERSION_19_1_EXT_1, 0);
604 let caps = skip_protocol_message(&mut TtcReader::new(&bytes))
605 .expect("19c profile decodes")
606 .expect("compile caps present");
607
608 assert_eq!(
609 caps.ttc_field_version, TNS_CCAP_FIELD_VERSION_19_1_EXT_1,
610 "server field version caps the client at the 19c profile"
611 );
612 assert_eq!(caps.max_string_size, 4_000);
613 assert_eq!(caps.charset_id, 873);
614 }
615}