1
0
Fork 0

Adding upstream version 0.0.22.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-08 18:41:54 +02:00
parent 2f814b513a
commit b06d3acde8
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
190 changed files with 61565 additions and 0 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,8 @@
use buildstructor::Builder;
use crate::storage::CommonConfig;
#[derive(Debug, Builder, Clone)]
pub struct MemConfig {
pub common_config: CommonConfig,
}

View file

@ -0,0 +1,365 @@
use std::collections::HashMap;
use {ab_radix_trie::Trie, buildstructor::Builder};
use crate::error::RdapServerError;
/// A structure for searching DNS labels as specified in RFC 9082.
/// For RDAP, type T is likely RdapResponse or Arc<RdapResponse>.
#[derive(Builder)]
pub struct SearchLabels<T: Clone> {
label_suffixes: HashMap<String, Trie<T>>,
}
impl<T: Clone> SearchLabels<T> {
/// Insert a value based on a domain name.
pub(crate) fn insert(&mut self, text: &str, value: T) {
// char_indices gets the UTF8 indices as well as the character
for (i, char) in text.char_indices() {
if char == '.' && i != 0 {
let prefix = &text[..i];
// find the next UTF8 character index
let mut next_i = i + 1;
while !text.is_char_boundary(next_i) {
next_i += 1;
}
let suffix = &text[next_i..];
self.label_suffixes
.entry(suffix.to_owned())
.or_insert(Trie::new())
.insert(prefix, Some(value.clone()));
}
}
// the root
self.label_suffixes
.entry(String::default())
.or_insert(Trie::new())
.insert(text, Some(value.clone()));
}
/// Search values based on a label search
pub(crate) fn search(&self, search: &str) -> Result<Vec<T>, RdapServerError> {
// search string is invalid if it doesn't have only one asterisk ('*')
if search.chars().filter(|c| *c == '*').count() != 1 {
return Err(RdapServerError::InvalidArg(
"Search string must contain one and only one asterisk ('*')".to_string(),
));
}
// asterisk must not be followed by a character other than dot ('.')
let star = search
.find('*')
.expect("internal error. previous check should have caught this");
if star != search.chars().count() - 1
&& search
.chars()
.nth(star + 1)
.expect("should have been short circuited")
!= '.'
{
return Err(RdapServerError::InvalidArg(
"Search string asterisk ('*') must terminate domain label".to_string(),
));
}
let parts = search
.split_once('*')
.expect("internal error. previous check should insure there is an asterisk");
// this is a limitation of the trie in that it requires a prefix
if parts.0.is_empty() {
return Err(RdapServerError::InvalidArg(
"Search string must have a prefix".to_string(),
));
}
if let Some(trie) = self.label_suffixes.get(parts.1.trim_start_matches('.')) {
if let Some(entries) = trie.get_suffixes_values(parts.0) {
if !entries.is_empty() {
let values = entries
.iter()
.filter_map(|e| e.val.clone())
.collect::<Vec<T>>();
return Ok(values);
}
}
}
Ok(vec![])
}
}
#[cfg(test)]
#[allow(non_snake_case)]
mod tests {
use ab_radix_trie::{Entry, Trie};
use super::SearchLabels;
#[test]
fn GIVEN_domain_names_WHEN_inserting_THEN_search_labels_is_correct() {
// GIVEN
let mut search = SearchLabels::builder().build();
// WHEN
search.insert("foo.example.com", "foo.example.com".to_owned());
search.insert("bar.example.com", "bar.example.com".to_owned());
search.insert("foo.example.net", "foo.example.net".to_owned());
search.insert("bar.example.net", "bar.example.net".to_owned());
// THEN
dbg!(&search.label_suffixes);
assert_eq!(search.label_suffixes.len(), 5);
// root
let root = search.label_suffixes.get("").expect("no root");
assert_trie(
root,
"foo.example.",
&["foo.example.com", "foo.example.net"],
&["bar.example.com", "bar.example.net"],
);
assert_trie(
root,
"bar.example.",
&["bar.example.com", "bar.example.net"],
&["foo.example.com", "foo.example.net"],
);
// com
let com = search.label_suffixes.get("com").expect("no trie");
assert_trie(
com,
"foo.example",
&["foo.example.com"],
&["bar.example.com", "bar.example.net", "foo.example.net"],
);
assert_trie(
com,
"bar.example",
&["bar.example.com"],
&["foo.example.com", "foo.example.net", "bar.example.net"],
);
// net
let net = search.label_suffixes.get("net").expect("no trie");
assert_trie(
net,
"foo.example",
&["foo.example.net"],
&["bar.example.net", "bar.example.com", "foo.example.com"],
);
assert_trie(
net,
"bar.example",
&["bar.example.net"],
&["foo.example.com", "foo.example.net", "bar.example.com"],
);
// example.com
let example_com = search.label_suffixes.get("example.com").expect("no trie");
assert_trie(
example_com,
"foo",
&["foo.example.com"],
&["bar.example.com", "bar.example.net", "foo.example.net"],
);
assert_trie(
example_com,
"bar",
&["bar.example.com"],
&["foo.example.com", "foo.example.net", "bar.example.net"],
);
// example.net
let example_net = search.label_suffixes.get("example.net").expect("no trie");
assert_trie(
example_net,
"foo",
&["foo.example.net"],
&["bar.example.net", "bar.example.com", "foo.example.com"],
);
assert_trie(
example_net,
"bar",
&["bar.example.net"],
&["foo.example.com", "foo.example.net", "bar.example.com"],
);
}
fn assert_trie(trie: &Trie<String>, suffix: &str, must_have: &[&str], must_not_have: &[&str]) {
let entries = trie
.get_suffixes_values(suffix)
.expect("no values in entries");
for s in must_have {
assert!(
trie_contains(&entries, s),
"suffix = {suffix} did not find {s}"
);
}
for s in must_not_have {
assert!(!trie_contains(&entries, s), "suffix = {suffix} found {s}");
}
}
fn trie_contains(entries: &[Entry<'_, String>], value: &str) -> bool {
entries
.iter()
.any(|e| e.val.as_ref().expect("no entry value") == value)
}
#[test]
fn GIVEN_search_string_with_two_asterisks_WHEN_search_THEN_error() {
// GIVEN
let labels: SearchLabels<String> = SearchLabels::builder().build();
let search = "foo.*.*";
// WHEN
let actual = labels.search(search);
// THEN
assert!(actual.is_err());
}
#[test]
fn GIVEN_search_string_with_asterisk_suffix_WHEN_search_THEN_error() {
// GIVEN
let labels: SearchLabels<String> = SearchLabels::builder().build();
let search = "foo.*example.net";
// WHEN
let actual = labels.search(search);
// THEN
assert!(actual.is_err());
}
#[test]
fn GIVEN_search_string_with_no_asterisk_WHEN_search_THEN_error() {
// GIVEN
let labels: SearchLabels<String> = SearchLabels::builder().build();
let search = "foo.example.net";
// WHEN
let actual = labels.search(search);
// THEN
assert!(actual.is_err());
}
#[test]
fn GIVEN_empty_search_string_WHEN_search_THEN_error() {
// GIVEN
let labels: SearchLabels<String> = SearchLabels::builder().build();
let search = "";
// WHEN
let actual = labels.search(search);
// THEN
assert!(actual.is_err());
}
#[test]
fn GIVEN_root_search_WHEN_search_THEN_correct_values_found() {
// GIVEN
let mut labels = SearchLabels::builder().build();
labels.insert("foo.example.com", "foo.example.com".to_owned());
labels.insert("bar.example.com", "bar.example.com".to_owned());
labels.insert("foo.example.net", "foo.example.net".to_owned());
labels.insert("bar.example.net", "bar.example.net".to_owned());
// WHEN
let actual = labels.search("foo.example.*").expect("search is invalid");
// THEN
dbg!(&actual);
assert_eq!(actual.len(), 2);
assert!(actual.contains(&"foo.example.com".to_string()));
assert!(actual.contains(&"foo.example.net".to_string()));
}
#[test]
fn GIVEN_root_search_WHEN_search_with_prefix_THEN_correct_values_found() {
// GIVEN
let mut labels = SearchLabels::builder().build();
labels.insert("foo.example.com", "foo.example.com".to_owned());
labels.insert("bar.example.com", "bar.example.com".to_owned());
labels.insert("foo.example.net", "foo.example.net".to_owned());
labels.insert("bar.example.net", "bar.example.net".to_owned());
// WHEN
let actual = labels.search("foo.example.n*").expect("search is invalid");
// THEN
dbg!(&actual);
assert_eq!(actual.len(), 1);
assert!(actual.contains(&"foo.example.net".to_string()));
}
#[test]
fn GIVEN_labels_WHEN_sld_search_with_prefix_THEN_correct_values_found() {
// GIVEN
let mut labels = SearchLabels::builder().build();
labels.insert("foo.example.com", "foo.example.com".to_owned());
labels.insert("bar.example.com", "bar.example.com".to_owned());
labels.insert("foo.example.net", "foo.example.net".to_owned());
labels.insert("bar.example.net", "bar.example.net".to_owned());
// WHEN
let actual = labels.search("foo.ex*.com").expect("search is invalid");
// THEN
dbg!(&actual);
assert_eq!(actual.len(), 1);
assert!(actual.contains(&"foo.example.com".to_string()));
}
#[test]
fn GIVEN_labels_WHEN_3ld_search_with_prefix_THEN_correct_values_found() {
// GIVEN
let mut labels = SearchLabels::builder().build();
labels.insert("foo.example.com", "foo.example.com".to_owned());
labels.insert("bar.example.com", "bar.example.com".to_owned());
labels.insert("foo.example.net", "foo.example.net".to_owned());
labels.insert("bar.example.net", "bar.example.net".to_owned());
// WHEN
let actual = labels.search("fo*.example.com").expect("search is invalid");
// THEN
dbg!(&actual);
assert_eq!(actual.len(), 1);
assert!(actual.contains(&"foo.example.com".to_string()));
}
#[test]
fn GIVEN_labels_WHEN_sld_search_THEN_correct_values_found() {
// GIVEN
let mut labels = SearchLabels::builder().build();
labels.insert("foo.example.com", "foo.example.com".to_owned());
labels.insert("bar.example.com", "bar.example.com".to_owned());
labels.insert("foo.example.net", "foo.example.net".to_owned());
labels.insert("bar.example.net", "bar.example.net".to_owned());
// WHEN
let actual = labels.search("foo.*.com").expect("search is invalid");
// THEN
dbg!(&actual);
assert_eq!(actual.len(), 1);
assert!(actual.contains(&"foo.example.com".to_string()));
}
#[test]
fn GIVEN_labels_WHEN_3ld_search_THEN_error() {
// GIVEN
let mut labels = SearchLabels::builder().build();
labels.insert("foo.example.com", "foo.example.com".to_owned());
labels.insert("bar.example.com", "bar.example.com".to_owned());
labels.insert("foo.example.net", "foo.example.net".to_owned());
labels.insert("bar.example.net", "bar.example.net".to_owned());
// WHEN
let actual = labels.search("*.example.com");
// THEN
dbg!(&actual);
assert!(actual.is_err());
}
}

View file

@ -0,0 +1,6 @@
#![allow(dead_code)] // TODO remove
pub mod config;
mod label_search;
pub mod ops;
pub mod tx;

View file

@ -0,0 +1,201 @@
use std::{collections::HashMap, net::IpAddr, str::FromStr, sync::Arc};
use {
async_trait::async_trait,
btree_range_map::RangeMap,
icann_rdap_common::{
prelude::ToResponse,
response::{Domain, DomainSearchResults, RdapResponse},
},
ipnet::{IpNet, Ipv4Net, Ipv6Net},
prefix_trie::PrefixMap,
tokio::sync::RwLock,
};
use crate::{
error::RdapServerError,
rdap::response::{NOT_FOUND, NOT_IMPLEMENTED},
storage::{CommonConfig, StoreOps, TxHandle},
};
use super::{config::MemConfig, label_search::SearchLabels, tx::MemTx};
#[derive(Clone)]
pub struct Mem {
pub(crate) autnums: Arc<RwLock<RangeMap<u32, Arc<RdapResponse>>>>,
pub(crate) ip4: Arc<RwLock<PrefixMap<Ipv4Net, Arc<RdapResponse>>>>,
pub(crate) ip6: Arc<RwLock<PrefixMap<Ipv6Net, Arc<RdapResponse>>>>,
pub(crate) domains: Arc<RwLock<HashMap<String, Arc<RdapResponse>>>>,
pub(crate) domains_by_name: Arc<RwLock<SearchLabels<Arc<RdapResponse>>>>,
pub(crate) idns: Arc<RwLock<HashMap<String, Arc<RdapResponse>>>>,
pub(crate) nameservers: Arc<RwLock<HashMap<String, Arc<RdapResponse>>>>,
pub(crate) entities: Arc<RwLock<HashMap<String, Arc<RdapResponse>>>>,
pub(crate) srvhelps: Arc<RwLock<HashMap<String, Arc<RdapResponse>>>>,
pub(crate) config: MemConfig,
}
impl Mem {
pub fn new(config: MemConfig) -> Self {
Self {
autnums: <_>::default(),
ip4: <_>::default(),
ip6: <_>::default(),
domains: <_>::default(),
domains_by_name: Arc::new(RwLock::new(SearchLabels::builder().build())),
idns: <_>::default(),
nameservers: <_>::default(),
entities: <_>::default(),
srvhelps: <_>::default(),
config,
}
}
}
impl Default for Mem {
fn default() -> Self {
Self::new(
MemConfig::builder()
.common_config(CommonConfig::default())
.build(),
)
}
}
#[async_trait]
impl StoreOps for Mem {
async fn init(&self) -> Result<(), RdapServerError> {
Ok(())
}
async fn new_tx(&self) -> Result<Box<dyn TxHandle>, RdapServerError> {
Ok(Box::new(MemTx::new(self).await))
}
async fn new_truncate_tx(&self) -> Result<Box<dyn TxHandle>, RdapServerError> {
Ok(Box::new(MemTx::new_truncate(self)))
}
async fn get_domain_by_ldh(&self, ldh: &str) -> Result<RdapResponse, RdapServerError> {
let domains = self.domains.read().await;
let result = domains.get(ldh);
match result {
Some(domain) => Ok(RdapResponse::clone(domain)),
None => Ok(NOT_FOUND.clone()),
}
}
async fn get_domain_by_unicode(&self, unicode: &str) -> Result<RdapResponse, RdapServerError> {
let idns = self.idns.read().await;
let result = idns.get(unicode);
match result {
Some(domain) => Ok(RdapResponse::clone(domain)),
None => Ok(NOT_FOUND.clone()),
}
}
async fn get_entity_by_handle(&self, handle: &str) -> Result<RdapResponse, RdapServerError> {
let entities = self.entities.read().await;
let result = entities.get(handle);
match result {
Some(entity) => Ok(RdapResponse::clone(entity)),
None => Ok(NOT_FOUND.clone()),
}
}
async fn get_nameserver_by_ldh(&self, ldh: &str) -> Result<RdapResponse, RdapServerError> {
let nameservers = self.nameservers.read().await;
let result = nameservers.get(ldh);
match result {
Some(nameserver) => Ok(RdapResponse::clone(nameserver)),
None => Ok(NOT_FOUND.clone()),
}
}
async fn get_autnum_by_num(&self, num: u32) -> Result<RdapResponse, RdapServerError> {
let autnums = self.autnums.read().await;
let result = autnums.get(num);
match result {
Some(autnum) => Ok(RdapResponse::clone(autnum)),
None => Ok(NOT_FOUND.clone()),
}
}
async fn get_network_by_ipaddr(&self, ipaddr: &str) -> Result<RdapResponse, RdapServerError> {
let addr = ipaddr.parse::<IpAddr>()?;
match addr {
IpAddr::V4(v4) => {
let slash32 = Ipv4Net::new(v4, 32)?;
let ip4s = self.ip4.read().await;
let result = ip4s.get_lpm(&slash32);
match result {
Some(network) => Ok(RdapResponse::clone(network.1)),
None => Ok(NOT_FOUND.clone()),
}
}
IpAddr::V6(v6) => {
let slash128 = Ipv6Net::new(v6, 128)?;
let ip6s = self.ip6.read().await;
let result = ip6s.get_lpm(&slash128);
match result {
Some(network) => Ok(RdapResponse::clone(network.1)),
None => Ok(NOT_FOUND.clone()),
}
}
}
}
async fn get_network_by_cidr(&self, cidr: &str) -> Result<RdapResponse, RdapServerError> {
let net = IpNet::from_str(cidr)?;
match net {
IpNet::V4(ipv4net) => {
let ip4s = self.ip4.read().await;
let result = ip4s.get_lpm(&ipv4net);
match result {
Some(network) => Ok(RdapResponse::clone(network.1)),
None => Ok(NOT_FOUND.clone()),
}
}
IpNet::V6(ipv6net) => {
let ip6s = self.ip6.read().await;
let result = ip6s.get_lpm(&ipv6net);
match result {
Some(network) => Ok(RdapResponse::clone(network.1)),
None => Ok(NOT_FOUND.clone()),
}
}
}
}
async fn get_srv_help(&self, host: Option<&str>) -> Result<RdapResponse, RdapServerError> {
let host = host.unwrap_or("..default");
let srvhelps = self.srvhelps.read().await;
let result = srvhelps.get(host);
match result {
Some(srvhelp) => Ok(RdapResponse::clone(srvhelp)),
None => Ok(NOT_FOUND.clone()),
}
}
async fn search_domains_by_name(&self, name: &str) -> Result<RdapResponse, RdapServerError> {
if !self.config.common_config.domain_search_by_name_enable {
return Ok(NOT_IMPLEMENTED.clone());
}
//else
let domains_by_name = self.domains_by_name.read().await;
let results = domains_by_name
.search(name)
.unwrap_or_default()
.into_iter()
.map(Arc::<RdapResponse>::unwrap_or_clone)
.filter_map(|d| match d {
RdapResponse::Domain(d) => Some(*d),
_ => None,
})
.collect::<Vec<Domain>>();
let response = DomainSearchResults::builder()
.results(results)
.build()
.to_response();
Ok(response)
}
}

View file

@ -0,0 +1,330 @@
use std::{collections::HashMap, net::IpAddr, str::FromStr, sync::Arc};
use {
async_trait::async_trait,
btree_range_map::RangeMap,
icann_rdap_common::{
prelude::ToResponse,
response::{Autnum, Domain, Entity, Help, Nameserver, Network, RdapResponse, Rfc9083Error},
},
ipnet::{IpSubnets, Ipv4Net, Ipv4Subnets, Ipv6Net, Ipv6Subnets},
prefix_trie::PrefixMap,
};
use crate::{
error::RdapServerError,
storage::{
data::{AutnumId, DomainId, EntityId, NameserverId, NetworkId},
TxHandle,
},
};
use super::{label_search::SearchLabels, ops::Mem};
pub struct MemTx {
mem: Mem,
autnums: RangeMap<u32, Arc<RdapResponse>>,
ip4: PrefixMap<Ipv4Net, Arc<RdapResponse>>,
ip6: PrefixMap<Ipv6Net, Arc<RdapResponse>>,
domains: HashMap<String, Arc<RdapResponse>>,
domains_by_name: SearchLabels<Arc<RdapResponse>>,
idns: HashMap<String, Arc<RdapResponse>>,
nameservers: HashMap<String, Arc<RdapResponse>>,
entities: HashMap<String, Arc<RdapResponse>>,
srvhelps: HashMap<String, Arc<RdapResponse>>,
}
impl MemTx {
pub async fn new(mem: &Mem) -> Self {
let domains = Arc::clone(&mem.domains).read_owned().await.clone();
let mut domains_by_name = SearchLabels::builder().build();
// only do load up domain search labels if search by domain names is supported
if mem.config.common_config.domain_search_by_name_enable {
for (name, value) in domains.iter() {
domains_by_name.insert(name, value.clone());
}
}
Self {
mem: mem.clone(),
autnums: Arc::clone(&mem.autnums).read_owned().await.clone(),
ip4: Arc::clone(&mem.ip4).read_owned().await.clone(),
ip6: Arc::clone(&mem.ip6).read_owned().await.clone(),
domains,
domains_by_name,
idns: Arc::clone(&mem.idns).read_owned().await.clone(),
nameservers: Arc::clone(&mem.nameservers).read_owned().await.clone(),
entities: Arc::clone(&mem.entities).read_owned().await.clone(),
srvhelps: Arc::clone(&mem.srvhelps).read_owned().await.clone(),
}
}
pub fn new_truncate(mem: &Mem) -> Self {
Self {
mem: mem.clone(),
autnums: RangeMap::new(),
ip4: PrefixMap::new(),
ip6: PrefixMap::new(),
domains: HashMap::new(),
domains_by_name: SearchLabels::builder().build(),
idns: HashMap::new(),
nameservers: HashMap::new(),
entities: HashMap::new(),
srvhelps: HashMap::new(),
}
}
}
#[async_trait]
impl TxHandle for MemTx {
async fn add_entity(&mut self, entity: &Entity) -> Result<(), RdapServerError> {
let handle = entity
.object_common
.handle
.as_ref()
.ok_or_else(|| RdapServerError::EmptyIndexData("handle".to_string()))?;
self.entities
.insert(handle.to_owned(), Arc::new(entity.clone().to_response()));
Ok(())
}
async fn add_entity_err(
&mut self,
entity_id: &EntityId,
error: &Rfc9083Error,
) -> Result<(), RdapServerError> {
self.entities.insert(
entity_id.handle.to_owned(),
Arc::new(error.clone().to_response()),
);
Ok(())
}
async fn add_domain(&mut self, domain: &Domain) -> Result<(), RdapServerError> {
let domain_response = Arc::new(domain.clone().to_response());
// add the domain as LDH, which is required.
let ldh_name = domain
.ldh_name
.as_ref()
.ok_or_else(|| RdapServerError::EmptyIndexData("ldhName".to_string()))?;
self.domains
.insert(ldh_name.to_owned(), domain_response.clone());
// add the domain by unicodeName
if let Some(unicode_name) = domain.unicode_name.as_ref() {
self.idns
.insert(unicode_name.to_owned(), domain_response.clone());
};
if self.mem.config.common_config.domain_search_by_name_enable {
self.domains_by_name.insert(ldh_name, domain_response);
}
Ok(())
}
async fn add_domain_err(
&mut self,
domain_id: &DomainId,
error: &Rfc9083Error,
) -> Result<(), RdapServerError> {
self.domains.insert(
domain_id.ldh_name.to_owned(),
Arc::new(error.clone().to_response()),
);
Ok(())
}
async fn add_nameserver(&mut self, nameserver: &Nameserver) -> Result<(), RdapServerError> {
let ldh_name = nameserver
.ldh_name
.as_ref()
.ok_or_else(|| RdapServerError::EmptyIndexData("ldhName".to_string()))?;
self.nameservers.insert(
ldh_name.to_owned(),
Arc::new(nameserver.clone().to_response()),
);
Ok(())
}
async fn add_nameserver_err(
&mut self,
nameserver_id: &NameserverId,
error: &Rfc9083Error,
) -> Result<(), RdapServerError> {
self.nameservers.insert(
nameserver_id.ldh_name.to_owned(),
Arc::new(error.clone().to_response()),
);
Ok(())
}
async fn add_autnum(&mut self, autnum: &Autnum) -> Result<(), RdapServerError> {
let start_num = autnum
.start_autnum
.as_ref()
.and_then(|n| n.as_u32())
.ok_or_else(|| RdapServerError::EmptyIndexData("startNum".to_string()))?;
let end_num = autnum
.end_autnum
.as_ref()
.and_then(|n| n.as_u32())
.ok_or_else(|| RdapServerError::EmptyIndexData("endNum".to_string()))?;
self.autnums.insert(
(start_num)..=(end_num),
Arc::new(autnum.clone().to_response()),
);
Ok(())
}
async fn add_autnum_err(
&mut self,
autnum_id: &AutnumId,
error: &Rfc9083Error,
) -> Result<(), RdapServerError> {
self.autnums.insert(
(autnum_id.start_autnum)..=(autnum_id.end_autnum),
Arc::new(error.clone().to_response()),
);
Ok(())
}
async fn add_network(&mut self, network: &Network) -> Result<(), RdapServerError> {
let start_addr = network
.start_address
.as_ref()
.ok_or_else(|| RdapServerError::EmptyIndexData("startAddress".to_string()))?;
let end_addr = network
.end_address
.as_ref()
.ok_or_else(|| RdapServerError::EmptyIndexData("endAddress".to_string()))?;
let ip_type = network
.ip_version
.as_ref()
.ok_or_else(|| RdapServerError::EmptyIndexData("ipVersion".to_string()))?;
let is_v4 = ip_type.eq_ignore_ascii_case("v4");
if is_v4 {
let subnets = Ipv4Subnets::new(start_addr.parse()?, end_addr.parse()?, 0);
for net in subnets {
self.ip4
.insert(net, Arc::new(network.clone().to_response()));
}
} else {
let subnets = Ipv6Subnets::new(start_addr.parse()?, end_addr.parse()?, 0);
for net in subnets {
self.ip6
.insert(net, Arc::new(network.clone().to_response()));
}
};
Ok(())
}
async fn add_network_err(
&mut self,
network_id: &NetworkId,
error: &Rfc9083Error,
) -> Result<(), RdapServerError> {
let subnets = match &network_id.network_id {
crate::storage::data::NetworkIdType::Cidr(cidr) => cidr.subnets(cidr.prefix_len())?,
crate::storage::data::NetworkIdType::Range {
start_address,
end_address,
} => {
let start_addr = IpAddr::from_str(start_address)?;
let end_addr = IpAddr::from_str(end_address)?;
if start_addr.is_ipv4() && end_addr.is_ipv4() {
let IpAddr::V4(start_addr) = start_addr else {
panic!("check failed")
};
let IpAddr::V4(end_addr) = end_addr else {
panic!("check failed")
};
IpSubnets::from(Ipv4Subnets::new(start_addr, end_addr, 0))
} else if start_addr.is_ipv6() && end_addr.is_ipv6() {
let IpAddr::V6(start_addr) = start_addr else {
panic!("check failed")
};
let IpAddr::V6(end_addr) = end_addr else {
panic!("check failed")
};
IpSubnets::from(Ipv6Subnets::new(start_addr, end_addr, 0))
} else {
return Err(RdapServerError::EmptyIndexData(
"mismatch ip version".to_string(),
));
}
}
};
match subnets {
IpSubnets::V4(subnets) => {
for net in subnets {
self.ip4.insert(net, Arc::new(error.clone().to_response()));
}
}
IpSubnets::V6(subnets) => {
for net in subnets {
self.ip6.insert(net, Arc::new(error.clone().to_response()));
}
}
}
Ok(())
}
async fn add_srv_help(
&mut self,
help: &Help,
host: Option<&str>,
) -> Result<(), RdapServerError> {
let host = host.unwrap_or("..default");
self.srvhelps
.insert(host.to_string(), Arc::new(help.clone().to_response()));
Ok(())
}
async fn commit(mut self: Box<Self>) -> Result<(), RdapServerError> {
// autnums
let mut autnum_g = self.mem.autnums.write().await;
std::mem::swap(&mut self.autnums, &mut autnum_g);
// ip4
let mut ip4_g = self.mem.ip4.write().await;
std::mem::swap(&mut self.ip4, &mut ip4_g);
// ip6
let mut ip6_g = self.mem.ip6.write().await;
std::mem::swap(&mut self.ip6, &mut ip6_g);
// domains
let mut domains_g = self.mem.domains.write().await;
std::mem::swap(&mut self.domains, &mut domains_g);
//domains by name
let mut domains_by_name_g = self.mem.domains_by_name.write().await;
std::mem::swap(&mut self.domains_by_name, &mut domains_by_name_g);
//idns
let mut idns_g = self.mem.idns.write().await;
std::mem::swap(&mut self.idns, &mut idns_g);
// nameservers
let mut nameservers_g = self.mem.nameservers.write().await;
std::mem::swap(&mut self.nameservers, &mut nameservers_g);
// entities
let mut entities_g = self.mem.entities.write().await;
std::mem::swap(&mut self.entities, &mut entities_g);
//srvhelps
let mut srvhelps_g = self.mem.srvhelps.write().await;
std::mem::swap(&mut self.srvhelps, &mut srvhelps_g);
Ok(())
}
async fn rollback(self: Box<Self>) -> Result<(), RdapServerError> {
// Nothing to do.
Ok(())
}
}

View file

@ -0,0 +1,142 @@
use {
async_trait::async_trait,
buildstructor::Builder,
icann_rdap_common::response::{
Autnum, Domain, Entity, Help, Nameserver, Network, RdapResponse, Rfc9083Error,
},
};
use crate::error::RdapServerError;
use self::data::{AutnumId, DomainId, EntityId, NameserverId, NetworkId};
pub mod data;
pub mod mem;
pub mod pg;
pub type DynStoreOps = dyn StoreOps + Send + Sync;
/// This trait defines the operations for a storage engine.
#[async_trait]
pub trait StoreOps: Send + Sync {
/// Initializes the backend storage
async fn init(&self) -> Result<(), RdapServerError>;
/// Gets a new transaction.
async fn new_tx(&self) -> Result<Box<dyn TxHandle>, RdapServerError>;
/// Gets a new transaction in which all the previous data has been truncated (cleared).
async fn new_truncate_tx(&self) -> Result<Box<dyn TxHandle>, RdapServerError>;
/// Get a domain from storage using the 'ldhName' as the key.
async fn get_domain_by_ldh(&self, ldh: &str) -> Result<RdapResponse, RdapServerError>;
/// Get a domain from storage using the 'unicodeName' as the key.
async fn get_domain_by_unicode(&self, unicode: &str) -> Result<RdapResponse, RdapServerError>;
/// Get an entity from storage using the 'handle' of the entity as the key.
async fn get_entity_by_handle(&self, handle: &str) -> Result<RdapResponse, RdapServerError>;
/// Get a nameserver from storage using the 'ldhName' as the key.
async fn get_nameserver_by_ldh(&self, ldh: &str) -> Result<RdapResponse, RdapServerError>;
/// Get an autnum from storage using an autonomous system numbers as the key.
async fn get_autnum_by_num(&self, num: u32) -> Result<RdapResponse, RdapServerError>;
/// Get a network from storage using an IP address. The network returned should be the
/// most specific (longest prefix) network containing the IP address.
async fn get_network_by_ipaddr(&self, ipaddr: &str) -> Result<RdapResponse, RdapServerError>;
/// Get a network from storage using a CIDR notation network (e.g. "10.0.0.0/8"). The IP address
/// portion of the CIDR should be assumed to be complete, that is not "10.0/8". The network
/// returned should be the most specific (longest prefix) network containing the IP address.
async fn get_network_by_cidr(&self, cidr: &str) -> Result<RdapResponse, RdapServerError>;
/// Get server help.
async fn get_srv_help(&self, host: Option<&str>) -> Result<RdapResponse, RdapServerError>;
/// Search for domains by name.
async fn search_domains_by_name(&self, name: &str) -> Result<RdapResponse, RdapServerError>;
}
/// Represents a handle to a transaction.
/// The implementation of the transaction
/// are dependent on the storage type.
#[async_trait]
pub trait TxHandle: Send {
/// Add a domain name to storage.
async fn add_domain(&mut self, domain: &Domain) -> Result<(), RdapServerError>;
/// Add an error as a domain to storage. This is useful for specifying redirects.
async fn add_domain_err(
&mut self,
domain_id: &DomainId,
error: &Rfc9083Error,
) -> Result<(), RdapServerError>;
/// Add an entitty to storage.
async fn add_entity(&mut self, entity: &Entity) -> Result<(), RdapServerError>;
/// Add an error as an entity to storage. This is useful for specifying redirects.
async fn add_entity_err(
&mut self,
entity_id: &EntityId,
error: &Rfc9083Error,
) -> Result<(), RdapServerError>;
/// Add a nameserver to storage.
async fn add_nameserver(&mut self, nameserver: &Nameserver) -> Result<(), RdapServerError>;
/// Add an error as a nameserver to storage. This is useful for specifying redirects.
async fn add_nameserver_err(
&mut self,
nameserver_id: &NameserverId,
error: &Rfc9083Error,
) -> Result<(), RdapServerError>;
/// Add a nameserver to storage.
async fn add_autnum(&mut self, autnum: &Autnum) -> Result<(), RdapServerError>;
/// Add an error as an autnum to storage. This is useful for specifying redirects.
async fn add_autnum_err(
&mut self,
autnum_id: &AutnumId,
error: &Rfc9083Error,
) -> Result<(), RdapServerError>;
/// Add a network to storage.
async fn add_network(&mut self, network: &Network) -> Result<(), RdapServerError>;
/// Add a network as an autnum to storage. This is useful for specifying redirects.
async fn add_network_err(
&mut self,
network_id: &NetworkId,
error: &Rfc9083Error,
) -> Result<(), RdapServerError>;
async fn add_srv_help(
&mut self,
help: &Help,
host: Option<&str>,
) -> Result<(), RdapServerError>;
/// Commit the transaction.
async fn commit(self: Box<Self>) -> Result<(), RdapServerError>;
/// Rollback the transaction.
async fn rollback(self: Box<Self>) -> Result<(), RdapServerError>;
}
/// Common configuration for storage back ends.
#[derive(Debug, Clone, Copy, Builder)]
pub struct CommonConfig {
pub domain_search_by_name_enable: bool,
}
impl Default for CommonConfig {
fn default() -> Self {
CommonConfig {
domain_search_by_name_enable: true,
}
}
}

View file

@ -0,0 +1,9 @@
use buildstructor::Builder;
use crate::storage::CommonConfig;
#[derive(Debug, Builder, Clone)]
pub struct PgConfig {
pub db_url: String,
pub common_config: CommonConfig,
}

View file

@ -0,0 +1,5 @@
#![allow(dead_code)] // TODO remove
pub mod config;
pub mod ops;
pub mod tx;

View file

@ -0,0 +1,78 @@
#![allow(clippy::diverging_sub_expression)]
use {
async_trait::async_trait,
icann_rdap_common::response::RdapResponse,
sqlx::{query, PgPool},
tracing::{debug, info},
};
use crate::{
error::RdapServerError,
storage::{StoreOps, TxHandle},
};
use super::{config::PgConfig, tx::PgTx};
#[derive(Clone)]
pub struct Pg {
pg_pool: PgPool,
}
impl Pg {
pub async fn new(config: PgConfig) -> Result<Self, RdapServerError> {
let pg_pool = PgPool::connect(&config.db_url).await?;
Ok(Self { pg_pool })
}
}
#[async_trait]
impl StoreOps for Pg {
async fn init(&self) -> Result<(), RdapServerError> {
debug!("Testing database connection.");
let mut conn = self.pg_pool.acquire().await?;
query("select 1").fetch_one(&mut *conn).await?;
info!("Database connection test is successful.");
Ok(())
}
async fn new_tx(&self) -> Result<Box<dyn TxHandle>, RdapServerError> {
Ok(Box::new(PgTx::new(&self.pg_pool).await?))
}
async fn new_truncate_tx(&self) -> Result<Box<dyn TxHandle>, RdapServerError> {
Ok(Box::new(PgTx::new_truncate(&self.pg_pool).await?))
}
async fn get_domain_by_ldh(&self, _ldh: &str) -> Result<RdapResponse, RdapServerError> {
todo!()
}
async fn get_domain_by_unicode(&self, _unicode: &str) -> Result<RdapResponse, RdapServerError> {
todo!()
}
async fn get_entity_by_handle(&self, _handle: &str) -> Result<RdapResponse, RdapServerError> {
todo!()
}
async fn get_nameserver_by_ldh(&self, _ldh: &str) -> Result<RdapResponse, RdapServerError> {
todo!()
}
async fn get_autnum_by_num(&self, _num: u32) -> Result<RdapResponse, RdapServerError> {
todo!()
}
async fn get_network_by_ipaddr(&self, _ipaddr: &str) -> Result<RdapResponse, RdapServerError> {
todo!()
}
async fn get_network_by_cidr(&self, _cidr: &str) -> Result<RdapResponse, RdapServerError> {
todo!()
}
async fn get_srv_help(&self, _host: Option<&str>) -> Result<RdapResponse, RdapServerError> {
todo!()
}
async fn search_domains_by_name(&self, _name: &str) -> Result<RdapResponse, RdapServerError> {
todo!()
}
}

View file

@ -0,0 +1,119 @@
#![allow(clippy::diverging_sub_expression)]
use {
async_trait::async_trait,
icann_rdap_common::response::{Autnum, Domain, Entity, Nameserver, Network, Rfc9083Error},
sqlx::{PgPool, Postgres},
};
use crate::{
error::RdapServerError,
storage::{
data::{AutnumId, DomainId, EntityId, NameserverId, NetworkId},
TxHandle,
},
};
pub struct PgTx<'a> {
db_tx: sqlx::Transaction<'a, Postgres>,
}
impl<'a> PgTx<'a> {
pub async fn new(pg_pool: &PgPool) -> Result<Self, RdapServerError> {
let db_tx = pg_pool.begin().await?;
Ok(Self { db_tx })
}
pub async fn new_truncate(pg_pool: &PgPool) -> Result<Self, RdapServerError> {
let mut db_tx = pg_pool.begin().await?;
// TODO actually complete this
// this is just here to make sure something will compile
sqlx::query("truncate domain").execute(&mut *db_tx).await?;
Ok(Self { db_tx })
}
}
#[async_trait]
impl TxHandle for PgTx<'_> {
async fn add_entity(&mut self, _entity: &Entity) -> Result<(), RdapServerError> {
todo!()
}
async fn add_entity_err(
&mut self,
_entity_id: &EntityId,
_error: &Rfc9083Error,
) -> Result<(), RdapServerError> {
todo!()
}
async fn add_domain(&mut self, _domain: &Domain) -> Result<(), RdapServerError> {
// TODO actually complete this
// this is just here to make sure something will compile
sqlx::query("insert domain")
.execute(&mut *self.db_tx)
.await?;
Ok(())
}
async fn add_domain_err(
&mut self,
_domain_id: &DomainId,
_error: &Rfc9083Error,
) -> Result<(), RdapServerError> {
todo!()
}
async fn add_nameserver(&mut self, _nameserver: &Nameserver) -> Result<(), RdapServerError> {
todo!()
}
async fn add_nameserver_err(
&mut self,
_nameserver_id: &NameserverId,
_error: &Rfc9083Error,
) -> Result<(), RdapServerError> {
todo!()
}
async fn add_autnum(&mut self, _autnum: &Autnum) -> Result<(), RdapServerError> {
todo!()
}
async fn add_autnum_err(
&mut self,
_autnum_id: &AutnumId,
_error: &Rfc9083Error,
) -> Result<(), RdapServerError> {
todo!()
}
async fn add_network(&mut self, _network: &Network) -> Result<(), RdapServerError> {
todo!()
}
async fn add_network_err(
&mut self,
_network_id: &NetworkId,
_error: &Rfc9083Error,
) -> Result<(), RdapServerError> {
todo!()
}
async fn add_srv_help(
&mut self,
_help: &icann_rdap_common::response::Help,
_host: Option<&str>,
) -> Result<(), RdapServerError> {
todo!()
}
async fn commit(self: Box<Self>) -> Result<(), RdapServerError> {
self.db_tx.commit().await?;
Ok(())
}
async fn rollback(self: Box<Self>) -> Result<(), RdapServerError> {
self.db_tx.rollback().await?;
Ok(())
}
}