1use std::{
2 fs::{self, File},
3 io::{BufRead, BufReader, Read},
4 path::PathBuf,
5};
6
7use crate::dirs::config_dir;
8
9use {
10 icann_rdap_client::iana::{BootstrapStore, RegistryHasNotExpired},
11 icann_rdap_common::{
12 httpdata::HttpData,
13 iana::{BootstrapRegistry, IanaRegistry, IanaRegistryType},
14 },
15 tracing::debug,
16};
17
18use super::bootstrap_cache_path;
19
20pub struct FileCacheBootstrapStore;
21
22impl BootstrapStore for FileCacheBootstrapStore {
23 fn has_bootstrap_registry(
24 &self,
25 reg_type: &IanaRegistryType,
26 ) -> Result<bool, icann_rdap_client::RdapClientError> {
27 let file_name = reg_type.file_name();
28 let path = bootstrap_cache_path().join(file_name);
29 if path.exists() {
30 debug!("Looking for {file_name} bootstrap information.");
31 let fc_reg = read_bootstrap_cache_file(path, |_| {})?;
32 return Ok(Some(fc_reg).registry_has_not_expired());
33 }
34 Ok(false)
35 }
36
37 fn put_bootstrap_registry(
38 &self,
39 reg_type: &IanaRegistryType,
40 registry: IanaRegistry,
41 http_data: HttpData,
42 ) -> Result<(), icann_rdap_client::RdapClientError> {
43 let bootstrap_cache_path = bootstrap_cache_path().join(reg_type.file_name());
44 let data = serde_json::to_string_pretty(®istry)?;
45 let cache_contents = http_data.to_lines(&data)?;
46 fs::write(bootstrap_cache_path, cache_contents)?;
47 Ok(())
48 }
49
50 fn get_dns_urls(
51 &self,
52 ldh: &str,
53 ) -> Result<Option<Vec<String>>, icann_rdap_client::RdapClientError> {
54 self.get_bootstrap_urls(&IanaRegistryType::RdapBootstrapDns, ldh)
55 }
56
57 fn get_asn_urls(
58 &self,
59 asn: &str,
60 ) -> Result<Option<Vec<String>>, icann_rdap_client::RdapClientError> {
61 self.get_bootstrap_urls(&IanaRegistryType::RdapBootstrapAsn, asn)
62 }
63
64 fn get_ipv4_urls(
65 &self,
66 ipv4: &str,
67 ) -> Result<Option<Vec<String>>, icann_rdap_client::RdapClientError> {
68 self.get_bootstrap_urls(&IanaRegistryType::RdapBootstrapIpv4, ipv4)
69 }
70
71 fn get_ipv6_urls(
72 &self,
73 ipv6: &str,
74 ) -> Result<Option<Vec<String>>, icann_rdap_client::RdapClientError> {
75 self.get_bootstrap_urls(&IanaRegistryType::RdapBootstrapIpv6, ipv6)
76 }
77
78 fn get_tag_urls(
79 &self,
80 tag: &str,
81 ) -> Result<Option<Vec<String>>, icann_rdap_client::RdapClientError> {
82 self.get_bootstrap_urls(&IanaRegistryType::RdapObjectTags, tag)
83 }
84}
85
86impl FileCacheBootstrapStore {
87 fn get_bootstrap_urls(
88 &self,
89 reg_type: &IanaRegistryType,
90 key: &str,
91 ) -> Result<Option<Vec<String>>, icann_rdap_client::RdapClientError> {
92 let file_name = reg_type.file_name();
93
94 let config_bootstrap_path = config_dir().join(file_name);
96 if config_bootstrap_path.exists() {
97 let iana = read_bootstrap_config_file(config_bootstrap_path, |s| debug!("Reading {s}"));
98 match iana {
99 Ok(iana) => {
100 let urls = match reg_type {
101 IanaRegistryType::RdapBootstrapDns => iana.get_dns_bootstrap_urls(key),
102 IanaRegistryType::RdapBootstrapAsn => iana.get_asn_bootstrap_urls(key),
103 IanaRegistryType::RdapBootstrapIpv4 => iana.get_ipv4_bootstrap_urls(key),
104 IanaRegistryType::RdapBootstrapIpv6 => iana.get_ipv6_bootstrap_urls(key),
105 IanaRegistryType::RdapObjectTags => iana.get_tag_bootstrap_urls(key),
106 };
107 match urls {
108 Ok(Some(urls)) => {
109 debug!("Bootstrap URLs found in configured bootstrap override.");
110 return Ok(Some(urls));
111 }
112 Ok(None) => {}
113 Err(e) => return Err(e.into()),
114 }
115 }
116 Err(err) => return Err(err),
117 }
118 }
119
120 let bootstrap_cache_path = bootstrap_cache_path().join(file_name);
122 let (iana, _http_data) =
123 read_bootstrap_cache_file(bootstrap_cache_path, |s| debug!("Reading {s}"))?;
124 let urls = match reg_type {
125 IanaRegistryType::RdapBootstrapDns => iana.get_dns_bootstrap_urls(key),
126 IanaRegistryType::RdapBootstrapAsn => iana.get_asn_bootstrap_urls(key),
127 IanaRegistryType::RdapBootstrapIpv4 => iana.get_ipv4_bootstrap_urls(key),
128 IanaRegistryType::RdapBootstrapIpv6 => iana.get_ipv6_bootstrap_urls(key),
129 IanaRegistryType::RdapObjectTags => iana.get_tag_bootstrap_urls(key),
130 };
131 Ok(urls?)
132 }
133}
134
135pub fn read_bootstrap_cache_file<F>(
136 path: PathBuf,
137 callback: F,
138) -> Result<(IanaRegistry, HttpData), std::io::Error>
139where
140 F: FnOnce(String),
141{
142 let input = File::open(&path)?;
143 let buf = BufReader::new(input);
144 let mut lines = vec![];
145 for line in buf.lines() {
146 lines.push(line?);
147 }
148 let cache_data = HttpData::from_lines(&lines)?;
149 callback(path.display().to_string());
150 let iana: IanaRegistry = serde_json::from_str(&cache_data.1.join(""))?;
151 Ok((iana, cache_data.0))
152}
153
154pub fn read_bootstrap_config_file<F>(
155 path: PathBuf,
156 callback: F,
157) -> Result<IanaRegistry, icann_rdap_client::RdapClientError>
158where
159 F: FnOnce(String),
160{
161 let mut input = File::open(&path)?;
162 let mut content = String::new();
163 input.read_to_string(&mut content)?;
164 callback(path.display().to_string());
165 let iana: IanaRegistry = serde_json::from_str(&content)?;
166 Ok(iana)
167}
168
169#[cfg(test)]
170mod test {
171 use {
172 icann_rdap_client::{
173 iana::{BootstrapStore, PreferredUrl},
174 rdap::QueryType,
175 },
176 icann_rdap_common::{
177 httpdata::HttpData,
178 iana::{BootstrapRegistry, IanaRegistry, IanaRegistryType},
179 },
180 serial_test::serial,
181 test_dir::{DirBuilder, FileType, TestDir},
182 };
183
184 use crate::dirs::{self, bootstrap_cache_path, config_dir, fcbs::FileCacheBootstrapStore};
185
186 fn test_dir() -> TestDir {
187 let test_dir = TestDir::temp()
188 .create("cache", FileType::Dir)
189 .create("config", FileType::Dir);
190 unsafe {
191 std::env::set_var("XDG_CACHE_HOME", test_dir.path("cache"));
192 std::env::set_var("XDG_CONFIG_HOME", test_dir.path("config"));
193 };
194 dirs::init().expect("unable to init directories");
195 test_dir
196 }
197
198 #[test]
199 #[serial]
200 fn test_fcbootstrap_with_dns() {
201 let _test_dir = test_dir();
203 let bs = FileCacheBootstrapStore;
204 let bootstrap = r#"
205 {
206 "version": "1.0",
207 "publication": "2024-01-07T10:11:12Z",
208 "description": "Some text",
209 "services": [
210 [
211 ["net", "com"],
212 [
213 "https://registry.example.com/myrdap/"
214 ]
215 ],
216 [
217 ["org", "mytld"],
218 [
219 "https://example.org/"
220 ]
221 ]
222 ]
223 }
224 "#;
225 let iana =
226 serde_json::from_str::<IanaRegistry>(bootstrap).expect("cannot parse domain bootstrap");
227 bs.put_bootstrap_registry(
228 &IanaRegistryType::RdapBootstrapDns,
229 iana,
230 HttpData::example().build(),
231 )
232 .expect("put iana registry");
233
234 let actual = bs
236 .get_domain_query_urls(&QueryType::domain("example.org").expect("invalid domain name"))
237 .expect("get bootstrap url")
238 .preferred_url()
239 .expect("preferred url");
240
241 assert_eq!(actual, "https://example.org/")
243 }
244
245 #[test]
246 #[serial]
247 fn test_fcbootstrap_with_autnum() {
248 let _test_dir = test_dir();
250 let bs = FileCacheBootstrapStore;
251 let bootstrap = r#"
252 {
253 "version": "1.0",
254 "publication": "2024-01-07T10:11:12Z",
255 "description": "RDAP Bootstrap file for example registries.",
256 "services": [
257 [
258 ["64496-64496"],
259 [
260 "https://rir3.example.com/myrdap/"
261 ]
262 ],
263 [
264 ["64497-64510", "65536-65551"],
265 [
266 "https://example.org/"
267 ]
268 ],
269 [
270 ["64512-65534"],
271 [
272 "http://example.net/rdaprir2/",
273 "https://example.net/rdaprir2/"
274 ]
275 ]
276 ]
277 }
278 "#;
279 let iana =
280 serde_json::from_str::<IanaRegistry>(bootstrap).expect("cannot parse autnum bootstrap");
281 bs.put_bootstrap_registry(
282 &IanaRegistryType::RdapBootstrapAsn,
283 iana,
284 HttpData::example().build(),
285 )
286 .expect("put iana registry");
287
288 let actual = bs
290 .get_autnum_query_urls(&QueryType::autnum("as64512").expect("invalid autnum"))
291 .expect("get bootstrap url")
292 .preferred_url()
293 .expect("preferred url");
294
295 assert_eq!(actual, "https://example.net/rdaprir2/");
297 }
298
299 #[test]
300 #[serial]
301 fn test_fcbootstrap_with_ipv4() {
302 let _test_dir = test_dir();
304 let bs = FileCacheBootstrapStore;
305 let bootstrap = r#"
306 {
307 "version": "1.0",
308 "publication": "2024-01-07T10:11:12Z",
309 "description": "RDAP Bootstrap file for example registries.",
310 "services": [
311 [
312 ["198.51.100.0/24", "192.0.0.0/8"],
313 [
314 "https://rir1.example.com/myrdap/"
315 ]
316 ],
317 [
318 ["203.0.113.0/24", "192.0.2.0/24"],
319 [
320 "https://example.org/"
321 ]
322 ],
323 [
324 ["203.0.113.0/28"],
325 [
326 "https://example.net/rdaprir2/",
327 "http://example.net/rdaprir2/"
328 ]
329 ]
330 ]
331 }
332 "#;
333 let iana =
334 serde_json::from_str::<IanaRegistry>(bootstrap).expect("cannot parse autnum bootstrap");
335 bs.put_bootstrap_registry(
336 &IanaRegistryType::RdapBootstrapIpv4,
337 iana,
338 HttpData::example().build(),
339 )
340 .expect("put iana registry");
341
342 let actual = bs
344 .get_ipv4_query_urls(&QueryType::ipv4("198.51.100.1").expect("invalid IP address"))
345 .expect("get bootstrap url")
346 .preferred_url()
347 .expect("preferred url");
348
349 assert_eq!(actual, "https://rir1.example.com/myrdap/");
351 }
352
353 #[test]
354 #[serial]
355 fn test_fcbootstrap_with_ipv6() {
356 let _test_dir = test_dir();
358 let bs = FileCacheBootstrapStore;
359 let bootstrap = r#"
360 {
361 "version": "1.0",
362 "publication": "2024-01-07T10:11:12Z",
363 "description": "RDAP Bootstrap file for example registries.",
364 "services": [
365 [
366 ["2001:db8::/34"],
367 [
368 "https://rir2.example.com/myrdap/"
369 ]
370 ],
371 [
372 ["2001:db8:4000::/36", "2001:db8:ffff::/48"],
373 [
374 "https://example.org/"
375 ]
376 ],
377 [
378 ["2001:db8:1000::/36"],
379 [
380 "https://example.net/rdaprir2/",
381 "http://example.net/rdaprir2/"
382 ]
383 ]
384 ]
385 }
386 "#;
387 let iana =
388 serde_json::from_str::<IanaRegistry>(bootstrap).expect("cannot parse autnum bootstrap");
389 bs.put_bootstrap_registry(
390 &IanaRegistryType::RdapBootstrapIpv6,
391 iana,
392 HttpData::example().build(),
393 )
394 .expect("put iana registry");
395
396 let actual = bs
398 .get_ipv6_query_urls(&QueryType::ipv6("2001:db8::1").expect("invalid IP address"))
399 .expect("get bootstrap url")
400 .preferred_url()
401 .expect("preferred url");
402
403 assert_eq!(actual, "https://rir2.example.com/myrdap/");
405 }
406
407 #[test]
408 #[serial]
409 fn test_fcbootstrap_with_tag() {
410 let _test_dir = test_dir();
412 let bs = FileCacheBootstrapStore;
413 let bootstrap = r#"
414 {
415 "version": "1.0",
416 "publication": "YYYY-MM-DDTHH:MM:SSZ",
417 "description": "RDAP bootstrap file for service provider object tags",
418 "services": [
419 [
420 ["contact@example.com"],
421 ["YYYY"],
422 [
423 "https://example.com/rdap/"
424 ]
425 ],
426 [
427 ["contact@example.org"],
428 ["ZZ54"],
429 [
430 "http://rdap.example.org/"
431 ]
432 ],
433 [
434 ["contact@example.net"],
435 ["1754"],
436 [
437 "https://example.net/rdap/",
438 "http://example.net/rdap/"
439 ]
440 ]
441 ]
442 }
443 "#;
444 let iana =
445 serde_json::from_str::<IanaRegistry>(bootstrap).expect("cannot parse autnum bootstrap");
446 bs.put_bootstrap_registry(
447 &IanaRegistryType::RdapObjectTags,
448 iana,
449 HttpData::example().build(),
450 )
451 .expect("put iana registry");
452
453 let actual = bs
455 .get_entity_handle_query_urls(&QueryType::Entity("foo-YYYY".to_string()))
456 .expect("get bootstrap url")
457 .preferred_url()
458 .expect("preferred url");
459
460 assert_eq!(actual, "https://example.com/rdap/")
462 }
463
464 #[test]
465 #[serial]
466 fn test_fcbootstrap_dns_config_override() {
467 let _test_dir = test_dir();
469 let bs = FileCacheBootstrapStore;
470
471 let cache_bootstrap = r#"
472 {
473 "version": "1.0",
474 "publication": "2024-01-07T10:11:12Z",
475 "description": "Cache bootstrap",
476 "services": [
477 [
478 ["org"],
479 [
480 "https://cache.example.org/"
481 ]
482 ]
483 ]
484 }
485 "#;
486 let iana = serde_json::from_str::<IanaRegistry>(cache_bootstrap)
487 .expect("cannot parse cache bootstrap");
488 bs.put_bootstrap_registry(
489 &IanaRegistryType::RdapBootstrapDns,
490 iana,
491 HttpData::example().build(),
492 )
493 .expect("put cache iana registry");
494
495 let config_bootstrap = r#"
496 {
497 "version": "1.0",
498 "publication": "2024-01-07T10:11:12Z",
499 "description": "Config override bootstrap",
500 "services": [
501 [
502 ["org"],
503 [
504 "https://config.example.org/"
505 ]
506 ]
507 ]
508 }
509 "#;
510 let bootstrap_config_path = config_dir().join("dns.json");
511 std::fs::write(&bootstrap_config_path, config_bootstrap).expect("write config bootstrap");
512
513 let actual = bs
515 .get_domain_query_urls(&QueryType::domain("example.org").expect("invalid domain name"))
516 .expect("get bootstrap url")
517 .preferred_url()
518 .expect("preferred url");
519
520 assert_eq!(actual, "https://config.example.org/")
522 }
523
524 #[test]
525 #[serial]
526 fn test_fcbootstrap_asn_config_override() {
527 let _test_dir = test_dir();
529 let bs = FileCacheBootstrapStore;
530
531 let cache_bootstrap = r#"
532 {
533 "version": "1.0",
534 "publication": "2024-01-07T10:11:12Z",
535 "description": "Cache bootstrap",
536 "services": [
537 [
538 ["64496-64496"],
539 [
540 "https://cache.example.com/"
541 ]
542 ]
543 ]
544 }
545 "#;
546 let iana = serde_json::from_str::<IanaRegistry>(cache_bootstrap)
547 .expect("cannot parse cache bootstrap");
548 bs.put_bootstrap_registry(
549 &IanaRegistryType::RdapBootstrapAsn,
550 iana,
551 HttpData::example().build(),
552 )
553 .expect("put cache iana registry");
554
555 let config_bootstrap = r#"
556 {
557 "version": "1.0",
558 "publication": "2024-01-07T10:11:12Z",
559 "description": "Config override bootstrap",
560 "services": [
561 [
562 ["64496-64496"],
563 [
564 "https://config.example.com/"
565 ]
566 ]
567 ]
568 }
569 "#;
570 let bootstrap_config_path = config_dir().join("asn.json");
571 std::fs::write(&bootstrap_config_path, config_bootstrap).expect("write config bootstrap");
572
573 let actual = bs
575 .get_autnum_query_urls(&QueryType::autnum("as64496").expect("invalid autnum"))
576 .expect("get bootstrap url")
577 .preferred_url()
578 .expect("preferred url");
579
580 assert_eq!(actual, "https://config.example.com/")
582 }
583
584 #[test]
585 #[serial]
586 fn test_fcbootstrap_ipv4_config_override() {
587 let _test_dir = test_dir();
589 let bs = FileCacheBootstrapStore;
590
591 let cache_bootstrap = r#"
592 {
593 "version": "1.0",
594 "publication": "2024-01-07T10:11:12Z",
595 "description": "Cache bootstrap",
596 "services": [
597 [
598 ["198.51.100.0/24"],
599 [
600 "https://cache.example.com/"
601 ]
602 ]
603 ]
604 }
605 "#;
606 let iana = serde_json::from_str::<IanaRegistry>(cache_bootstrap)
607 .expect("cannot parse cache bootstrap");
608 bs.put_bootstrap_registry(
609 &IanaRegistryType::RdapBootstrapIpv4,
610 iana,
611 HttpData::example().build(),
612 )
613 .expect("put cache iana registry");
614
615 let config_bootstrap = r#"
616 {
617 "version": "1.0",
618 "publication": "2024-01-07T10:11:12Z",
619 "description": "Config override bootstrap",
620 "services": [
621 [
622 ["198.51.100.0/24"],
623 [
624 "https://config.example.com/"
625 ]
626 ]
627 ]
628 }
629 "#;
630 let bootstrap_config_path = config_dir().join("ipv4.json");
631 std::fs::write(&bootstrap_config_path, config_bootstrap).expect("write config bootstrap");
632
633 let actual = bs
635 .get_ipv4_query_urls(&QueryType::ipv4("198.51.100.1").expect("invalid IP address"))
636 .expect("get bootstrap url")
637 .preferred_url()
638 .expect("preferred url");
639
640 assert_eq!(actual, "https://config.example.com/")
642 }
643
644 #[test]
645 #[serial]
646 fn test_fcbootstrap_ipv6_config_override() {
647 let _test_dir = test_dir();
649 let bs = FileCacheBootstrapStore;
650
651 let cache_bootstrap = r#"
652 {
653 "version": "1.0",
654 "publication": "2024-01-07T10:11:12Z",
655 "description": "Cache bootstrap",
656 "services": [
657 [
658 ["2001:db8::/32"],
659 [
660 "https://cache.example.com/"
661 ]
662 ]
663 ]
664 }
665 "#;
666 let iana = serde_json::from_str::<IanaRegistry>(cache_bootstrap)
667 .expect("cannot parse cache bootstrap");
668 bs.put_bootstrap_registry(
669 &IanaRegistryType::RdapBootstrapIpv6,
670 iana,
671 HttpData::example().build(),
672 )
673 .expect("put cache iana registry");
674
675 let config_bootstrap = r#"
676 {
677 "version": "1.0",
678 "publication": "2024-01-07T10:11:12Z",
679 "description": "Config override bootstrap",
680 "services": [
681 [
682 ["2001:db8::/32"],
683 [
684 "https://config.example.com/"
685 ]
686 ]
687 ]
688 }
689 "#;
690 let bootstrap_config_path = config_dir().join("ipv6.json");
691 std::fs::write(&bootstrap_config_path, config_bootstrap).expect("write config bootstrap");
692
693 let actual = bs
695 .get_ipv6_query_urls(&QueryType::ipv6("2001:db8::1").expect("invalid IP address"))
696 .expect("get bootstrap url")
697 .preferred_url()
698 .expect("preferred url");
699
700 assert_eq!(actual, "https://config.example.com/")
702 }
703
704 #[test]
705 #[serial]
706 fn test_fcbootstrap_tag_config_override() {
707 let _test_dir = test_dir();
709 let bs = FileCacheBootstrapStore;
710
711 let cache_bootstrap = r#"
712 {
713 "version": "1.0",
714 "publication": "YYYY-MM-DDTHH:MM:SSZ",
715 "description": "Cache bootstrap",
716 "services": [
717 [
718 ["contact@example.com"],
719 ["YYYY"],
720 [
721 "https://cache.example.com/rdap/"
722 ]
723 ]
724 ]
725 }
726 "#;
727 let iana = serde_json::from_str::<IanaRegistry>(cache_bootstrap)
728 .expect("cannot parse cache bootstrap");
729 bs.put_bootstrap_registry(
730 &IanaRegistryType::RdapObjectTags,
731 iana,
732 HttpData::example().build(),
733 )
734 .expect("put cache iana registry");
735
736 let config_bootstrap = r#"
737 {
738 "version": "1.0",
739 "publication": "YYYY-MM-DDTHH:MM:SSZ",
740 "description": "Config override bootstrap",
741 "services": [
742 [
743 ["contact@example.com"],
744 ["YYYY"],
745 [
746 "https://config.example.com/rdap/"
747 ]
748 ]
749 ]
750 }
751 "#;
752 let bootstrap_config_path = config_dir().join("object-tags.json");
753 std::fs::write(&bootstrap_config_path, config_bootstrap).expect("write config bootstrap");
754
755 let actual = bs
757 .get_entity_handle_query_urls(&QueryType::Entity("foo-YYYY".to_string()))
758 .expect("get bootstrap url")
759 .preferred_url()
760 .expect("preferred url");
761
762 assert_eq!(actual, "https://config.example.com/rdap/")
764 }
765
766 #[test]
767 fn test_iana_registry_propagates_invalid_asn_input_error() {
768 let bootstrap = r#"
770 {
771 "version": "1.0",
772 "publication": "2024-01-07T10:11:12Z",
773 "description": "ASN bootstrap",
774 "services": [
775 [
776 ["64496-64496"],
777 [
778 "https://example.org/"
779 ]
780 ]
781 ]
782 }
783 "#;
784 let iana: IanaRegistry =
785 serde_json::from_str::<IanaRegistry>(bootstrap).expect("cannot parse bootstrap");
786
787 let result = iana.get_asn_bootstrap_urls("notanumber");
789
790 assert!(
792 result.is_err(),
793 "Expected error for invalid ASN input but got: {:?}",
794 result
795 );
796 }
797
798 #[test]
799 #[serial]
800 fn test_fcbootstrap_propagates_invalid_asn_via_store() {
801 let _test_dir = test_dir();
803 let bs = FileCacheBootstrapStore;
804 let bootstrap = r#"
805 {
806 "version": "1.0",
807 "publication": "2024-01-07T10:11:12Z",
808 "description": "ASN bootstrap",
809 "services": [
810 [
811 ["64496-64496"],
812 [
813 "https://example.org/"
814 ]
815 ]
816 ]
817 }
818 "#;
819 let iana = serde_json::from_str::<IanaRegistry>(bootstrap).expect("cannot parse bootstrap");
820 bs.put_bootstrap_registry(
821 &IanaRegistryType::RdapBootstrapAsn,
822 iana,
823 HttpData::example().build(),
824 )
825 .expect("put iana registry");
826
827 let result = bs.get_asn_urls("notanumber");
829
830 assert!(
832 result.is_err(),
833 "Expected error for invalid ASN but got: {:?}",
834 result
835 );
836 }
837
838 #[test]
839 #[serial]
840 fn test_fcbootstrap_config_override_propagates_errors() {
841 let _test_dir = test_dir();
843 let bs = FileCacheBootstrapStore;
844
845 let config_bootstrap = r#"
847 {
848 "version": "1.0",
849 "publication": "2024-01-07T10:11:12Z",
850 "description": "Config bootstrap",
851 "services": [
852 [
853 ["com"],
854 [
855 "https://config.example.org/"
856 ]
857 ]
858 ]
859 }
860 "#;
861 let bootstrap_config_path = config_dir().join("dns.json");
862 std::fs::write(&bootstrap_config_path, config_bootstrap).expect("write config bootstrap");
863
864 let cache_bootstrap = r#"
866 {
867 "version": "1.0",
868 "publication": "2024-01-07T10:11:12Z",
869 "description": "Cache bootstrap",
870 "services": [
871 [
872 ["net"],
873 [
874 "https://cache.example.org/"
875 ]
876 ]
877 ]
878 }
879 "#;
880 let iana =
881 serde_json::from_str::<IanaRegistry>(cache_bootstrap).expect("cannot parse bootstrap");
882 bs.put_bootstrap_registry(
883 &IanaRegistryType::RdapBootstrapDns,
884 iana,
885 HttpData::example().build(),
886 )
887 .expect("put iana registry");
888
889 let result = bs
891 .get_domain_query_urls(&QueryType::domain("example.org").expect("invalid domain name"));
892
893 assert!(result.is_ok(), "Expected Ok but got: {:?}", result);
895 assert!(result.unwrap().is_none(), "Expected None but got Some");
896 }
897
898 #[test]
899 #[serial]
900 fn test_fcbootstrap_propagates_invalid_json_error() {
901 let _test_dir = test_dir();
903 let bs = FileCacheBootstrapStore;
904
905 let invalid_json = r#"{ "version": "1.0", invalid json }"#;
907 let bootstrap_config_path = config_dir().join("dns.json");
908 std::fs::write(&bootstrap_config_path, invalid_json).expect("write invalid json");
909
910 let result = bs
912 .get_domain_query_urls(&QueryType::domain("example.org").expect("invalid domain name"));
913
914 assert!(
916 result.is_err(),
917 "Expected error for invalid JSON but got: {:?}",
918 result
919 );
920 }
921
922 #[test]
923 #[serial]
924 fn test_fcbootstrap_propagates_invalid_json_in_cache_error() {
925 let _test_dir = test_dir();
927 let bs = FileCacheBootstrapStore;
928
929 let invalid_json = r#"{ "version": "1.0", bad json }"#;
931 let cache_path = bootstrap_cache_path().join("dns.json");
932 std::fs::write(&cache_path, invalid_json).expect("write invalid json to cache");
933
934 let result = bs
936 .get_domain_query_urls(&QueryType::domain("example.org").expect("invalid domain name"));
937
938 assert!(
940 result.is_err(),
941 "Expected error for invalid JSON in cache but got: {:?}",
942 result
943 );
944 }
945}