parsing questions back and forth

This commit is contained in:
Andy Pack 2024-01-29 22:11:12 +00:00
parent 985adbae68
commit 7cf99b610a
Signed by: sarsoo
GPG Key ID: A55BA3536A5E0ED7
3 changed files with 243 additions and 70 deletions

68
Cargo.lock generated
View File

@ -130,16 +130,7 @@ name = "dnstplib"
version = "0.1.0"
dependencies = [
"log",
"url",
]
[[package]]
name = "form_urlencoded"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
dependencies = [
"percent-encoding",
"urlencoding",
]
[[package]]
@ -148,16 +139,6 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "idna"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
dependencies = [
"unicode-bidi",
"unicode-normalization",
]
[[package]]
name = "itoa"
version = "1.0.10"
@ -185,12 +166,6 @@ dependencies = [
"libc",
]
[[package]]
name = "percent-encoding"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "powerfmt"
version = "0.2.0"
@ -303,27 +278,6 @@ dependencies = [
"time-core",
]
[[package]]
name = "tinyvec"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "unicode-bidi"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
[[package]]
name = "unicode-ident"
version = "1.0.12"
@ -331,24 +285,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "unicode-normalization"
version = "0.1.22"
name = "urlencoding"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
dependencies = [
"tinyvec",
]
[[package]]
name = "url"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
]
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
[[package]]
name = "utf8parse"

View File

@ -7,4 +7,4 @@ edition = "2021"
[dependencies]
log = "0.4.20"
url = "2.5.0"
urlencoding = "2.1.3"

View File

@ -1,4 +1,5 @@
use url::form_urlencoded;
use std::ops::Sub;
use urlencoding::{encode, decode};
#[derive(Ord, PartialOrd, Eq, PartialEq, Debug, Copy, Clone)]
enum QType {
@ -17,6 +18,29 @@ enum QType {
SRV = 33
}
impl TryFrom<u8> for QType {
type Error = ();
fn try_from(v: u8) -> Result<Self, Self::Error> {
match v {
x if x == QType::A as u8 => Ok(QType::A),
x if x == QType::NS as u8 => Ok(QType::NS),
x if x == QType::CNAME as u8 => Ok(QType::CNAME),
x if x == QType::SOA as u8 => Ok(QType::SOA),
x if x == QType::WKS as u8 => Ok(QType::WKS),
x if x == QType::PTR as u8 => Ok(QType::PTR),
x if x == QType::HINFO as u8 => Ok(QType::HINFO),
x if x == QType::MINFO as u8 => Ok(QType::MINFO),
x if x == QType::MX as u8 => Ok(QType::MX),
x if x == QType::TXT as u8 => Ok(QType::TXT),
x if x == QType::RP as u8 => Ok(QType::RP),
x if x == QType::AAAA as u8 => Ok(QType::AAAA),
x if x == QType::SRV as u8 => Ok(QType::SRV),
_ => Err(()),
}
}
}
#[derive(Ord, PartialOrd, Eq, PartialEq, Debug, Copy, Clone)]
enum QClass {
Internet = 1,
@ -24,6 +48,20 @@ enum QClass {
Hesiod = 4,
}
impl TryFrom<u8> for QClass {
type Error = ();
fn try_from(v: u8) -> Result<Self, Self::Error> {
match v {
x if x == QClass::Internet as u8 => Ok(QClass::Internet),
x if x == QClass::Chaos as u8 => Ok(QClass::Chaos),
x if x == QClass::Hesiod as u8 => Ok(QClass::Hesiod),
_ => Err(()),
}
}
}
#[derive(Ord, PartialOrd, Eq, PartialEq, Debug)]
struct DNSQuestion {
qname: String,
qtype: QType,
@ -42,16 +80,15 @@ impl DNSQuestion {
pub fn to_bytes(&self) -> Vec<u8>
{
let mut ret: Vec<u8> = vec!();
let mut ret: Vec<u8> = Vec::with_capacity(self.qname.len() + 2 + 3);
for part in self.qname.split(".")
{
let encoded_string: String = form_urlencoded::byte_serialize(part.as_bytes()).collect();
let encoded_string = encode(part);
let count = encoded_string.len();
ret.push(count as u8);
ret.reserve(count);
for x in encoded_string.into_bytes() {
for x in encoded_string.bytes() {
ret.push(x);
};
}
@ -64,3 +101,199 @@ impl DNSQuestion {
ret
}
}
pub fn questions_from_bytes(bytes: Vec<u8>, total_questions: u8) -> Result<Vec<DNSQuestion>, ()>
{
if (bytes.len() < 4)
{
return Err(());
}
let mut questions: Vec<DNSQuestion> = Vec::with_capacity(total_questions as usize);
let mut current_query: Option<Vec<u8>> = None;
let mut current_length: Option<u8> = None;
let mut remaining_length: Box<u8> = Box::from(0);
let mut current_qtype: Option<u8> = None;
let mut current_qclass: Option<u8> = None;
let mut trailers_reached = false;
for byte in bytes {
match current_length {
None => { // next question, init lengths
current_length = Some(byte);
remaining_length = Box::from(byte);
current_query = Some(Vec::with_capacity(10));
}
Some(_) => {
if byte == 0 {
trailers_reached = true;
continue
}
if *remaining_length == 0 && !trailers_reached {
current_query.as_mut().unwrap().push('.' as u8);
current_length = Some(byte);
remaining_length = Box::from(byte);
}
else if trailers_reached { // trailer fields
match current_qtype {
None => {
current_qtype = Some(byte);
}
Some(qtype_b) => {
match current_qclass {
None => {
current_qclass = Some(byte);
}
Some(qclass_b) => {
match (qtype_b.try_into(), qclass_b.try_into()) {
(Ok(qtype), Ok(qclass)) => {
questions.push(DNSQuestion {
qname: String::from_utf8(current_query.unwrap()).unwrap(),
qtype,
qclass
});
current_length = Some(byte);
remaining_length = Box::from(byte);
current_query = Some(Vec::with_capacity(10));
current_qtype = None;
current_qclass = None;
trailers_reached = false;
}
_ => {
return Err(());
}
}
}
}
}
}
}
else
{
current_query.as_mut().unwrap().push(byte);
*remaining_length = remaining_length.sub(1);
}
}
}
}
match (current_qtype, current_qclass) {
(Some(qtype), Some(qclass)) => {
match (qtype.try_into(), qclass.try_into()) {
(Ok(qtype), Ok(qclass)) => {
questions.push(DNSQuestion {
qname: String::from_utf8(current_query.unwrap()).unwrap(),
qtype,
qclass
});
}
_ => {
return Err(());
}
}
}
_ => {
return Err(());
}
}
Ok(questions)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn one_question_back_and_forth() {
let q = DNSQuestion {
qname: "google.com".to_string(),
qclass: QClass::Internet,
qtype: QType::A
};
let q_bytes = q.to_bytes();
let q_reconstructed = questions_from_bytes(q_bytes, 1).unwrap();
assert_eq!(q.qname, q_reconstructed[0].qname);
assert_eq!(q.qclass, q_reconstructed[0].qclass);
assert_eq!(q.qtype, q_reconstructed[0].qtype);
}
#[test]
fn two_questions_back_and_forth() {
let q = DNSQuestion {
qname: "google.com".to_string(),
qclass: QClass::Internet,
qtype: QType::A
};
let q2 = DNSQuestion {
qname: "duck.com".to_string(),
qclass: QClass::Internet,
qtype: QType::AAAA
};
let mut q_bytes = q.to_bytes();
let mut q2_bytes = q2.to_bytes();
q_bytes.append(&mut q2_bytes);
let q_reconstructed = questions_from_bytes(q_bytes, 2).unwrap();
assert_eq!(q.qname, q_reconstructed[0].qname);
assert_eq!(q.qclass, q_reconstructed[0].qclass);
assert_eq!(q.qtype, q_reconstructed[0].qtype);
assert_eq!(q2.qname, q_reconstructed[1].qname);
assert_eq!(q2.qclass, q_reconstructed[1].qclass);
assert_eq!(q2.qtype, q_reconstructed[1].qtype);
}
#[test]
fn three_questions_back_and_forth() {
let q = DNSQuestion {
qname: "google.com".to_string(),
qclass: QClass::Internet,
qtype: QType::A
};
let q2 = DNSQuestion {
qname: "duck.com".to_string(),
qclass: QClass::Internet,
qtype: QType::AAAA
};
let q3 = DNSQuestion {
qname: "facebook.com".to_string(),
qclass: QClass::Hesiod,
qtype: QType::CNAME
};
let mut q_bytes = q.to_bytes();
let mut q2_bytes = q2.to_bytes();
let mut q3_bytes = q3.to_bytes();
q_bytes.append(&mut q2_bytes);
q_bytes.append(&mut q3_bytes);
let q_reconstructed = questions_from_bytes(q_bytes, 2).unwrap();
assert_eq!(q.qname, q_reconstructed[0].qname);
assert_eq!(q.qclass, q_reconstructed[0].qclass);
assert_eq!(q.qtype, q_reconstructed[0].qtype);
assert_eq!(q2.qname, q_reconstructed[1].qname);
assert_eq!(q2.qclass, q_reconstructed[1].qclass);
assert_eq!(q2.qtype, q_reconstructed[1].qtype);
assert_eq!(q3.qname, q_reconstructed[2].qname);
assert_eq!(q3.qclass, q_reconstructed[2].qclass);
assert_eq!(q3.qtype, q_reconstructed[2].qtype);
}
}