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
431fn err_descriptor(connect_string: &str, char_offset: usize, reason: &str) -> ProtocolError {
439 let trimmed = connect_string.trim();
440 let snippet = context_snippet(trimmed, char_offset);
441 ProtocolError::InvalidConnectDescriptor(format!(
442 "invalid connect descriptor \"{connect_string}\": {reason} at offset {char_offset}\n{snippet}"
443 ))
444}
445
446fn err_cannot_parse(connect_string: &str) -> ProtocolError {
447 ProtocolError::InvalidConnectDescriptor(format!(
448 "cannot parse connect string \"{connect_string}\""
449 ))
450}
451
452fn context_snippet(trimmed: &str, char_offset: usize) -> String {
455 let chars: Vec<char> = trimmed.chars().collect();
456 let start = char_offset.saturating_sub(20);
457 let end = (char_offset + 20).min(chars.len());
458 let window: String = chars[start..end].iter().collect();
459 let caret_pos = char_offset - start;
460 let mut caret = String::new();
461 for _ in 0..caret_pos {
462 caret.push(' ');
463 }
464 caret.push('^');
465 format!(" {window}\n {caret}")
466}
467
468#[derive(Clone, Debug)]
475enum ArgValue {
476 Simple(String),
477 Node(ArgMap),
478}
479
480#[derive(Clone, Debug, Default)]
483struct ArgMap {
484 entries: Vec<(String, Vec<ArgValue>)>,
485}
486
487impl ArgMap {
488 fn get(&self, key: &str) -> Option<&Vec<ArgValue>> {
489 self.entries.iter().find(|(k, _)| k == key).map(|(_, v)| v)
490 }
491
492 fn take(&mut self, key: &str) -> Option<Vec<ArgValue>> {
493 if let Some(idx) = self.entries.iter().position(|(k, _)| k == key) {
494 Some(self.entries.remove(idx).1)
495 } else {
496 None
497 }
498 }
499
500 fn push(&mut self, key: String, value: ArgValue) {
501 if let Some((_, values)) = self.entries.iter_mut().find(|(k, _)| *k == key) {
502 values.push(value);
503 } else {
504 self.entries.push((key, vec![value]));
505 }
506 }
507}
508
509fn canonical_param_name(name: &str) -> &str {
519 match name {
520 "pool_connection_class" => "cclass",
521 "pool_purity" => "purity",
522 "server" => "server_type",
523 "transport_connect_timeout" | "connect_timeout" => "tcp_connect_timeout",
524 "my_wallet_directory" => "wallet_location",
525 other => other,
526 }
527}
528
529fn is_container_param(name: &str) -> bool {
532 matches!(
533 name,
534 "address"
535 | "address_list"
536 | "connect_data"
537 | "description"
538 | "description_list"
539 | "security"
540 )
541}
542
543const MAX_DESCRIPTOR_DEPTH: usize = 128;
558
559struct DescriptorParser<'a> {
560 chars: &'a [char],
561 raw: &'a str,
562 pos: usize,
564 temp_pos: usize,
566 depth: usize,
568}
569
570impl<'a> DescriptorParser<'a> {
571 fn new(chars: &'a [char], raw: &'a str) -> Self {
572 Self {
573 chars,
574 raw,
575 pos: 0,
576 temp_pos: 0,
577 depth: 0,
578 }
579 }
580
581 fn current(&self) -> char {
582 self.chars[self.temp_pos]
583 }
584
585 fn skip_spaces(&mut self) {
586 while self.temp_pos < self.chars.len() && self.chars[self.temp_pos].is_whitespace() {
587 self.temp_pos += 1;
588 }
589 }
590
591 fn parse_keyword(&mut self) {
594 while self.temp_pos < self.chars.len() {
595 let ch = self.current();
596 if !ch.is_alphanumeric() && ch != '_' && ch != '.' {
597 break;
598 }
599 self.temp_pos += 1;
600 }
601 }
602
603 fn parse_quoted_string(&mut self, quote: char) -> Result<()> {
607 while self.temp_pos < self.chars.len() {
608 let ch = self.current();
609 self.temp_pos += 1;
610 if ch == quote {
611 self.pos = self.temp_pos;
612 return Ok(());
613 }
614 }
615 let reason = if quote == '\'' {
616 "missing ending quote (')"
617 } else {
618 "missing ending quote (\")"
619 };
620 Err(err_descriptor(self.raw, self.temp_pos, reason))
621 }
622
623 fn parse_descriptor(&mut self) -> Result<ArgMap> {
627 let mut args = ArgMap::default();
628 self.parse_key_value_pair(&mut args)?;
629 Ok(args)
630 }
631
632 fn parse_key_value_pair(&mut self, args: &mut ArgMap) -> Result<()> {
636 let mut is_simple_value = false;
637 let mut simple_start = 0usize;
638 let mut value: Option<ArgValue> = None;
639
640 self.skip_spaces();
642 let start_pos = self.temp_pos;
643 self.parse_keyword();
644 if self.temp_pos == start_pos {
645 return Err(err_descriptor(
646 self.raw,
647 self.temp_pos,
648 "expected a keyword",
649 ));
650 }
651 let raw_name: String = self.chars[start_pos..self.temp_pos]
652 .iter()
653 .collect::<String>()
654 .to_ascii_lowercase();
655 let name = canonical_param_name(&raw_name).to_string();
656
657 self.skip_spaces();
659 let mut ch = '\0';
660 if self.temp_pos < self.chars.len() {
661 ch = self.current();
662 }
663 if ch != '=' {
664 return Err(err_descriptor(
665 self.raw,
666 self.temp_pos,
667 "expected '=' after keyword",
668 ));
669 }
670 self.temp_pos += 1;
671 self.skip_spaces();
672
673 while self.temp_pos < self.chars.len() {
675 ch = self.current();
676 if ch == '"' {
677 if is_simple_value {
678 return Err(err_descriptor(
679 self.raw,
680 self.temp_pos,
681 "unexpected quote inside a simple value",
682 ));
683 }
684 self.temp_pos += 1;
685 let q_start = self.temp_pos;
686 self.parse_quoted_string('"')?;
687 if self.temp_pos > q_start + 1 {
688 let v: String = self.chars[q_start..self.temp_pos - 1].iter().collect();
689 value = Some(ArgValue::Simple(v));
690 }
691 break;
692 } else if ch == '(' {
693 if is_simple_value {
694 return Err(err_descriptor(
695 self.raw,
696 self.temp_pos,
697 "unexpected '(' inside a simple value",
698 ));
699 }
700 self.temp_pos += 1;
701 let mut node = match value.take() {
702 Some(ArgValue::Node(n)) => n,
703 _ => ArgMap::default(),
704 };
705 self.depth += 1;
706 if self.depth > MAX_DESCRIPTOR_DEPTH {
707 return Err(err_descriptor(
708 self.raw,
709 self.temp_pos,
710 "connect descriptor nesting too deep",
711 ));
712 }
713 let result = self.parse_key_value_pair(&mut node);
714 self.depth -= 1;
715 result?;
716 value = Some(ArgValue::Node(node));
717 continue;
718 } else if ch == ')' {
719 break;
720 } else if !is_simple_value && !ch.is_whitespace() {
721 if value.is_some() || is_container_param(&name) {
722 return Err(err_descriptor(
723 self.raw,
724 self.temp_pos,
725 "unexpected simple value for a container keyword",
726 ));
727 }
728 simple_start = self.temp_pos;
729 is_simple_value = true;
730 }
731 self.temp_pos += 1;
732 }
733 if is_simple_value {
734 let v: String = self.chars[simple_start..self.temp_pos]
735 .iter()
736 .collect::<String>()
737 .trim()
738 .to_string();
739 value = Some(ArgValue::Simple(v));
740 }
741 self.skip_spaces();
742 if self.temp_pos < self.chars.len() {
743 ch = self.current();
744 if ch != ')' {
745 return Err(err_descriptor(
746 self.raw,
747 self.temp_pos,
748 "expected ')' to close the keyword",
749 ));
750 }
751 self.temp_pos += 1;
752 } else {
753 return Err(err_descriptor(
754 self.raw,
755 self.temp_pos,
756 "unbalanced parenthesis: expected ')'",
757 ));
758 }
759 self.skip_spaces();
760 self.pos = self.temp_pos;
761
762 if let Some(value) = value {
763 self.set_descriptor_arg(args, name, value);
764 }
765 Ok(())
766 }
767
768 fn set_descriptor_arg(&self, args: &mut ArgMap, name: String, value: ArgValue) {
771 if args.get(&name).is_none() {
772 if name == "address" && args.get("address_list").is_some() {
773 let mut wrapper = ArgMap::default();
774 wrapper.push("address".to_string(), value);
775 self.set_descriptor_arg(args, "address_list".to_string(), ArgValue::Node(wrapper));
776 return;
777 } else if name == "address_list" && args.get("address").is_some() {
778 let addresses = args.take("address").unwrap_or_default();
779 for addr in addresses {
782 let mut wrapper = ArgMap::default();
783 wrapper.push("address".to_string(), addr);
784 args.push("address_list".to_string(), ArgValue::Node(wrapper));
785 }
786 args.push(name, value);
787 return;
788 }
789 args.push(name, value);
790 } else {
791 args.push(name, value);
792 }
793 }
794}
795
796pub mod tnsnames;
808
809mod builders;
810use builders::build_descriptor;
811#[cfg(test)]
812mod tests {
813 use super::*;
814
815 fn parse_ok(input: &str) -> Descriptor {
816 parse(input)
817 .unwrap_or_else(|e| panic!("parse({input:?}) should succeed but failed: {e}"))
818 .unwrap_or_else(|| panic!("parse({input:?}) should be a descriptor, not a tns alias"))
819 }
820
821 fn hosts(d: &Descriptor) -> Vec<String> {
824 d.addresses().filter_map(|a| a.host.clone()).collect()
825 }
826
827 fn ports(d: &Descriptor) -> Vec<u16> {
828 d.addresses().map(|a| a.port).collect()
829 }
830
831 fn protocols(d: &Descriptor) -> Vec<Protocol> {
832 d.addresses().map(|a| a.protocol).collect()
833 }
834
835 #[test]
836 fn parses_simple_name_value_descriptor() {
837 let d = parse_ok(
839 "(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=my_host4)(PORT=1589))\
840 (CONNECT_DATA=(SERVICE_NAME=my_service_name4)))",
841 );
842 let addr = d.first_address().expect("descriptor has an address");
843 assert_eq!(addr.host.as_deref(), Some("my_host4"));
844 assert_eq!(addr.port, 1589);
845 assert_eq!(addr.protocol, Protocol::Tcp);
846 assert_eq!(
847 d.first_description().connect_data.service_name.as_deref(),
848 Some("my_service_name4")
849 );
850 }
851
852 #[test]
855 fn parses_easy_connect_with_port() {
856 let d = parse_ok("my_host:1578/my_service_name");
858 let a = d.first_address().unwrap();
859 assert_eq!(a.host.as_deref(), Some("my_host"));
860 assert_eq!(a.port, 1578);
861 assert_eq!(
862 d.first_description().connect_data.service_name.as_deref(),
863 Some("my_service_name")
864 );
865 }
866
867 #[test]
868 fn parses_easy_connect_default_port() {
869 let d = parse_ok("my_host2/my_service_name2");
871 let a = d.first_address().unwrap();
872 assert_eq!(a.host.as_deref(), Some("my_host2"));
873 assert_eq!(a.port, 1521);
874 }
875
876 #[test]
877 fn parses_easy_connect_drcp_server_type() {
878 let d = parse_ok("my_host3.org/my_service_name3:pooled");
880 assert_eq!(
881 d.first_description().connect_data.server_type,
882 Some(ServerType::Pooled)
883 );
884 let d = parse_ok("my_host3/my_service_name3:ShArEd");
885 assert_eq!(
886 d.first_description().connect_data.server_type,
887 Some(ServerType::Shared)
888 );
889 }
890
891 #[test]
892 fn parses_easy_connect_tcps_protocol() {
893 let d = parse_ok("tcps://my_host6/my_service_name6");
895 assert_eq!(d.first_address().unwrap().protocol, Protocol::Tcps);
896 }
897
898 #[test]
899 fn parses_easy_connect_no_service() {
900 let d = parse_ok("my_host15:1578/");
902 let a = d.first_address().unwrap();
903 assert_eq!(a.host.as_deref(), Some("my_host15"));
904 assert_eq!(a.port, 1578);
905 assert!(d.first_description().connect_data.service_name.is_none());
906 }
907
908 #[test]
909 fn parses_easy_connect_missing_port_value() {
910 let d = parse_ok("my_host17:/my_service_name17");
912 let a = d.first_address().unwrap();
913 assert_eq!(a.host.as_deref(), Some("my_host17"));
914 assert_eq!(a.port, 1521);
915 assert_eq!(
916 d.first_description().connect_data.service_name.as_deref(),
917 Some("my_service_name17")
918 );
919 }
920
921 #[test]
922 fn parses_easy_connect_ipv6() {
923 let d = parse_ok("[::1]:4547/service_name_4547");
925 let a = d.first_address().unwrap();
926 assert_eq!(a.host.as_deref(), Some("::1"));
927 assert_eq!(a.port, 4547);
928 assert_eq!(
929 d.first_description().connect_data.service_name.as_deref(),
930 Some("service_name_4547")
931 );
932 }
933
934 #[test]
935 fn parses_easy_connect_multiple_hosts_different_ports() {
936 let d = parse_ok("host4548a,host4548b:4548,host4548c,host4548d:4549/service_name_4548");
938 assert_eq!(
939 hosts(&d),
940 vec!["host4548a", "host4548b", "host4548c", "host4548d"]
941 );
942 assert_eq!(ports(&d), vec![4548, 4548, 4549, 4549]);
943 }
944
945 #[test]
946 fn parses_easy_connect_multiple_address_lists() {
947 let d = parse_ok("host4549a;host4549b,host4549c:4549;host4549d/service_name_4549");
949 assert_eq!(
950 hosts(&d),
951 vec!["host4549a", "host4549b", "host4549c", "host4549d"]
952 );
953 assert_eq!(ports(&d), vec![1521, 4549, 4549, 1521]);
954 }
955
956 #[test]
957 fn parses_easy_connect_degenerate_protocol() {
958 let d = parse_ok("//host_4552:4552/service_name_4552");
960 let a = d.first_address().unwrap();
961 assert_eq!(a.host.as_deref(), Some("host_4552"));
962 assert_eq!(a.port, 4552);
963 }
964
965 #[test]
966 fn parses_easy_connect_instance_name() {
967 let d = parse_ok("host_4571:4571/service_4571/instance_4571");
969 assert_eq!(
970 d.first_description().connect_data.instance_name.as_deref(),
971 Some("instance_4571")
972 );
973 assert_eq!(
974 d.first_description().connect_data.service_name.as_deref(),
975 Some("service_4571")
976 );
977 }
978
979 #[test]
980 fn parses_easy_connect_extended_params() {
981 let d = parse_ok(
983 "my_host21/my_server_name21?expire_time=5&retry_delay=10&retry_count=12&transport_connect_timeout=2.5",
984 );
985 let desc = d.first_description();
986 assert_eq!(desc.expire_time, 5);
987 assert_eq!(desc.retry_delay, 10);
988 assert_eq!(desc.retry_count, 12);
989 assert!((desc.tcp_connect_timeout - 2.5).abs() < 1e-9);
990 }
991
992 #[test]
993 fn connect_timeout_aliases_transport_connect_timeout() {
994 let d = parse_ok(
999 "(DESCRIPTION=(CONNECT_TIMEOUT=3)(ADDRESS=(PROTOCOL=tcp)(HOST=h)(PORT=1521))\
1000 (CONNECT_DATA=(SERVICE_NAME=svc)))",
1001 );
1002 assert!((d.first_description().tcp_connect_timeout - 3.0).abs() < 1e-9);
1003 let ez = parse_ok("h:1521/svc?connect_timeout=4");
1004 assert!((ez.first_description().tcp_connect_timeout - 4.0).abs() < 1e-9);
1005 }
1006
1007 #[test]
1008 fn parses_easy_connect_security_params() {
1009 let d = parse_ok(
1011 "tcps://host_4580:4580/service_4580?ssl_server_dn_match=true&ssl_server_cert_dn='cn=sales'&wallet_location='/tmp/oracle'",
1012 );
1013 let sec = &d.first_description().security;
1017 assert!(sec.ssl_server_dn_match);
1018 assert_eq!(sec.ssl_server_cert_dn.as_deref(), Some("'cn=sales'"));
1019 assert_eq!(sec.wallet_location.as_deref(), Some("'/tmp/oracle'"));
1020 }
1021
1022 #[test]
1023 fn rejects_invalid_protocol_in_easy_connect() {
1024 let err = parse("invalid_proto://my_host7/my_service_name7").unwrap_err();
1026 assert!(format!("{err}").contains("invalid protocol"));
1027 }
1028
1029 #[test]
1032 fn diagnostic_points_at_unbalanced_paren() {
1033 let err = parse("(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=h)(PORT=1521))").unwrap_err();
1034 let msg = format!("{err}");
1035 assert!(msg.contains("offset"), "expected offset in: {msg}");
1036 assert!(msg.contains('^'), "expected caret context in: {msg}");
1037 }
1038
1039 #[test]
1040 fn diagnostic_for_missing_addresses() {
1041 let err = parse(
1043 "(DESRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=h)(PORT=1))(CONNECT_DATA=(SERVICE_NAME=s)))",
1044 )
1045 .unwrap_err();
1046 assert!(format!("{err}").contains("no addresses are defined"));
1047 }
1048
1049 #[test]
1050 fn protocol_default_port_resolves_for_unported_address() {
1051 let d = parse_ok("tcps://h/svc");
1052 assert_eq!(d.first_address().unwrap().port, 2484);
1053 }
1054
1055 #[test]
1056 fn describe_dumps_addresses() {
1057 let d = parse_ok(
1058 "(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=h1)(PORT=1521))\
1059 (CONNECT_DATA=(SERVICE_NAME=svc)))",
1060 );
1061 let text = d.describe();
1062 assert!(text.contains("tcp://h1:1521"));
1063 assert!(text.contains("service_name=svc"));
1064 }
1065
1066 #[test]
1067 fn keeps_protocols_for_multi_list_descriptor() {
1068 let d = parse_ok(
1070 "(DESCRIPTION=(LOAD_BALANCE=ON)(RETRY_COUNT=5)(RETRY_DELAY=2)\
1071 (ADDRESS_LIST=(LOAD_BALANCE=ON)\
1072 (ADDRESS=(PROTOCOL=tcp)(PORT=1521)(HOST=my_host26))\
1073 (ADDRESS=(PROTOCOL=tcp)(PORT=222)(HOST=my_host27)))\
1074 (ADDRESS_LIST=(LOAD_BALANCE=ON)\
1075 (ADDRESS=(PROTOCOL=tcps)(PORT=5555)(HOST=my_host28))\
1076 (ADDRESS=(PROTOCOL=tcps)(PORT=444)(HOST=my_host29)))\
1077 (CONNECT_DATA=(SERVICE_NAME=my_service_name26)))",
1078 );
1079 assert_eq!(
1080 hosts(&d),
1081 vec!["my_host26", "my_host27", "my_host28", "my_host29"]
1082 );
1083 assert_eq!(ports(&d), vec![1521, 222, 5555, 444]);
1084 assert_eq!(
1085 protocols(&d),
1086 vec![Protocol::Tcp, Protocol::Tcp, Protocol::Tcps, Protocol::Tcps]
1087 );
1088 }
1089
1090 #[test]
1091 fn parses_multiple_descriptions() {
1092 let d = parse_ok(
1094 "(DESCRIPTION_LIST=(FAIL_OVER=ON)(LOAD_BALANCE=OFF)\
1095 (DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=tcp)(PORT=5001)(HOST=my_host30))\
1096 (ADDRESS=(PROTOCOL=tcp)(PORT=1521)(HOST=my_host31)))\
1097 (CONNECT_DATA=(SERVICE_NAME=svc27)))\
1098 (DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=tcp)(PORT=5002)(HOST=my_host34)))\
1099 (CONNECT_DATA=(SERVICE_NAME=svc28))))",
1100 );
1101 assert_eq!(hosts(&d), vec!["my_host30", "my_host31", "my_host34"]);
1102 assert_eq!(d.descriptions.len(), 2);
1103 }
1104
1105 #[test]
1106 fn interleaves_address_and_address_list_small_first() {
1107 let d = parse_ok(
1109 "(DESCRIPTION=\
1110 (ADDRESS=(PROTOCOL=tcp)(HOST=host1)(PORT=1521))\
1111 (ADDRESS_LIST=(ADDRESS=(PROTOCOL=tcp)(HOST=host2a)(PORT=1522))\
1112 (ADDRESS=(PROTOCOL=tcp)(HOST=host2b)(PORT=1523)))\
1113 (ADDRESS=(PROTOCOL=tcp)(HOST=host3)(PORT=1524))\
1114 (CONNECT_DATA=(SERVICE_NAME=svc)))",
1115 );
1116 assert_eq!(hosts(&d), vec!["host1", "host2a", "host2b", "host3"]);
1117 }
1118
1119 #[test]
1125 fn corpus_valid_inputs() {
1126 let cases: &[(&str, &str, u16, Option<&str>, Protocol)] = &[
1127 ("h/s", "h", 1521, Some("s"), Protocol::Tcp),
1129 ("h:1600/s", "h", 1600, Some("s"), Protocol::Tcp),
1130 ("tcp://h/s", "h", 1521, Some("s"), Protocol::Tcp),
1131 ("tcps://h/s", "h", 2484, Some("s"), Protocol::Tcps),
1132 ("tcps://h:9999/s", "h", 9999, Some("s"), Protocol::Tcps),
1133 ("h.example.org/s.dom", "h.example.org", 1521, Some("s.dom"), Protocol::Tcp),
1134 ("h:1521/", "h", 1521, None, Protocol::Tcp),
1135 ("h:/s", "h", 1521, Some("s"), Protocol::Tcp),
1136 ("[2001:db8::1]:1521/s", "2001:db8::1", 1521, Some("s"), Protocol::Tcp),
1137 ("[::1]/s", "::1", 1521, Some("s"), Protocol::Tcp),
1138 ("//h:1521/s", "h", 1521, Some("s"), Protocol::Tcp),
1139 ("h1,h2:1700/s", "h1", 1700, Some("s"), Protocol::Tcp),
1140 ("h/s:dedicated", "h", 1521, Some("s"), Protocol::Tcp),
1141 ("h/s/inst", "h", 1521, Some("s"), Protocol::Tcp),
1142 ("h/s?sdu=16384", "h", 1521, Some("s"), Protocol::Tcp),
1143 ("h/s?pyo.stmtcachesize=40", "h", 1521, Some("s"), Protocol::Tcp),
1144 (
1146 "(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=dh)(PORT=1599))(CONNECT_DATA=(SERVICE_NAME=ds)))",
1147 "dh",
1148 1599,
1149 Some("ds"),
1150 Protocol::Tcp,
1151 ),
1152 (
1153 "(DESCRIPTION=(ADDRESS=(PROTOCOL=tcps)(HOST=sh)(PORT=2484))(CONNECT_DATA=(SID=mysid)))",
1154 "sh",
1155 2484,
1156 None,
1157 Protocol::Tcps,
1158 ),
1159 (
1160 "(DESCRIPTION =(ADDRESS=(PROTOCOL=tcp) (HOST = wh) (PORT = 1521))(CONNECT_DATA=(SERVICE_NAME=ws)))",
1161 "wh",
1162 1521,
1163 Some("ws"),
1164 Protocol::Tcp,
1165 ),
1166 (
1167 "(DESCRIPTION=(ADDRESS=(HTTPS_PROXY=px)(HTTPS_PROXY_PORT=8080)(PROTOCOL=tcps)(HOST=ph)(PORT=443))(CONNECT_DATA=(SERVICE_NAME=ps)))",
1168 "ph",
1169 443,
1170 Some("ps"),
1171 Protocol::Tcps,
1172 ),
1173 ];
1174 for (cs, host, port, service, protocol) in cases {
1175 let d = parse_ok(cs);
1176 let a = d
1177 .first_address()
1178 .unwrap_or_else(|| panic!("no address for {cs:?}"));
1179 assert_eq!(a.host.as_deref(), Some(*host), "host mismatch for {cs:?}");
1180 assert_eq!(a.port, *port, "port mismatch for {cs:?}");
1181 assert_eq!(a.protocol, *protocol, "protocol mismatch for {cs:?}");
1182 assert_eq!(
1183 d.first_description().connect_data.service_name.as_deref(),
1184 *service,
1185 "service mismatch for {cs:?}"
1186 );
1187 }
1188 }
1189
1190 #[test]
1192 fn corpus_malformed_inputs() {
1193 let cases: &[(&str, &str)] = &[
1194 (
1196 "(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=h)(PORT=1)",
1197 "offset",
1198 ),
1199 ("(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp", "offset"),
1200 (
1202 "(DESRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=h)(PORT=1))(CONNECT_DATA=(SERVICE_NAME=s)))",
1203 "no addresses are defined",
1204 ),
1205 ("badproto://h/s", "invalid protocol"),
1207 (
1208 "(DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(KEY=k))(CONNECT_DATA=(SERVICE_NAME=s)))",
1209 "invalid protocol",
1210 ),
1211 (
1213 "(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=h)(PORT=1))(CONNECT_DATA=(SERVER=BOGUS)(SERVICE_NAME=s)))",
1214 "invalid server_type",
1215 ),
1216 (
1218 "(DESCRIPTION=(RETRY_COUNT=wrong)(ADDRESS=(PROTOCOL=tcp)(HOST=h)(PORT=1))(CONNECT_DATA=(SERVICE_NAME=s)))",
1219 "not a non-negative integer",
1220 ),
1221 ("(address=5)", "container"),
1223 (
1225 "(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=h)(PORT=1))(CONNECT_DATA=(SERVER=DEDICATED) SERVICE_NAME=s))",
1226 "offset",
1227 ),
1228 ("", "must not be empty"),
1230 ];
1231 for (cs, needle) in cases {
1232 let err = parse(cs)
1233 .err()
1234 .unwrap_or_else(|| panic!("expected error for {cs:?}"));
1235 let msg = format!("{err}");
1236 assert!(
1237 msg.contains(needle),
1238 "diagnostic for {cs:?} = {msg:?} should contain {needle:?}"
1239 );
1240 }
1241 }
1242
1243 #[test]
1244 fn tns_alias_returns_none() {
1245 assert!(parse("my_tns_alias")
1248 .expect("alias is not an error")
1249 .is_none());
1250 }
1251
1252 #[test]
1253 fn sdu_is_clamped() {
1254 let d = parse_ok("(DESCRIPTION=(SDU=1)(ADDRESS=(PROTOCOL=tcp)(HOST=h)(PORT=1))(CONNECT_DATA=(SERVICE_NAME=s)))");
1256 assert_eq!(d.first_description().sdu, 512);
1257 let d = parse_ok("(DESCRIPTION=(SDU=99999999)(ADDRESS=(PROTOCOL=tcp)(HOST=h)(PORT=1))(CONNECT_DATA=(SERVICE_NAME=s)))");
1258 assert_eq!(d.first_description().sdu, 2_097_152);
1259 }
1260
1261 #[test]
1262 fn duration_units_parse() {
1263 let base = "(DESCRIPTION=(TRANSPORT_CONNECT_TIMEOUT=UNIT)(ADDRESS=(PROTOCOL=tcp)(HOST=h)(PORT=1))(CONNECT_DATA=(SERVICE_NAME=s)))";
1265 let cases = [
1266 ("500 ms", 0.5_f64),
1267 ("15 SEC", 15.0),
1268 ("5 min", 300.0),
1269 ("34", 34.0),
1270 ];
1271 for (unit, expected) in cases {
1272 let d = parse_ok(&base.replace("UNIT", unit));
1273 assert!(
1274 (d.first_description().tcp_connect_timeout - expected).abs() < 1e-9,
1275 "duration {unit:?} -> {}",
1276 d.first_description().tcp_connect_timeout
1277 );
1278 }
1279 }
1280
1281 #[test]
1282 fn passthrough_extras_preserved_in_connect_data() {
1283 let d = parse_ok(
1285 "(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=h)(PORT=1))(CONNECT_DATA=(SERVICE_NAME=s)(COLOCATION_TAG=Tag1)))",
1286 );
1287 let extra = &d.first_description().connect_data.extra;
1288 assert!(extra
1289 .iter()
1290 .any(|(k, v)| k == "COLOCATION_TAG" && v == "Tag1"));
1291 }
1292
1293 #[test]
1294 fn wallet_and_cert_dn_in_security() {
1295 let d = parse_ok(
1297 "(DESCRIPTION=(ADDRESS=(PROTOCOL=tcps)(HOST=h)(PORT=1))(CONNECT_DATA=(SERVICE_NAME=s))\
1298 (SECURITY=(SSL_SERVER_CERT_DN=\"CN=unknown\")(SSL_SERVER_DN_MATCH=Off)(MY_WALLET_DIRECTORY=\"/tmp/w\")))",
1299 );
1300 let sec = &d.first_description().security;
1301 assert_eq!(sec.ssl_server_cert_dn.as_deref(), Some("CN=unknown"));
1302 assert_eq!(sec.wallet_location.as_deref(), Some("/tmp/w"));
1303 assert!(!sec.ssl_server_dn_match);
1304 }
1305}