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 {
512 match name {
513 "pool_connection_class" => "cclass",
514 "pool_purity" => "purity",
515 "server" => "server_type",
516 "transport_connect_timeout" => "tcp_connect_timeout",
517 "my_wallet_directory" => "wallet_location",
518 other => other,
519 }
520}
521
522fn is_container_param(name: &str) -> bool {
525 matches!(
526 name,
527 "address"
528 | "address_list"
529 | "connect_data"
530 | "description"
531 | "description_list"
532 | "security"
533 )
534}
535
536const MAX_DESCRIPTOR_DEPTH: usize = 128;
551
552struct DescriptorParser<'a> {
553 chars: &'a [char],
554 raw: &'a str,
555 pos: usize,
557 temp_pos: usize,
559 depth: usize,
561}
562
563impl<'a> DescriptorParser<'a> {
564 fn new(chars: &'a [char], raw: &'a str) -> Self {
565 Self {
566 chars,
567 raw,
568 pos: 0,
569 temp_pos: 0,
570 depth: 0,
571 }
572 }
573
574 fn current(&self) -> char {
575 self.chars[self.temp_pos]
576 }
577
578 fn skip_spaces(&mut self) {
579 while self.temp_pos < self.chars.len() && self.chars[self.temp_pos].is_whitespace() {
580 self.temp_pos += 1;
581 }
582 }
583
584 fn parse_keyword(&mut self) {
587 while self.temp_pos < self.chars.len() {
588 let ch = self.current();
589 if !ch.is_alphanumeric() && ch != '_' && ch != '.' {
590 break;
591 }
592 self.temp_pos += 1;
593 }
594 }
595
596 fn parse_quoted_string(&mut self, quote: char) -> Result<()> {
600 while self.temp_pos < self.chars.len() {
601 let ch = self.current();
602 self.temp_pos += 1;
603 if ch == quote {
604 self.pos = self.temp_pos;
605 return Ok(());
606 }
607 }
608 let reason = if quote == '\'' {
609 "missing ending quote (')"
610 } else {
611 "missing ending quote (\")"
612 };
613 Err(err_descriptor(self.raw, self.temp_pos, reason))
614 }
615
616 fn parse_descriptor(&mut self) -> Result<ArgMap> {
620 let mut args = ArgMap::default();
621 self.parse_key_value_pair(&mut args)?;
622 Ok(args)
623 }
624
625 fn parse_key_value_pair(&mut self, args: &mut ArgMap) -> Result<()> {
629 let mut is_simple_value = false;
630 let mut simple_start = 0usize;
631 let mut value: Option<ArgValue> = None;
632
633 self.skip_spaces();
635 let start_pos = self.temp_pos;
636 self.parse_keyword();
637 if self.temp_pos == start_pos {
638 return Err(err_descriptor(
639 self.raw,
640 self.temp_pos,
641 "expected a keyword",
642 ));
643 }
644 let raw_name: String = self.chars[start_pos..self.temp_pos]
645 .iter()
646 .collect::<String>()
647 .to_ascii_lowercase();
648 let name = canonical_param_name(&raw_name).to_string();
649
650 self.skip_spaces();
652 let mut ch = '\0';
653 if self.temp_pos < self.chars.len() {
654 ch = self.current();
655 }
656 if ch != '=' {
657 return Err(err_descriptor(
658 self.raw,
659 self.temp_pos,
660 "expected '=' after keyword",
661 ));
662 }
663 self.temp_pos += 1;
664 self.skip_spaces();
665
666 while self.temp_pos < self.chars.len() {
668 ch = self.current();
669 if ch == '"' {
670 if is_simple_value {
671 return Err(err_descriptor(
672 self.raw,
673 self.temp_pos,
674 "unexpected quote inside a simple value",
675 ));
676 }
677 self.temp_pos += 1;
678 let q_start = self.temp_pos;
679 self.parse_quoted_string('"')?;
680 if self.temp_pos > q_start + 1 {
681 let v: String = self.chars[q_start..self.temp_pos - 1].iter().collect();
682 value = Some(ArgValue::Simple(v));
683 }
684 break;
685 } else if ch == '(' {
686 if is_simple_value {
687 return Err(err_descriptor(
688 self.raw,
689 self.temp_pos,
690 "unexpected '(' inside a simple value",
691 ));
692 }
693 self.temp_pos += 1;
694 let mut node = match value.take() {
695 Some(ArgValue::Node(n)) => n,
696 _ => ArgMap::default(),
697 };
698 self.depth += 1;
699 if self.depth > MAX_DESCRIPTOR_DEPTH {
700 return Err(err_descriptor(
701 self.raw,
702 self.temp_pos,
703 "connect descriptor nesting too deep",
704 ));
705 }
706 let result = self.parse_key_value_pair(&mut node);
707 self.depth -= 1;
708 result?;
709 value = Some(ArgValue::Node(node));
710 continue;
711 } else if ch == ')' {
712 break;
713 } else if !is_simple_value && !ch.is_whitespace() {
714 if value.is_some() || is_container_param(&name) {
715 return Err(err_descriptor(
716 self.raw,
717 self.temp_pos,
718 "unexpected simple value for a container keyword",
719 ));
720 }
721 simple_start = self.temp_pos;
722 is_simple_value = true;
723 }
724 self.temp_pos += 1;
725 }
726 if is_simple_value {
727 let v: String = self.chars[simple_start..self.temp_pos]
728 .iter()
729 .collect::<String>()
730 .trim()
731 .to_string();
732 value = Some(ArgValue::Simple(v));
733 }
734 self.skip_spaces();
735 if self.temp_pos < self.chars.len() {
736 ch = self.current();
737 if ch != ')' {
738 return Err(err_descriptor(
739 self.raw,
740 self.temp_pos,
741 "expected ')' to close the keyword",
742 ));
743 }
744 self.temp_pos += 1;
745 } else {
746 return Err(err_descriptor(
747 self.raw,
748 self.temp_pos,
749 "unbalanced parenthesis: expected ')'",
750 ));
751 }
752 self.skip_spaces();
753 self.pos = self.temp_pos;
754
755 if let Some(value) = value {
756 self.set_descriptor_arg(args, name, value);
757 }
758 Ok(())
759 }
760
761 fn set_descriptor_arg(&self, args: &mut ArgMap, name: String, value: ArgValue) {
764 if args.get(&name).is_none() {
765 if name == "address" && args.get("address_list").is_some() {
766 let mut wrapper = ArgMap::default();
767 wrapper.push("address".to_string(), value);
768 self.set_descriptor_arg(args, "address_list".to_string(), ArgValue::Node(wrapper));
769 return;
770 } else if name == "address_list" && args.get("address").is_some() {
771 let addresses = args.take("address").unwrap_or_default();
772 for addr in addresses {
775 let mut wrapper = ArgMap::default();
776 wrapper.push("address".to_string(), addr);
777 args.push("address_list".to_string(), ArgValue::Node(wrapper));
778 }
779 args.push(name, value);
780 return;
781 }
782 args.push(name, value);
783 } else {
784 args.push(name, value);
785 }
786 }
787}
788
789pub mod tnsnames;
801
802mod builders;
803use builders::build_descriptor;
804#[cfg(test)]
805mod tests {
806 use super::*;
807
808 fn parse_ok(input: &str) -> Descriptor {
809 parse(input)
810 .unwrap_or_else(|e| panic!("parse({input:?}) should succeed but failed: {e}"))
811 .unwrap_or_else(|| panic!("parse({input:?}) should be a descriptor, not a tns alias"))
812 }
813
814 fn hosts(d: &Descriptor) -> Vec<String> {
817 d.addresses().filter_map(|a| a.host.clone()).collect()
818 }
819
820 fn ports(d: &Descriptor) -> Vec<u16> {
821 d.addresses().map(|a| a.port).collect()
822 }
823
824 fn protocols(d: &Descriptor) -> Vec<Protocol> {
825 d.addresses().map(|a| a.protocol).collect()
826 }
827
828 #[test]
829 fn parses_simple_name_value_descriptor() {
830 let d = parse_ok(
832 "(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=my_host4)(PORT=1589))\
833 (CONNECT_DATA=(SERVICE_NAME=my_service_name4)))",
834 );
835 let addr = d.first_address().expect("descriptor has an address");
836 assert_eq!(addr.host.as_deref(), Some("my_host4"));
837 assert_eq!(addr.port, 1589);
838 assert_eq!(addr.protocol, Protocol::Tcp);
839 assert_eq!(
840 d.first_description().connect_data.service_name.as_deref(),
841 Some("my_service_name4")
842 );
843 }
844
845 #[test]
848 fn parses_easy_connect_with_port() {
849 let d = parse_ok("my_host:1578/my_service_name");
851 let a = d.first_address().unwrap();
852 assert_eq!(a.host.as_deref(), Some("my_host"));
853 assert_eq!(a.port, 1578);
854 assert_eq!(
855 d.first_description().connect_data.service_name.as_deref(),
856 Some("my_service_name")
857 );
858 }
859
860 #[test]
861 fn parses_easy_connect_default_port() {
862 let d = parse_ok("my_host2/my_service_name2");
864 let a = d.first_address().unwrap();
865 assert_eq!(a.host.as_deref(), Some("my_host2"));
866 assert_eq!(a.port, 1521);
867 }
868
869 #[test]
870 fn parses_easy_connect_drcp_server_type() {
871 let d = parse_ok("my_host3.org/my_service_name3:pooled");
873 assert_eq!(
874 d.first_description().connect_data.server_type,
875 Some(ServerType::Pooled)
876 );
877 let d = parse_ok("my_host3/my_service_name3:ShArEd");
878 assert_eq!(
879 d.first_description().connect_data.server_type,
880 Some(ServerType::Shared)
881 );
882 }
883
884 #[test]
885 fn parses_easy_connect_tcps_protocol() {
886 let d = parse_ok("tcps://my_host6/my_service_name6");
888 assert_eq!(d.first_address().unwrap().protocol, Protocol::Tcps);
889 }
890
891 #[test]
892 fn parses_easy_connect_no_service() {
893 let d = parse_ok("my_host15:1578/");
895 let a = d.first_address().unwrap();
896 assert_eq!(a.host.as_deref(), Some("my_host15"));
897 assert_eq!(a.port, 1578);
898 assert!(d.first_description().connect_data.service_name.is_none());
899 }
900
901 #[test]
902 fn parses_easy_connect_missing_port_value() {
903 let d = parse_ok("my_host17:/my_service_name17");
905 let a = d.first_address().unwrap();
906 assert_eq!(a.host.as_deref(), Some("my_host17"));
907 assert_eq!(a.port, 1521);
908 assert_eq!(
909 d.first_description().connect_data.service_name.as_deref(),
910 Some("my_service_name17")
911 );
912 }
913
914 #[test]
915 fn parses_easy_connect_ipv6() {
916 let d = parse_ok("[::1]:4547/service_name_4547");
918 let a = d.first_address().unwrap();
919 assert_eq!(a.host.as_deref(), Some("::1"));
920 assert_eq!(a.port, 4547);
921 assert_eq!(
922 d.first_description().connect_data.service_name.as_deref(),
923 Some("service_name_4547")
924 );
925 }
926
927 #[test]
928 fn parses_easy_connect_multiple_hosts_different_ports() {
929 let d = parse_ok("host4548a,host4548b:4548,host4548c,host4548d:4549/service_name_4548");
931 assert_eq!(
932 hosts(&d),
933 vec!["host4548a", "host4548b", "host4548c", "host4548d"]
934 );
935 assert_eq!(ports(&d), vec![4548, 4548, 4549, 4549]);
936 }
937
938 #[test]
939 fn parses_easy_connect_multiple_address_lists() {
940 let d = parse_ok("host4549a;host4549b,host4549c:4549;host4549d/service_name_4549");
942 assert_eq!(
943 hosts(&d),
944 vec!["host4549a", "host4549b", "host4549c", "host4549d"]
945 );
946 assert_eq!(ports(&d), vec![1521, 4549, 4549, 1521]);
947 }
948
949 #[test]
950 fn parses_easy_connect_degenerate_protocol() {
951 let d = parse_ok("//host_4552:4552/service_name_4552");
953 let a = d.first_address().unwrap();
954 assert_eq!(a.host.as_deref(), Some("host_4552"));
955 assert_eq!(a.port, 4552);
956 }
957
958 #[test]
959 fn parses_easy_connect_instance_name() {
960 let d = parse_ok("host_4571:4571/service_4571/instance_4571");
962 assert_eq!(
963 d.first_description().connect_data.instance_name.as_deref(),
964 Some("instance_4571")
965 );
966 assert_eq!(
967 d.first_description().connect_data.service_name.as_deref(),
968 Some("service_4571")
969 );
970 }
971
972 #[test]
973 fn parses_easy_connect_extended_params() {
974 let d = parse_ok(
976 "my_host21/my_server_name21?expire_time=5&retry_delay=10&retry_count=12&transport_connect_timeout=2.5",
977 );
978 let desc = d.first_description();
979 assert_eq!(desc.expire_time, 5);
980 assert_eq!(desc.retry_delay, 10);
981 assert_eq!(desc.retry_count, 12);
982 assert!((desc.tcp_connect_timeout - 2.5).abs() < 1e-9);
983 }
984
985 #[test]
986 fn parses_easy_connect_security_params() {
987 let d = parse_ok(
989 "tcps://host_4580:4580/service_4580?ssl_server_dn_match=true&ssl_server_cert_dn='cn=sales'&wallet_location='/tmp/oracle'",
990 );
991 let sec = &d.first_description().security;
995 assert!(sec.ssl_server_dn_match);
996 assert_eq!(sec.ssl_server_cert_dn.as_deref(), Some("'cn=sales'"));
997 assert_eq!(sec.wallet_location.as_deref(), Some("'/tmp/oracle'"));
998 }
999
1000 #[test]
1001 fn rejects_invalid_protocol_in_easy_connect() {
1002 let err = parse("invalid_proto://my_host7/my_service_name7").unwrap_err();
1004 assert!(format!("{err}").contains("invalid protocol"));
1005 }
1006
1007 #[test]
1010 fn diagnostic_points_at_unbalanced_paren() {
1011 let err = parse("(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=h)(PORT=1521))").unwrap_err();
1012 let msg = format!("{err}");
1013 assert!(msg.contains("offset"), "expected offset in: {msg}");
1014 assert!(msg.contains('^'), "expected caret context in: {msg}");
1015 }
1016
1017 #[test]
1018 fn diagnostic_for_missing_addresses() {
1019 let err = parse(
1021 "(DESRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=h)(PORT=1))(CONNECT_DATA=(SERVICE_NAME=s)))",
1022 )
1023 .unwrap_err();
1024 assert!(format!("{err}").contains("no addresses are defined"));
1025 }
1026
1027 #[test]
1028 fn protocol_default_port_resolves_for_unported_address() {
1029 let d = parse_ok("tcps://h/svc");
1030 assert_eq!(d.first_address().unwrap().port, 2484);
1031 }
1032
1033 #[test]
1034 fn describe_dumps_addresses() {
1035 let d = parse_ok(
1036 "(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=h1)(PORT=1521))\
1037 (CONNECT_DATA=(SERVICE_NAME=svc)))",
1038 );
1039 let text = d.describe();
1040 assert!(text.contains("tcp://h1:1521"));
1041 assert!(text.contains("service_name=svc"));
1042 }
1043
1044 #[test]
1045 fn keeps_protocols_for_multi_list_descriptor() {
1046 let d = parse_ok(
1048 "(DESCRIPTION=(LOAD_BALANCE=ON)(RETRY_COUNT=5)(RETRY_DELAY=2)\
1049 (ADDRESS_LIST=(LOAD_BALANCE=ON)\
1050 (ADDRESS=(PROTOCOL=tcp)(PORT=1521)(HOST=my_host26))\
1051 (ADDRESS=(PROTOCOL=tcp)(PORT=222)(HOST=my_host27)))\
1052 (ADDRESS_LIST=(LOAD_BALANCE=ON)\
1053 (ADDRESS=(PROTOCOL=tcps)(PORT=5555)(HOST=my_host28))\
1054 (ADDRESS=(PROTOCOL=tcps)(PORT=444)(HOST=my_host29)))\
1055 (CONNECT_DATA=(SERVICE_NAME=my_service_name26)))",
1056 );
1057 assert_eq!(
1058 hosts(&d),
1059 vec!["my_host26", "my_host27", "my_host28", "my_host29"]
1060 );
1061 assert_eq!(ports(&d), vec![1521, 222, 5555, 444]);
1062 assert_eq!(
1063 protocols(&d),
1064 vec![Protocol::Tcp, Protocol::Tcp, Protocol::Tcps, Protocol::Tcps]
1065 );
1066 }
1067
1068 #[test]
1069 fn parses_multiple_descriptions() {
1070 let d = parse_ok(
1072 "(DESCRIPTION_LIST=(FAIL_OVER=ON)(LOAD_BALANCE=OFF)\
1073 (DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=tcp)(PORT=5001)(HOST=my_host30))\
1074 (ADDRESS=(PROTOCOL=tcp)(PORT=1521)(HOST=my_host31)))\
1075 (CONNECT_DATA=(SERVICE_NAME=svc27)))\
1076 (DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=tcp)(PORT=5002)(HOST=my_host34)))\
1077 (CONNECT_DATA=(SERVICE_NAME=svc28))))",
1078 );
1079 assert_eq!(hosts(&d), vec!["my_host30", "my_host31", "my_host34"]);
1080 assert_eq!(d.descriptions.len(), 2);
1081 }
1082
1083 #[test]
1084 fn interleaves_address_and_address_list_small_first() {
1085 let d = parse_ok(
1087 "(DESCRIPTION=\
1088 (ADDRESS=(PROTOCOL=tcp)(HOST=host1)(PORT=1521))\
1089 (ADDRESS_LIST=(ADDRESS=(PROTOCOL=tcp)(HOST=host2a)(PORT=1522))\
1090 (ADDRESS=(PROTOCOL=tcp)(HOST=host2b)(PORT=1523)))\
1091 (ADDRESS=(PROTOCOL=tcp)(HOST=host3)(PORT=1524))\
1092 (CONNECT_DATA=(SERVICE_NAME=svc)))",
1093 );
1094 assert_eq!(hosts(&d), vec!["host1", "host2a", "host2b", "host3"]);
1095 }
1096
1097 #[test]
1103 fn corpus_valid_inputs() {
1104 let cases: &[(&str, &str, u16, Option<&str>, Protocol)] = &[
1105 ("h/s", "h", 1521, Some("s"), Protocol::Tcp),
1107 ("h:1600/s", "h", 1600, Some("s"), Protocol::Tcp),
1108 ("tcp://h/s", "h", 1521, Some("s"), Protocol::Tcp),
1109 ("tcps://h/s", "h", 2484, Some("s"), Protocol::Tcps),
1110 ("tcps://h:9999/s", "h", 9999, Some("s"), Protocol::Tcps),
1111 ("h.example.org/s.dom", "h.example.org", 1521, Some("s.dom"), Protocol::Tcp),
1112 ("h:1521/", "h", 1521, None, Protocol::Tcp),
1113 ("h:/s", "h", 1521, Some("s"), Protocol::Tcp),
1114 ("[2001:db8::1]:1521/s", "2001:db8::1", 1521, Some("s"), Protocol::Tcp),
1115 ("[::1]/s", "::1", 1521, Some("s"), Protocol::Tcp),
1116 ("//h:1521/s", "h", 1521, Some("s"), Protocol::Tcp),
1117 ("h1,h2:1700/s", "h1", 1700, Some("s"), Protocol::Tcp),
1118 ("h/s:dedicated", "h", 1521, Some("s"), Protocol::Tcp),
1119 ("h/s/inst", "h", 1521, Some("s"), Protocol::Tcp),
1120 ("h/s?sdu=16384", "h", 1521, Some("s"), Protocol::Tcp),
1121 ("h/s?pyo.stmtcachesize=40", "h", 1521, Some("s"), Protocol::Tcp),
1122 (
1124 "(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=dh)(PORT=1599))(CONNECT_DATA=(SERVICE_NAME=ds)))",
1125 "dh",
1126 1599,
1127 Some("ds"),
1128 Protocol::Tcp,
1129 ),
1130 (
1131 "(DESCRIPTION=(ADDRESS=(PROTOCOL=tcps)(HOST=sh)(PORT=2484))(CONNECT_DATA=(SID=mysid)))",
1132 "sh",
1133 2484,
1134 None,
1135 Protocol::Tcps,
1136 ),
1137 (
1138 "(DESCRIPTION =(ADDRESS=(PROTOCOL=tcp) (HOST = wh) (PORT = 1521))(CONNECT_DATA=(SERVICE_NAME=ws)))",
1139 "wh",
1140 1521,
1141 Some("ws"),
1142 Protocol::Tcp,
1143 ),
1144 (
1145 "(DESCRIPTION=(ADDRESS=(HTTPS_PROXY=px)(HTTPS_PROXY_PORT=8080)(PROTOCOL=tcps)(HOST=ph)(PORT=443))(CONNECT_DATA=(SERVICE_NAME=ps)))",
1146 "ph",
1147 443,
1148 Some("ps"),
1149 Protocol::Tcps,
1150 ),
1151 ];
1152 for (cs, host, port, service, protocol) in cases {
1153 let d = parse_ok(cs);
1154 let a = d
1155 .first_address()
1156 .unwrap_or_else(|| panic!("no address for {cs:?}"));
1157 assert_eq!(a.host.as_deref(), Some(*host), "host mismatch for {cs:?}");
1158 assert_eq!(a.port, *port, "port mismatch for {cs:?}");
1159 assert_eq!(a.protocol, *protocol, "protocol mismatch for {cs:?}");
1160 assert_eq!(
1161 d.first_description().connect_data.service_name.as_deref(),
1162 *service,
1163 "service mismatch for {cs:?}"
1164 );
1165 }
1166 }
1167
1168 #[test]
1170 fn corpus_malformed_inputs() {
1171 let cases: &[(&str, &str)] = &[
1172 (
1174 "(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=h)(PORT=1)",
1175 "offset",
1176 ),
1177 ("(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp", "offset"),
1178 (
1180 "(DESRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=h)(PORT=1))(CONNECT_DATA=(SERVICE_NAME=s)))",
1181 "no addresses are defined",
1182 ),
1183 ("badproto://h/s", "invalid protocol"),
1185 (
1186 "(DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(KEY=k))(CONNECT_DATA=(SERVICE_NAME=s)))",
1187 "invalid protocol",
1188 ),
1189 (
1191 "(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=h)(PORT=1))(CONNECT_DATA=(SERVER=BOGUS)(SERVICE_NAME=s)))",
1192 "invalid server_type",
1193 ),
1194 (
1196 "(DESCRIPTION=(RETRY_COUNT=wrong)(ADDRESS=(PROTOCOL=tcp)(HOST=h)(PORT=1))(CONNECT_DATA=(SERVICE_NAME=s)))",
1197 "not a non-negative integer",
1198 ),
1199 ("(address=5)", "container"),
1201 (
1203 "(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=h)(PORT=1))(CONNECT_DATA=(SERVER=DEDICATED) SERVICE_NAME=s))",
1204 "offset",
1205 ),
1206 ("", "must not be empty"),
1208 ];
1209 for (cs, needle) in cases {
1210 let err = parse(cs)
1211 .err()
1212 .unwrap_or_else(|| panic!("expected error for {cs:?}"));
1213 let msg = format!("{err}");
1214 assert!(
1215 msg.contains(needle),
1216 "diagnostic for {cs:?} = {msg:?} should contain {needle:?}"
1217 );
1218 }
1219 }
1220
1221 #[test]
1222 fn tns_alias_returns_none() {
1223 assert!(parse("my_tns_alias")
1226 .expect("alias is not an error")
1227 .is_none());
1228 }
1229
1230 #[test]
1231 fn sdu_is_clamped() {
1232 let d = parse_ok("(DESCRIPTION=(SDU=1)(ADDRESS=(PROTOCOL=tcp)(HOST=h)(PORT=1))(CONNECT_DATA=(SERVICE_NAME=s)))");
1234 assert_eq!(d.first_description().sdu, 512);
1235 let d = parse_ok("(DESCRIPTION=(SDU=99999999)(ADDRESS=(PROTOCOL=tcp)(HOST=h)(PORT=1))(CONNECT_DATA=(SERVICE_NAME=s)))");
1236 assert_eq!(d.first_description().sdu, 2_097_152);
1237 }
1238
1239 #[test]
1240 fn duration_units_parse() {
1241 let base = "(DESCRIPTION=(TRANSPORT_CONNECT_TIMEOUT=UNIT)(ADDRESS=(PROTOCOL=tcp)(HOST=h)(PORT=1))(CONNECT_DATA=(SERVICE_NAME=s)))";
1243 let cases = [
1244 ("500 ms", 0.5_f64),
1245 ("15 SEC", 15.0),
1246 ("5 min", 300.0),
1247 ("34", 34.0),
1248 ];
1249 for (unit, expected) in cases {
1250 let d = parse_ok(&base.replace("UNIT", unit));
1251 assert!(
1252 (d.first_description().tcp_connect_timeout - expected).abs() < 1e-9,
1253 "duration {unit:?} -> {}",
1254 d.first_description().tcp_connect_timeout
1255 );
1256 }
1257 }
1258
1259 #[test]
1260 fn passthrough_extras_preserved_in_connect_data() {
1261 let d = parse_ok(
1263 "(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=h)(PORT=1))(CONNECT_DATA=(SERVICE_NAME=s)(COLOCATION_TAG=Tag1)))",
1264 );
1265 let extra = &d.first_description().connect_data.extra;
1266 assert!(extra
1267 .iter()
1268 .any(|(k, v)| k == "COLOCATION_TAG" && v == "Tag1"));
1269 }
1270
1271 #[test]
1272 fn wallet_and_cert_dn_in_security() {
1273 let d = parse_ok(
1275 "(DESCRIPTION=(ADDRESS=(PROTOCOL=tcps)(HOST=h)(PORT=1))(CONNECT_DATA=(SERVICE_NAME=s))\
1276 (SECURITY=(SSL_SERVER_CERT_DN=\"CN=unknown\")(SSL_SERVER_DN_MATCH=Off)(MY_WALLET_DIRECTORY=\"/tmp/w\")))",
1277 );
1278 let sec = &d.first_description().security;
1279 assert_eq!(sec.ssl_server_cert_dn.as_deref(), Some("CN=unknown"));
1280 assert_eq!(sec.wallet_location.as_deref(), Some("/tmp/w"));
1281 assert!(!sec.ssl_server_dn_match);
1282 }
1283}