1#![forbid(unsafe_code)]
2use crate::{ProtocolError, Result};
32
33pub const DEFAULT_PORT: u16 = 1521;
35pub const DEFAULT_TCPS_PORT: u16 = 2484;
37pub const DEFAULT_SDU: u32 = 8192;
39pub const MIN_SDU: u32 = 512;
41pub const MAX_SDU: u32 = 2_097_152;
43pub const DEFAULT_RETRY_DELAY: u32 = 1;
45pub const DEFAULT_TCP_CONNECT_TIMEOUT: f64 = 20.0;
47
48#[derive(Clone, Copy, Debug, Eq, PartialEq, Default)]
51pub enum Protocol {
52 #[default]
54 Tcp,
55 Tcps,
57}
58
59impl Protocol {
60 #[must_use]
62 pub fn default_port(self) -> u16 {
63 match self {
64 Self::Tcp => DEFAULT_PORT,
65 Self::Tcps => DEFAULT_TCPS_PORT,
66 }
67 }
68
69 #[must_use]
71 pub fn is_tls(self) -> bool {
72 matches!(self, Self::Tcps)
73 }
74
75 #[must_use]
77 pub fn as_str(self) -> &'static str {
78 match self {
79 Self::Tcp => "tcp",
80 Self::Tcps => "tcps",
81 }
82 }
83
84 fn from_keyword(value: &str) -> Result<Self> {
85 match value.to_ascii_lowercase().as_str() {
86 "tcp" => Ok(Self::Tcp),
87 "tcps" => Ok(Self::Tcps),
88 other => Err(ProtocolError::InvalidConnectDescriptor(format!(
89 "invalid protocol \"{other}\""
90 ))),
91 }
92 }
93}
94
95#[derive(Clone, Copy, Debug, Eq, PartialEq)]
97pub enum ServerType {
98 Dedicated,
100 Shared,
102 Pooled,
104}
105
106impl ServerType {
107 #[must_use]
109 pub fn as_str(self) -> &'static str {
110 match self {
111 Self::Dedicated => "dedicated",
112 Self::Shared => "shared",
113 Self::Pooled => "pooled",
114 }
115 }
116
117 fn from_keyword(value: &str) -> Result<Self> {
118 match value.to_ascii_lowercase().as_str() {
119 "dedicated" => Ok(Self::Dedicated),
120 "shared" => Ok(Self::Shared),
121 "pooled" => Ok(Self::Pooled),
122 other => Err(ProtocolError::InvalidConnectDescriptor(format!(
123 "invalid server_type: {other}"
124 ))),
125 }
126 }
127}
128
129#[derive(Clone, Copy, Debug, Eq, PartialEq)]
131pub enum Purity {
132 Self_,
134 New,
136}
137
138impl Purity {
139 fn from_keyword(value: &str) -> Result<Self> {
140 match value.to_ascii_uppercase().as_str() {
141 "SELF" => Ok(Self::Self_),
142 "NEW" => Ok(Self::New),
143 other => Err(ProtocolError::InvalidConnectDescriptor(format!(
144 "invalid value for enum Purity: {other}"
145 ))),
146 }
147 }
148}
149
150#[derive(Clone, Debug, Eq, PartialEq)]
152pub struct Address {
153 pub host: Option<String>,
155 pub port: u16,
157 pub protocol: Protocol,
159 pub https_proxy: Option<String>,
161 pub https_proxy_port: u16,
163}
164
165impl Default for Address {
166 fn default() -> Self {
167 Self {
168 host: None,
169 port: DEFAULT_PORT,
170 protocol: Protocol::Tcp,
171 https_proxy: None,
172 https_proxy_port: 0,
173 }
174 }
175}
176
177#[derive(Clone, Debug, Default, Eq, PartialEq)]
179pub struct AddressList {
180 pub addresses: Vec<Address>,
182 pub load_balance: bool,
184 pub failover: bool,
186 pub source_route: bool,
188}
189
190#[derive(Clone, Debug, Default, Eq, PartialEq)]
192pub struct ConnectData {
193 pub service_name: Option<String>,
195 pub sid: Option<String>,
197 pub instance_name: Option<String>,
199 pub server_type: Option<ServerType>,
201 pub cclass: Option<String>,
203 pub purity: Option<Purity>,
205 pub pool_boundary: Option<String>,
207 pub pool_name: Option<String>,
209 pub connection_id_prefix: Option<String>,
211 pub use_tcp_fast_open: bool,
213 pub extra: Vec<(String, String)>,
216}
217
218#[derive(Clone, Debug, Eq, PartialEq)]
220pub struct Security {
221 pub ssl_server_dn_match: bool,
223 pub ssl_server_cert_dn: Option<String>,
225 pub wallet_location: Option<String>,
227 pub extra: Vec<(String, String)>,
229}
230
231impl Default for Security {
232 fn default() -> Self {
233 Self {
234 ssl_server_dn_match: true,
235 ssl_server_cert_dn: None,
236 wallet_location: None,
237 extra: Vec::new(),
238 }
239 }
240}
241
242#[derive(Clone, Debug, PartialEq)]
244pub struct Description {
245 pub address_lists: Vec<AddressList>,
247 pub connect_data: ConnectData,
249 pub security: Security,
251 pub retry_count: u32,
253 pub retry_delay: u32,
255 pub expire_time: u32,
257 pub tcp_connect_timeout: f64,
259 pub sdu: u32,
261 pub load_balance: bool,
263 pub failover: bool,
265 pub source_route: bool,
267 pub use_sni: bool,
269 pub extra: Vec<(String, String)>,
271}
272
273impl Default for Description {
274 fn default() -> Self {
275 Self {
276 address_lists: Vec::new(),
277 connect_data: ConnectData::default(),
278 security: Security::default(),
279 retry_count: 0,
280 retry_delay: DEFAULT_RETRY_DELAY,
281 expire_time: 0,
282 tcp_connect_timeout: DEFAULT_TCP_CONNECT_TIMEOUT,
283 sdu: DEFAULT_SDU,
284 load_balance: false,
285 failover: true,
286 source_route: false,
287 use_sni: false,
288 extra: Vec::new(),
289 }
290 }
291}
292
293impl Description {
294 pub fn addresses(&self) -> impl Iterator<Item = &Address> {
296 self.address_lists
297 .iter()
298 .flat_map(|list| list.addresses.iter())
299 }
300}
301
302#[derive(Clone, Debug, PartialEq)]
304pub struct Descriptor {
305 pub descriptions: Vec<Description>,
308 pub load_balance: bool,
310 pub failover: bool,
312 pub source_route: bool,
314}
315
316impl Descriptor {
317 #[must_use]
319 pub fn first_description(&self) -> &Description {
320 &self.descriptions[0]
321 }
322
323 pub fn addresses(&self) -> impl Iterator<Item = &Address> {
325 self.descriptions.iter().flat_map(Description::addresses)
326 }
327
328 #[must_use]
330 pub fn first_address(&self) -> Option<&Address> {
331 self.addresses().find(|addr| addr.host.is_some())
332 }
333
334 #[must_use]
337 pub fn describe(&self) -> String {
338 let mut out = String::new();
339 out.push_str("Descriptor {\n");
340 if self.descriptions.len() > 1 || self.load_balance || self.source_route || !self.failover {
341 out.push_str(&format!(
342 " description_list: load_balance={}, failover={}, source_route={}\n",
343 self.load_balance, self.failover, self.source_route
344 ));
345 }
346 for (di, desc) in self.descriptions.iter().enumerate() {
347 out.push_str(&format!(" description[{di}]:\n"));
348 for (li, list) in desc.address_lists.iter().enumerate() {
349 out.push_str(&format!(
350 " address_list[{li}]: load_balance={}, failover={}, source_route={}\n",
351 list.load_balance, list.failover, list.source_route
352 ));
353 for addr in &list.addresses {
354 out.push_str(&format!(
355 " {}://{}:{}\n",
356 addr.protocol.as_str(),
357 addr.host.as_deref().unwrap_or("<none>"),
358 addr.port
359 ));
360 }
361 }
362 let cd = &desc.connect_data;
363 out.push_str(" connect_data:");
364 if let Some(s) = &cd.service_name {
365 out.push_str(&format!(" service_name={s}"));
366 }
367 if let Some(s) = &cd.sid {
368 out.push_str(&format!(" sid={s}"));
369 }
370 if let Some(s) = &cd.instance_name {
371 out.push_str(&format!(" instance_name={s}"));
372 }
373 if let Some(s) = cd.server_type {
374 out.push_str(&format!(" server={}", s.as_str()));
375 }
376 out.push('\n');
377 if desc.retry_count != 0 {
378 out.push_str(&format!(
379 " retry_count={}, retry_delay={}\n",
380 desc.retry_count, desc.retry_delay
381 ));
382 }
383 }
384 out.push('}');
385 out
386 }
387}
388
389pub fn parse(connect_string: &str) -> Result<Option<Descriptor>> {
397 let trimmed = connect_string.trim();
398 if trimmed.is_empty() {
399 return Err(err_descriptor(
400 connect_string,
401 0,
402 "connect string must not be empty",
403 ));
404 }
405 let chars: Vec<char> = trimmed.chars().collect();
406 if chars[0] == '(' {
407 let mut parser = DescriptorParser::new(&chars, connect_string);
408 parser.pos = 1;
409 parser.temp_pos = 1;
410 let args = parser.parse_descriptor()?;
411 let descriptor = build_descriptor(connect_string, &args)?;
412 if parser.pos != chars.len() {
415 return Err(err_cannot_parse(connect_string));
416 }
417 Ok(Some(descriptor))
418 } else {
419 easy_connect::parse(&chars, connect_string)
420 }
421}
422
423mod easy_connect {
430 use super::*;
431
432 const PROXY_HOST_KEY: &str = "\0https_proxy_host";
435 const PROXY_PORT_KEY: &str = "\0https_proxy_port";
436
437 fn is_common_param(name: &str) -> bool {
440 matches!(
441 name,
442 "expire_time"
443 | "failover"
444 | "https_proxy"
445 | "https_proxy_port"
446 | "load_balance"
447 | "pool_boundary"
448 | "pool_name"
449 | "pool_connection_class"
450 | "pool_purity"
451 | "retry_count"
452 | "retry_delay"
453 | "sdu"
454 | "source_route"
455 | "ssl_server_cert_dn"
456 | "ssl_server_dn_match"
457 | "transport_connect_timeout"
458 | "use_sni"
459 | "wallet_location"
460 )
461 }
462
463 fn is_extra_description_param(name: &str) -> bool {
466 matches!(name, "enable" | "recv_buf_size" | "send_buf_size")
467 }
468
469 fn is_host_or_service_char(ch: char) -> bool {
470 ch.is_alphanumeric() || matches!(ch, '-' | '_' | '.')
471 }
472
473 struct Ez<'a> {
475 chars: &'a [char],
476 pos: usize,
477 temp_pos: usize,
478 }
479
480 impl<'a> Ez<'a> {
481 fn current(&self) -> char {
482 self.chars[self.temp_pos]
483 }
484
485 fn skip_spaces(&mut self) {
486 while self.temp_pos < self.chars.len() && self.chars[self.temp_pos].is_whitespace() {
487 self.temp_pos += 1;
488 }
489 }
490
491 fn parse_keyword(&mut self) {
492 while self.temp_pos < self.chars.len() {
493 let ch = self.current();
494 if !ch.is_alphanumeric() && ch != '_' && ch != '.' {
495 break;
496 }
497 self.temp_pos += 1;
498 }
499 }
500
501 fn parse_protocol(&mut self) -> Option<String> {
505 let mut start_sep_pos = self.pos;
506 let mut num_sep_chars = 0i32;
507 let mut protocol: Option<String> = None;
508 self.temp_pos = self.pos;
509 while self.temp_pos < self.chars.len() {
510 let ch = self.current();
511 if ch == ':' {
512 protocol = Some(
513 self.chars[self.pos..self.temp_pos]
514 .iter()
515 .collect::<String>()
516 .to_ascii_lowercase(),
517 );
518 start_sep_pos = self.temp_pos + 1;
519 } else if ch == '/' && (self.temp_pos - start_sep_pos) as i32 == num_sep_chars {
520 num_sep_chars += 1;
521 if num_sep_chars == 2 {
522 self.temp_pos += 1;
523 self.pos = self.temp_pos;
524 break;
525 }
526 } else if !ch.is_alphabetic() && ch != '-' && ch != '_' {
527 break;
528 }
529 self.temp_pos += 1;
530 }
531 if protocol.is_some() && num_sep_chars == 2 {
532 protocol
533 } else {
534 None
535 }
536 }
537
538 fn parse_host(&mut self, address: &mut Address) {
541 let mut found_bracket = false;
542 let mut found_host = false;
543 let mut start_pos = self.temp_pos;
544 while self.temp_pos < self.chars.len() {
545 let ch = self.current();
546 if !found_bracket && !found_host && ch == '[' {
547 found_bracket = true;
548 start_pos = self.temp_pos + 1;
549 } else if found_bracket && ch == ']' {
550 address.host = Some(self.chars[start_pos..self.temp_pos].iter().collect());
551 self.temp_pos += 1;
552 self.pos = self.temp_pos;
553 break;
554 } else if found_bracket || is_host_or_service_char(ch) {
555 self.temp_pos += 1;
556 found_host = true;
557 } else {
558 if found_host {
559 address.host = Some(self.chars[start_pos..self.temp_pos].iter().collect());
560 self.pos = self.temp_pos;
561 }
562 break;
563 }
564 }
565 if found_host && self.temp_pos == self.chars.len() && address.host.is_none() {
567 address.host = Some(self.chars[start_pos..self.temp_pos].iter().collect());
568 self.pos = self.temp_pos;
569 }
570 }
571
572 fn parse_port(&mut self, address: &mut Address) {
574 let start = self.temp_pos;
575 let mut found = false;
576 while self.temp_pos < self.chars.len() && self.current().is_ascii_digit() {
577 found = true;
578 self.temp_pos += 1;
579 }
580 if found {
581 let digits: String = self.chars[start..self.temp_pos].iter().collect();
582 if let Ok(port) = digits.parse::<u16>() {
583 address.port = port;
584 }
585 }
586 }
587 }
588
589 #[allow(clippy::too_many_lines)]
593 pub(super) fn parse(chars: &[char], connect_string: &str) -> Result<Option<Descriptor>> {
594 let mut ez = Ez {
595 chars,
596 pos: 0,
597 temp_pos: 0,
598 };
599
600 let template_protocol = match ez.parse_protocol() {
602 Some(protocol) => Protocol::from_keyword(&protocol)?,
603 None => Protocol::Tcp,
604 };
605
606 let mut address_lists: Vec<Vec<Address>> = Vec::new();
609 let mut current_list: Vec<Address> = Vec::new();
610 ez.temp_pos = ez.pos;
611 let mut port_index = 0usize;
612 loop {
613 let mut address = Address {
614 protocol: template_protocol,
615 port: template_protocol.default_port(),
616 ..Address::default()
617 };
618 ez.parse_host(&mut address);
619 if ez.temp_pos != ez.pos || ez.pos >= chars.len() {
621 if ez.pos >= chars.len() && address.host.is_some() {
624 current_list.push(address);
625 }
626 break;
627 }
628 ez.pos = ez.temp_pos;
629 current_list.push(address);
630 if ez.temp_pos >= chars.len() {
631 break;
632 }
633 let mut ch = ez.current();
634 if ch == ':' {
635 ez.temp_pos += 1;
636 if let Some(last) = current_list.last_mut() {
637 ez.parse_port(last);
638 let port = last.port;
639 ez.pos = ez.temp_pos;
640 if ez.pos >= chars.len() {
641 break;
642 }
643 let upper = current_list.len() - 1;
646 for addr in current_list.iter_mut().take(upper).skip(port_index) {
647 addr.port = port;
648 }
649 port_index = current_list.len();
650 }
651 ch = ez.current();
652 }
653 if ch == ';' {
654 address_lists.push(std::mem::take(&mut current_list));
655 port_index = 0;
656 } else if ch != ',' {
657 break;
658 }
659 ez.temp_pos += 1;
660 }
661 address_lists.push(current_list);
662
663 let mut description = Description::default();
665 let mut found_service_section = false;
666 parse_service_name(&mut ez, chars, &mut description, &mut found_service_section);
667 if found_service_section {
668 parse_instance_name(&mut ez, chars, &mut description);
669 }
670
671 if !found_service_section {
674 return Ok(None);
675 }
676
677 parse_parameters(&mut ez, chars, connect_string, &mut description)?;
678
679 if ez.pos != chars.len() {
681 if ez.pos > 0 {
682 return Err(err_cannot_parse(connect_string));
683 }
684 return Ok(None);
685 }
686
687 let mut lists: Vec<AddressList> = Vec::new();
690 for hosts in address_lists {
691 if hosts.is_empty() {
692 continue;
693 }
694 lists.push(AddressList {
695 addresses: hosts,
696 failover: true,
697 ..AddressList::default()
698 });
699 }
700 if lists.is_empty() {
701 return Ok(None);
702 }
703 description.address_lists = lists;
704
705 let proxy_host = description
708 .extra
709 .iter()
710 .find(|(k, _)| k == PROXY_HOST_KEY)
711 .map(|(_, v)| v.clone());
712 let proxy_port = description
713 .extra
714 .iter()
715 .find(|(k, _)| k == PROXY_PORT_KEY)
716 .and_then(|(_, v)| v.parse::<u16>().ok());
717 description
718 .extra
719 .retain(|(k, _)| k != PROXY_HOST_KEY && k != PROXY_PORT_KEY);
720 if proxy_host.is_some() || proxy_port.is_some() {
721 for list in &mut description.address_lists {
722 for addr in &mut list.addresses {
723 if let Some(host) = &proxy_host {
724 addr.https_proxy = Some(host.clone());
725 }
726 if let Some(port) = proxy_port {
727 addr.https_proxy_port = port;
728 }
729 }
730 }
731 }
732
733 Ok(Some(Descriptor {
734 descriptions: vec![description],
735 load_balance: false,
736 failover: true,
737 source_route: false,
738 }))
739 }
740
741 fn parse_service_name(
743 ez: &mut Ez,
744 chars: &[char],
745 description: &mut Description,
746 found_slash_out: &mut bool,
747 ) {
748 let mut found_service_name = false;
749 let mut found_server_type = false;
750 let mut found_slash = false;
751 let mut found_colon = false;
752 let mut service_name_end_pos = 0usize;
753 ez.temp_pos = ez.pos;
754 while ez.temp_pos < chars.len() {
755 let ch = ez.current();
756 if !found_slash && ch == '/' {
757 found_slash = true;
758 } else if found_service_name && !found_colon && ch == ':' {
759 found_colon = true;
760 } else if found_slash && !found_colon && is_host_or_service_char(ch) {
761 found_service_name = true;
762 service_name_end_pos = ez.temp_pos + 1;
763 } else if found_colon && ch.is_alphabetic() {
764 found_server_type = true;
765 } else {
766 break;
767 }
768 ez.temp_pos += 1;
769 }
770 if found_service_name {
771 description.connect_data.service_name =
772 Some(chars[ez.pos + 1..service_name_end_pos].iter().collect());
773 }
774 if found_slash {
775 ez.pos = ez.temp_pos;
776 *found_slash_out = true;
777 }
778 if found_server_type {
779 let value: String = chars[service_name_end_pos + 1..ez.temp_pos]
780 .iter()
781 .collect();
782 if let Ok(server_type) = ServerType::from_keyword(&value) {
783 description.connect_data.server_type = Some(server_type);
784 }
785 }
786 }
787
788 fn parse_instance_name(ez: &mut Ez, chars: &[char], description: &mut Description) {
790 let mut found_instance_name = false;
791 let mut found_slash = false;
792 let mut instance_name_end_pos = 0usize;
793 ez.temp_pos = ez.pos;
794 while ez.temp_pos < chars.len() {
795 let ch = ez.current();
796 if !found_slash && ch == '/' {
797 found_slash = true;
798 } else if found_slash && is_host_or_service_char(ch) {
799 found_instance_name = true;
800 instance_name_end_pos = ez.temp_pos + 1;
801 } else {
802 break;
803 }
804 ez.temp_pos += 1;
805 }
806 if found_instance_name {
807 description.connect_data.instance_name =
808 Some(chars[ez.pos + 1..instance_name_end_pos].iter().collect());
809 ez.pos = ez.temp_pos;
810 }
811 }
812
813 fn parse_parameters(
815 ez: &mut Ez,
816 chars: &[char],
817 connect_string: &str,
818 description: &mut Description,
819 ) -> Result<()> {
820 let mut expected_sep = '?';
821 ez.temp_pos = ez.pos;
822 while ez.temp_pos < chars.len() {
823 let ch = ez.current();
824 if ch != expected_sep {
825 break;
826 }
827 expected_sep = '&';
828 ez.temp_pos += 1;
829 parse_one_parameter(ez, chars, connect_string, description)?;
830 }
831 Ok(())
832 }
833
834 fn parse_one_parameter(
835 ez: &mut Ez,
836 chars: &[char],
837 connect_string: &str,
838 description: &mut Description,
839 ) -> Result<()> {
840 ez.skip_spaces();
842 let start = ez.temp_pos;
843 ez.parse_keyword();
844 if ez.temp_pos == start || ez.temp_pos >= chars.len() {
845 return Ok(());
846 }
847 let raw_name: String = chars[start..ez.temp_pos]
848 .iter()
849 .collect::<String>()
850 .to_ascii_lowercase();
851 let (name, keep) = if let Some(stripped) = raw_name.strip_prefix("pyo.") {
852 (stripped.to_string(), true)
853 } else {
854 let keep = is_common_param(&raw_name) || is_extra_description_param(&raw_name);
855 (canonical_param_name(&raw_name).to_string(), keep)
856 };
857
858 ez.skip_spaces();
860 if ez.temp_pos >= chars.len() {
861 return Ok(());
862 }
863 if ez.current() != '=' {
864 return Ok(());
865 }
866 ez.temp_pos += 1;
867
868 ez.skip_spaces();
870 let mut start_pos = ez.temp_pos;
871 let mut end_pos = ez.temp_pos;
872 while ez.temp_pos < chars.len() {
873 let ch = ez.current();
874 if ch == '"' {
875 if ez.temp_pos > start_pos {
876 return Ok(());
877 }
878 ez.temp_pos += 1;
879 start_pos = ez.temp_pos;
880 let mut closed = false;
882 while ez.temp_pos < chars.len() {
883 let qc = ez.current();
884 ez.temp_pos += 1;
885 if qc == '"' {
886 closed = true;
887 break;
888 }
889 }
890 if !closed {
891 return Err(err_descriptor(
892 connect_string,
893 ez.temp_pos,
894 "missing ending quote (\")",
895 ));
896 }
897 end_pos = ez.temp_pos - 1;
898 break;
899 } else if ch == '&' {
900 end_pos = ez.temp_pos;
901 break;
902 }
903 ez.temp_pos += 1;
904 end_pos = ez.temp_pos;
905 }
906 if end_pos > start_pos && keep {
907 let value: String = chars[start_pos..end_pos].iter().collect();
908 apply_easy_param(connect_string, description, &name, &value)?;
909 }
910 ez.skip_spaces();
911 ez.pos = ez.temp_pos;
912 Ok(())
913 }
914
915 fn apply_easy_param(
917 connect_string: &str,
918 description: &mut Description,
919 name: &str,
920 value: &str,
921 ) -> Result<()> {
922 match name {
923 "expire_time" => {
924 description.expire_time = parse_uint(connect_string, "EXPIRE_TIME", value)?
925 }
926 "retry_count" => {
927 description.retry_count = parse_uint(connect_string, "RETRY_COUNT", value)?
928 }
929 "retry_delay" => {
930 description.retry_delay = parse_uint(connect_string, "RETRY_DELAY", value)?
931 }
932 "sdu" => {
933 description.sdu = parse_uint(connect_string, "SDU", value)?.clamp(MIN_SDU, MAX_SDU);
934 }
935 "tcp_connect_timeout" => {
936 description.tcp_connect_timeout =
937 parse_duration(connect_string, "TRANSPORT_CONNECT_TIMEOUT", value)?;
938 }
939 "failover" => description.failover = parse_bool(value),
940 "load_balance" => description.load_balance = parse_bool(value),
941 "source_route" => description.source_route = parse_bool(value),
942 "use_sni" => description.use_sni = parse_bool(value),
943 "ssl_server_dn_match" => description.security.ssl_server_dn_match = parse_bool(value),
944 "ssl_server_cert_dn" => {
945 description.security.ssl_server_cert_dn = Some(value.to_string());
946 }
947 "wallet_location" => description.security.wallet_location = Some(value.to_string()),
948 "https_proxy" => description
952 .extra
953 .push((PROXY_HOST_KEY.to_string(), value.to_string())),
954 "https_proxy_port" => description
955 .extra
956 .push((PROXY_PORT_KEY.to_string(), value.to_string())),
957 "pool_boundary" => description.connect_data.pool_boundary = Some(value.to_string()),
958 "pool_name" => description.connect_data.pool_name = Some(value.to_string()),
959 "cclass" => {
960 if !value.is_empty() {
961 description.connect_data.cclass = Some(value.to_string());
962 }
963 }
964 "purity" => {
965 description.connect_data.purity = Some(Purity::from_keyword(value)?);
966 }
967 "enable" | "recv_buf_size" | "send_buf_size" => {
968 description
969 .extra
970 .push((name.to_ascii_uppercase(), value.to_string()));
971 }
972 _ => {}
975 }
976 Ok(())
977 }
978}
979
980fn err_descriptor(connect_string: &str, char_offset: usize, reason: &str) -> ProtocolError {
988 let trimmed = connect_string.trim();
989 let snippet = context_snippet(trimmed, char_offset);
990 ProtocolError::InvalidConnectDescriptor(format!(
991 "invalid connect descriptor \"{connect_string}\": {reason} at offset {char_offset}\n{snippet}"
992 ))
993}
994
995fn err_cannot_parse(connect_string: &str) -> ProtocolError {
996 ProtocolError::InvalidConnectDescriptor(format!(
997 "cannot parse connect string \"{connect_string}\""
998 ))
999}
1000
1001fn context_snippet(trimmed: &str, char_offset: usize) -> String {
1004 let chars: Vec<char> = trimmed.chars().collect();
1005 let start = char_offset.saturating_sub(20);
1006 let end = (char_offset + 20).min(chars.len());
1007 let window: String = chars[start..end].iter().collect();
1008 let caret_pos = char_offset - start;
1009 let mut caret = String::new();
1010 for _ in 0..caret_pos {
1011 caret.push(' ');
1012 }
1013 caret.push('^');
1014 format!(" {window}\n {caret}")
1015}
1016
1017#[derive(Clone, Debug)]
1024enum ArgValue {
1025 Simple(String),
1026 Node(ArgMap),
1027}
1028
1029#[derive(Clone, Debug, Default)]
1032struct ArgMap {
1033 entries: Vec<(String, Vec<ArgValue>)>,
1034}
1035
1036impl ArgMap {
1037 fn get(&self, key: &str) -> Option<&Vec<ArgValue>> {
1038 self.entries.iter().find(|(k, _)| k == key).map(|(_, v)| v)
1039 }
1040
1041 fn take(&mut self, key: &str) -> Option<Vec<ArgValue>> {
1042 if let Some(idx) = self.entries.iter().position(|(k, _)| k == key) {
1043 Some(self.entries.remove(idx).1)
1044 } else {
1045 None
1046 }
1047 }
1048
1049 fn push(&mut self, key: String, value: ArgValue) {
1050 if let Some((_, values)) = self.entries.iter_mut().find(|(k, _)| *k == key) {
1051 values.push(value);
1052 } else {
1053 self.entries.push((key, vec![value]));
1054 }
1055 }
1056}
1057
1058fn canonical_param_name(name: &str) -> &str {
1061 match name {
1062 "pool_connection_class" => "cclass",
1063 "pool_purity" => "purity",
1064 "server" => "server_type",
1065 "transport_connect_timeout" => "tcp_connect_timeout",
1066 "my_wallet_directory" => "wallet_location",
1067 other => other,
1068 }
1069}
1070
1071fn is_container_param(name: &str) -> bool {
1074 matches!(
1075 name,
1076 "address"
1077 | "address_list"
1078 | "connect_data"
1079 | "description"
1080 | "description_list"
1081 | "security"
1082 )
1083}
1084
1085const MAX_DESCRIPTOR_DEPTH: usize = 128;
1100
1101struct DescriptorParser<'a> {
1102 chars: &'a [char],
1103 raw: &'a str,
1104 pos: usize,
1106 temp_pos: usize,
1108 depth: usize,
1110}
1111
1112impl<'a> DescriptorParser<'a> {
1113 fn new(chars: &'a [char], raw: &'a str) -> Self {
1114 Self {
1115 chars,
1116 raw,
1117 pos: 0,
1118 temp_pos: 0,
1119 depth: 0,
1120 }
1121 }
1122
1123 fn current(&self) -> char {
1124 self.chars[self.temp_pos]
1125 }
1126
1127 fn skip_spaces(&mut self) {
1128 while self.temp_pos < self.chars.len() && self.chars[self.temp_pos].is_whitespace() {
1129 self.temp_pos += 1;
1130 }
1131 }
1132
1133 fn parse_keyword(&mut self) {
1136 while self.temp_pos < self.chars.len() {
1137 let ch = self.current();
1138 if !ch.is_alphanumeric() && ch != '_' && ch != '.' {
1139 break;
1140 }
1141 self.temp_pos += 1;
1142 }
1143 }
1144
1145 fn parse_quoted_string(&mut self, quote: char) -> Result<()> {
1149 while self.temp_pos < self.chars.len() {
1150 let ch = self.current();
1151 self.temp_pos += 1;
1152 if ch == quote {
1153 self.pos = self.temp_pos;
1154 return Ok(());
1155 }
1156 }
1157 let reason = if quote == '\'' {
1158 "missing ending quote (')"
1159 } else {
1160 "missing ending quote (\")"
1161 };
1162 Err(err_descriptor(self.raw, self.temp_pos, reason))
1163 }
1164
1165 fn parse_descriptor(&mut self) -> Result<ArgMap> {
1169 let mut args = ArgMap::default();
1170 self.parse_key_value_pair(&mut args)?;
1171 Ok(args)
1172 }
1173
1174 fn parse_key_value_pair(&mut self, args: &mut ArgMap) -> Result<()> {
1178 let mut is_simple_value = false;
1179 let mut simple_start = 0usize;
1180 let mut value: Option<ArgValue> = None;
1181
1182 self.skip_spaces();
1184 let start_pos = self.temp_pos;
1185 self.parse_keyword();
1186 if self.temp_pos == start_pos {
1187 return Err(err_descriptor(
1188 self.raw,
1189 self.temp_pos,
1190 "expected a keyword",
1191 ));
1192 }
1193 let raw_name: String = self.chars[start_pos..self.temp_pos]
1194 .iter()
1195 .collect::<String>()
1196 .to_ascii_lowercase();
1197 let name = canonical_param_name(&raw_name).to_string();
1198
1199 self.skip_spaces();
1201 let mut ch = '\0';
1202 if self.temp_pos < self.chars.len() {
1203 ch = self.current();
1204 }
1205 if ch != '=' {
1206 return Err(err_descriptor(
1207 self.raw,
1208 self.temp_pos,
1209 "expected '=' after keyword",
1210 ));
1211 }
1212 self.temp_pos += 1;
1213 self.skip_spaces();
1214
1215 while self.temp_pos < self.chars.len() {
1217 ch = self.current();
1218 if ch == '"' {
1219 if is_simple_value {
1220 return Err(err_descriptor(
1221 self.raw,
1222 self.temp_pos,
1223 "unexpected quote inside a simple value",
1224 ));
1225 }
1226 self.temp_pos += 1;
1227 let q_start = self.temp_pos;
1228 self.parse_quoted_string('"')?;
1229 if self.temp_pos > q_start + 1 {
1230 let v: String = self.chars[q_start..self.temp_pos - 1].iter().collect();
1231 value = Some(ArgValue::Simple(v));
1232 }
1233 break;
1234 } else if ch == '(' {
1235 if is_simple_value {
1236 return Err(err_descriptor(
1237 self.raw,
1238 self.temp_pos,
1239 "unexpected '(' inside a simple value",
1240 ));
1241 }
1242 self.temp_pos += 1;
1243 let mut node = match value.take() {
1244 Some(ArgValue::Node(n)) => n,
1245 _ => ArgMap::default(),
1246 };
1247 self.depth += 1;
1248 if self.depth > MAX_DESCRIPTOR_DEPTH {
1249 return Err(err_descriptor(
1250 self.raw,
1251 self.temp_pos,
1252 "connect descriptor nesting too deep",
1253 ));
1254 }
1255 let result = self.parse_key_value_pair(&mut node);
1256 self.depth -= 1;
1257 result?;
1258 value = Some(ArgValue::Node(node));
1259 continue;
1260 } else if ch == ')' {
1261 break;
1262 } else if !is_simple_value && !ch.is_whitespace() {
1263 if value.is_some() || is_container_param(&name) {
1264 return Err(err_descriptor(
1265 self.raw,
1266 self.temp_pos,
1267 "unexpected simple value for a container keyword",
1268 ));
1269 }
1270 simple_start = self.temp_pos;
1271 is_simple_value = true;
1272 }
1273 self.temp_pos += 1;
1274 }
1275 if is_simple_value {
1276 let v: String = self.chars[simple_start..self.temp_pos]
1277 .iter()
1278 .collect::<String>()
1279 .trim()
1280 .to_string();
1281 value = Some(ArgValue::Simple(v));
1282 }
1283 self.skip_spaces();
1284 if self.temp_pos < self.chars.len() {
1285 ch = self.current();
1286 if ch != ')' {
1287 return Err(err_descriptor(
1288 self.raw,
1289 self.temp_pos,
1290 "expected ')' to close the keyword",
1291 ));
1292 }
1293 self.temp_pos += 1;
1294 } else {
1295 return Err(err_descriptor(
1296 self.raw,
1297 self.temp_pos,
1298 "unbalanced parenthesis: expected ')'",
1299 ));
1300 }
1301 self.skip_spaces();
1302 self.pos = self.temp_pos;
1303
1304 if let Some(value) = value {
1305 self.set_descriptor_arg(args, name, value);
1306 }
1307 Ok(())
1308 }
1309
1310 fn set_descriptor_arg(&self, args: &mut ArgMap, name: String, value: ArgValue) {
1313 if args.get(&name).is_none() {
1314 if name == "address" && args.get("address_list").is_some() {
1315 let mut wrapper = ArgMap::default();
1316 wrapper.push("address".to_string(), value);
1317 self.set_descriptor_arg(args, "address_list".to_string(), ArgValue::Node(wrapper));
1318 return;
1319 } else if name == "address_list" && args.get("address").is_some() {
1320 let addresses = args.take("address").unwrap_or_default();
1321 for addr in addresses {
1324 let mut wrapper = ArgMap::default();
1325 wrapper.push("address".to_string(), addr);
1326 args.push("address_list".to_string(), ArgValue::Node(wrapper));
1327 }
1328 args.push(name, value);
1329 return;
1330 }
1331 args.push(name, value);
1332 } else {
1333 args.push(name, value);
1334 }
1335 }
1336}
1337
1338pub mod tnsnames {
1350 use crate::{ProtocolError, Result};
1351 use std::collections::HashSet;
1352 use std::path::{Path, PathBuf};
1353
1354 #[derive(Debug, Default)]
1356 pub struct TnsnamesReader {
1357 entries: Vec<(String, String)>,
1360 file_name: PathBuf,
1362 }
1363
1364 impl TnsnamesReader {
1365 pub fn read(config_dir: &Path) -> Result<Self> {
1367 let primary = config_dir.join("tnsnames.ora");
1368 let mut reader = TnsnamesReader {
1369 entries: Vec::new(),
1370 file_name: primary.clone(),
1371 };
1372 let mut in_progress: Vec<PathBuf> = Vec::new();
1373 let mut seen: HashSet<PathBuf> = HashSet::new();
1374 reader.read_file(&primary, &mut in_progress, &mut seen)?;
1375 Ok(reader)
1376 }
1377
1378 #[must_use]
1380 pub fn get(&self, alias: &str) -> Option<&str> {
1381 let upper = alias.to_ascii_uppercase();
1382 self.entries
1383 .iter()
1384 .find(|(name, _)| *name == upper)
1385 .map(|(_, value)| value.as_str())
1386 }
1387
1388 #[must_use]
1390 pub fn service_names(&self) -> Vec<String> {
1391 self.entries.iter().map(|(name, _)| name.clone()).collect()
1392 }
1393
1394 #[must_use]
1396 pub fn file_name(&self) -> &Path {
1397 &self.file_name
1398 }
1399
1400 fn set_entry(&mut self, name: String, value: String) {
1401 if let Some(slot) = self.entries.iter_mut().find(|(n, _)| *n == name) {
1404 slot.1 = value;
1405 } else {
1406 self.entries.push((name, value));
1407 }
1408 }
1409
1410 fn read_file(
1411 &mut self,
1412 path: &Path,
1413 in_progress: &mut Vec<PathBuf>,
1414 seen: &mut HashSet<PathBuf>,
1415 ) -> Result<()> {
1416 let canonical = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
1417 if in_progress.contains(&canonical) {
1418 let including = in_progress
1419 .last()
1420 .map(|p| p.display().to_string())
1421 .unwrap_or_default();
1422 return Err(ProtocolError::InvalidConnectDescriptor(format!(
1423 "file '{including}' includes file '{}', which forms a cycle",
1424 path.display()
1425 )));
1426 }
1427 let contents = std::fs::read_to_string(path).map_err(|_| {
1428 ProtocolError::InvalidConnectDescriptor(format!(
1429 "file '{}' is missing or unreadable",
1430 path.display()
1431 ))
1432 })?;
1433 in_progress.push(canonical.clone());
1434 seen.insert(canonical);
1435
1436 let dir = path.parent().unwrap_or_else(|| Path::new("."));
1437 let parsed = parse_file(&contents);
1440 for (key, value) in parsed {
1441 if key.eq_ignore_ascii_case("ifile") {
1442 let mut inc = value.trim().to_string();
1443 if inc.starts_with('"') && inc.ends_with('"') && inc.len() >= 2 {
1444 inc = inc[1..inc.len() - 1].to_string();
1445 }
1446 let inc_path = if Path::new(&inc).is_absolute() {
1447 PathBuf::from(&inc)
1448 } else {
1449 dir.join(&inc)
1450 };
1451 self.read_file(&inc_path, in_progress, seen)?;
1452 } else {
1453 for raw_alias in key.split(',') {
1456 let alias = raw_alias.trim().lines().last().unwrap_or("").trim();
1457 if alias.is_empty() {
1458 continue;
1459 }
1460 self.set_entry(alias.to_ascii_uppercase(), value.clone());
1461 }
1462 }
1463 }
1464 in_progress.pop();
1465 Ok(())
1466 }
1467 }
1468
1469 fn parse_file(contents: &str) -> Vec<(String, String)> {
1474 let chars: Vec<char> = contents.chars().collect();
1475 let mut parser = FileParser {
1476 chars: &chars,
1477 temp_pos: 0,
1478 pos: 0,
1479 };
1480 let mut out = Vec::new();
1481 while parser.temp_pos < parser.chars.len() {
1482 let key = parser.parse_key();
1483 let value = parser.parse_value();
1484 if let (Some(key), Some(value)) = (key, value) {
1485 if !key.is_empty() && !value.is_empty() {
1486 out.push((key, value.trim().to_string()));
1487 }
1488 }
1489 }
1490 out
1491 }
1492
1493 #[cfg(fuzzing)]
1504 pub fn fuzz_parse_file(contents: &str) -> Vec<(String, String)> {
1505 parse_file(contents)
1506 }
1507
1508 struct FileParser<'a> {
1509 chars: &'a [char],
1510 temp_pos: usize,
1511 pos: usize,
1512 }
1513
1514 impl FileParser<'_> {
1515 fn current(&self) -> char {
1516 self.chars[self.temp_pos]
1517 }
1518
1519 fn skip_spaces(&mut self) {
1520 while self.temp_pos < self.chars.len() && self.chars[self.temp_pos].is_whitespace() {
1521 self.temp_pos += 1;
1522 }
1523 }
1524
1525 fn skip_to_end_of_line(&mut self) {
1526 while self.temp_pos < self.chars.len() {
1527 let ch = self.current();
1528 self.temp_pos += 1;
1529 if ch == '\n' || ch == '\r' {
1530 break;
1531 }
1532 }
1533 self.pos = self.temp_pos;
1534 self.skip_spaces();
1535 }
1536
1537 fn parse_key(&mut self) -> Option<String> {
1540 let mut found_key = false;
1541 let mut start_pos = 0usize;
1542 self.skip_spaces();
1543 while self.temp_pos < self.chars.len() {
1544 let ch = self.current();
1545 if ch == '(' || ch == ')' || ch == '#' {
1546 self.skip_to_end_of_line();
1547 found_key = false;
1548 continue;
1549 } else if ch == '=' {
1550 if !found_key {
1551 self.skip_to_end_of_line();
1552 continue;
1553 }
1554 self.temp_pos += 1;
1555 self.pos = self.temp_pos;
1556 let key: String = self.chars[start_pos..self.temp_pos - 1].iter().collect();
1557 return Some(key.trim().to_string());
1558 } else if !found_key {
1559 found_key = true;
1560 start_pos = self.temp_pos;
1561 }
1562 self.temp_pos += 1;
1563 }
1564 None
1565 }
1566
1567 fn parse_value(&mut self) -> Option<String> {
1569 let mut num_parens: isize = 0;
1570 let mut parts: Vec<String> = Vec::new();
1571 while self.temp_pos < self.chars.len() {
1572 if let Some(part) = self.parse_value_part(&mut num_parens) {
1573 parts.push(part);
1574 }
1575 if num_parens == 0 {
1576 break;
1577 }
1578 }
1579 if parts.is_empty() {
1580 None
1581 } else {
1582 Some(parts.join("\n"))
1583 }
1584 }
1585
1586 fn parse_value_part(&mut self, num_parens: &mut isize) -> Option<String> {
1588 let mut start_pos = 0usize;
1589 let mut end_pos = 0usize;
1590 let mut found_part = false;
1591 self.skip_spaces();
1592 while self.temp_pos < self.chars.len() {
1593 let ch = self.current();
1594 if ch == '#' {
1595 end_pos = self.temp_pos;
1596 self.skip_to_end_of_line();
1597 if found_part {
1598 break;
1599 }
1600 continue;
1601 }
1602 if found_part && *num_parens == 0 {
1603 if ch == '\n' || ch == '\r' {
1604 end_pos = self.temp_pos;
1605 break;
1606 }
1607 } else if ch == '(' {
1608 *num_parens += 1;
1609 } else if ch == ')' && *num_parens > 0 {
1610 *num_parens -= 1;
1611 }
1612 if !found_part {
1613 found_part = true;
1614 start_pos = self.temp_pos;
1615 }
1616 self.temp_pos += 1;
1617 end_pos = self.temp_pos;
1618 }
1619 if found_part {
1620 let part: String = self.chars[start_pos..end_pos].iter().collect();
1621 Some(part.trim().to_string())
1622 } else {
1623 None
1624 }
1625 }
1626 }
1627}
1628
1629fn simple(map: &ArgMap, key: &str) -> Option<String> {
1635 match map.get(key)?.first()? {
1636 ArgValue::Simple(s) => Some(s.clone()),
1637 ArgValue::Node(_) => None,
1638 }
1639}
1640
1641fn parse_bool(value: &str) -> bool {
1644 matches!(
1645 value.trim().to_ascii_lowercase().as_str(),
1646 "on" | "yes" | "true"
1647 )
1648}
1649
1650fn parse_uint(connect_string: &str, key: &str, value: &str) -> Result<u32> {
1654 value.trim().parse::<u32>().map_err(|_| {
1655 ProtocolError::InvalidConnectDescriptor(format!(
1656 "invalid connect descriptor \"{connect_string}\": {key} value \"{value}\" is not a \
1657 non-negative integer"
1658 ))
1659 })
1660}
1661
1662fn parse_duration(connect_string: &str, key: &str, value: &str) -> Result<f64> {
1665 let v = value.trim().to_ascii_lowercase();
1666 let (num, scale) = if let Some(stripped) = v.strip_suffix("sec") {
1667 (stripped.trim(), 1.0)
1668 } else if let Some(stripped) = v.strip_suffix("ms") {
1669 (stripped.trim(), 0.001)
1670 } else if let Some(stripped) = v.strip_suffix("min") {
1671 (stripped.trim(), 60.0)
1672 } else {
1673 (v.as_str(), 1.0)
1674 };
1675 num.parse::<f64>().map(|n| n * scale).map_err(|_| {
1676 ProtocolError::InvalidConnectDescriptor(format!(
1677 "invalid connect descriptor \"{connect_string}\": {key} value \"{value}\" is not a \
1678 valid duration"
1679 ))
1680 })
1681}
1682
1683fn value_repr(value: &ArgValue) -> String {
1687 match value {
1688 ArgValue::Simple(s) => s.clone(),
1689 ArgValue::Node(node) => {
1690 let mut out = String::new();
1691 for (key, values) in &node.entries {
1692 for v in values {
1693 out.push('(');
1694 out.push_str(&key.to_ascii_uppercase());
1695 out.push('=');
1696 out.push_str(&value_repr(v));
1697 out.push(')');
1698 }
1699 }
1700 out
1701 }
1702 }
1703}
1704
1705fn collect_extras(map: &ArgMap, allowed: &[&str]) -> Vec<(String, String)> {
1708 let mut extras = Vec::new();
1709 for (key, values) in &map.entries {
1710 if allowed.contains(&key.as_str()) {
1711 continue;
1712 }
1713 for v in values {
1714 extras.push((key.to_ascii_uppercase(), value_repr(v)));
1715 }
1716 }
1717 extras
1718}
1719
1720fn build_descriptor(connect_string: &str, args: &ArgMap) -> Result<Descriptor> {
1723 let mut descriptor = Descriptor {
1724 descriptions: Vec::new(),
1725 load_balance: false,
1726 failover: true,
1727 source_route: false,
1728 };
1729
1730 let list_node = args.get("description_list").and_then(|v| match v.first() {
1732 Some(ArgValue::Node(n)) => Some(n),
1733 _ => None,
1734 });
1735 let description_container = if let Some(list_node) = list_node {
1736 descriptor.load_balance = list_node.get("load_balance").is_some()
1737 && simple(list_node, "load_balance").is_some_and(|v| parse_bool(&v));
1738 if let Some(v) = simple(list_node, "failover") {
1739 descriptor.failover = parse_bool(&v);
1740 }
1741 descriptor.source_route = simple(list_node, "source_route").is_some_and(|v| parse_bool(&v));
1742 list_node
1743 } else {
1744 args
1745 };
1746
1747 let descriptions: Vec<&ArgMap> = match description_container.get("description") {
1751 Some(values) => {
1752 let mut out = Vec::new();
1753 for v in values {
1754 if let ArgValue::Node(n) = v {
1755 out.push(n);
1756 }
1757 }
1758 out
1759 }
1760 None => vec![description_container],
1761 };
1762
1763 for desc_args in descriptions {
1764 let description = build_description(connect_string, desc_args)?;
1765 descriptor.descriptions.push(description);
1766 }
1767
1768 if descriptor.addresses().next().is_none() {
1769 return Err(ProtocolError::InvalidConnectDescriptor(format!(
1770 "no addresses are defined in connect descriptor: {connect_string}"
1771 )));
1772 }
1773 Ok(descriptor)
1774}
1775
1776const DESCRIPTION_PARAM_NAMES: &[&str] = &[
1777 "address",
1778 "address_list",
1779 "connect_data",
1780 "expire_time",
1781 "failover",
1782 "load_balance",
1783 "source_route",
1784 "retry_count",
1785 "retry_delay",
1786 "sdu",
1787 "tcp_connect_timeout",
1788 "use_sni",
1789 "security",
1790];
1791
1792const CONNECT_DATA_PARAM_NAMES: &[&str] = &[
1793 "cclass",
1794 "connection_id_prefix",
1795 "instance_name",
1796 "pool_boundary",
1797 "pool_name",
1798 "purity",
1799 "server_type",
1800 "service_name",
1801 "sid",
1802 "use_tcp_fast_open",
1803];
1804
1805const SECURITY_PARAM_NAMES: &[&str] = &[
1806 "ssl_server_cert_dn",
1807 "ssl_server_dn_match",
1808 "ssl_version",
1809 "wallet_location",
1810];
1811
1812fn build_description(connect_string: &str, desc_args: &ArgMap) -> Result<Description> {
1813 let mut description = Description::default();
1814
1815 if let Some(v) = simple(desc_args, "expire_time") {
1817 description.expire_time = parse_uint(connect_string, "EXPIRE_TIME", &v)?;
1818 }
1819 if let Some(v) = simple(desc_args, "failover") {
1820 description.failover = parse_bool(&v);
1821 }
1822 if let Some(v) = simple(desc_args, "load_balance") {
1823 description.load_balance = parse_bool(&v);
1824 }
1825 if let Some(v) = simple(desc_args, "source_route") {
1826 description.source_route = parse_bool(&v);
1827 }
1828 if let Some(v) = simple(desc_args, "retry_count") {
1829 description.retry_count = parse_uint(connect_string, "RETRY_COUNT", &v)?;
1830 }
1831 if let Some(v) = simple(desc_args, "retry_delay") {
1832 description.retry_delay = parse_uint(connect_string, "RETRY_DELAY", &v)?;
1833 }
1834 if let Some(v) = simple(desc_args, "use_sni") {
1835 description.use_sni = parse_bool(&v);
1836 }
1837 if let Some(v) = simple(desc_args, "sdu") {
1838 description.sdu = parse_uint(connect_string, "SDU", &v)?.clamp(MIN_SDU, MAX_SDU);
1839 }
1840 if let Some(v) = simple(desc_args, "tcp_connect_timeout") {
1841 description.tcp_connect_timeout =
1842 parse_duration(connect_string, "TRANSPORT_CONNECT_TIMEOUT", &v)?;
1843 }
1844 description.extra = collect_extras(desc_args, DESCRIPTION_PARAM_NAMES);
1845
1846 if let Some(ArgValue::Node(cd)) = desc_args.get("connect_data").and_then(|v| v.first()) {
1848 description.connect_data = build_connect_data(connect_string, cd)?;
1849 }
1850
1851 if let Some(ArgValue::Node(sec)) = desc_args.get("security").and_then(|v| v.first()) {
1853 description.security = build_security(sec);
1854 }
1855
1856 let address_list_nodes: Vec<&ArgMap> = match desc_args.get("address_list") {
1859 Some(values) => values
1860 .iter()
1861 .filter_map(|v| match v {
1862 ArgValue::Node(n) => Some(n),
1863 ArgValue::Simple(_) => None,
1864 })
1865 .collect(),
1866 None => {
1867 description.source_route = false;
1868 vec![desc_args]
1869 }
1870 };
1871
1872 for list_args in address_list_nodes {
1873 let mut address_list = AddressList {
1874 failover: true,
1875 ..AddressList::default()
1876 };
1877 if let Some(v) = simple(list_args, "failover") {
1878 address_list.failover = parse_bool(&v);
1879 }
1880 if let Some(v) = simple(list_args, "load_balance") {
1881 address_list.load_balance = parse_bool(&v);
1882 }
1883 if let Some(v) = simple(list_args, "source_route") {
1884 address_list.source_route = parse_bool(&v);
1885 }
1886 if let Some(addresses) = list_args.get("address") {
1887 for addr in addresses {
1888 if let ArgValue::Node(addr_node) = addr {
1889 address_list.addresses.push(build_address(addr_node)?);
1890 }
1891 }
1892 }
1893 description.address_lists.push(address_list);
1894 }
1895
1896 Ok(description)
1897}
1898
1899fn build_address(addr: &ArgMap) -> Result<Address> {
1900 let mut address = Address::default();
1901 if let Some(host) = simple(addr, "host") {
1902 address.host = Some(host);
1903 }
1904 if let Some(port) = simple(addr, "port") {
1905 address.port = port.trim().parse::<u16>().map_err(|_| {
1906 ProtocolError::InvalidConnectDescriptor(format!("invalid port: {port}"))
1907 })?;
1908 }
1909 if let Some(protocol) = simple(addr, "protocol") {
1910 address.protocol = Protocol::from_keyword(&protocol)?;
1911 }
1912 if let Some(proxy) = simple(addr, "https_proxy") {
1913 address.https_proxy = Some(proxy);
1914 }
1915 if let Some(proxy_port) = simple(addr, "https_proxy_port") {
1916 address.https_proxy_port = proxy_port.trim().parse::<u16>().unwrap_or(0);
1917 }
1918 Ok(address)
1919}
1920
1921fn build_connect_data(connect_string: &str, cd: &ArgMap) -> Result<ConnectData> {
1922 let mut data = ConnectData {
1923 service_name: simple(cd, "service_name"),
1924 instance_name: simple(cd, "instance_name"),
1925 sid: simple(cd, "sid"),
1926 ..ConnectData::default()
1927 };
1928 if let Some(server) = simple(cd, "server_type") {
1929 data.server_type = Some(ServerType::from_keyword(&server)?);
1930 }
1931 if let Some(cclass) = simple(cd, "cclass") {
1932 if !cclass.is_empty() {
1933 data.cclass = Some(cclass);
1934 }
1935 }
1936 if let Some(purity) = simple(cd, "purity") {
1937 data.purity = Some(Purity::from_keyword(&purity).map_err(|_| {
1938 ProtocolError::InvalidConnectDescriptor(format!(
1939 "invalid connect descriptor \"{connect_string}\": invalid POOL_PURITY \"{purity}\""
1940 ))
1941 })?);
1942 }
1943 data.pool_boundary = simple(cd, "pool_boundary");
1944 data.pool_name = simple(cd, "pool_name");
1945 data.connection_id_prefix = simple(cd, "connection_id_prefix");
1946 if let Some(v) = simple(cd, "use_tcp_fast_open") {
1947 data.use_tcp_fast_open = parse_bool(&v);
1948 }
1949 data.extra = collect_extras(cd, CONNECT_DATA_PARAM_NAMES);
1950 Ok(data)
1951}
1952
1953fn build_security(sec: &ArgMap) -> Security {
1954 let mut security = Security::default();
1955 if let Some(v) = simple(sec, "ssl_server_dn_match") {
1956 security.ssl_server_dn_match = parse_bool(&v);
1957 }
1958 security.ssl_server_cert_dn = simple(sec, "ssl_server_cert_dn");
1959 security.wallet_location = simple(sec, "wallet_location");
1960 security.extra = collect_extras(sec, SECURITY_PARAM_NAMES);
1961 security
1962}
1963
1964#[cfg(test)]
1965mod tests {
1966 use super::*;
1967
1968 fn parse_ok(input: &str) -> Descriptor {
1969 parse(input)
1970 .unwrap_or_else(|e| panic!("parse({input:?}) should succeed but failed: {e}"))
1971 .unwrap_or_else(|| panic!("parse({input:?}) should be a descriptor, not a tns alias"))
1972 }
1973
1974 fn hosts(d: &Descriptor) -> Vec<String> {
1977 d.addresses().filter_map(|a| a.host.clone()).collect()
1978 }
1979
1980 fn ports(d: &Descriptor) -> Vec<u16> {
1981 d.addresses().map(|a| a.port).collect()
1982 }
1983
1984 fn protocols(d: &Descriptor) -> Vec<Protocol> {
1985 d.addresses().map(|a| a.protocol).collect()
1986 }
1987
1988 #[test]
1989 fn parses_simple_name_value_descriptor() {
1990 let d = parse_ok(
1992 "(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=my_host4)(PORT=1589))\
1993 (CONNECT_DATA=(SERVICE_NAME=my_service_name4)))",
1994 );
1995 let addr = d.first_address().expect("descriptor has an address");
1996 assert_eq!(addr.host.as_deref(), Some("my_host4"));
1997 assert_eq!(addr.port, 1589);
1998 assert_eq!(addr.protocol, Protocol::Tcp);
1999 assert_eq!(
2000 d.first_description().connect_data.service_name.as_deref(),
2001 Some("my_service_name4")
2002 );
2003 }
2004
2005 #[test]
2008 fn parses_easy_connect_with_port() {
2009 let d = parse_ok("my_host:1578/my_service_name");
2011 let a = d.first_address().unwrap();
2012 assert_eq!(a.host.as_deref(), Some("my_host"));
2013 assert_eq!(a.port, 1578);
2014 assert_eq!(
2015 d.first_description().connect_data.service_name.as_deref(),
2016 Some("my_service_name")
2017 );
2018 }
2019
2020 #[test]
2021 fn parses_easy_connect_default_port() {
2022 let d = parse_ok("my_host2/my_service_name2");
2024 let a = d.first_address().unwrap();
2025 assert_eq!(a.host.as_deref(), Some("my_host2"));
2026 assert_eq!(a.port, 1521);
2027 }
2028
2029 #[test]
2030 fn parses_easy_connect_drcp_server_type() {
2031 let d = parse_ok("my_host3.org/my_service_name3:pooled");
2033 assert_eq!(
2034 d.first_description().connect_data.server_type,
2035 Some(ServerType::Pooled)
2036 );
2037 let d = parse_ok("my_host3/my_service_name3:ShArEd");
2038 assert_eq!(
2039 d.first_description().connect_data.server_type,
2040 Some(ServerType::Shared)
2041 );
2042 }
2043
2044 #[test]
2045 fn parses_easy_connect_tcps_protocol() {
2046 let d = parse_ok("tcps://my_host6/my_service_name6");
2048 assert_eq!(d.first_address().unwrap().protocol, Protocol::Tcps);
2049 }
2050
2051 #[test]
2052 fn parses_easy_connect_no_service() {
2053 let d = parse_ok("my_host15:1578/");
2055 let a = d.first_address().unwrap();
2056 assert_eq!(a.host.as_deref(), Some("my_host15"));
2057 assert_eq!(a.port, 1578);
2058 assert!(d.first_description().connect_data.service_name.is_none());
2059 }
2060
2061 #[test]
2062 fn parses_easy_connect_missing_port_value() {
2063 let d = parse_ok("my_host17:/my_service_name17");
2065 let a = d.first_address().unwrap();
2066 assert_eq!(a.host.as_deref(), Some("my_host17"));
2067 assert_eq!(a.port, 1521);
2068 assert_eq!(
2069 d.first_description().connect_data.service_name.as_deref(),
2070 Some("my_service_name17")
2071 );
2072 }
2073
2074 #[test]
2075 fn parses_easy_connect_ipv6() {
2076 let d = parse_ok("[::1]:4547/service_name_4547");
2078 let a = d.first_address().unwrap();
2079 assert_eq!(a.host.as_deref(), Some("::1"));
2080 assert_eq!(a.port, 4547);
2081 assert_eq!(
2082 d.first_description().connect_data.service_name.as_deref(),
2083 Some("service_name_4547")
2084 );
2085 }
2086
2087 #[test]
2088 fn parses_easy_connect_multiple_hosts_different_ports() {
2089 let d = parse_ok("host4548a,host4548b:4548,host4548c,host4548d:4549/service_name_4548");
2091 assert_eq!(
2092 hosts(&d),
2093 vec!["host4548a", "host4548b", "host4548c", "host4548d"]
2094 );
2095 assert_eq!(ports(&d), vec![4548, 4548, 4549, 4549]);
2096 }
2097
2098 #[test]
2099 fn parses_easy_connect_multiple_address_lists() {
2100 let d = parse_ok("host4549a;host4549b,host4549c:4549;host4549d/service_name_4549");
2102 assert_eq!(
2103 hosts(&d),
2104 vec!["host4549a", "host4549b", "host4549c", "host4549d"]
2105 );
2106 assert_eq!(ports(&d), vec![1521, 4549, 4549, 1521]);
2107 }
2108
2109 #[test]
2110 fn parses_easy_connect_degenerate_protocol() {
2111 let d = parse_ok("//host_4552:4552/service_name_4552");
2113 let a = d.first_address().unwrap();
2114 assert_eq!(a.host.as_deref(), Some("host_4552"));
2115 assert_eq!(a.port, 4552);
2116 }
2117
2118 #[test]
2119 fn parses_easy_connect_instance_name() {
2120 let d = parse_ok("host_4571:4571/service_4571/instance_4571");
2122 assert_eq!(
2123 d.first_description().connect_data.instance_name.as_deref(),
2124 Some("instance_4571")
2125 );
2126 assert_eq!(
2127 d.first_description().connect_data.service_name.as_deref(),
2128 Some("service_4571")
2129 );
2130 }
2131
2132 #[test]
2133 fn parses_easy_connect_extended_params() {
2134 let d = parse_ok(
2136 "my_host21/my_server_name21?expire_time=5&retry_delay=10&retry_count=12&transport_connect_timeout=2.5",
2137 );
2138 let desc = d.first_description();
2139 assert_eq!(desc.expire_time, 5);
2140 assert_eq!(desc.retry_delay, 10);
2141 assert_eq!(desc.retry_count, 12);
2142 assert!((desc.tcp_connect_timeout - 2.5).abs() < 1e-9);
2143 }
2144
2145 #[test]
2146 fn parses_easy_connect_security_params() {
2147 let d = parse_ok(
2149 "tcps://host_4580:4580/service_4580?ssl_server_dn_match=true&ssl_server_cert_dn='cn=sales'&wallet_location='/tmp/oracle'",
2150 );
2151 let sec = &d.first_description().security;
2155 assert!(sec.ssl_server_dn_match);
2156 assert_eq!(sec.ssl_server_cert_dn.as_deref(), Some("'cn=sales'"));
2157 assert_eq!(sec.wallet_location.as_deref(), Some("'/tmp/oracle'"));
2158 }
2159
2160 #[test]
2161 fn rejects_invalid_protocol_in_easy_connect() {
2162 let err = parse("invalid_proto://my_host7/my_service_name7").unwrap_err();
2164 assert!(format!("{err}").contains("invalid protocol"));
2165 }
2166
2167 #[test]
2170 fn diagnostic_points_at_unbalanced_paren() {
2171 let err = parse("(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=h)(PORT=1521))").unwrap_err();
2172 let msg = format!("{err}");
2173 assert!(msg.contains("offset"), "expected offset in: {msg}");
2174 assert!(msg.contains('^'), "expected caret context in: {msg}");
2175 }
2176
2177 #[test]
2178 fn diagnostic_for_missing_addresses() {
2179 let err = parse(
2181 "(DESRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=h)(PORT=1))(CONNECT_DATA=(SERVICE_NAME=s)))",
2182 )
2183 .unwrap_err();
2184 assert!(format!("{err}").contains("no addresses are defined"));
2185 }
2186
2187 #[test]
2188 fn protocol_default_port_resolves_for_unported_address() {
2189 let d = parse_ok("tcps://h/svc");
2190 assert_eq!(d.first_address().unwrap().port, 2484);
2191 }
2192
2193 #[test]
2194 fn describe_dumps_addresses() {
2195 let d = parse_ok(
2196 "(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=h1)(PORT=1521))\
2197 (CONNECT_DATA=(SERVICE_NAME=svc)))",
2198 );
2199 let text = d.describe();
2200 assert!(text.contains("tcp://h1:1521"));
2201 assert!(text.contains("service_name=svc"));
2202 }
2203
2204 #[test]
2205 fn keeps_protocols_for_multi_list_descriptor() {
2206 let d = parse_ok(
2208 "(DESCRIPTION=(LOAD_BALANCE=ON)(RETRY_COUNT=5)(RETRY_DELAY=2)\
2209 (ADDRESS_LIST=(LOAD_BALANCE=ON)\
2210 (ADDRESS=(PROTOCOL=tcp)(PORT=1521)(HOST=my_host26))\
2211 (ADDRESS=(PROTOCOL=tcp)(PORT=222)(HOST=my_host27)))\
2212 (ADDRESS_LIST=(LOAD_BALANCE=ON)\
2213 (ADDRESS=(PROTOCOL=tcps)(PORT=5555)(HOST=my_host28))\
2214 (ADDRESS=(PROTOCOL=tcps)(PORT=444)(HOST=my_host29)))\
2215 (CONNECT_DATA=(SERVICE_NAME=my_service_name26)))",
2216 );
2217 assert_eq!(
2218 hosts(&d),
2219 vec!["my_host26", "my_host27", "my_host28", "my_host29"]
2220 );
2221 assert_eq!(ports(&d), vec![1521, 222, 5555, 444]);
2222 assert_eq!(
2223 protocols(&d),
2224 vec![Protocol::Tcp, Protocol::Tcp, Protocol::Tcps, Protocol::Tcps]
2225 );
2226 }
2227
2228 #[test]
2229 fn parses_multiple_descriptions() {
2230 let d = parse_ok(
2232 "(DESCRIPTION_LIST=(FAIL_OVER=ON)(LOAD_BALANCE=OFF)\
2233 (DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=tcp)(PORT=5001)(HOST=my_host30))\
2234 (ADDRESS=(PROTOCOL=tcp)(PORT=1521)(HOST=my_host31)))\
2235 (CONNECT_DATA=(SERVICE_NAME=svc27)))\
2236 (DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=tcp)(PORT=5002)(HOST=my_host34)))\
2237 (CONNECT_DATA=(SERVICE_NAME=svc28))))",
2238 );
2239 assert_eq!(hosts(&d), vec!["my_host30", "my_host31", "my_host34"]);
2240 assert_eq!(d.descriptions.len(), 2);
2241 }
2242
2243 #[test]
2244 fn interleaves_address_and_address_list_small_first() {
2245 let d = parse_ok(
2247 "(DESCRIPTION=\
2248 (ADDRESS=(PROTOCOL=tcp)(HOST=host1)(PORT=1521))\
2249 (ADDRESS_LIST=(ADDRESS=(PROTOCOL=tcp)(HOST=host2a)(PORT=1522))\
2250 (ADDRESS=(PROTOCOL=tcp)(HOST=host2b)(PORT=1523)))\
2251 (ADDRESS=(PROTOCOL=tcp)(HOST=host3)(PORT=1524))\
2252 (CONNECT_DATA=(SERVICE_NAME=svc)))",
2253 );
2254 assert_eq!(hosts(&d), vec!["host1", "host2a", "host2b", "host3"]);
2255 }
2256
2257 #[test]
2263 fn corpus_valid_inputs() {
2264 let cases: &[(&str, &str, u16, Option<&str>, Protocol)] = &[
2265 ("h/s", "h", 1521, Some("s"), Protocol::Tcp),
2267 ("h:1600/s", "h", 1600, Some("s"), Protocol::Tcp),
2268 ("tcp://h/s", "h", 1521, Some("s"), Protocol::Tcp),
2269 ("tcps://h/s", "h", 2484, Some("s"), Protocol::Tcps),
2270 ("tcps://h:9999/s", "h", 9999, Some("s"), Protocol::Tcps),
2271 ("h.example.org/s.dom", "h.example.org", 1521, Some("s.dom"), Protocol::Tcp),
2272 ("h:1521/", "h", 1521, None, Protocol::Tcp),
2273 ("h:/s", "h", 1521, Some("s"), Protocol::Tcp),
2274 ("[2001:db8::1]:1521/s", "2001:db8::1", 1521, Some("s"), Protocol::Tcp),
2275 ("[::1]/s", "::1", 1521, Some("s"), Protocol::Tcp),
2276 ("//h:1521/s", "h", 1521, Some("s"), Protocol::Tcp),
2277 ("h1,h2:1700/s", "h1", 1700, Some("s"), Protocol::Tcp),
2278 ("h/s:dedicated", "h", 1521, Some("s"), Protocol::Tcp),
2279 ("h/s/inst", "h", 1521, Some("s"), Protocol::Tcp),
2280 ("h/s?sdu=16384", "h", 1521, Some("s"), Protocol::Tcp),
2281 ("h/s?pyo.stmtcachesize=40", "h", 1521, Some("s"), Protocol::Tcp),
2282 (
2284 "(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=dh)(PORT=1599))(CONNECT_DATA=(SERVICE_NAME=ds)))",
2285 "dh",
2286 1599,
2287 Some("ds"),
2288 Protocol::Tcp,
2289 ),
2290 (
2291 "(DESCRIPTION=(ADDRESS=(PROTOCOL=tcps)(HOST=sh)(PORT=2484))(CONNECT_DATA=(SID=mysid)))",
2292 "sh",
2293 2484,
2294 None,
2295 Protocol::Tcps,
2296 ),
2297 (
2298 "(DESCRIPTION =(ADDRESS=(PROTOCOL=tcp) (HOST = wh) (PORT = 1521))(CONNECT_DATA=(SERVICE_NAME=ws)))",
2299 "wh",
2300 1521,
2301 Some("ws"),
2302 Protocol::Tcp,
2303 ),
2304 (
2305 "(DESCRIPTION=(ADDRESS=(HTTPS_PROXY=px)(HTTPS_PROXY_PORT=8080)(PROTOCOL=tcps)(HOST=ph)(PORT=443))(CONNECT_DATA=(SERVICE_NAME=ps)))",
2306 "ph",
2307 443,
2308 Some("ps"),
2309 Protocol::Tcps,
2310 ),
2311 ];
2312 for (cs, host, port, service, protocol) in cases {
2313 let d = parse_ok(cs);
2314 let a = d
2315 .first_address()
2316 .unwrap_or_else(|| panic!("no address for {cs:?}"));
2317 assert_eq!(a.host.as_deref(), Some(*host), "host mismatch for {cs:?}");
2318 assert_eq!(a.port, *port, "port mismatch for {cs:?}");
2319 assert_eq!(a.protocol, *protocol, "protocol mismatch for {cs:?}");
2320 assert_eq!(
2321 d.first_description().connect_data.service_name.as_deref(),
2322 *service,
2323 "service mismatch for {cs:?}"
2324 );
2325 }
2326 }
2327
2328 #[test]
2330 fn corpus_malformed_inputs() {
2331 let cases: &[(&str, &str)] = &[
2332 (
2334 "(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=h)(PORT=1)",
2335 "offset",
2336 ),
2337 ("(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp", "offset"),
2338 (
2340 "(DESRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=h)(PORT=1))(CONNECT_DATA=(SERVICE_NAME=s)))",
2341 "no addresses are defined",
2342 ),
2343 ("badproto://h/s", "invalid protocol"),
2345 (
2346 "(DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(KEY=k))(CONNECT_DATA=(SERVICE_NAME=s)))",
2347 "invalid protocol",
2348 ),
2349 (
2351 "(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=h)(PORT=1))(CONNECT_DATA=(SERVER=BOGUS)(SERVICE_NAME=s)))",
2352 "invalid server_type",
2353 ),
2354 (
2356 "(DESCRIPTION=(RETRY_COUNT=wrong)(ADDRESS=(PROTOCOL=tcp)(HOST=h)(PORT=1))(CONNECT_DATA=(SERVICE_NAME=s)))",
2357 "not a non-negative integer",
2358 ),
2359 ("(address=5)", "container"),
2361 (
2363 "(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=h)(PORT=1))(CONNECT_DATA=(SERVER=DEDICATED) SERVICE_NAME=s))",
2364 "offset",
2365 ),
2366 ("", "must not be empty"),
2368 ];
2369 for (cs, needle) in cases {
2370 let err = parse(cs)
2371 .err()
2372 .unwrap_or_else(|| panic!("expected error for {cs:?}"));
2373 let msg = format!("{err}");
2374 assert!(
2375 msg.contains(needle),
2376 "diagnostic for {cs:?} = {msg:?} should contain {needle:?}"
2377 );
2378 }
2379 }
2380
2381 #[test]
2382 fn tns_alias_returns_none() {
2383 assert!(parse("my_tns_alias")
2386 .expect("alias is not an error")
2387 .is_none());
2388 }
2389
2390 #[test]
2391 fn sdu_is_clamped() {
2392 let d = parse_ok("(DESCRIPTION=(SDU=1)(ADDRESS=(PROTOCOL=tcp)(HOST=h)(PORT=1))(CONNECT_DATA=(SERVICE_NAME=s)))");
2394 assert_eq!(d.first_description().sdu, 512);
2395 let d = parse_ok("(DESCRIPTION=(SDU=99999999)(ADDRESS=(PROTOCOL=tcp)(HOST=h)(PORT=1))(CONNECT_DATA=(SERVICE_NAME=s)))");
2396 assert_eq!(d.first_description().sdu, 2_097_152);
2397 }
2398
2399 #[test]
2400 fn duration_units_parse() {
2401 let base = "(DESCRIPTION=(TRANSPORT_CONNECT_TIMEOUT=UNIT)(ADDRESS=(PROTOCOL=tcp)(HOST=h)(PORT=1))(CONNECT_DATA=(SERVICE_NAME=s)))";
2403 let cases = [
2404 ("500 ms", 0.5_f64),
2405 ("15 SEC", 15.0),
2406 ("5 min", 300.0),
2407 ("34", 34.0),
2408 ];
2409 for (unit, expected) in cases {
2410 let d = parse_ok(&base.replace("UNIT", unit));
2411 assert!(
2412 (d.first_description().tcp_connect_timeout - expected).abs() < 1e-9,
2413 "duration {unit:?} -> {}",
2414 d.first_description().tcp_connect_timeout
2415 );
2416 }
2417 }
2418
2419 #[test]
2420 fn passthrough_extras_preserved_in_connect_data() {
2421 let d = parse_ok(
2423 "(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=h)(PORT=1))(CONNECT_DATA=(SERVICE_NAME=s)(COLOCATION_TAG=Tag1)))",
2424 );
2425 let extra = &d.first_description().connect_data.extra;
2426 assert!(extra
2427 .iter()
2428 .any(|(k, v)| k == "COLOCATION_TAG" && v == "Tag1"));
2429 }
2430
2431 #[test]
2432 fn wallet_and_cert_dn_in_security() {
2433 let d = parse_ok(
2435 "(DESCRIPTION=(ADDRESS=(PROTOCOL=tcps)(HOST=h)(PORT=1))(CONNECT_DATA=(SERVICE_NAME=s))\
2436 (SECURITY=(SSL_SERVER_CERT_DN=\"CN=unknown\")(SSL_SERVER_DN_MATCH=Off)(MY_WALLET_DIRECTORY=\"/tmp/w\")))",
2437 );
2438 let sec = &d.first_description().security;
2439 assert_eq!(sec.ssl_server_cert_dn.as_deref(), Some("CN=unknown"));
2440 assert_eq!(sec.wallet_location.as_deref(), Some("/tmp/w"));
2441 assert!(!sec.ssl_server_dn_match);
2442 }
2443}
2444
2445#[cfg(test)]
2446mod tnsnames_tests {
2447 use super::tnsnames::TnsnamesReader;
2448 use super::*;
2449 use std::io::Write;
2450
2451 fn write_file(dir: &std::path::Path, name: &str, contents: &str) {
2453 let path = dir.join(name);
2454 let mut f = std::fs::File::create(&path).expect("create tns file");
2455 f.write_all(contents.as_bytes()).expect("write tns file");
2456 }
2457
2458 fn temp_dir() -> std::path::PathBuf {
2459 let base = std::env::var("TMPDIR").unwrap_or_else(|_| "/tmp".to_string());
2460 let unique = format!(
2461 "hk6_tns_{}_{}",
2462 std::process::id(),
2463 std::time::SystemTime::now()
2464 .duration_since(std::time::UNIX_EPOCH)
2465 .unwrap()
2466 .as_nanos()
2467 );
2468 let dir = std::path::Path::new(&base).join(unique);
2469 std::fs::create_dir_all(&dir).expect("create temp dir");
2470 dir
2471 }
2472
2473 #[test]
2474 fn resolves_simple_alias() {
2475 let dir = temp_dir();
2477 write_file(
2478 &dir,
2479 "tnsnames.ora",
2480 "nsn_7200 = (DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=host_7200)(PORT=7200))\
2481 (CONNECT_DATA=(SERVICE_NAME=service_7200)))",
2482 );
2483 let reader = TnsnamesReader::read(&dir).expect("read tnsnames");
2484 let cs = reader.get("nsn_7200").expect("alias present");
2485 let d = parse(cs).unwrap().unwrap();
2486 let a = d.first_address().unwrap();
2487 assert_eq!(a.host.as_deref(), Some("host_7200"));
2488 assert_eq!(a.port, 7200);
2489 }
2490
2491 #[test]
2492 fn missing_entry_is_none() {
2493 let dir = temp_dir();
2495 write_file(&dir, "tnsnames.ora", "# no entries");
2496 let reader = TnsnamesReader::read(&dir).expect("read tnsnames");
2497 assert!(reader.get("nsn_7201").is_none());
2498 assert!(reader.service_names().is_empty());
2499 }
2500
2501 #[test]
2502 fn missing_file_errors() {
2503 let dir = temp_dir();
2505 let err = TnsnamesReader::read(&dir).unwrap_err();
2506 assert!(format!("{err}").contains("missing or unreadable"));
2507 }
2508
2509 #[test]
2510 fn ignores_garbage_lines() {
2511 let dir = temp_dir();
2513 write_file(
2514 &dir,
2515 "tnsnames.ora",
2516 "some garbage data which is not a valid entry\n\
2517 nsn_7203 = host_7203:7203/service_7203\n",
2518 );
2519 let reader = TnsnamesReader::read(&dir).expect("read tnsnames");
2520 assert!(reader.get("nsn_7203").is_some());
2521 }
2522
2523 #[test]
2524 fn multiple_aliases_one_line() {
2525 let dir = temp_dir();
2527 write_file(
2528 &dir,
2529 "tnsnames.ora",
2530 "nsn_7204a,nsn_7204b = host_7204:7204/service_7204\n",
2531 );
2532 let reader = TnsnamesReader::read(&dir).expect("read tnsnames");
2533 assert!(reader.get("nsn_7204a").is_some());
2534 assert!(reader.get("nsn_7204b").is_some());
2535 assert_eq!(reader.service_names(), vec!["NSN_7204A", "NSN_7204B"]);
2536 }
2537
2538 #[test]
2539 fn case_insensitive_alias_lookup() {
2540 let dir = temp_dir();
2541 write_file(&dir, "tnsnames.ora", "Nsn_X = host:1521/svc\n");
2542 let reader = TnsnamesReader::read(&dir).expect("read tnsnames");
2543 assert!(reader.get("nsn_x").is_some());
2544 assert!(reader.get("NSN_X").is_some());
2545 }
2546
2547 #[test]
2548 fn ifile_same_directory() {
2549 let dir = temp_dir();
2551 write_file(&dir, "inc_7207.ora", "nsn_7207b = host_b:72072/service_b");
2552 write_file(
2553 &dir,
2554 "tnsnames.ora",
2555 "nsn_7207a = host_a:72071/service_a\nifile = inc_7207.ora",
2556 );
2557 let reader = TnsnamesReader::read(&dir).expect("read tnsnames");
2558 assert!(reader.get("nsn_7207a").is_some());
2559 assert!(reader.get("nsn_7207b").is_some());
2560 }
2561
2562 #[test]
2563 fn ifile_cycle_detected() {
2564 let dir = temp_dir();
2566 write_file(
2567 &dir,
2568 "tnsnames.ora",
2569 "nsn_7209 = some_host/some_service\nIFILE = tnsnames.ora",
2570 );
2571 let err = TnsnamesReader::read(&dir).unwrap_err();
2572 assert!(format!("{err}").contains("cycle"));
2573 }
2574
2575 #[test]
2576 fn ifile_quoted_path() {
2577 let dir = temp_dir();
2579 let inc = dir.join("inc_q.ora");
2580 write_file(&dir, "inc_q.ora", "nsn_q = host_q:1521/svc_q");
2581 write_file(
2582 &dir,
2583 "tnsnames.ora",
2584 &format!(
2585 "nsn_main = host_m:1521/svc_m\nifile = \"{}\"",
2586 inc.display()
2587 ),
2588 );
2589 let reader = TnsnamesReader::read(&dir).expect("read tnsnames");
2590 assert!(reader.get("nsn_q").is_some());
2591 }
2592
2593 #[test]
2594 fn duplicate_entry_last_wins() {
2595 let dir = temp_dir();
2597 write_file(
2598 &dir,
2599 "tnsnames.ora",
2600 "nsn = host_a:7213/svc_a\nother = h/s\nnsn = host_b:7213/svc_b\n",
2601 );
2602 let reader = TnsnamesReader::read(&dir).expect("read tnsnames");
2603 let d = parse(reader.get("nsn").unwrap()).unwrap().unwrap();
2604 assert_eq!(d.first_address().unwrap().host.as_deref(), Some("host_b"));
2605 }
2606
2607 #[test]
2608 fn multiline_aliases() {
2609 let dir = temp_dir();
2611 write_file(
2612 &dir,
2613 "tnsnames.ora",
2614 "nsn_a,\nnsn_b,\nnsn_c = host:1521/svc",
2615 );
2616 let reader = TnsnamesReader::read(&dir).expect("read tnsnames");
2617 assert!(reader.get("nsn_a").is_some());
2618 assert!(reader.get("nsn_b").is_some());
2619 assert!(reader.get("nsn_c").is_some());
2620 }
2621
2622 #[test]
2623 fn embedded_comment_in_descriptor() {
2624 let dir = temp_dir();
2626 write_file(
2627 &dir,
2628 "tnsnames.ora",
2629 "nsn_7220 = (DESCRIPTION=\n(ADDRESS=(PROTOCOL=TCP)(HOST=host_7220)(PORT=7220))\n\
2630 (CONNECT_DATA=\n(SERVICE_NAME=service_7220)\n# embedded comment\n)\n)\n",
2631 );
2632 let reader = TnsnamesReader::read(&dir).expect("read tnsnames");
2633 let d = parse(reader.get("nsn_7220").unwrap()).unwrap().unwrap();
2634 assert_eq!(
2635 d.first_address().unwrap().host.as_deref(),
2636 Some("host_7220")
2637 );
2638 }
2639
2640 #[test]
2641 fn missing_ifile_errors() {
2642 let dir = temp_dir();
2644 write_file(&dir, "tnsnames.ora", "IFILE = missing.ora\n");
2645 let err = TnsnamesReader::read(&dir).unwrap_err();
2646 assert!(format!("{err}").contains("missing or unreadable"));
2647 }
2648
2649 #[test]
2652 fn deeply_nested_descriptor_errors_not_crashes() {
2653 let depth = 5000;
2657 let mut s = String::with_capacity(depth * 4);
2658 for _ in 0..depth {
2659 s.push_str("(A=");
2660 }
2661 s.push('1');
2662 for _ in 0..depth {
2663 s.push(')');
2664 }
2665 let err = parse(&s).unwrap_err();
2666 assert!(
2667 format!("{err}").contains("nesting too deep"),
2668 "expected a nesting-depth error, got: {err}"
2669 );
2670 }
2671
2672 #[test]
2673 fn legitimately_deep_descriptor_still_parses() {
2674 let ok = "(DESCRIPTION_LIST=(DESCRIPTION=(ADDRESS_LIST=\
2676 (ADDRESS=(PROTOCOL=tcp)(HOST=h)(PORT=1521)))\
2677 (CONNECT_DATA=(SERVICE_NAME=svc))))";
2678 assert!(parse(ok).is_ok(), "a real ~5-deep descriptor must parse");
2679 }
2680}