#![allow(clippy::use_self)]
use std::collections::HashMap;
#[cfg(feature = "serde-config")]
use serde::{Deserialize, Serialize};
use tracing::warn;
use crate::error::*;
use crate::serialize::binary::*;
#[cfg(feature = "dnssec")]
use crate::rr::dnssec::SupportedAlgorithms;
#[cfg_attr(feature = "serde-config", derive(Deserialize, Serialize))]
#[derive(Default, Debug, PartialEq, Eq, Clone)]
pub struct OPT {
options: HashMap<EdnsCode, EdnsOption>,
}
impl OPT {
pub fn new(options: HashMap<EdnsCode, EdnsOption>) -> Self {
Self { options }
}
#[deprecated(note = "Please use as_ref() or as_mut() for shared/mutable references")]
pub fn options(&self) -> &HashMap<EdnsCode, EdnsOption> {
&self.options
}
pub fn get(&self, code: EdnsCode) -> Option<&EdnsOption> {
self.options.get(&code)
}
pub fn insert(&mut self, option: EdnsOption) {
self.options.insert((&option).into(), option);
}
pub fn remove(&mut self, option: EdnsCode) {
self.options.remove(&option);
}
}
impl AsMut<HashMap<EdnsCode, EdnsOption>> for OPT {
fn as_mut(&mut self) -> &mut HashMap<EdnsCode, EdnsOption> {
&mut self.options
}
}
impl AsRef<HashMap<EdnsCode, EdnsOption>> for OPT {
fn as_ref(&self) -> &HashMap<EdnsCode, EdnsOption> {
&self.options
}
}
pub fn read(decoder: &mut BinDecoder<'_>, rdata_length: Restrict<u16>) -> ProtoResult<OPT> {
let mut state: OptReadState = OptReadState::ReadCode;
let mut options: HashMap<EdnsCode, EdnsOption> = HashMap::new();
let start_idx = decoder.index();
let rdata_length =
rdata_length.map(|u| u as usize).unverified();
while rdata_length > decoder.index() - start_idx {
match state {
OptReadState::ReadCode => {
state = OptReadState::Code {
code: EdnsCode::from(
decoder.read_u16()?.unverified(),
),
};
}
OptReadState::Code { code } => {
let length = decoder
.read_u16()?
.map(|u| u as usize)
.verify_unwrap(|u| *u <= rdata_length)
.map_err(|_| ProtoError::from("OPT value length exceeds rdata length"))?;
state = if length == 0 {
options.insert(code, (code, &[] as &[u8]).into());
OptReadState::ReadCode
} else {
OptReadState::Data {
code,
length,
collected: Vec::<u8>::with_capacity(length),
}
};
}
OptReadState::Data {
code,
length,
mut collected,
} => {
collected.push(decoder.pop()?.unverified());
if length == collected.len() {
options.insert(code, (code, &collected as &[u8]).into());
state = OptReadState::ReadCode;
} else {
state = OptReadState::Data {
code,
length,
collected,
};
}
}
}
}
if state != OptReadState::ReadCode {
warn!("incomplete or poorly formatted EDNS options: {:?}", state);
options.clear();
}
Ok(OPT::new(options))
}
pub fn emit(encoder: &mut BinEncoder<'_>, opt: &OPT) -> ProtoResult<()> {
for (edns_code, edns_option) in opt.as_ref().iter() {
encoder.emit_u16(u16::from(*edns_code))?;
encoder.emit_u16(edns_option.len())?;
edns_option.emit(encoder)?
}
Ok(())
}
#[derive(Debug, PartialEq, Eq)]
enum OptReadState {
ReadCode,
Code {
code: EdnsCode,
}, Data {
code: EdnsCode,
length: usize,
collected: Vec<u8>,
}, }
#[cfg_attr(feature = "serde-config", derive(Deserialize, Serialize))]
#[derive(Hash, Debug, Copy, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum EdnsCode {
Zero,
LLQ,
UL,
NSID,
DAU,
DHU,
N3U,
Subnet,
Expire,
Cookie,
Keepalive,
Padding,
Chain,
Unknown(u16),
}
impl From<u16> for EdnsCode {
fn from(value: u16) -> Self {
match value {
0 => Self::Zero,
1 => Self::LLQ,
2 => Self::UL,
3 => Self::NSID,
5 => Self::DAU,
6 => Self::DHU,
7 => Self::N3U,
8 => Self::Subnet,
9 => Self::Expire,
10 => Self::Cookie,
11 => Self::Keepalive,
12 => Self::Padding,
13 => Self::Chain,
_ => Self::Unknown(value),
}
}
}
impl From<EdnsCode> for u16 {
fn from(value: EdnsCode) -> Self {
match value {
EdnsCode::Zero => 0,
EdnsCode::LLQ => 1,
EdnsCode::UL => 2,
EdnsCode::NSID => 3,
EdnsCode::DAU => 5,
EdnsCode::DHU => 6,
EdnsCode::N3U => 7,
EdnsCode::Subnet => 8,
EdnsCode::Expire => 9,
EdnsCode::Cookie => 10,
EdnsCode::Keepalive => 11,
EdnsCode::Padding => 12,
EdnsCode::Chain => 13,
EdnsCode::Unknown(value) => value,
}
}
}
#[cfg_attr(feature = "serde-config", derive(Deserialize, Serialize))]
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Hash)]
#[non_exhaustive]
pub enum EdnsOption {
#[cfg(feature = "dnssec")]
#[cfg_attr(docsrs, doc(cfg(feature = "dnssec")))]
DAU(SupportedAlgorithms),
#[cfg(feature = "dnssec")]
#[cfg_attr(docsrs, doc(cfg(feature = "dnssec")))]
DHU(SupportedAlgorithms),
#[cfg(feature = "dnssec")]
#[cfg_attr(docsrs, doc(cfg(feature = "dnssec")))]
N3U(SupportedAlgorithms),
Unknown(u16, Vec<u8>),
}
impl EdnsOption {
pub fn len(&self) -> u16 {
match *self {
#[cfg(feature = "dnssec")]
EdnsOption::DAU(ref algorithms)
| EdnsOption::DHU(ref algorithms)
| EdnsOption::N3U(ref algorithms) => algorithms.len(),
EdnsOption::Unknown(_, ref data) => data.len() as u16, }
}
pub fn is_empty(&self) -> bool {
match *self {
#[cfg(feature = "dnssec")]
EdnsOption::DAU(ref algorithms)
| EdnsOption::DHU(ref algorithms)
| EdnsOption::N3U(ref algorithms) => algorithms.is_empty(),
EdnsOption::Unknown(_, ref data) => data.is_empty(),
}
}
}
impl BinEncodable for EdnsOption {
fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> {
match *self {
#[cfg(feature = "dnssec")]
EdnsOption::DAU(ref algorithms)
| EdnsOption::DHU(ref algorithms)
| EdnsOption::N3U(ref algorithms) => algorithms.emit(encoder),
EdnsOption::Unknown(_, ref data) => encoder.emit_vec(data), }
}
}
impl<'a> From<(EdnsCode, &'a [u8])> for EdnsOption {
#[allow(clippy::match_single_binding)]
fn from(value: (EdnsCode, &'a [u8])) -> Self {
match value.0 {
#[cfg(feature = "dnssec")]
EdnsCode::DAU => Self::DAU(value.1.into()),
#[cfg(feature = "dnssec")]
EdnsCode::DHU => Self::DHU(value.1.into()),
#[cfg(feature = "dnssec")]
EdnsCode::N3U => Self::N3U(value.1.into()),
_ => Self::Unknown(value.0.into(), value.1.to_vec()),
}
}
}
impl<'a> From<&'a EdnsOption> for Vec<u8> {
fn from(value: &'a EdnsOption) -> Self {
match *value {
#[cfg(feature = "dnssec")]
EdnsOption::DAU(ref algorithms)
| EdnsOption::DHU(ref algorithms)
| EdnsOption::N3U(ref algorithms) => algorithms.into(),
EdnsOption::Unknown(_, ref data) => data.clone(), }
}
}
impl<'a> From<&'a EdnsOption> for EdnsCode {
fn from(value: &'a EdnsOption) -> Self {
match *value {
#[cfg(feature = "dnssec")]
EdnsOption::DAU(..) => Self::DAU,
#[cfg(feature = "dnssec")]
EdnsOption::DHU(..) => Self::DHU,
#[cfg(feature = "dnssec")]
EdnsOption::N3U(..) => Self::N3U,
EdnsOption::Unknown(code, _) => code.into(),
}
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::dbg_macro, clippy::print_stdout)]
use super::*;
#[test]
#[cfg(feature = "dnssec")]
fn test() {
let mut rdata = OPT::default();
rdata.insert(EdnsOption::DAU(SupportedAlgorithms::all()));
let mut bytes = Vec::new();
let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes);
assert!(emit(&mut encoder, &rdata).is_ok());
let bytes = encoder.into_bytes();
println!("bytes: {:?}", bytes);
let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes);
let restrict = Restrict::new(bytes.len() as u16);
let read_rdata = read(&mut decoder, restrict).expect("Decoding error");
assert_eq!(rdata, read_rdata);
}
#[test]
fn test_read_empty_option_at_end_of_opt() {
let bytes: Vec<u8> = vec![
0x00, 0x0a, 0x00, 0x08, 0x0b, 0x64, 0xb4, 0xdc, 0xd7, 0xb0, 0xcc, 0x8f, 0x00, 0x08,
0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00,
];
let mut decoder: BinDecoder<'_> = BinDecoder::new(&bytes);
let read_rdata = read(&mut decoder, Restrict::new(bytes.len() as u16));
assert!(
read_rdata.is_ok(),
"error decoding: {:?}",
read_rdata.unwrap_err()
);
let opt = read_rdata.unwrap();
let mut options = HashMap::default();
options.insert(EdnsCode::Subnet, EdnsOption::Unknown(8, vec![0, 1, 0, 0]));
options.insert(
EdnsCode::Cookie,
EdnsOption::Unknown(10, vec![0x0b, 0x64, 0xb4, 0xdc, 0xd7, 0xb0, 0xcc, 0x8f]),
);
options.insert(EdnsCode::Keepalive, EdnsOption::Unknown(11, vec![]));
let options = OPT::new(options);
assert_eq!(opt, options);
}
}