Skip to content

Commit

Permalink
Add Domain struct, replace bytes_are_domain
Browse files Browse the repository at this point in the history
Since it's expected to do many operation on individual labels, a
`Domain` representation is needed. And, since there's a struct now, the
standalone `bytes_are_domain` function is replaced with a `try_from`
trait implementation.
  • Loading branch information
matei-radu committed Jul 5, 2024
1 parent 9eb3d66 commit 665bc6d
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 18 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion lib/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "dns_lib"
version = "0.2.1"
version = "0.3.0"
description = "An implementation of the DNS protocol from scratch based on the many DNS RFCs."

rust-version.workspace = true
Expand Down
89 changes: 73 additions & 16 deletions lib/src/domain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,70 @@
// limitations under the License.

const MAX_LABEL_LENGTH: usize = 63;
const LABEL_SEPARATOR: u8 = b'.';
const LABEL_SEPARATOR: char = '.';

/// Checks if the byte array is a valid DNS `domain`, that is, a string
/// consisting of one of more `label`s separated by dots (".").
#[derive(Debug)]
pub struct InvalidDomainError;

/// Representation of a DNS domain name.
///
/// See [RFC 1034, Section 3.5 - Preferred name syntax](https://datatracker.ietf.org/doc/html/rfc1034#section-3.5)
pub fn bytes_are_domain(bytes: &[u8]) -> bool {
let labels: Vec<&[u8]> = bytes.split(|&byte| byte == LABEL_SEPARATOR).collect();
labels.iter().all(|&label| bytes_are_label(label))
/// A domain name consists of one or more labels. Each label starts with a
/// letter, ends with a letter or digit, and can contain letters, digits,
/// and hyphens in between.
///
/// When represented as a string, each label is separated by dots (`.`):
///
/// > `www.example.com`
///
/// For more details, see [RFC 1034, Section 3.5].
///
/// [RFC 1034, Section 3.5]: https://datatracker.ietf.org/doc/html/rfc1034#section-3.5
#[derive(Debug, PartialEq)]
pub struct Domain {
labels: Vec<String>,
}

impl TryFrom<String> for Domain {
type Error = InvalidDomainError;

/// Tries to convert a [`String`] into a `Domain`.
///
/// A valid DNS domain name string consists of one or more labels separated
/// by dots (`.`). Each label starts with a letter, ends with a letter or
/// digit, and can contain letters, digits, and hyphens in between.
///
/// For more details, see [RFC 1034, Section 3.5].
///
/// # Example
/// ```
/// use dns_lib::domain::Domain;
///
/// let valid_domain = "example.com".to_string();
/// assert!(Domain::try_from(valid_domain).is_ok());
///
/// let invalid_domain = "foo-..bar".to_string();
/// assert!(Domain::try_from(invalid_domain).is_err());
/// ```
///
/// [RFC 1034, Section 3.5]: https://datatracker.ietf.org/doc/html/rfc1034#section-3.5
fn try_from(value: String) -> Result<Self, Self::Error> {
let labels: Vec<String> = value
.split(|character| character == LABEL_SEPARATOR)
.map(String::from)
.collect();

if labels.is_empty() {
return Err(InvalidDomainError);
}

for label in &labels {
if !bytes_are_label(label.as_bytes()) {
return Err(InvalidDomainError);
}
}

Ok(Domain { labels })
}
}

/// Checks if the byte array is a valid DNS `label`, that is, a string that
Expand Down Expand Up @@ -104,14 +159,16 @@ mod tests {
}

#[rstest]
#[case(b"a", true)]
#[case(b"example", true)]
#[case(b"example.com", true)]
#[case(b"mercedes-benz.de", true)]
#[case(b"live-365", true)]
#[case(b"live-365.com", true)]
#[case(b"d111111abcdef8.cloudfront.net", true)]
fn bytes_are_domain_works_correctly(#[case] input: &[u8], #[case] expected: bool) {
assert_eq!(bytes_are_domain(input), expected);
#[case("a", Domain{ labels: vec!["a".to_string()]})]
#[case("example", Domain{ labels: vec!["example".to_string()]})]
#[case("example.com", Domain{ labels: vec!["example".to_string(), "com".to_string()]})]
#[case("mercedes-benz.de", Domain{ labels: vec!["mercedes-benz".to_string(), "de".to_string()]})]
#[case("live-365", Domain{ labels: vec!["live-365".to_string()]})]
#[case("live-365.com", Domain{ labels: vec!["live-365".to_string(), "com".to_string()]})]
#[case("d111111abcdef8.cloudfront.net", Domain{ labels: vec!["d111111abcdef8".to_string(), "cloudfront".to_string(), "net".to_string()]})]
fn domain_try_from_succeeds(#[case] input: String, #[case] ok: Domain) {
let result = Domain::try_from(input);
assert!(result.is_ok());
assert_eq!(result.unwrap(), ok);
}
}

0 comments on commit 665bc6d

Please sign in to comment.