diff --git a/src/bits/compose.rs b/src/bits/compose.rs index e80a620e..d453a8c5 100644 --- a/src/bits/compose.rs +++ b/src/bits/compose.rs @@ -16,7 +16,7 @@ use std::ops; use std::collections::HashMap; -use bytes::{BigEndian, BufMut, BytesMut}; +use bytes::{BigEndian, BufMut, Bytes, BytesMut}; use super::error::ShortBuf; use super::name::{Dname, Label, ToDname}; @@ -108,6 +108,16 @@ impl Composable for u32 { } } +impl Composable for [u8] { + fn compose_len(&self) -> usize { + self.len() + } + + fn compose(&self, buf: &mut B) { + buf.put_slice(self) + } +} + //------------ Compressable -------------------------------------------------- @@ -123,19 +133,104 @@ pub trait Compressable { /// This type is used for name compression through the /// `Composable::compose_compressed` method. /// -/// Note: The implementation is rather naive right now and could do with a -/// smarter approach. +/// Note: Name compression is currently implemented in a rather naive way +/// and could do with a smarter approach. #[derive(Clone, Debug, )] pub struct Compressor { + /// The bytes buffer we work on. buf: BytesMut, + + /// Index of where in `buf` the message starts. start: usize, - map: HashMap, + + /// The maximum size of `buf` in bytes. + limit: usize, + + /// The number of bytes to grow each time we run out of space. + /// + /// If this is 0, we grow exactly once to the size given by `limit`. + page_size: usize, + + /// The optional compression map. + /// + /// This keeps the position relative to the start of the message for each + /// name we’ve ever written. + map: Option>, } impl Compressor { - /// Creates a new empty compressor. - pub fn new(buf: BytesMut) -> Self { - Compressor { start: buf.remaining_mut(), buf, map: HashMap::new() } + /// Creates a compressor from the given bytes buffer. + /// + /// The compressor will have a default limit equal to the buffer’s current + /// capacity and a page size of 0. + pub fn from_buf(buf: BytesMut) -> Self { + Compressor { + start: buf.remaining_mut(), + limit: buf.capacity(), + page_size: 0, + buf, + map: None } + } + + /// Creates a new compressor with the given capacity. + /// + /// The compressor will have a default limit equal to the given capacity + /// and a page size of 0. + pub fn with_capacity(capacity: usize) -> Self { + Self::from_buf(BytesMut::with_capacity(capacity)) + } + + pub fn enable_compression(&mut self) { + if self.map.is_none() { + self.map = Some(HashMap::new()) + } + } + + /// Sets the size limit for the compressor. + /// + /// This limit only regards the part of the underlying buffer that is + /// being built by the compressor. That is, if the compressor was created + /// on top of a buffer that already contained data, the buffer will never + /// exceed that amount of data plus `limit`. + /// + /// If you try to set the limit to a value smaller than what’s already + /// there, it will silently be increased to the current size. + /// + /// A new compressor starts out with a size limit equal to the capacity + /// of the buffer it is being created with. + pub fn set_limit(&mut self, limit: usize) { + let limit = limit + self.start; + self.limit = ::std::cmp::max(limit, self.buf.len()) + } + + /// Sets the number of bytes by which the buffer should be grown. + /// + /// Each time the buffer runs out of capacity and is still below its + /// size limit, it will be grown by `page_size` bytes. This may result in + /// a buffer with more capacity than the limit. + /// + /// If `page_size` is set to 0, the buffer will be expanded only once to + /// match the size limit. + /// + /// A new compressor starts out with a page size of 0. + pub fn set_page_size(&mut self, page_size: usize) { + self.page_size = page_size + } + + pub fn unwrap(self) -> BytesMut { + self.buf + } + + pub fn freeze(self) -> Bytes { + self.buf.freeze() + } + + pub fn slice(&self) -> &[u8] { + &self.buf.as_ref()[self.start..] + } + + pub fn slice_mut(&mut self) -> &mut [u8] { + &mut self.buf.as_mut()[self.start..] } /// Composes a the given name compressed into the buffer. @@ -148,7 +243,7 @@ impl Compressor { } let pos = { let first = name.first(); - let pos = self.start - self.buf.remaining_mut(); + let pos = self.buf.len() - self.start; self.compose(first)?; pos }; @@ -169,24 +264,29 @@ impl Compressor { pub fn compose(&mut self, what: &C) -> Result<(), ShortBuf> where C: Composable + ?Sized { - if self.buf.remaining_mut() < what.compose_len() { + if self.remaining_mut() < what.compose_len() { return Err(ShortBuf) } - what.compose(&mut self.buf); + what.compose(self); Ok(()) } fn add_name(&mut self, name: &Dname, pos: usize) { - if pos > 0x3FFF { - // Position exceeds encodable position. Don’t add the name, then. - return + if let Some(ref mut map) = self.map { + if pos > 0x3FFF { + // Position exceeds encodable position. Don’t add. + return + } + map.insert(name.clone(), pos as u16); } - self.map.insert(name.clone(), pos as u16); } /// Returns the index of a name if it is known. fn get_pos(&self, name: &Dname) -> Option { - self.map.get(name).map(|v| *v) + match self.map { + Some(ref map) => map.get(name).map(|v| *v), + None => None + } } pub fn as_slice(&self) -> &[u8] { @@ -196,6 +296,16 @@ impl Compressor { pub fn as_slice_mut(&mut self) -> &mut [u8] { self.buf.as_mut() } + + fn grow(&mut self) { + if self.page_size == 0 { + let additional = self.limit - self.buf.capacity(); + self.buf.reserve(additional) + } + else { + self.buf.reserve(self.page_size) + } + } } @@ -247,14 +357,21 @@ impl ops::DerefMut for Compressor { impl BufMut for Compressor { fn remaining_mut(&self) -> usize { - self.buf.remaining_mut() + self.limit - self.buf.len() } unsafe fn advance_mut(&mut self, cnt: usize) { + assert!(cnt <= self.remaining_mut()); + while cnt > self.buf.remaining_mut() { + self.grow(); + } self.buf.advance_mut(cnt) } unsafe fn bytes_mut(&mut self) -> &mut [u8] { + if self.buf.remaining_mut() == 0 && self.remaining_mut() > 0 { + self.grow() + } self.buf.bytes_mut() } } diff --git a/src/bits/header.rs b/src/bits/header.rs index 9e3ec9f5..05f72fb1 100644 --- a/src/bits/header.rs +++ b/src/bits/header.rs @@ -19,8 +19,11 @@ //! [RFC 1035]: https://tools.ietf.org/html/rfc1035 use std::mem; -use bytes::{BigEndian, ByteOrder}; +use bytes::{BigEndian, BufMut, ByteOrder}; use ::iana::{Opcode, Rcode}; +use super::compose::Composable; +use super::error::ShortBuf; +use super::parse::{Parseable, Parser}; //------------ Header -------------------------------------------------- @@ -51,7 +54,7 @@ use ::iana::{Opcode, Rcode}; /// [Field Access]: #field-access /// [RFC 1035]: https://tools.ietf.org/html/rfc1035 /// [RFC 4035]: https://tools.ietf.org/html/rfc4035 -#[derive(Clone, Debug, Default, PartialEq)] +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub struct Header { /// The actual header in its wire format representation. /// @@ -278,7 +281,7 @@ impl Header { /// /// [RFC 1035]: https://tools.ietf.org/html/rfc1035 /// [RFC 2136]: https://tools.ietf.org/html/rfc2136 -#[derive(Clone, Debug, Default, PartialEq)] +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub struct HeaderCounts { /// The actual headers in their wire-format representation. /// @@ -324,6 +327,16 @@ impl HeaderCounts { pub fn as_slice(&self) -> &[u8] { &self.inner } + + /// Returns a mutable reference to the underlying byte slice. + pub fn as_slice_mut(&mut self) -> &mut [u8] { + &mut self.inner + } + + /// Sets the counts to those from `counts`. + pub fn set(&mut self, counts: &HeaderCounts) { + self.as_slice_mut().copy_from_slice(counts.as_slice()) + } } @@ -345,6 +358,17 @@ impl HeaderCounts { self.set_u16(0, value) } + /// Increases the value of the QDCOUNT field by own. + /// + /// # Panics + /// + /// This method panics if the count is already at its maximum. + pub fn inc_qdcount(&mut self) { + let count = self.qdcount(); + assert!(count < ::std::u16::MAX); + self.set_qdcount(count + 1); + } + /// Returns the value of the ANCOUNT field. /// /// This field contains the number of resource records in the second @@ -358,6 +382,17 @@ impl HeaderCounts { self.set_u16(2, value) } + /// Increases the value of the ANCOUNT field by own. + /// + /// # Panics + /// + /// This method panics if the count is already at its maximum. + pub fn inc_ancount(&mut self) { + let count = self.ancount(); + assert!(count < ::std::u16::MAX); + self.set_ancount(count + 1); + } + /// Returns the value of the NSCOUNT field. /// /// This field contains the number of resource records in the third @@ -371,6 +406,17 @@ impl HeaderCounts { self.set_u16(4, value) } + /// Increases the value of the NSCOUNT field by own. + /// + /// # Panics + /// + /// This method panics if the count is already at its maximum. + pub fn inc_nscount(&mut self) { + let count = self.nscount(); + assert!(count < ::std::u16::MAX); + self.set_nscount(count + 1); + } + /// Returns the value of the ARCOUNT field. /// /// This field contains the number of resource records in the fourth @@ -384,6 +430,17 @@ impl HeaderCounts { self.set_u16(6, value) } + /// Increases the value of the ARCOUNT field by own. + /// + /// # Panics + /// + /// This method panics if the count is already at its maximum. + pub fn inc_arcount(&mut self) { + let count = self.arcount(); + assert!(count < ::std::u16::MAX); + self.set_arcount(count + 1); + } + //--- Count fields in UPDATE messages @@ -459,7 +516,7 @@ impl HeaderCounts { /// The complete header section of a DNS message. /// /// Consists of a `Header` and a `HeaderCounts`. -#[derive(Clone, Debug, Default, PartialEq)] +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub struct HeaderSection { inner: [u8; 12] } @@ -526,6 +583,30 @@ impl HeaderSection { } +//--- Parseable and Composable + +impl Parseable for HeaderSection { + type Err = ShortBuf; + + fn parse(parser: &mut Parser) -> Result { + let slice = parser.peek(12)?; + let mut res = Self::default(); + res.inner.copy_from_slice(slice); + Ok(res) + } +} + +impl Composable for HeaderSection { + fn compose_len(&self) -> usize { + 12 + } + + fn compose(&self, buf: &mut B) { + buf.put_slice(&self.inner) + } +} + + //============ Testing ====================================================== #[cfg(test)] diff --git a/src/bits/message_builder.rs b/src/bits/message_builder.rs index 8479c9dc..32151e45 100644 --- a/src/bits/message_builder.rs +++ b/src/bits/message_builder.rs @@ -83,13 +83,18 @@ //! [`new()`]: struct.MessageBuilder.html#method.new //! [`from_vec()`]: struct.MessageBuilder.html#method.from_vec -use std::mem; -use ::iana::{Class, OptRcode, Rtype}; -use super::{Composer, ComposeError, ComposeMode, ComposeResult, - ComposeSnapshot, DName, DNameSlice, HeaderSection, Header, - HeaderCounts, Message, Question, Record, RecordData}; -use super::record::RecordBuilder; -use super::opt::OptData; +use std::{mem, ops}; +use std::marker::PhantomData; +use bytes::{BigEndian, BufMut, ByteOrder, Bytes, BytesMut}; +use iana::opt::OptionCode; +use super::compose::{Composable, Compressable, Compressor}; +use super::error::ShortBuf; +use super::header::{Header, HeaderCounts, HeaderSection}; +use super::name::ToDname; +use super::opt::{OptData, OptHeader}; +use super::question::Question; +use super::rdata::RecordData; +use super::record::Record; //------------ MessageBuilder ----------------------------------------------- @@ -106,44 +111,35 @@ pub struct MessageBuilder { } -/// # Creation +/// # Creation and Preparation /// impl MessageBuilder { - /// Creates a new empty DNS message. - /// - /// The `mode` argument decides whether the message will have a size - /// limit and whether it should include the length prefix for use with - /// stream transports. If `compress` is `true`, name compression will - /// be enabled for the message. - /// - /// This function may fail if the size limit in `mode` is too small to - /// even add the header section. - pub fn new(mode: ComposeMode, compress: bool) -> ComposeResult { - Self::from_composer(Composer::new(mode, compress)) + pub fn from_buf(buf: BytesMut) -> Self { + MessageBuilder { target: MessageTarget::from_buf(buf) } } - /// Creates a new DNS message appended to the content of a bytes vector. - /// - /// The `mode` argument decides whether the message will have a size - /// limit and whether it should include the length prefix for use with - /// stream transports. If `compress` is `true`, name compression will - /// be enabled for the message. - /// - /// This function may fail if the size limit in `mode` is too small to - /// even add the header section. - pub fn from_vec(vec: Vec, mode: ComposeMode, compress: bool) - -> ComposeResult { - Self::from_composer(Composer::from_vec(vec, mode, compress)) + pub fn with_capacity(capacity: usize) -> Self { + Self::from_buf(BytesMut::with_capacity(capacity)) } - /// Creates a new DNS message atop an existing composer. - /// - /// This doesn’t reset the composer but starts off after whatever is in - /// there already. As this may result in invalid message, user discretion - /// is advised. - pub fn from_composer(mut composer: Composer) -> ComposeResult { - try!(composer.compose_empty(mem::size_of::())); - Ok(MessageBuilder{target: MessageTarget::new(composer)}) + pub fn with_params(initial: usize, limit: usize, page_size: usize) + -> Self { + let mut res = Self::with_capacity(initial); + res.set_limit(limit); + res.set_page_size(page_size); + res + } + + pub fn enable_compression(&mut self) { + self.target.buf.enable_compression() + } + + pub fn set_limit(&mut self, limit: usize) { + self.target.buf.set_limit(limit) + } + + pub fn set_page_size(&mut self, page_size: usize) { + self.target.buf.set_page_size(page_size) } } @@ -161,6 +157,14 @@ impl MessageBuilder { self.target.header_mut() } + pub fn snapshot(&self) -> Snapshot { + self.target.snapshot() + } + + pub fn rewind(&mut self, snapshot: Snapshot) { + self.target.rewind(snapshot) + } + /// Appends a new question to the message. /// /// This function is generic over anything that can be converted into a @@ -170,22 +174,15 @@ impl MessageBuilder { /// the latter case. /// /// [`Question`]: ../question/struct.Question.html - pub fn push>>(&mut self, question: Q) - -> ComposeResult<()> { - self.target.push(|target| question.into().compose(target), - |counts| counts.inc_qdcount(1)) - } - - /// Rewinds to the beginning of the question section. - /// - /// This drops all previously assembled questions. - pub fn rewind(&mut self) { - self.target.rewind(|counts| counts.set_qdcount(0)); + pub fn push(&mut self, question: &Question) + -> Result<(), ShortBuf> { + self.target.push(|target| question.compress(target), + |counts| counts.inc_qdcount()) } /// Proceeds to building the answer section. pub fn answer(self) -> AnswerBuilder { - AnswerBuilder::new(self.target.commit()) + AnswerBuilder::new(self.target) } /// Proceeds to building the authority section, skipping the answer. @@ -200,6 +197,11 @@ impl MessageBuilder { self.answer().authority().additional() } + /// Proceeds to building the OPT record. + pub fn opt(self) -> Result { + self.additional().opt() + } + /// Returns a reference to the message assembled so far. /// /// This method requires a `&mut self` since it may need to update some @@ -211,11 +213,15 @@ impl MessageBuilder { self.target.preview() } - /// Finishes the message and returns the underlying target. + /// Finishes the message and returns the underlying bytes buffer. /// /// This will result in a message with all three record sections empty. - pub fn finish(self) -> Vec { - self.target.finish() + pub fn finish(self) -> BytesMut { + self.target.unwrap() + } + + pub fn freeze(self) -> Bytes { + self.target.freeze() } } @@ -238,10 +244,12 @@ pub struct AnswerBuilder { impl AnswerBuilder { /// Creates a new answer builder from a compser. - fn new(composer: Composer) -> Self { - AnswerBuilder { - target: MessageTarget::new(composer) - } + fn new(target: MessageTarget) -> Self { + AnswerBuilder { target } + } + + pub fn set_limit(&mut self, limit: usize) { + self.target.buf.set_limit(limit) } /// Returns a reference to the messages header. @@ -254,6 +262,14 @@ impl AnswerBuilder { self.target.header_mut() } + pub fn snapshot(&self) -> Snapshot { + self.target.snapshot() + } + + pub fn rewind(&mut self, snapshot: Snapshot) { + self.target.rewind(snapshot) + } + /// Appends a new resource record to the answer section. /// /// This method is generic over anything that can be converted into a @@ -262,24 +278,17 @@ impl AnswerBuilder { /// the class which will then be assumed to be `Class::In`. /// /// [`Record`]: ../record/struct.Record.html - pub fn push(&mut self, record: R) -> ComposeResult<()> - where N: DName, - D: RecordData, - R: Into> { - self.target.push(|target| record.into().compose(target), - |counts| counts.inc_ancount(1)) - } - - /// Rewinds to the beginning of the answer section. - /// - /// This drops all previously assembled answer records. - pub fn rewind(&mut self) { - self.target.rewind(|counts| counts.set_ancount(0)) + pub fn push(&mut self, record: &Record) + -> Result<(), ShortBuf> + where N: ToDname, + D: RecordData { + self.target.push(|target| record.compress(target), + |counts| counts.inc_ancount()) } /// Proceeds to building the authority section. pub fn authority(self) -> AuthorityBuilder { - AuthorityBuilder::new(self.target.commit()) + AuthorityBuilder::new(self.target) } /// Proceeds to building the additional section, skipping authority. @@ -287,6 +296,11 @@ impl AnswerBuilder { self.authority().additional() } + /// Proceeds to building the OPT record. + pub fn opt(self) -> Result { + self.additional().opt() + } + /// Returns a reference to the message assembled so far. /// /// This method requires a `&mut self` since it may need to update some @@ -298,12 +312,15 @@ impl AnswerBuilder { self.target.preview() } - /// Finishes the message. + /// Finishes the message and returns the underlying bytes buffer. /// - /// The resulting message will have empty authority and additional - /// sections. - pub fn finish(self) -> Vec { - self.target.finish() + /// This will result in a message with all three record sections empty. + pub fn finish(self) -> BytesMut { + self.target.unwrap() + } + + pub fn freeze(self) -> Bytes { + self.target.freeze() } } @@ -327,10 +344,12 @@ pub struct AuthorityBuilder { impl AuthorityBuilder { /// Creates a new authority builder from a compser. - fn new(composer: Composer) -> Self { - AuthorityBuilder { - target: MessageTarget::new(composer) - } + fn new(target: MessageTarget) -> Self { + AuthorityBuilder { target } + } + + pub fn set_limit(&mut self, limit: usize) { + self.target.buf.set_limit(limit) } /// Returns a reference to the messages header. @@ -343,6 +362,14 @@ impl AuthorityBuilder { self.target.header_mut() } + pub fn snapshot(&self) -> Snapshot { + self.target.snapshot() + } + + pub fn rewind(&mut self, snapshot: Snapshot) { + self.target.rewind(snapshot) + } + /// Appends a new resource record to the authority section. /// /// This method is generic over anything that can be converted into a @@ -351,24 +378,22 @@ impl AuthorityBuilder { /// the class which will then be assumed to be `Class::In`. /// /// [`Record`]: ../record/struct.Record.html - pub fn push(&mut self, record: R) -> ComposeResult<()> - where N: DName, - D: RecordData, - R: Into> { - self.target.push(|target| record.into().compose(target), - |counts| counts.inc_nscount(1)) - } - - /// Rewinds to the beginning of the authority section. - /// - /// This drops all previously assembled authority records. - pub fn rewind(&mut self) { - self.target.rewind(|counts| counts.set_nscount(0)) + pub fn push(&mut self, record: &Record) + -> Result<(), ShortBuf> + where N: ToDname, + D: RecordData { + self.target.push(|target| record.compress(target), + |counts| counts.inc_nscount()) } /// Proceeds to building the additional section. pub fn additional(self) -> AdditionalBuilder { - AdditionalBuilder::new(self.target.commit()) + AdditionalBuilder::new(self.target) + } + + /// Proceeds to building the OPT record. + pub fn opt(self) -> Result { + self.additional().opt() } /// Returns a reference to the message assembled so far. @@ -382,11 +407,15 @@ impl AuthorityBuilder { self.target.preview() } - /// Finishes the message. + /// Finishes the message and returns the underlying bytes buffer. /// - /// The resulting message will have an empty additional section. - pub fn finish(self) -> Vec { - self.target.finish() + /// This will result in a message with all three record sections empty. + pub fn finish(self) -> BytesMut { + self.target.unwrap() + } + + pub fn freeze(self) -> Bytes { + self.target.freeze() } } @@ -411,10 +440,8 @@ pub struct AdditionalBuilder { impl AdditionalBuilder { /// Creates a new additional builder from a compser. - fn new(composer: Composer) -> Self { - AdditionalBuilder { - target: MessageTarget::new(composer) - } + fn new(target: MessageTarget) -> Self { + AdditionalBuilder { target } } /// Returns a reference to the messages header. @@ -422,11 +449,23 @@ impl AdditionalBuilder { self.target.header() } + pub fn set_limit(&mut self, limit: usize) { + self.target.buf.set_limit(limit) + } + /// Returns a mutable reference to the messages header. pub fn header_mut(&mut self) -> &mut Header { self.target.header_mut() } + pub fn snapshot(&self) -> Snapshot { + self.target.snapshot() + } + + pub fn rewind(&mut self, snapshot: Snapshot) { + self.target.rewind(snapshot) + } + /// Appends a new resource record to the additional section. /// /// This method is generic over anything that can be converted into a @@ -435,44 +474,17 @@ impl AdditionalBuilder { /// the class which will then be assumed to be `Class::In`. /// /// [`Record`]: ../record/struct.Record.html - pub fn push(&mut self, record: R) -> ComposeResult<()> - where N: DName, - D: RecordData, - R: Into> { - self.target.push(|target| record.into().compose(target), - |counts| counts.inc_arcount(1)) + pub fn push(&mut self, record: &Record) + -> Result<(), ShortBuf> + where N: ToDname, + D: RecordData { + self.target.push(|target| record.compress(target), + |counts| counts.inc_arcount()) } - /// Starts appending an OPT record to the section. - /// - /// The method expects the values of the OPT record that are encoded in - /// various fields of the record header. - /// - /// The *payload_size* field contains - /// the maximum size of UDP payload a requestor can assemble and process. - /// - /// The `rcode` argument should contain the Rcode used for a response - /// or `OptRcode::NoError` for a message. Only the upper eight bits are - /// used here, the lower for bits go into the message header’s rcode - /// field. - /// - /// The `dnssec_ok` flag indicates whether a sender is prepared to - /// receive and process DNSSEC-related resource records in a response. - /// In a response it must be equal to its value in a request. - /// - /// This method trades in the additional section builder for an OPT - /// record builder. Once the record is finished, it can be traded back - /// to continue building the additional section. - pub fn build_opt(self, payload_size: u16, rcode: OptRcode, - dnssec_ok: bool) -> ComposeResult { - OptBuilder::new(self, payload_size, rcode, dnssec_ok) - } - - /// Rewinds to the beginning of the additional section. - /// - /// This drops all previously assembled additonal records. - pub fn rewind(&mut self) { - self.target.rewind(|counts| counts.set_arcount(0)) + /// Proceeds to building the OPT record. + pub fn opt(self) -> Result { + OptBuilder::new(self.target) } /// Returns a reference to the message assembled so far. @@ -486,63 +498,94 @@ impl AdditionalBuilder { self.target.preview() } - /// Finishes the message. - pub fn finish(self) -> Vec { - self.target.finish() + /// Finishes the message and returns the underlying bytes buffer. + /// + /// This will result in a message with all three record sections empty. + pub fn finish(self) -> BytesMut { + self.target.unwrap() } -} -impl AsRef for AdditionalBuilder { - fn as_ref(&self) -> &Message { - self.target.as_ref() + pub fn freeze(self) -> Bytes { + self.target.freeze() } } //------------ OptBuilder ---------------------------------------------------- -/// A type for building an OPT record on the fly. -/// -/// The OPT record is part of the additional section. You can therefore get -/// hold of a value of this type through the `AdditionalBuilder::build_opt()` -/// method. -/// -/// You use this value to add options to the record via the `push()` method. -/// Once you are done, call `complete()` to finish up the record and get the -/// additional builder back. #[derive(Clone, Debug)] pub struct OptBuilder { - builder: RecordBuilder, + target: MessageTarget, + pos: usize, } impl OptBuilder { - /// Creates a new OPT builder from an additional builder - fn new(builder: AdditionalBuilder, payload_size: u16, rcode: OptRcode, - dnssec_ok: bool) -> ComposeResult { - let mut ttl = (rcode.ext() as u32) << 24; - if dnssec_ok { - ttl |= 0x8000 - } - let builder = RecordBuilder::new(builder.target.composer, - &DNameSlice::root(), - Class::Int(payload_size), - Rtype::Opt, ttl)?; - Ok(OptBuilder { builder }) + fn new(mut target: MessageTarget) -> Result { + let pos = target.len(); + target.compose(&OptHeader::default())?; + target.compose(&0u16)?; + Ok(OptBuilder { pos, target }) } /// Pushes an option to the OPT record. - pub fn push(&mut self, option: O) -> ComposeResult<()> { - option.compose(&mut self.builder) + pub fn push(&mut self, option: &O) -> Result<(), ShortBuf> { + self.target.compose(&option.code())?; + let len = option.compose_len(); + assert!(len <= ::std::u16::MAX as usize); + self.target.compose(&(len as u16))?; + self.target.compose(option) + } + + pub(super) fn build(&mut self, code: OptionCode, len: u16, op: F) + -> Result<(), ShortBuf> + where F: FnOnce(&mut Compressor) + -> Result<(), ShortBuf> { + self.target.compose(&code)?; + self.target.compose(&len)?; + op(&mut self.target.buf) } /// Completes the OPT record and returns the additional section builder. - pub fn complete(self) -> ComposeResult { - let mut target = MessageTarget { composer: self.builder.finish()? }; - target.counts_mut().inc_arcount(1)?; - Ok(AdditionalBuilder { target }) + pub fn additional(self) -> AdditionalBuilder { + AdditionalBuilder::new(self.complete()) + } + + /// Finishes the message and returns the underlying bytes buffer. + /// + /// This will result in a message with all three record sections empty. + pub fn finish(self) -> BytesMut { + self.complete().unwrap() + } + + pub fn freeze(self) -> Bytes { + self.complete().freeze() + } + + fn complete(mut self) -> MessageTarget { + let len = self.target.len() + - (self.pos + mem::size_of::() + 2); + assert!(len <= ::std::u16::MAX as usize); + BigEndian::write_u16(&mut self.target.as_slice_mut()[self.pos..], + len as u16); + self.target.counts_mut().inc_arcount(); + self.target } } +impl ops::Deref for OptBuilder { + type Target = OptHeader; + + fn deref(&self) -> &Self::Target { + OptHeader::for_record_slice(&self.target.as_slice()[self.pos..]) + } +} + +impl ops::DerefMut for OptBuilder { + fn deref_mut(&mut self) -> &mut Self::Target { + OptHeader::for_record_slice_mut(&mut self.target.as_slice_mut() + [self.pos..]) + } +} //------------ MessageTarget ------------------------------------------------- @@ -551,81 +594,114 @@ impl OptBuilder { /// This private type does all the heavy lifting for constructing messages. #[derive(Clone, Debug)] struct MessageTarget { - composer: ComposeSnapshot, + buf: Compressor, + start: usize, } impl MessageTarget { - /// Creates a new message target atop a given composer. - fn new(composer: Composer) -> Self { - MessageTarget{composer: composer.snapshot()} + /// Creates a new message target atop a given buffer. + fn from_buf(mut buf: BytesMut) -> Self { + let start = buf.len(); + if buf.remaining_mut() < 2 + mem::size_of::() { + let additional = 2 + mem::size_of::() + - buf.remaining_mut(); + buf.reserve(additional) + } + 0u16.compose(&mut buf); + let mut buf = Compressor::from_buf(buf); + HeaderSection::default().compose(&mut buf); + MessageTarget { buf, start } } /// Returns a reference to the message’s header. fn header(&self) -> &Header { - Header::from_message(self.composer.so_far()) + Header::for_message_slice(self.buf.slice()) } /// Returns a mutable reference to the message’s header. fn header_mut(&mut self) -> &mut Header { - Header::from_message_mut(self.composer.so_far_mut()) + Header::for_message_slice_mut(self.buf.slice_mut()) + } + + fn counts(&self) -> &HeaderCounts { + HeaderCounts::for_message_slice(self.buf.slice()) } /// Returns a mutable reference to the message’s header counts. fn counts_mut(&mut self) -> &mut HeaderCounts { - HeaderCounts::from_message_mut(self.composer.so_far_mut()) + HeaderCounts::for_message_slice_mut(self.buf.slice_mut()) } /// Pushes something to the end of the message. /// /// There’s two closures here. The first one, `composeop` actually /// writes the data. The second, `incop` increments the counter in the - /// messages header to reflect the new element. - fn push(&mut self, composeop: O, incop: I) -> ComposeResult<()> - where O: FnOnce(&mut Composer) -> ComposeResult<()>, - I: FnOnce(&mut HeaderCounts) -> ComposeResult<()> { - if !self.composer.is_truncated() { - self.composer.mark_checkpoint(); - match composeop(&mut self.composer) { - Ok(()) => { - try!(incop(self.counts_mut())); - Ok(()) - } - Err(ComposeError::SizeExceeded) => Ok(()), - Err(error) => Err(error) - } + /// messages header to reflect the new element. The latter is assumed to + /// never fail. This means you need to check before you push whether + /// there is still space in whatever counter you plan to increase. + /// `HeaderCount`’s `inc_*` methods, which are supposed to be used here, + /// have assertions for your own safety. + fn push(&mut self, composeop: O, incop: I) -> Result<(), E> + where O: FnOnce(&mut Compressor) -> Result<(), E>, + I: FnOnce(&mut HeaderCounts) { + composeop(&mut self.buf).map(|()| incop(self.counts_mut())) + } + + fn snapshot(&self) -> Snapshot { + Snapshot { + pos: self.buf.len(), + counts: self.counts().clone(), + marker: PhantomData, } - else { Ok(()) } } - /// Returns a reference to the message assembled so far. + fn rewind(&mut self, snapshot: Snapshot) { + self.buf.truncate(snapshot.pos); + self.counts_mut().set(&snapshot.counts); + } + + fn update_shim(&mut self) { + let len = (self.buf.len() - self.start) as u16; + BigEndian::write_u16(&mut self.buf.as_slice_mut()[self.start..], len); + } + fn preview(&mut self) -> &[u8] { - self.composer.preview() + self.update_shim(); + self.buf.as_slice() } - /// Finishes the message building and extracts the underlying vector. - fn finish(mut self) -> Vec { - let tc = self.composer.is_truncated(); - self.header_mut().set_tc(tc); - self.composer.commit().finish() + fn unwrap(mut self) -> BytesMut { + self.update_shim(); + self.buf.unwrap() } - /// Rewinds the compose snapshots and allows updating the header counts. - fn rewind(&mut self, op: F) - where F: FnOnce(&mut HeaderCounts) { - op(self.counts_mut()); - self.composer.rewind() + fn freeze(mut self) -> Bytes { + self.update_shim(); + self.unwrap().freeze() } +} - /// Commit the compose snapshot. - fn commit(self) -> Composer { - self.composer.commit() +impl ops::Deref for MessageTarget { + type Target = Compressor; + + fn deref(&self) -> &Self::Target { + &self.buf + } +} + +impl ops::DerefMut for MessageTarget { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.buf } } -impl AsRef for MessageTarget { - fn as_ref(&self) -> &Message { - unsafe { Message::from_bytes_unsafe(self.composer.so_far()) } - } +//------------ Snapshot ------------------------------------------------------ + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct Snapshot { + pos: usize, + counts: HeaderCounts, + marker: PhantomData, } diff --git a/src/bits/mod.rs b/src/bits/mod.rs index 4c893455..0a922977 100644 --- a/src/bits/mod.rs +++ b/src/bits/mod.rs @@ -1,7 +1,3 @@ -// TODO: -// -// o Rename ShortParser into something more generic. -// //! DNS data. //! //! This module provides types and traits for working with DNS data as well @@ -103,6 +99,7 @@ pub mod compose; pub mod error; pub mod header; pub mod message; +pub mod message_builder; pub mod name; pub mod opt; pub mod parse; @@ -110,6 +107,3 @@ pub mod question; pub mod rdata; pub mod record; -/* -pub mod message_builder; -*/ diff --git a/src/bits/name/traits.rs b/src/bits/name/traits.rs index fc52f06d..de86aa0b 100644 --- a/src/bits/name/traits.rs +++ b/src/bits/name/traits.rs @@ -1,6 +1,6 @@ //! Domain name-related traits. -use ::bits::compose::Composable; +use ::bits::compose::{Composable, Compressable}; use bytes::BytesMut; use super::dname::Dname; use super::label::Label; @@ -105,7 +105,7 @@ pub trait ToRelativeDname: Composable + for<'a> ToLabelIter<'a> { /// [`Chain`]: struct.Chain.html /// [`Dname`]: struct.Dname.html /// [`ParsedDname`]: struct.ParsedDname.html -pub trait ToDname: Composable + for<'a> ToLabelIter<'a> { +pub trait ToDname: Composable + Compressable + for<'a> ToLabelIter<'a> { /// Creates an uncompressed value of the domain name. /// /// The method has a default implementation that composes the name into diff --git a/src/bits/opt/mod.rs b/src/bits/opt/mod.rs index 3c2ef5af..69617222 100644 --- a/src/bits/opt/mod.rs +++ b/src/bits/opt/mod.rs @@ -6,11 +6,13 @@ //! `rdata` module and the types defined for operating on them differ from //! how other record types are handled. -use bytes::{BufMut, Bytes}; +use std::mem; use std::marker::PhantomData; -use ::iana::{OptionCode, Rtype}; +use bytes::{BigEndian, BufMut, ByteOrder, Bytes}; +use ::iana::{OptionCode, OptRcode, Rtype}; use super::compose::{Composable, Compressable, Compressor}; use super::error::ShortBuf; +use super::header::Header; use super::parse::Parser; use super::rdata::RecordData; @@ -82,6 +84,94 @@ impl Compressable for Opt { } +//------------ OptHeader ----------------------------------------------------- + +/// The header of an OPT record. +/// +/// The OPT record reappropriates the record header for encoding some +/// basic information. This type provides access to this information. It +/// consists of the record header accept for its `rdlen` field. +/// +/// This is so that `OptBuilder` can safely deref to this type. +/// +// +------------+--------------+------------------------------+ +// | Field Name | Field Type | Description | +// +------------+--------------+------------------------------+ +// | NAME | domain name | MUST be 0 (root domain) | +// | TYPE | u_int16_t | OPT (41) | +// | CLASS | u_int16_t | requestor's UDP payload size | +// | TTL | u_int32_t | extended RCODE and flags | +// | RDLEN | u_int16_t | length of all RDATA | +// | RDATA | octet stream | {attribute,value} pairs | +// +------------+--------------+------------------------------+ +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct OptHeader { + /// The bytes of the header. + inner: [u8; 9], +} + +impl OptHeader { + pub fn for_record_slice(slice: &[u8]) -> &OptHeader { + assert!(slice.len() >= mem::size_of::()); + unsafe { &*(slice.as_ptr() as *const OptHeader) } + } + + pub fn for_record_slice_mut(slice: &mut [u8]) -> &mut OptHeader { + assert!(slice.len() >= mem::size_of::()); + unsafe { &mut *(slice.as_ptr() as *mut OptHeader) } + } + + pub fn udp_payload_size(&self) -> u16 { + BigEndian::read_u16(&self.inner[3..]) + } + + pub fn set_udp_payload_size(&mut self, value: u16) { + BigEndian::write_u16(&mut self.inner[3..], value) + } + + pub fn rcode(&self, header: &Header) -> OptRcode { + OptRcode::from_parts(header.rcode(), self.inner[5]) + } + + pub fn set_rcode(&mut self, rcode: OptRcode) { + self.inner[5] = rcode.ext() + } + + pub fn version(&self) -> u8 { + self.inner[6] + } + + pub fn dnssec_ok(&self) -> bool { + self.inner[7] & 0x80 != 0 + } + + pub fn set_dnssec_ok(&mut self, value: bool) { + if value { + self.inner[7] |= 0x80 + } + else { + self.inner[7] &= 0x7F + } + } +} + +impl Default for OptHeader { + fn default() -> Self { + OptHeader { inner: [0, 41, 0, 0, 0, 0, 0, 0, 0] } + } +} + +impl Composable for OptHeader { + fn compose_len(&self) -> usize { + 9 + } + + fn compose(&self, buf: &mut B) { + buf.put_slice(&self.inner) + } +} + + //------------ OptIter ------------------------------------------------------- #[derive(Clone, Debug)] diff --git a/src/bits/opt/rfc5001.rs b/src/bits/opt/rfc5001.rs index 9c134a51..e525d759 100644 --- a/src/bits/opt/rfc5001.rs +++ b/src/bits/opt/rfc5001.rs @@ -4,6 +4,7 @@ use std::fmt; use bytes::{BufMut, Bytes}; use ::bits::compose::Composable; use ::bits::error::ShortBuf; +use ::bits::message_builder::OptBuilder; use ::bits::parse::Parser; use ::iana::OptionCode; use super::OptData; @@ -23,6 +24,15 @@ impl Nsid { pub fn new(bytes: Bytes) -> Self { Nsid { bytes } } + + pub fn push>(builder: &mut OptBuilder, data: &T) + -> Result<(), ShortBuf> { + let data = data.as_ref(); + assert!(data.len() <= ::std::u16::MAX as usize); + builder.build(OptionCode::Nsid, data.len() as u16, |buf| { + buf.compose(data) + }) + } } impl OptData for Nsid { diff --git a/src/bits/opt/rfc6975.rs b/src/bits/opt/rfc6975.rs index 2afd82a8..220b7dbb 100644 --- a/src/bits/opt/rfc6975.rs +++ b/src/bits/opt/rfc6975.rs @@ -4,6 +4,7 @@ use std::slice; use bytes::{BufMut, Bytes}; use ::bits::compose::Composable; use ::bits::error::ShortBuf; +use ::bits::message_builder::OptBuilder; use ::bits::parse::Parser; use ::iana::{OptionCode, SecAlg}; use super::OptData; @@ -26,6 +27,17 @@ macro_rules! option_type { pub fn iter(&self) -> SecAlgsIter { SecAlgsIter::new(self.bytes.as_ref()) } + + pub fn push(builder: &mut OptBuilder, algs: &[SecAlg]) + -> Result<(), ShortBuf> { + assert!(algs.len() <= ::std::u16::MAX as usize); + builder.build(OptionCode::$name, algs.len() as u16, |buf| { + for alg in algs { + buf.compose(&alg.to_int())? + } + Ok(()) + }) + } } //--- Composable and OptData diff --git a/src/bits/opt/rfc7314.rs b/src/bits/opt/rfc7314.rs index b73bc711..d47b9137 100644 --- a/src/bits/opt/rfc7314.rs +++ b/src/bits/opt/rfc7314.rs @@ -2,6 +2,8 @@ use bytes::BufMut; use ::bits::compose::Composable; +use ::bits::error::ShortBuf; +use ::bits::message_builder::OptBuilder; use ::bits::parse::Parser; use ::iana::OptionCode; use super::{OptData, OptionParseError}; @@ -17,6 +19,11 @@ impl Expire { Expire(expire) } + pub fn push(builder: &mut OptBuilder, expire: Option) + -> Result<(), ShortBuf> { + builder.push(&Self::new(expire)) + } + pub fn expire(&self) -> Option { self.0 } diff --git a/src/bits/opt/rfc7828.rs b/src/bits/opt/rfc7828.rs index a307a25d..4927d8f4 100644 --- a/src/bits/opt/rfc7828.rs +++ b/src/bits/opt/rfc7828.rs @@ -2,6 +2,8 @@ use bytes::BufMut; use ::bits::compose::Composable; +use ::bits::error::ShortBuf; +use ::bits::message_builder::OptBuilder; use ::bits::parse::Parser; use ::iana::OptionCode; use super::{OptData, OptionParseError}; @@ -17,6 +19,11 @@ impl TcpKeepalive { TcpKeepalive(timeout) } + pub fn push(builder: &mut OptBuilder, timeout: u16) + -> Result<(), ShortBuf> { + builder.push(&Self::new(timeout)) + } + pub fn timeout(&self) -> u16 { self.0 } diff --git a/src/bits/opt/rfc7830.rs b/src/bits/opt/rfc7830.rs index 1f34a2f1..a8272051 100644 --- a/src/bits/opt/rfc7830.rs +++ b/src/bits/opt/rfc7830.rs @@ -4,6 +4,7 @@ use bytes::BufMut; use rand::random; use ::bits::compose::Composable; use ::bits::error::ShortBuf; +use ::bits::message_builder::OptBuilder; use ::bits::parse::Parser; use ::iana::OptionCode; use super::OptData; @@ -31,6 +32,11 @@ impl Padding { pub fn new(len: u16, mode: PaddingMode) -> Self { Padding { len, mode } } + + pub fn push(builder: &mut OptBuilder, len: u16, mode: PaddingMode) + -> Result<(), ShortBuf> { + builder.push(&Self::new(len, mode)) + } pub fn len(&self) -> u16 { self.len diff --git a/src/bits/opt/rfc7871.rs b/src/bits/opt/rfc7871.rs index 98d222b4..09e05a2f 100644 --- a/src/bits/opt/rfc7871.rs +++ b/src/bits/opt/rfc7871.rs @@ -5,6 +5,7 @@ use std::net::IpAddr; use bytes::BufMut; use ::bits::compose::Composable; use ::bits::error::ShortBuf; +use ::bits::message_builder::OptBuilder; use ::bits::parse::Parser; use ::iana::OptionCode; use super::OptData; @@ -26,6 +27,11 @@ impl ClientSubnet { ClientSubnet { source_prefix_len, scope_prefix_len, addr } } + pub fn push(builder: &mut OptBuilder, source_prefix_len: u8, + scope_prefix_len: u8, addr: IpAddr) -> Result<(), ShortBuf> { + builder.push(&Self::new(source_prefix_len, scope_prefix_len, addr)) + } + pub fn source_prefix_len(&self) -> u8 { self.source_prefix_len } pub fn scope_prefix_len(&self) -> u8 { self.scope_prefix_len } pub fn addr(&self) -> IpAddr { self.addr } diff --git a/src/bits/opt/rfc7873.rs b/src/bits/opt/rfc7873.rs index 46d50a8a..1c97377c 100644 --- a/src/bits/opt/rfc7873.rs +++ b/src/bits/opt/rfc7873.rs @@ -3,6 +3,8 @@ use std::mem; use bytes::BufMut; use ::bits::compose::Composable; +use ::bits::error::ShortBuf; +use ::bits::message_builder::OptBuilder; use ::bits::parse::Parser; use ::iana::OptionCode; use super::{OptData, OptionParseError}; @@ -18,6 +20,11 @@ impl Cookie { Cookie(cookie) } + pub fn push(builder: &mut OptBuilder, cookie: [u8; 8]) + -> Result<(), ShortBuf> { + builder.push(&Self::new(cookie)) + } + pub fn cookie(&self) -> &[u8; 8] { &self.0 } diff --git a/src/bits/opt/rfc7901.rs b/src/bits/opt/rfc7901.rs index fccd0500..08eeeec4 100644 --- a/src/bits/opt/rfc7901.rs +++ b/src/bits/opt/rfc7901.rs @@ -3,7 +3,8 @@ use bytes::BufMut; use ::bits::compose::Composable; use ::bits::error::ShortBuf; -use ::bits::name::{Dname, DnameError}; +use ::bits::message_builder::OptBuilder; +use ::bits::name::{Dname, DnameError, ToDname}; use ::bits::parse::Parser; use ::iana::OptionCode; use super::OptData; @@ -21,6 +22,15 @@ impl Chain { Chain { start } } + pub fn push(builder: &mut OptBuilder, start: &N) + -> Result<(), ShortBuf> { + let len = start.compose_len(); + assert!(len <= ::std::u16::MAX as usize); + builder.build(OptionCode::Chain, len as u16, |buf| { + buf.compose(start) + }) + } + pub fn start(&self) -> &Dname { &self.start } diff --git a/src/bits/opt/rfc8145.rs b/src/bits/opt/rfc8145.rs index 67660c0b..1fe3d7f4 100644 --- a/src/bits/opt/rfc8145.rs +++ b/src/bits/opt/rfc8145.rs @@ -1,7 +1,9 @@ //! EDNS Options from RFC 8145. -use bytes::{BufMut, Bytes}; +use bytes::{BigEndian, BufMut, ByteOrder, Bytes}; use ::bits::compose::Composable; +use ::bits::error::ShortBuf; +use ::bits::message_builder::OptBuilder; use ::bits::parse::Parser; use ::iana::OptionCode; use super::{OptData, OptionParseError}; @@ -18,6 +20,22 @@ impl KeyTag { pub fn new(bytes: Bytes) -> Self { KeyTag { bytes } } + + pub fn push(builder: &mut OptBuilder, tags: &[u16]) + -> Result<(), ShortBuf> { + let len = tags.len() * 2; + assert!(len <= ::std::u16::MAX as usize); + builder.build(OptionCode::EdnsKeyTag, len as u16, |buf| { + for tag in tags { + buf.compose(&tag)? + } + Ok(()) + }) + } + + pub fn iter(&self) -> KeyTagIter { + KeyTagIter(self.bytes.as_ref()) + } } impl Composable for KeyTag { @@ -48,3 +66,34 @@ impl OptData for KeyTag { Ok(Some(Self::new(parser.parse_bytes(len)?))) } } + +impl<'a> IntoIterator for &'a KeyTag { + type Item = u16; + type IntoIter = KeyTagIter<'a>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + + +//------------ KeyTagIter ---------------------------------------------------- + +#[derive(Clone, Copy, Debug)] +pub struct KeyTagIter<'a>(&'a [u8]); + +impl<'a> Iterator for KeyTagIter<'a> { + type Item = u16; + + fn next(&mut self) -> Option { + if self.0.len() < 2 { + None + } + else { + let (item, tail) = self.0.split_at(2); + self.0 = tail; + Some(BigEndian::read_u16(item)) + } + } +} + diff --git a/src/bits/question.rs b/src/bits/question.rs index aec1d33c..7dd98c96 100644 --- a/src/bits/question.rs +++ b/src/bits/question.rs @@ -45,6 +45,21 @@ impl Question { } +//--- From + +impl From<(N, Rtype, Class)> for Question { + fn from((name, rtype, class): (N, Rtype, Class)) -> Self { + Question::new(name, rtype, class) + } +} + +impl From<(N, Rtype)> for Question { + fn from((name, rtype): (N, Rtype)) -> Self { + Question::new(name, rtype, Class::In) + } +} + + //--- Parseable, Composable, and Compressable impl Parseable for Question { diff --git a/src/bits/rdata.rs b/src/bits/rdata.rs index bb78be00..fc681d8d 100644 --- a/src/bits/rdata.rs +++ b/src/bits/rdata.rs @@ -27,7 +27,7 @@ use std::fmt; use bytes::{BufMut, Bytes}; use ::iana::Rtype; -use super::compose::Composable; +use super::compose::{Composable, Compressable, Compressor}; use super::error::ShortBuf; use super::parse::Parser; @@ -35,7 +35,7 @@ use super::parse::Parser; //----------- RecordData ----------------------------------------------------- /// A trait for types representing record data. -pub trait RecordData: Composable + Sized { +pub trait RecordData: Composable + Compressable + Sized { /// The type of an error returned when parsing fails. type ParseErr: Clone; @@ -100,7 +100,7 @@ impl UnknownRecordData { } -//--- Composable and RecordData +//--- Composable, Compressable, and RecordData impl Composable for UnknownRecordData { fn compose_len(&self) -> usize { @@ -112,6 +112,12 @@ impl Composable for UnknownRecordData { } } +impl Compressable for UnknownRecordData { + fn compress(&self, buf: &mut Compressor) -> Result<(), ShortBuf> { + buf.compose(self) + } +} + impl RecordData for UnknownRecordData { type ParseErr = ShortBuf; diff --git a/src/iana/opt.rs b/src/iana/opt.rs index c36cd749..7cff2363 100644 --- a/src/iana/opt.rs +++ b/src/iana/opt.rs @@ -3,6 +3,10 @@ use std::cmp; use std::fmt; use std::hash; +use bytes::BufMut; +use ::bits::compose::Composable; +use ::bits::error::ShortBuf; +use ::bits::parse::{Parseable, Parser}; //------------ OptionCode --------------------------------------------------- @@ -81,6 +85,27 @@ impl OptionCode { } +//--- Parseable and Composable + +impl Parseable for OptionCode { + type Err = ShortBuf; + + fn parse(parser: &mut Parser) -> Result { + u16::parse(parser).map(OptionCode::from_int) + } +} + +impl Composable for OptionCode { + fn compose_len(&self) -> usize { + 2 + } + + fn compose(&self, buf: &mut B) { + self.to_int().compose(buf) + } +} + + //--- From impl From for OptionCode {