diff --git a/src/decoder.rs b/src/decoder.rs index 84230e9..710282b 100644 --- a/src/decoder.rs +++ b/src/decoder.rs @@ -39,6 +39,13 @@ impl<'a> Decoder<'a> { Ok(u8::from_be_bytes(self.peek::<{ mem::size_of::() }>()?)) } + /// peek at the next u16 without advancing the internal pointer + pub fn peek_u16(&self) -> DecodeResult { + Ok(u16::from_be_bytes( + self.peek::<{ mem::size_of::() }>()?, + )) + } + /// read a u8 pub fn read_u8(&mut self) -> DecodeResult { Ok(u8::from_be_bytes(self.read::<{ mem::size_of::() }>()?)) diff --git a/src/v6/duid.rs b/src/v6/duid.rs new file mode 100644 index 0000000..8ab00e0 --- /dev/null +++ b/src/v6/duid.rs @@ -0,0 +1,90 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use crate::v4::HType; +use crate::v6::{DecodeResult, EncodeResult, Ipv6Addr}; +use crate::{Decodable, Decoder, Encodable, Encoder}; + +/// Duid helper type +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Duid(Vec); +// TODO: define specific duid types + +impl Duid { + /// new DUID link layer address with time + pub fn link_layer_time(htype: HType, time: u32, addr: Ipv6Addr) -> Self { + let mut buf = Vec::new(); + let mut e = Encoder::new(&mut buf); + e.write_u16(1).unwrap(); // duid type + e.write_u16(u8::from(htype) as u16).unwrap(); + e.write_u32(time).unwrap(); + e.write_u128(addr.into()).unwrap(); + Self(buf) + } + /// new DUID enterprise number + pub fn enterprise(enterprise: u32, id: &[u8]) -> Self { + let mut buf = Vec::new(); + let mut e = Encoder::new(&mut buf); + e.write_u16(2).unwrap(); // duid type + e.write_u32(enterprise).unwrap(); + e.write_slice(id).unwrap(); + Self(buf) + } + /// new link layer DUID + pub fn link_layer(htype: HType, addr: Ipv6Addr) -> Self { + let mut buf = Vec::new(); + let mut e = Encoder::new(&mut buf); + e.write_u16(3).unwrap(); // duid type + e.write_u16(u8::from(htype) as u16).unwrap(); + e.write_u128(addr.into()).unwrap(); + Self(buf) + } + /// new DUID-UUID + /// `uuid` must be 16 bytes long + pub fn uuid(uuid: &[u8]) -> Self { + assert!(uuid.len() == 16); + let mut buf = Vec::new(); + let mut e = Encoder::new(&mut buf); + e.write_u16(4).unwrap(); // duid type + e.write_slice(uuid).unwrap(); + Self(buf) + } + /// create a DUID of unknown type + pub fn unknown(duid: &[u8]) -> Self { + Self(duid.to_vec()) + } + /// total length of contained DUID + pub fn len(&self) -> usize { + self.0.len() + } + /// is contained DUID empty + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +impl AsRef<[u8]> for Duid { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl From> for Duid { + fn from(v: Vec) -> Self { + Self(v) + } +} + +impl Decodable for Duid { + fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { + Ok(Duid(decoder.buffer().into())) + } +} + +impl Encodable for Duid { + fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { + e.write_slice(&self.0)?; + Ok(()) + } +} diff --git a/src/v6/messages.rs b/src/v6/messages.rs new file mode 100644 index 0000000..fd4e920 --- /dev/null +++ b/src/v6/messages.rs @@ -0,0 +1,697 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use crate::{ + decoder::{Decodable, Decoder}, + encoder::{Encodable, Encoder}, + error::{DecodeResult, EncodeResult}, + v6::options::{option_builder, DhcpOption}, + v6::*, +}; + +///Bulk lease query messages for use over TCP +///Note: The u16 message-size from the start of the TCP message is not read or written, and only a buffer containing a one complete message will decode correctly. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum BulkLeaseQueryMessage { + LeaseQuery(LeaseQuery), + LeaseQueryReply(LeaseQueryReply), + LeaseQueryDone(LeaseQueryDone), + LeaseQueryData(LeaseQueryData), + Unknown(Vec), +} + +impl BulkLeaseQueryMessage { + pub fn msg_type(&self) -> MessageType { + use BulkLeaseQueryMessage::*; + match self { + LeaseQuery(_) => MessageType::LeaseQuery, + LeaseQueryReply(_) => MessageType::LeaseQueryReply, + LeaseQueryDone(_) => MessageType::LeaseQueryDone, + LeaseQueryData(_) => MessageType::LeaseQueryData, + Unknown(v) => MessageType::Unknown(v[0]), + } + } +} + +impl Encodable for BulkLeaseQueryMessage { + fn encode(&self, e: &mut Encoder<'_>) -> EncodeResult<()> { + use BulkLeaseQueryMessage::*; + match self { + LeaseQuery(message) => message.encode(e), + LeaseQueryReply(message) => message.encode(e), + LeaseQueryDone(message) => message.encode(e), + LeaseQueryData(message) => message.encode(e), + Unknown(message) => e.write_slice(message), + } + } +} + +impl Decodable for BulkLeaseQueryMessage { + fn decode(decoder: &mut Decoder<'_>) -> DecodeResult { + Ok(match MessageType::from(decoder.peek_u8()?) { + MessageType::LeaseQuery => { + BulkLeaseQueryMessage::LeaseQuery(LeaseQuery::decode(decoder)?) + } + MessageType::LeaseQueryReply => { + BulkLeaseQueryMessage::LeaseQueryReply(LeaseQueryReply::decode(decoder)?) + } + MessageType::LeaseQueryDone => { + BulkLeaseQueryMessage::LeaseQueryDone(LeaseQueryDone::decode(decoder)?) + } + MessageType::LeaseQueryData => { + BulkLeaseQueryMessage::LeaseQueryData(LeaseQueryData::decode(decoder)?) + } + _ => BulkLeaseQueryMessage::Unknown({ + let mut buf = vec![]; + while let Ok(b) = decoder.read_u8() { + buf.push(b); + } + buf + }), + }) + } +} + +/// See RFC 8415 for updated DHCPv6 info +/// [DHCP for Ipv6](https://datatracker.ietf.org/doc/html/rfc8415) +/// +/// All DHCP messages sent between clients and servers share an identical +/// fixed-format header and a variable-format area for options. +/// +/// All values in the message header and in options are in network byte +/// order. +/// +/// Options are stored serially in the "options" field, with no padding +/// between the options. Options are byte-aligned but are not aligned in +/// any other way (such as on 2-byte or 4-byte boundaries). +/// +/// The following diagram illustrates the format of DHCP messages sent +/// between clients and servers: +/// +/// ```text +/// 0 1 2 3 +/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | msg-type | transaction-id | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | | +/// . options . +/// . (variable number and length) . +/// | | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// +/// msg-type Identifies the DHCP message type; the +/// available message types are listed in +/// Section 7.3. A 1-octet field. +/// +/// transaction-id The transaction ID for this message exchange. +/// A 3-octet field. +/// +/// options Options carried in this message; options are +/// described in Section 21. A variable-length +/// field (4 octets less than the size of the +/// message). +/// ``` + +///Dhcp messages for use over UDP +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Message { + Solicit(Solicit), + Advertise(Advertise), + Request(Request), + Confirm(Confirm), + Renew(Renew), + Rebind(Rebind), + Reply(Reply), + Release(Release), + Decline(Decline), + Reconfigure(Reconfigure), + InformationRequest(InformationRequest), + RelayForw(RelayForw), + RelayRepl(RelayRepl), + LeaseQuery(LeaseQuery), + LeaseQueryReply(LeaseQueryReply), + /* + ReconfigureRequest(ReconfigureRequest), + ReconfigureReply(ReconfigureReply), + DHCPv4Query(DHCPv4Query), + DHCPv4Response(DHCPv4Response), + */ + Unknown(Vec), +} + +impl Message { + pub fn msg_type(&self) -> MessageType { + use Message::*; + match self { + Solicit(_) => MessageType::Solicit, + Advertise(_) => MessageType::Advertise, + Request(_) => MessageType::Request, + Confirm(_) => MessageType::Confirm, + Renew(_) => MessageType::Renew, + Rebind(_) => MessageType::Rebind, + Reply(_) => MessageType::Reply, + Release(_) => MessageType::Release, + Decline(_) => MessageType::Decline, + Reconfigure(_) => MessageType::Reconfigure, + InformationRequest(_) => MessageType::InformationRequest, + RelayForw(_) => MessageType::RelayForw, + RelayRepl(_) => MessageType::RelayRepl, + LeaseQuery(_) => MessageType::LeaseQuery, + LeaseQueryReply(_) => MessageType::LeaseQueryReply, + /* + ReconfigureRequest(_) => MessageType::ReconfigureRequest, + ReconfigureReply(_) => MessageType::ReconfigureReply, + DHCPv4Query(_) => MessageType::ReconfigureReply, + DHCPv4Response(_) => MessageType::ReconfigureReply, + */ + Unknown(v) => MessageType::Unknown(v[0]), + } + } +} + +impl Encodable for Message { + fn encode(&self, e: &mut Encoder<'_>) -> EncodeResult<()> { + use Message::*; + match self { + Solicit(message) => message.encode(e), + Advertise(message) => message.encode(e), + Request(message) => message.encode(e), + Confirm(message) => message.encode(e), + Renew(message) => message.encode(e), + Rebind(message) => message.encode(e), + Reply(message) => message.encode(e), + Release(message) => message.encode(e), + Decline(message) => message.encode(e), + Reconfigure(message) => message.encode(e), + InformationRequest(message) => message.encode(e), + RelayForw(message) => message.encode(e), + RelayRepl(message) => message.encode(e), + LeaseQuery(message) => message.encode(e), + LeaseQueryReply(message) => message.encode(e), + /* + ReconfigureRequest(message) => message.encode(e), + ReconfigureReply(message) => message.encode(e), + DHCPv4Query(message) => message.encode(e), + DHCPv4Response(message) => message.encode(e), + */ + Unknown(message) => e.write_slice(message), + } + } +} + +impl Decodable for Message { + fn decode(decoder: &mut Decoder<'_>) -> DecodeResult { + Ok(match MessageType::from(decoder.peek_u8()?) { + MessageType::Solicit => Message::Solicit(Solicit::decode(decoder)?), + MessageType::Advertise => Message::Advertise(Advertise::decode(decoder)?), + MessageType::Request => Message::Request(Request::decode(decoder)?), + MessageType::Confirm => Message::Confirm(Confirm::decode(decoder)?), + MessageType::Renew => Message::Renew(Renew::decode(decoder)?), + MessageType::Rebind => Message::Rebind(Rebind::decode(decoder)?), + MessageType::Reply => Message::Reply(Reply::decode(decoder)?), + MessageType::Release => Message::Release(Release::decode(decoder)?), + MessageType::Decline => Message::Decline(Decline::decode(decoder)?), + MessageType::Reconfigure => Message::Reconfigure(Reconfigure::decode(decoder)?), + MessageType::InformationRequest => { + Message::InformationRequest(InformationRequest::decode(decoder)?) + } + MessageType::RelayForw => Message::RelayForw(RelayForw::decode(decoder)?), + MessageType::RelayRepl => Message::RelayRepl(RelayRepl::decode(decoder)?), + MessageType::LeaseQuery => Message::LeaseQuery(LeaseQuery::decode(decoder)?), + MessageType::LeaseQueryReply => { + Message::LeaseQueryReply(LeaseQueryReply::decode(decoder)?) + } + /* + MessageType::ReconfigureRequest => Message::ReconfigureRequest(ReconfigureRequest::decode(decoder)?), + MessageType::ReconfigureReply => Message::ReconfigureReply(ReconfigureReply::decode(decoder)?), + MessageType::DHCPv4Query => Message::DHCPv4Query(DHCPv4Query::decode(decoder)?), + MessageType::DHCPv4Response => Message::DHCPv4Response(DHCPv4Response::decode(decoder)?), + */ + _ => Message::Unknown({ + let mut buf = vec![]; + while let Ok(b) = decoder.read_u8() { + buf.push(b); + } + buf + }), + }) + } +} + +option_builder!( + MessageOption, + MessageOptions, + IsMessageOption, + DhcpOption, + ClientId, + ServerId, + IANA, + IATA, + IAAddr, + IAPD, + IAPrefix, + ORO, + Preference, + ElapsedTime, + Auth, + Unicast, + StatusCode, + RapidCommit, + UserClass, + VendorClass, + VendorOpts, + ReconfMsg, + ReconfAccept, + InformationRefreshTime, + SolMaxRt, + InfMaxRt, + DNSServers, + DomainList +); + +option_builder!( + RelayMessageOption, + RelayMessageOptions, + IsRelayMessageOption, + DhcpOption, + RelayMsg, + VendorOpts, + InterfaceId +); + +option_builder!( + SolicitOption, + SolicitOptions, + IsSolicitOption, + DhcpOption, + ClientId, + IANA, + IATA, + IAPD, + ORO, + ElapsedTime, + RapidCommit, + UserClass, + VendorClass, + VendorOpts, + ReconfAccept +); + +option_builder!( + AdvertiseOption, + AdvertiseOptions, + IsAdvertiseOption, + DhcpOption, + ClientId, + ServerId, + IANA, + IATA, + IAPD, + Preference, + StatusCode, + UserClass, + VendorClass, + VendorOpts, + ReconfAccept, + SolMaxRt +); + +option_builder!( + RequestOption, + RequestOptions, + IsRequestOption, + DhcpOption, + ClientId, + ServerId, + IANA, + IATA, + IAPD, + ElapsedTime, + UserClass, + VendorClass, + VendorOpts, + ReconfAccept +); + +option_builder!( + ConfirmOption, + ConfirmOptions, + IsConfirmOption, + DhcpOption, + ClientId, + IANA, + IATA, + ElapsedTime, + UserClass, + VendorClass, + VendorOpts +); + +option_builder!( + RenewOption, + RenewOptions, + IsRenewOption, + DhcpOption, + ClientId, + ServerId, + IANA, + IATA, + IAPD, + ORO, + ElapsedTime, + UserClass, + VendorClass, + VendorOpts, + ReconfAccept +); + +option_builder!( + RebindOption, + RebindOptions, + IsRebindOption, + DhcpOption, + ClientId, + IANA, + IATA, + IAPD, + ORO, + ElapsedTime, + UserClass, + VendorClass, + VendorOpts, + ReconfAccept +); + +option_builder!( + DeclineOption, + DeclineOptions, + IsDeclineOption, + DhcpOption, + ClientId, + ServerId, + IANA, + IATA, + IAPD, + ElapsedTime, + UserClass, + VendorClass, + VendorOpts +); + +option_builder!( + ReleaseOption, + ReleaseOptions, + IsReleaseOption, + DhcpOption, + ClientId, + ServerId, + IANA, + IATA, + IAPD, + ElapsedTime, + UserClass, + VendorClass, + VendorOpts +); + +option_builder!( + ReplyOption, + ReplyOptions, + IsReplyOption, + DhcpOption, + ClientId, + ServerId, + IANA, + IATA, + IAPD, + Auth, + Unicast, + StatusCode, + RapidCommit, + UserClass, + VendorClass, + VendorOpts, + ReconfAccept, + InformationRefreshTime, + SolMaxRt, + InfMaxRt +); + +option_builder!( + ReconfigureOption, + ReconfigureOptions, + IsReconfigureOption, + DhcpOption, + ClientId, + ServerId, + Auth, + ReconfMsg +); + +option_builder!( + InformationRequestOption, + InformationRequestOptions, + IsInformationRequestOption, + DhcpOption, + ClientId, + ServerId, + ORO, + ElapsedTime, + UserClass, + VendorClass, + VendorOpts, + ReconfAccept +); + +option_builder!( + LeaseQueryOption, + LeaseQueryOptions, + IsLeaseQueryOption, + DhcpOption, + LqQuery +); + +option_builder!( + LeaseQueryReplyOption, + LeaseQueryReplyOptions, + IsLeaseQueryReplyOption, + DhcpOption, + ClientData, + LqRelayData, + LqClientLink +); + +//TODO: work out which options are alloud in LeaseQueryData message +option_builder!( + LeaseQueryDataOption, + LeaseQueryDataOptions, + IsLeaseQueryDataOption, + DhcpOption, +); +//TODO: work out which options are alloud in LeaseQueryDone message +option_builder!( + LeaseQueryDoneOption, + LeaseQueryDoneOptions, + IsLeaseQueryDoneOption, + DhcpOption, +); + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct TransactionId { + pub id: [u8; 3], +} + +impl Default for TransactionId { + fn default() -> Self { + Self { id: rand::random() } + } +} + +impl Encodable for TransactionId { + fn encode(&self, e: &mut Encoder<'_>) -> EncodeResult<()> { + e.write_slice(&self.id)?; + Ok(()) + } +} + +impl Decodable for TransactionId { + fn decode(decoder: &mut Decoder<'_>) -> DecodeResult { + Ok(TransactionId { + id: decoder.read::<3>()?, + }) + } +} + +macro_rules! base_message_builder { + ($name: ident, $options: ident, $($messagetype: ident),*) => { + $( + impl From<$name> for $messagetype { + fn from(message: $name) -> $messagetype { + $messagetype::$name(message) + } + } + )* + + impl $name { + /// Get a reference to the message's options. + pub fn opts(&self) -> &$options { + &self.opts + } + /// Get a mutable reference to the message's options. + pub fn opts_mut(&mut self) -> &mut $options { + &mut self.opts + } + } + }; +} + +macro_rules! client_server_message_builder { + ($name: ident, $options: ident, $($messagetype: ident),*) => { + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] + #[derive(Debug, Clone, PartialEq, Eq, Default)] + pub struct $name { + pub xid: TransactionId, + pub opts: $options, + } + + impl $name { + /// returns a new `Message` with a random xid and empty opt section + pub fn new() -> Self { + Self::default() + } + /// returns a new `Message` with an empty opt section + pub fn new_with_xid>(xid: T) -> Self { + Self { + xid: xid.into(), + ..Self::default() + } + } + } + + base_message_builder!($name, $options, $($messagetype),*); + + impl Encodable for $name { + fn encode(&self, e: &mut Encoder<'_>) -> EncodeResult<()> { + e.write_u8(MessageType::$name.into())?; + self.xid.encode(e)?; + self.opts.encode(e)?; + Ok(()) + } + } + + impl Decodable for $name { + fn decode(decoder: &mut Decoder<'_>) -> DecodeResult { + let _message_type = decoder.read_u8()?; + Ok(Self { + xid: TransactionId::decode(decoder)?, + opts: $options::decode(decoder)?, + }) + } + } + }; +} + +macro_rules! relay_message_builder { + ($name: ident, $options: ident, $($messagetype: ident),*) => { + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] + #[derive(Debug, Clone, PartialEq, Eq)] + pub struct $name { + pub hop_count: u8, + pub link_address: Ipv6Addr, + pub peer_address: Ipv6Addr, + pub opts: $options, + } + + base_message_builder!($name, $options, $($messagetype)*); + + impl Encodable for $name { + fn encode(&self, e: &mut Encoder<'_>) -> EncodeResult<()> { + e.write_u8(MessageType::$name.into())?; + e.write_u8(self.hop_count)?; + e.write::<16>(self.link_address.octets())?; + e.write::<16>(self.peer_address.octets())?; + self.opts.encode(e)?; + Ok(()) + } + } + + impl Decodable for $name { + fn decode(decoder: &mut Decoder<'_>) -> DecodeResult { + let _message_type = decoder.read_u8()?; + Ok(Self { + hop_count: decoder.read_u8()?, + link_address: decoder.read::<16>()?.into(), + peer_address: decoder.read::<16>()?.into(), + opts: $options::decode(decoder)?, + }) + } + } + }; +} + +/*macro_rules! dhcp4o6_message_builder { + ($name: ident, $options: ident, $($messagetype: ident),*) => { + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] + #[derive(Debug, Clone, PartialEq, Eq)] + pub struct $name { + pub flags: [u8;3], + pub opts: $options, + } + + base_message_builder!($name, $options, $($messagetype)*); + + impl Encodable for $name { + fn encode(&self, e: &mut Encoder<'_>) -> EncodeResult<()> { + e.write_u8(MessageType::$name.into())?; + e.write_slice(self.flags)?; + self.opts.encode(e)?; + Ok(()) + } + } + + impl Decodable for $name { + fn decode(decoder: &mut Decoder<'_>) -> DecodeResult { + let _message_type = decoder.read_u8()?; + Ok(Self { + flags: decoder.read::<3>()?, + opts: $options::decode(decoder)?, + }) + } + } + }; +}*/ + +client_server_message_builder!(Solicit, SolicitOptions, Message); +client_server_message_builder!(Advertise, AdvertiseOptions, Message); +client_server_message_builder!(Request, RequestOptions, Message); +client_server_message_builder!(Confirm, ConfirmOptions, Message); +client_server_message_builder!(Renew, RenewOptions, Message); +client_server_message_builder!(Rebind, RebindOptions, Message); +client_server_message_builder!(Reply, ReplyOptions, Message); +client_server_message_builder!(Decline, DeclineOptions, Message); +client_server_message_builder!(Release, ReleaseOptions, Message); +client_server_message_builder!(Reconfigure, ReconfigureOptions, Message); +client_server_message_builder!(InformationRequest, InformationRequestOptions, Message); + +relay_message_builder!(RelayForw, RelayMessageOptions, Message); +relay_message_builder!(RelayRepl, RelayMessageOptions, Message); + +client_server_message_builder!( + LeaseQuery, + LeaseQueryOptions, + Message, + BulkLeaseQueryMessage +); +client_server_message_builder!( + LeaseQueryReply, + LeaseQueryReplyOptions, + Message, + BulkLeaseQueryMessage +); + +client_server_message_builder!(LeaseQueryData, LeaseQueryDataOptions, BulkLeaseQueryMessage); +client_server_message_builder!(LeaseQueryDone, LeaseQueryDoneOptions, BulkLeaseQueryMessage); diff --git a/src/v6/mod.rs b/src/v6/mod.rs index 67434b2..c298817 100644 --- a/src/v6/mod.rs +++ b/src/v6/mod.rs @@ -8,14 +8,17 @@ //! # fn main() -> Result<(), Box> { //! use dhcproto::{v6, Encodable, Encoder}; //! // arbitrary DUID -//! let duid = vec![ +//! let duid = v6::Duid::from(vec![ //! 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, -//! ]; -//! // construct a new Message with a random xid -//! let mut msg = v6::Message::new(v6::MessageType::Solicit); +//! ]); +//! // construct a new Solicit Message with a random xid +//! let mut msg = v6::Solicit::new(); //! // set an option //! msg.opts_mut() -//! .insert(v6::DhcpOption::ClientId(duid)); +//! .insert(v6::ClientId{id: duid}); +//! +//! //access an option +//! let _id = msg.opts().get::(); //! //! // now encode to bytes //! let mut buf = Vec::new(); @@ -52,15 +55,34 @@ //! # Ok(()) } //! ``` //! -mod options; +mod duid; +mod option_codes; +pub mod options; +///options +pub use options::{ + Auth, ClientData, ClientId, CltTime, DNSServers, DomainList, ElapsedTime, IAAddr, IAPrefix, + InfMaxRt, InformationRefreshTime, InterfaceId, LinkAddress, LqClientLink, LqQuery, LqRelayData, + Preference, RapidCommit, ReconfAccept, ReconfMsg, RelayId, RelayMsg, ServerId, SolMaxRt, + StatusCode, Unicast, UserClass, VendorClass, VendorOpts, IANA, IAPD, IATA, ORO, +}; +pub mod messages; +mod oro_codes; +///messages +pub use messages::{ + Advertise, BulkLeaseQueryMessage, Confirm, Decline, InformationRequest, LeaseQuery, + LeaseQueryData, LeaseQueryDone, LeaseQueryReply, Message, Rebind, Reconfigure, RelayForw, + RelayRepl, Release, Renew, Reply, Request, Solicit, +}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; -use std::{convert::TryInto, fmt, net::Ipv6Addr}; +use std::{convert::TryInto, net::Ipv6Addr}; // re-export submodules from proto::msg -pub use self::options::*; +pub use self::duid::*; +pub use self::option_codes::*; +pub use self::oro_codes::*; pub use crate::{ decoder::{Decodable, Decoder}, @@ -73,142 +95,6 @@ pub const SERVER_PORT: u16 = 547; /// default dhcpv6 client port pub const CLIENT_PORT: u16 = 546; -/// See RFC 8415 for updated DHCPv6 info -/// [DHCP for Ipv6](https://datatracker.ietf.org/doc/html/rfc8415) -/// -/// All DHCP messages sent between clients and servers share an identical -/// fixed-format header and a variable-format area for options. -/// -/// All values in the message header and in options are in network byte -/// order. -/// -/// Options are stored serially in the "options" field, with no padding -/// between the options. Options are byte-aligned but are not aligned in -/// any other way (such as on 2-byte or 4-byte boundaries). -/// -/// The following diagram illustrates the format of DHCP messages sent -/// between clients and servers: -/// -/// ```text -/// 0 1 2 3 -/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 -/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -/// | msg-type | transaction-id | -/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -/// | | -/// . options . -/// . (variable number and length) . -/// | | -/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -/// -/// msg-type Identifies the DHCP message type; the -/// available message types are listed in -/// Section 7.3. A 1-octet field. -/// -/// transaction-id The transaction ID for this message exchange. -/// A 3-octet field. -/// -/// options Options carried in this message; options are -/// described in Section 21. A variable-length -/// field (4 octets less than the size of the -/// message). -/// ``` -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Message { - /// message type - /// - msg_type: MessageType, - /// transaction id - /// trns id must be the same for all messages in a DHCP transaction - /// - xid: [u8; 3], - /// Options - /// - opts: DhcpOptions, -} - -impl Default for Message { - fn default() -> Self { - Self { - msg_type: MessageType::Solicit, - xid: rand::random(), - opts: DhcpOptions::new(), - } - } -} - -impl Message { - /// returns a new `Message` with a random xid and empty opt section - pub fn new(msg_type: MessageType) -> Self { - Self { - msg_type, - ..Self::default() - } - } - - /// returns a new `Message` with a given xid and message type and empty opt section - pub fn new_with_id(msg_type: MessageType, xid: [u8; 3]) -> Self { - Self { - msg_type, - xid, - ..Self::default() - } - } - - /// Get the message's message type. - pub fn msg_type(&self) -> MessageType { - self.msg_type - } - - /// Set message type - pub fn set_msg_type(&mut self, msg_type: MessageType) -> &mut Self { - self.msg_type = msg_type; - self - } - - /// Get the message's transaction id. - pub fn xid(&self) -> [u8; 3] { - self.xid - } - - /// Get the msgs transaction id as a number - pub fn xid_num(&self) -> u32 { - u32::from_be_bytes([0, self.xid[0], self.xid[1], self.xid[2]]) - } - - /// Set transaction id - pub fn set_xid(&mut self, xid: [u8; 3]) -> &mut Self { - self.xid = xid; - self - } - - /// Set transaction id from u32, will only use last 3 bytes - pub fn set_xid_num(&mut self, xid: u32) -> &mut Self { - let arr = xid.to_be_bytes(); - self.xid = arr[1..=3] - .try_into() - .expect("a u32 has 4 bytes so this shouldn't fail"); - self - } - - /// Get a reference to the message's options. - pub fn opts(&self) -> &DhcpOptions { - &self.opts - } - - /// Set DHCP opts - pub fn set_opts(&mut self, opts: DhcpOptions) -> &mut Self { - self.opts = opts; - self - } - - /// Get a mutable reference to the message's options. - pub fn opts_mut(&mut self) -> &mut DhcpOptions { - &mut self.opts - } -} - /// DHCPv6 message types /// #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] @@ -335,120 +221,6 @@ impl From for u8 { } } -impl Decodable for Message { - fn decode(decoder: &mut Decoder<'_>) -> DecodeResult { - Ok(Message { - msg_type: decoder.read_u8()?.into(), - xid: decoder.read::<3>()?, - opts: DhcpOptions::decode(decoder)?, - }) - } -} - -impl Encodable for Message { - fn encode(&self, e: &mut Encoder<'_>) -> EncodeResult<()> { - e.write_u8(self.msg_type.into())?; - e.write(self.xid)?; - self.opts.encode(e)?; - Ok(()) - } -} - -impl fmt::Display for Message { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Message") - .field("xid", &self.xid_num()) - .field("msg_type", &self.msg_type()) - .field("opts", &self.opts()) - .finish() - } -} - -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct RelayMessage { - /// message type - /// - msg_type: MessageType, - /// hop count - /// - hop_count: u8, - /// link address - /// - link_addr: Ipv6Addr, - /// peer address - /// - peer_addr: Ipv6Addr, - /// Options - /// - opts: DhcpOptions, -} - -impl RelayMessage { - pub fn msg_type(&self) -> MessageType { - self.msg_type - } - pub fn hop_count(&self) -> u8 { - self.hop_count - } - pub fn link_addr(&self) -> Ipv6Addr { - self.link_addr - } - pub fn peer_addr(&self) -> Ipv6Addr { - self.peer_addr - } - /// Get a reference to the message's options. - pub fn opts(&self) -> &DhcpOptions { - &self.opts - } - - /// Set DHCP opts - pub fn set_opts(&mut self, opts: DhcpOptions) -> &mut Self { - self.opts = opts; - self - } - - /// Get a mutable reference to the message's options. - pub fn opts_mut(&mut self) -> &mut DhcpOptions { - &mut self.opts - } -} - -impl Decodable for RelayMessage { - fn decode(decoder: &mut Decoder<'_>) -> DecodeResult { - Ok(Self { - msg_type: decoder.read_u8()?.into(), - hop_count: decoder.read_u8()?, - link_addr: decoder.read::<16>()?.into(), - peer_addr: decoder.read::<16>()?.into(), - opts: DhcpOptions::decode(decoder)?, - }) - } -} - -impl Encodable for RelayMessage { - fn encode(&self, e: &mut Encoder<'_>) -> EncodeResult<()> { - e.write_u8(self.msg_type.into())?; - e.write_u8(self.hop_count)?; - e.write_slice(&self.link_addr.octets())?; - e.write_slice(&self.peer_addr.octets())?; - self.opts.encode(e)?; - Ok(()) - } -} - -impl fmt::Display for RelayMessage { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("RelayMessage") - .field("msg_type", &self.msg_type()) - .field("hop_count", &self.hop_count()) - .field("link_addr", &self.link_addr()) - .field("peer_addr", &self.peer_addr()) - .field("opts", &self.opts()) - .finish() - } -} - #[cfg(test)] mod tests { @@ -460,7 +232,7 @@ mod tests { // decode let msg = Message::decode(&mut Decoder::new(&input))?; dbg!(&msg); - assert_eq!(mtype, msg.msg_type); + assert_eq!(mtype, msg.msg_type()); // now encode let mut buf = Vec::new(); let mut e = Encoder::new(&mut buf); @@ -502,15 +274,10 @@ mod tests { #[test] fn xid_num() { - let mut msg = Message::default(); - msg.set_xid_num(16_777_215); - assert_eq!(msg.xid_num(), 16_777_215); - - msg.set_xid_num(16_777_000); - assert_eq!(msg.xid_num(), 16_777_000); + let msg = Solicit::default(); + let other_msg = Reply::new_with_xid(msg.xid); - msg.set_xid_num(8); - assert_eq!(msg.xid_num(), 8); + assert_eq!(msg.xid, other_msg.xid); } #[cfg(feature = "serde")] #[test] diff --git a/src/v6/option_codes.rs b/src/v6/option_codes.rs new file mode 100644 index 0000000..74ae5d0 --- /dev/null +++ b/src/v6/option_codes.rs @@ -0,0 +1,485 @@ +use crate::v6::options::DhcpOption; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// option code type +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum OptionCode { + ClientId, + ServerId, + IANA, + IATA, + IAAddr, + ORO, + Preference, + ElapsedTime, + RelayMsg, + Auth, + Unicast, + StatusCode, + RapidCommit, + UserClass, + VendorClass, + VendorOpts, + InterfaceId, + ReconfMsg, + ReconfAccept, + SipServerD, + SipServerA, + DNSServers, + DomainList, + IAPD, + IAPrefix, + NisServers, + NispServers, + NisDomainName, + NispDomainName, + SntpServers, + InformationRefreshTime, + BcmcsServerD, + BcmcsServerA, + GeoconfCivic, + RemoteId, + SubscriberId, + ClientFqdn, + PanaAgent, + NewPosixTimezone, + NewTzdbTimezone, + ERO, + LqQuery, + ClientData, + CltTime, + LqRelayData, + LqClientLink, + Mip6Hnidf, + Mip6Vdinf, + V6Lost, + CapwapAcV6, + RelayId, + Ipv6AddressMoS, + Ipv6FQDNMoS, + NtpServer, + V6AccessDomain, + SipUaCsList, + OptBootfileUrl, + OptBootfileParam, + ClientArchType, + Nii, + Geolocation, + AftrName, + ErpLocalDomainName, + Rsoo, + PdExclude, + Vss, + Mip6Idinf, + Mip6Udinf, + Mip6Hnp, + Mip6Haa, + Mip6Haf, + RdnssSelection, + KrbPrincipalName, + KrbRealmName, + KrbDefaultRealmName, + KrbKdc, + ClientLinklayerAddr, + LinkAddress, + Radius, + SolMaxRt, + InfMaxRt, + Addrsel, + AddrselTable, + V6PcpServer, + Dhcpv4Msg, + Dhcp4ODhcp6Server, + S46Rule, + S46Br, + S46Dmr, + S46V4v6bind, + S46Portparams, + S46ContMape, + S46ContMapt, + S46ContLw, + _4Rd, + _4RdMapRule, + _4RdNonMapRule, + LqBaseTime, + LqStartTime, + LqEndTime, + DhcpCaptivePortal, + MplParameters, + AniAtt, + AniNetworkName, + AniApName, + AniApBssid, + AniOperatorId, + AniOperatorRealm, + S46Priority, + MudUrlV6, + V6Prefix64, + FBindingStatus, + FConnectFlags, + Fdnsremovalinfo, + FDNSHostName, + FDNSZoneName, + Fdnsflags, + Fexpirationtime, + FMaxUnackedBndupd, + FMclt, + FPartnerLifetime, + FPartnerLifetimeSent, + FPartnerDownTime, + FPartnerRawCltTime, + FProtocolVersion, + FKeepaliveTime, + FReconfigureData, + FRelationshipName, + FServerFlags, + FServerState, + FStartTimeOfState, + FStateExpirationTime, + RelayPort, + Ipv6AddressANDSF, + Unknown(u16), +} + +impl From for u16 { + fn from(opt: OptionCode) -> Self { + use OptionCode::*; + match opt { + ClientId => 1, + ServerId => 2, + IANA => 3, + IATA => 4, + IAAddr => 5, + ORO => 6, + Preference => 7, + ElapsedTime => 8, + RelayMsg => 9, + Auth => 11, + Unicast => 12, + StatusCode => 13, + RapidCommit => 14, + UserClass => 15, + VendorClass => 16, + VendorOpts => 17, + InterfaceId => 18, + ReconfMsg => 19, + ReconfAccept => 20, + SipServerD => 21, + SipServerA => 22, + DNSServers => 23, + DomainList => 24, + IAPD => 25, + IAPrefix => 26, + NisServers => 27, + NispServers => 28, + NisDomainName => 29, + NispDomainName => 30, + SntpServers => 31, + InformationRefreshTime => 32, + BcmcsServerD => 33, + BcmcsServerA => 34, + GeoconfCivic => 36, + RemoteId => 37, + SubscriberId => 38, + ClientFqdn => 39, + PanaAgent => 40, + NewPosixTimezone => 41, + NewTzdbTimezone => 42, + ERO => 43, + LqQuery => 44, + ClientData => 45, + CltTime => 46, + LqRelayData => 47, + LqClientLink => 48, + Mip6Hnidf => 49, + Mip6Vdinf => 50, + V6Lost => 51, + CapwapAcV6 => 52, + RelayId => 53, + Ipv6AddressMoS => 54, + Ipv6FQDNMoS => 55, + NtpServer => 56, + V6AccessDomain => 57, + SipUaCsList => 58, + OptBootfileUrl => 59, + OptBootfileParam => 60, + ClientArchType => 61, + Nii => 62, + Geolocation => 63, + AftrName => 64, + ErpLocalDomainName => 65, + Rsoo => 66, + PdExclude => 67, + Vss => 68, + Mip6Idinf => 69, + Mip6Udinf => 70, + Mip6Hnp => 71, + Mip6Haa => 72, + Mip6Haf => 73, + RdnssSelection => 74, + KrbPrincipalName => 75, + KrbRealmName => 76, + KrbDefaultRealmName => 77, + KrbKdc => 78, + ClientLinklayerAddr => 79, + LinkAddress => 80, + Radius => 81, + SolMaxRt => 82, + InfMaxRt => 83, + Addrsel => 84, + AddrselTable => 85, + V6PcpServer => 86, + Dhcpv4Msg => 87, + Dhcp4ODhcp6Server => 88, + S46Rule => 89, + S46Br => 90, + S46Dmr => 91, + S46V4v6bind => 92, + S46Portparams => 93, + S46ContMape => 94, + S46ContMapt => 95, + S46ContLw => 96, + _4Rd => 97, + _4RdMapRule => 98, + _4RdNonMapRule => 99, + LqBaseTime => 100, + LqStartTime => 101, + LqEndTime => 102, + DhcpCaptivePortal => 103, + MplParameters => 104, + AniAtt => 105, + AniNetworkName => 106, + AniApName => 107, + AniApBssid => 108, + AniOperatorId => 109, + AniOperatorRealm => 110, + S46Priority => 111, + MudUrlV6 => 112, + V6Prefix64 => 113, + FBindingStatus => 114, + FConnectFlags => 115, + Fdnsremovalinfo => 116, + FDNSHostName => 117, + FDNSZoneName => 118, + Fdnsflags => 119, + Fexpirationtime => 120, + FMaxUnackedBndupd => 121, + FMclt => 122, + FPartnerLifetime => 123, + FPartnerLifetimeSent => 124, + FPartnerDownTime => 125, + FPartnerRawCltTime => 126, + FProtocolVersion => 127, + FKeepaliveTime => 128, + FReconfigureData => 129, + FRelationshipName => 130, + FServerFlags => 131, + FServerState => 132, + FStartTimeOfState => 133, + FStateExpirationTime => 134, + RelayPort => 135, + Ipv6AddressANDSF => 143, + Unknown(n) => n, + } + } +} + +impl From for OptionCode { + fn from(n: u16) -> Self { + use OptionCode::*; + match n { + 1 => ClientId, + 2 => ServerId, + 3 => IANA, + 4 => IATA, + 5 => IAAddr, + 6 => ORO, + 7 => Preference, + 8 => ElapsedTime, + 9 => RelayMsg, + 11 => Auth, + 12 => Unicast, + 13 => StatusCode, + 14 => RapidCommit, + 15 => UserClass, + 16 => VendorClass, + 17 => VendorOpts, + 18 => InterfaceId, + 19 => ReconfMsg, + 20 => ReconfAccept, + 21 => SipServerD, + 22 => SipServerA, + 23 => DNSServers, + 24 => DomainList, + 25 => IAPD, + 26 => IAPrefix, + 27 => NisServers, + 28 => NispServers, + 29 => NisDomainName, + 30 => NispDomainName, + 31 => SntpServers, + 32 => InformationRefreshTime, + 33 => BcmcsServerD, + 34 => BcmcsServerA, + 36 => GeoconfCivic, + 37 => RemoteId, + 38 => SubscriberId, + 39 => ClientFqdn, + 40 => PanaAgent, + 41 => NewPosixTimezone, + 42 => NewTzdbTimezone, + 43 => ERO, + 44 => LqQuery, + 45 => ClientData, + 46 => CltTime, + 47 => LqRelayData, + 48 => LqClientLink, + 49 => Mip6Hnidf, + 50 => Mip6Vdinf, + 51 => V6Lost, + 52 => CapwapAcV6, + 53 => RelayId, + 54 => Ipv6AddressMoS, + 55 => Ipv6FQDNMoS, + 56 => NtpServer, + 57 => V6AccessDomain, + 58 => SipUaCsList, + 59 => OptBootfileUrl, + 60 => OptBootfileParam, + 61 => ClientArchType, + 62 => Nii, + 63 => Geolocation, + 64 => AftrName, + 65 => ErpLocalDomainName, + 66 => Rsoo, + 67 => PdExclude, + 68 => Vss, + 69 => Mip6Idinf, + 70 => Mip6Udinf, + 71 => Mip6Hnp, + 72 => Mip6Haa, + 73 => Mip6Haf, + 74 => RdnssSelection, + 75 => KrbPrincipalName, + 76 => KrbRealmName, + 77 => KrbDefaultRealmName, + 78 => KrbKdc, + 79 => ClientLinklayerAddr, + 80 => LinkAddress, + 81 => Radius, + 82 => SolMaxRt, + 83 => InfMaxRt, + 84 => Addrsel, + 85 => AddrselTable, + 86 => V6PcpServer, + 87 => Dhcpv4Msg, + 88 => Dhcp4ODhcp6Server, + 89 => S46Rule, + 90 => S46Br, + 91 => S46Dmr, + 92 => S46V4v6bind, + 93 => S46Portparams, + 94 => S46ContMape, + 95 => S46ContMapt, + 96 => S46ContLw, + 97 => _4Rd, + 98 => _4RdMapRule, + 99 => _4RdNonMapRule, + 100 => LqBaseTime, + 101 => LqStartTime, + 102 => LqEndTime, + 103 => DhcpCaptivePortal, + 104 => MplParameters, + 105 => AniAtt, + 106 => AniNetworkName, + 107 => AniApName, + 108 => AniApBssid, + 109 => AniOperatorId, + 110 => AniOperatorRealm, + 111 => S46Priority, + 112 => MudUrlV6, + 113 => V6Prefix64, + 114 => FBindingStatus, + 115 => FConnectFlags, + 116 => Fdnsremovalinfo, + 117 => FDNSHostName, + 118 => FDNSZoneName, + 119 => Fdnsflags, + 120 => Fexpirationtime, + 121 => FMaxUnackedBndupd, + 122 => FMclt, + 123 => FPartnerLifetime, + 124 => FPartnerLifetimeSent, + 125 => FPartnerDownTime, + 126 => FPartnerRawCltTime, + 127 => FProtocolVersion, + 128 => FKeepaliveTime, + 129 => FReconfigureData, + 130 => FRelationshipName, + 131 => FServerFlags, + 132 => FServerState, + 133 => FStartTimeOfState, + 134 => FStateExpirationTime, + 135 => RelayPort, + 143 => Ipv6AddressANDSF, + _ => Unknown(n), + } + } +} + +impl PartialOrd for OptionCode { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for OptionCode { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + u16::from(*self).cmp(&u16::from(*other)) + } +} + +impl From<&DhcpOption> for OptionCode { + fn from(opt: &DhcpOption) -> Self { + use DhcpOption::*; + match opt { + ClientId(_) => OptionCode::ClientId, + ServerId(_) => OptionCode::ServerId, + IANA(_) => OptionCode::IANA, + IATA(_) => OptionCode::IATA, + IAAddr(_) => OptionCode::IAAddr, + ORO(_) => OptionCode::ORO, + Preference(_) => OptionCode::Preference, + ElapsedTime(_) => OptionCode::ElapsedTime, + RelayMsg(_) => OptionCode::RelayMsg, + Auth(_) => OptionCode::Auth, + Unicast(_) => OptionCode::Unicast, + StatusCode(_) => OptionCode::StatusCode, + RapidCommit(_) => OptionCode::RapidCommit, + UserClass(_) => OptionCode::UserClass, + VendorClass(_) => OptionCode::VendorClass, + VendorOpts(_) => OptionCode::VendorOpts, + InterfaceId(_) => OptionCode::InterfaceId, + ReconfMsg(_) => OptionCode::ReconfMsg, + ReconfAccept(_) => OptionCode::ReconfAccept, + DNSServers(_) => OptionCode::DNSServers, + DomainList(_) => OptionCode::DomainList, + IAPD(_) => OptionCode::IAPD, + IAPrefix(_) => OptionCode::IAPrefix, + InformationRefreshTime(_) => OptionCode::InformationRefreshTime, + SolMaxRt(_) => OptionCode::SolMaxRt, + InfMaxRt(_) => OptionCode::InfMaxRt, + LqQuery(_) => OptionCode::LqQuery, + ClientData(_) => OptionCode::ClientData, + CltTime(_) => OptionCode::CltTime, + LqRelayData(_) => OptionCode::LqRelayData, + LqClientLink(_) => OptionCode::LqClientLink, + RelayId(_) => OptionCode::RelayId, + LinkAddress(_) => OptionCode::LinkAddress, + Unknown(unknown) => unknown.into(), + } + } +} diff --git a/src/v6/options.rs b/src/v6/options.rs deleted file mode 100644 index e53e445..0000000 --- a/src/v6/options.rs +++ /dev/null @@ -1,1120 +0,0 @@ -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; -use trust_dns_proto::{ - rr::Name, - serialize::binary::{BinDecodable, BinDecoder, BinEncodable, BinEncoder}, -}; - -use std::{cmp::Ordering, net::Ipv6Addr, ops::RangeInclusive}; - -use crate::Domain; -use crate::{ - decoder::{Decodable, Decoder}, - encoder::{Encodable, Encoder}, - error::{DecodeResult, EncodeResult}, - v4::HType, - v6::{MessageType, RelayMessage}, -}; - -// server can send multiple IA_NA options to request multiple addresses -// so we must be able to handle multiple of the same option type -// -// TODO: consider HashMap> - -/// -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq, Default)] -pub struct DhcpOptions(Vec); -// vec maintains sorted on OptionCode - -impl DhcpOptions { - /// construct empty DhcpOptions - pub fn new() -> Self { - Self::default() - } - /// get the first element matching this option code - pub fn get(&self, code: OptionCode) -> Option<&DhcpOption> { - let first = first(&self.0, |x| OptionCode::from(x).cmp(&code))?; - // get_unchecked? - self.0.get(first) - } - /// get all elements matching this option code - pub fn get_all(&self, code: OptionCode) -> Option<&[DhcpOption]> { - let range = range_binsearch(&self.0, |x| OptionCode::from(x).cmp(&code))?; - Some(&self.0[range]) - } - /// get the first element matching this option code - pub fn get_mut(&mut self, code: OptionCode) -> Option<&mut DhcpOption> { - let first = first(&self.0, |x| OptionCode::from(x).cmp(&code))?; - self.0.get_mut(first) - } - /// get all elements matching this option code - pub fn get_mut_all(&mut self, code: OptionCode) -> Option<&mut [DhcpOption]> { - let range = range_binsearch(&self.0, |x| OptionCode::from(x).cmp(&code))?; - Some(&mut self.0[range]) - } - /// remove the first element with a matching option code - pub fn remove(&mut self, code: OptionCode) -> Option { - let first = first(&self.0, |x| OptionCode::from(x).cmp(&code))?; - Some(self.0.remove(first)) - } - /// remove all elements with a matching option code - pub fn remove_all( - &mut self, - code: OptionCode, - ) -> Option + '_> { - let range = range_binsearch(&self.0, |x| OptionCode::from(x).cmp(&code))?; - Some(self.0.drain(range)) - } - /// insert a new option into the list of opts - pub fn insert(&mut self, opt: DhcpOption) { - let i = self.0.partition_point(|x| x < &opt); - self.0.insert(i, opt) - } - /// return a reference to an iterator - pub fn iter(&self) -> impl Iterator { - self.0.iter() - } - /// return a mutable ref to an iterator - pub fn iter_mut(&mut self) -> impl Iterator { - self.0.iter_mut() - } -} - -impl IntoIterator for DhcpOptions { - type Item = DhcpOption; - - type IntoIter = std::vec::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -impl FromIterator for DhcpOptions { - fn from_iter>(iter: T) -> Self { - let mut opts = iter.into_iter().collect::>(); - opts.sort_unstable(); - DhcpOptions(opts) - } -} - -/// Duid helper type -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Duid(Vec); -// TODO: define specific duid types - -impl Duid { - /// new DUID link layer address with time - pub fn link_layer_time(htype: HType, time: u32, addr: Ipv6Addr) -> Self { - let mut buf = Vec::new(); - let mut e = Encoder::new(&mut buf); - e.write_u16(1).unwrap(); // duid type - e.write_u16(u8::from(htype) as u16).unwrap(); - e.write_u32(time).unwrap(); - e.write_u128(addr.into()).unwrap(); - Self(buf) - } - /// new DUID enterprise number - pub fn enterprise(enterprise: u32, id: &[u8]) -> Self { - let mut buf = Vec::new(); - let mut e = Encoder::new(&mut buf); - e.write_u16(2).unwrap(); // duid type - e.write_u32(enterprise).unwrap(); - e.write_slice(id).unwrap(); - Self(buf) - } - /// new link layer DUID - pub fn link_layer(htype: HType, addr: Ipv6Addr) -> Self { - let mut buf = Vec::new(); - let mut e = Encoder::new(&mut buf); - e.write_u16(3).unwrap(); // duid type - e.write_u16(u8::from(htype) as u16).unwrap(); - e.write_u128(addr.into()).unwrap(); - Self(buf) - } - /// new DUID-UUID - /// `uuid` must be 16 bytes long - pub fn uuid(uuid: &[u8]) -> Self { - assert!(uuid.len() == 16); - let mut buf = Vec::new(); - let mut e = Encoder::new(&mut buf); - e.write_u16(4).unwrap(); // duid type - e.write_slice(uuid).unwrap(); - Self(buf) - } - /// create a DUID of unknown type - pub fn unknown(duid: &[u8]) -> Self { - Self(duid.to_vec()) - } - /// total length of contained DUID - pub fn len(&self) -> usize { - self.0.len() - } - /// is contained DUID empty - pub fn is_empty(&self) -> bool { - self.len() == 0 - } -} - -impl AsRef<[u8]> for Duid { - fn as_ref(&self) -> &[u8] { - &self.0 - } -} - -impl From> for Duid { - fn from(v: Vec) -> Self { - Self(v) - } -} - -/// DHCPv6 option types -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum DhcpOption { - /// 1 - - ClientId(Vec), // should duid for this be bytes or string? - /// 2 - - ServerId(Vec), - /// 3 - - IANA(IANA), - /// 4 - - IATA(IATA), - /// 5 - - IAAddr(IAAddr), - /// 6 - - ORO(ORO), - /// 7 - - Preference(u8), - /// 8 - - /// Elapsed time in millis - ElapsedTime(u16), - /// 9 - - RelayMsg(RelayMessage), - /// 11 - - Authentication(Authentication), - /// 12 - - ServerUnicast(Ipv6Addr), - /// 13 - - StatusCode(StatusCode), - /// 14 - - RapidCommit, - /// 15 - - UserClass(UserClass), - /// 16 - - VendorClass(VendorClass), - /// 17 - - VendorOpts(VendorOpts), - /// 18 - - InterfaceId(Vec), - /// 19 - - ReconfMsg(MessageType), - /// 20 - - ReconfAccept, - /// 23 - - DNSNameServer(Vec), - /// 24 - - DomainSearchList(Vec), - /// 25 - - IAPD(IAPD), - /// 26 - - IAPDPrefix(IAPDPrefix), - /// An unknown or unimplemented option type - Unknown(UnknownOption), -} - -impl PartialOrd for DhcpOption { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for DhcpOption { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - OptionCode::from(self).cmp(&OptionCode::from(other)) - } -} - -/// wrapper around interface id -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct InterfaceId { - pub id: String, -} - -/// vendor options -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct VendorOpts { - pub num: u32, - // encapsulated options values - pub opts: DhcpOptions, -} - -/// vendor class -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct VendorClass { - pub num: u32, - pub data: Vec>, - // each item in data is [len (2 bytes) | data] -} - -/// user class -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct UserClass { - pub data: Vec>, - // each item in data is [len (2 bytes) | data] -} - -#[inline] -fn decode_data(decoder: &'_ mut Decoder<'_>) -> Vec> { - let mut data = Vec::new(); - while let Ok(len) = decoder.read_u16() { - // if we can read the len and the string - match decoder.read_slice(len as usize) { - Ok(s) => data.push(s.to_vec()), - // push, otherwise stop - _ => break, - } - } - data -} - -/// Server Unicast -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct StatusCode { - pub status: Status, - // 2 + len - pub msg: String, -} - -/// Status code for Server Unicast -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum Status { - Success, - UnspecFail, - NoAddrsAvail, - NoBinding, - NotOnLink, - UseMulticast, - NoPrefixAvail, - UnknownQueryType, - MalformedQuery, - NotConfigured, - NotAllowed, - QueryTerminated, - DataMissing, - CatchUpComplete, - NotSupported, - TLSConnectionRefused, - AddressInUse, - ConfigurationConflict, - MissingBindingInformation, - OutdatedBindingInformation, - ServerShuttingDown, - DNSUpdateNotSupported, - ExcessiveTimeSkew, - /// unknown/unimplemented message type - Unknown(u16), -} - -impl From for Status { - fn from(n: u16) -> Self { - use Status::*; - match n { - 0 => Success, - 1 => UnspecFail, - 2 => NoAddrsAvail, - 3 => NoBinding, - 4 => NotOnLink, - 5 => UseMulticast, - 6 => NoPrefixAvail, - 7 => UnknownQueryType, - 8 => MalformedQuery, - 9 => NotConfigured, - 10 => NotAllowed, - 11 => QueryTerminated, - 12 => DataMissing, - 13 => CatchUpComplete, - 14 => NotSupported, - 15 => TLSConnectionRefused, - 16 => AddressInUse, - 17 => ConfigurationConflict, - 18 => MissingBindingInformation, - 19 => OutdatedBindingInformation, - 20 => ServerShuttingDown, - 21 => DNSUpdateNotSupported, - 22 => ExcessiveTimeSkew, - _ => Unknown(n), - } - } -} -impl From for u16 { - fn from(n: Status) -> Self { - use Status::*; - match n { - Success => 0, - UnspecFail => 1, - NoAddrsAvail => 2, - NoBinding => 3, - NotOnLink => 4, - UseMulticast => 5, - NoPrefixAvail => 6, - UnknownQueryType => 7, - MalformedQuery => 8, - NotConfigured => 9, - NotAllowed => 10, - QueryTerminated => 11, - DataMissing => 12, - CatchUpComplete => 13, - NotSupported => 14, - TLSConnectionRefused => 15, - AddressInUse => 16, - ConfigurationConflict => 17, - MissingBindingInformation => 18, - OutdatedBindingInformation => 19, - ServerShuttingDown => 20, - DNSUpdateNotSupported => 21, - ExcessiveTimeSkew => 22, - Unknown(n) => n, - } - } -} - -/// Authentication -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Authentication { - pub proto: u8, - pub algo: u8, - pub rdm: u8, - pub replay_detection: u64, - // 11 + len - pub info: Vec, -} - -impl Decodable for Authentication { - fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { - let len = decoder.buffer().len(); - Ok(Authentication { - proto: decoder.read_u8()?, - algo: decoder.read_u8()?, - rdm: decoder.read_u8()?, - replay_detection: decoder.read_u64()?, - info: decoder.read_slice(len - 11)?.to_vec(), - }) - } -} - -/// Option Request Option -/// -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct ORO { - // 2 * num opts - pub opts: Vec, -} - -impl Decodable for ORO { - fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { - let len = decoder.buffer().len(); - Ok(ORO { - opts: { - decoder - .read_slice(len)? - .chunks_exact(2) - // TODO: use .array_chunks::<2>() when stable - .map(|code| OptionCode::from(u16::from_be_bytes([code[0], code[1]]))) - .collect() - }, - }) - } -} - -/// Identity Association for Temporary Addresses -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct IATA { - pub id: u32, - // 4 + opts.len() - // should this be Vec ? - // the RFC suggests it 'encapsulates options' - pub opts: DhcpOptions, -} - -impl Decodable for IATA { - fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { - Ok(IATA { - id: decoder.read_u32()?, - opts: DhcpOptions::decode(decoder)?, - }) - } -} - -/// Identity Association for Non-Temporary Addresses -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct IANA { - pub id: u32, - pub t1: u32, - pub t2: u32, - // 12 + opts.len() - pub opts: DhcpOptions, -} - -impl Decodable for IANA { - fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { - Ok(IANA { - id: decoder.read_u32()?, - t1: decoder.read_u32()?, - t2: decoder.read_u32()?, - opts: DhcpOptions::decode(decoder)?, - }) - } -} - -/// Identity Association Prefix Delegation -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct IAPD { - pub id: u32, - pub t1: u32, - pub t2: u32, - // 12 + opts.len() - pub opts: DhcpOptions, -} - -impl Decodable for IAPD { - fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { - Ok(IAPD { - id: decoder.read_u32()?, - t1: decoder.read_u32()?, - t2: decoder.read_u32()?, - opts: DhcpOptions::decode(decoder)?, - }) - } -} - -/// Identity Association Prefix Delegation Prefix Option -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct IAPDPrefix { - pub preferred_lifetime: u32, - pub valid_lifetime: u32, - pub prefix_len: u8, - pub prefix_ip: Ipv6Addr, - // 25 + opts.len() - pub opts: DhcpOptions, -} - -impl Decodable for IAPDPrefix { - fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { - Ok(IAPDPrefix { - preferred_lifetime: decoder.read_u32()?, - valid_lifetime: decoder.read_u32()?, - prefix_len: decoder.read_u8()?, - prefix_ip: decoder.read::<16>()?.into(), - opts: DhcpOptions::decode(decoder)?, - }) - } -} - -/// Identity Association Address -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct IAAddr { - pub addr: Ipv6Addr, - pub preferred_life: u32, - pub valid_life: u32, - // 24 + opts.len() - // should this be DhcpOptions ? - // the RFC suggests it 'encapsulates options' - pub opts: DhcpOptions, -} - -impl Decodable for IAAddr { - fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { - Ok(IAAddr { - addr: decoder.read::<16>()?.into(), - preferred_life: decoder.read_u32()?, - valid_life: decoder.read_u32()?, - opts: DhcpOptions::decode(decoder)?, - }) - } -} - -/// fallback for options not yet implemented -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct UnknownOption { - code: u16, - data: Vec, -} - -impl UnknownOption { - pub fn new(code: OptionCode, data: Vec) -> Self { - Self { - code: code.into(), - data, - } - } - /// return the option code - pub fn code(&self) -> OptionCode { - self.code.into() - } - /// return the data for this option - pub fn data(&self) -> &[u8] { - &self.data - } - /// consume option into its components - pub fn into_parts(self) -> (OptionCode, Vec) { - (self.code.into(), self.data) - } -} - -impl Decodable for DhcpOptions { - fn decode(decoder: &mut Decoder<'_>) -> DecodeResult { - let mut opts = Vec::new(); - while let Ok(opt) = DhcpOption::decode(decoder) { - opts.push(opt); - } - // sorts by OptionCode - opts.sort_unstable(); - Ok(DhcpOptions(opts)) - } -} - -impl Encodable for DhcpOptions { - fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { - self.0.iter().try_for_each(|opt| opt.encode(e)) - } -} - -impl Decodable for DhcpOption { - fn decode(decoder: &mut Decoder<'_>) -> DecodeResult { - let code = decoder.read_u16()?.into(); - let len = decoder.read_u16()? as usize; - Ok(match code { - OptionCode::ClientId => DhcpOption::ClientId(decoder.read_slice(len)?.to_vec()), - OptionCode::ServerId => DhcpOption::ServerId(decoder.read_slice(len)?.to_vec()), - OptionCode::IANA => { - let mut dec = Decoder::new(decoder.read_slice(len)?); - DhcpOption::IANA(IANA::decode(&mut dec)?) - } - OptionCode::IATA => { - let mut dec = Decoder::new(decoder.read_slice(len)?); - DhcpOption::IATA(IATA::decode(&mut dec)?) - } - OptionCode::IAAddr => { - let mut dec = Decoder::new(decoder.read_slice(len)?); - DhcpOption::IAAddr(IAAddr::decode(&mut dec)?) - } - OptionCode::ORO => { - let mut dec = Decoder::new(decoder.read_slice(len)?); - DhcpOption::ORO(ORO::decode(&mut dec)?) - } - OptionCode::Preference => DhcpOption::Preference(decoder.read_u8()?), - OptionCode::ElapsedTime => DhcpOption::ElapsedTime(decoder.read_u16()?), - OptionCode::RelayMsg => { - let mut relay_dec = Decoder::new(decoder.read_slice(len)?); - DhcpOption::RelayMsg(RelayMessage::decode(&mut relay_dec)?) - } - OptionCode::Authentication => { - let mut dec = Decoder::new(decoder.read_slice(len)?); - DhcpOption::Authentication(Authentication::decode(&mut dec)?) - } - OptionCode::ServerUnicast => DhcpOption::ServerUnicast(decoder.read::<16>()?.into()), - OptionCode::StatusCode => DhcpOption::StatusCode(StatusCode { - status: decoder.read_u16()?.into(), - msg: decoder.read_string(len - 1)?, - }), - OptionCode::RapidCommit => DhcpOption::RapidCommit, - OptionCode::UserClass => { - let buf = decoder.read_slice(len)?; - DhcpOption::UserClass(UserClass { - data: decode_data(&mut Decoder::new(buf)), - }) - } - OptionCode::VendorClass => { - let num = decoder.read_u32()?; - let buf = decoder.read_slice(len - 4)?; - DhcpOption::VendorClass(VendorClass { - num, - data: decode_data(&mut Decoder::new(buf)), - }) - } - OptionCode::VendorOpts => DhcpOption::VendorOpts(VendorOpts { - num: decoder.read_u32()?, - opts: { - let mut opt_decoder = Decoder::new(decoder.read_slice(len - 4)?); - DhcpOptions::decode(&mut opt_decoder)? - }, - }), - OptionCode::InterfaceId => DhcpOption::InterfaceId(decoder.read_slice(len)?.to_vec()), - OptionCode::ReconfMsg => DhcpOption::ReconfMsg(decoder.read_u8()?.into()), - OptionCode::ReconfAccept => DhcpOption::ReconfAccept, - OptionCode::DNSNameServer => DhcpOption::DNSNameServer(decoder.read_ipv6s(len)?), - OptionCode::IAPD => { - let mut dec = Decoder::new(decoder.read_slice(len)?); - DhcpOption::IAPD(IAPD::decode(&mut dec)?) - } - OptionCode::IAPDPrefix => { - let mut dec = Decoder::new(decoder.read_slice(len)?); - DhcpOption::IAPDPrefix(IAPDPrefix::decode(&mut dec)?) - } - OptionCode::DomainSearchList => { - let mut name_decoder = BinDecoder::new(decoder.read_slice(len as usize)?); - let mut names = Vec::new(); - while let Ok(name) = Name::read(&mut name_decoder) { - names.push(Domain(name)); - } - - DhcpOption::DomainSearchList(names) - } - // not yet implemented - OptionCode::Unknown(code) => DhcpOption::Unknown(UnknownOption { - code, - data: decoder.read_slice(len)?.to_vec(), - }), - }) - } -} -impl Encodable for DhcpOption { - fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { - let code: OptionCode = self.into(); - e.write_u16(code.into())?; - match self { - DhcpOption::ClientId(duid) | DhcpOption::ServerId(duid) => { - e.write_u16(duid.len() as u16)?; - e.write_slice(duid)?; - } - DhcpOption::IANA(IANA { id, t1, t2, opts }) - | DhcpOption::IAPD(IAPD { id, t1, t2, opts }) => { - // write len - let mut buf = Vec::new(); - let mut opt_enc = Encoder::new(&mut buf); - opts.encode(&mut opt_enc)?; - // buf now has total len - e.write_u16(12 + buf.len() as u16)?; - // write data - e.write_u32(*id)?; - e.write_u32(*t1)?; - e.write_u32(*t2)?; - e.write_slice(&buf)?; - } - DhcpOption::IATA(IATA { id, opts }) => { - // write len - let mut buf = Vec::new(); - let mut opt_enc = Encoder::new(&mut buf); - opts.encode(&mut opt_enc)?; - // buf now has total len - e.write_u16(4 + buf.len() as u16)?; - // data - e.write_u32(*id)?; - e.write_slice(&buf)?; - } - DhcpOption::IAAddr(IAAddr { - addr, - preferred_life, - valid_life, - opts, - }) => { - // write len - let mut buf = Vec::new(); - let mut opt_enc = Encoder::new(&mut buf); - opts.encode(&mut opt_enc)?; - // buf now has total len - e.write_u16(24 + buf.len() as u16)?; - // data - e.write_u128((*addr).into())?; - e.write_u32(*preferred_life)?; - e.write_u32(*valid_life)?; - e.write_slice(&buf)?; - } - DhcpOption::ORO(ORO { opts }) => { - // write len - e.write_u16(2 * opts.len() as u16)?; - // data - for code in opts { - e.write_u16(u16::from(*code))?; - } - } - DhcpOption::Preference(pref) => { - e.write_u16(1)?; - e.write_u8(*pref)?; - } - DhcpOption::ElapsedTime(elapsed) => { - e.write_u16(2)?; - e.write_u16(*elapsed)?; - } - DhcpOption::RelayMsg(msg) => { - let mut buf = Vec::new(); - let mut relay_enc = Encoder::new(&mut buf); - msg.encode(&mut relay_enc)?; - - e.write_u16(buf.len() as u16)?; - e.write_slice(&buf)?; - } - DhcpOption::Authentication(Authentication { - proto, - algo, - rdm, - replay_detection, - info, - }) => { - e.write_u16(11 + info.len() as u16)?; - e.write_u8(*proto)?; - e.write_u8(*algo)?; - e.write_u8(*rdm)?; - e.write_u64(*replay_detection)?; - e.write_slice(info)?; - } - DhcpOption::ServerUnicast(addr) => { - e.write_u16(16)?; - e.write_u128((*addr).into())?; - } - DhcpOption::StatusCode(StatusCode { status, msg }) => { - e.write_u16(2 + msg.len() as u16)?; - e.write_u16((*status).into())?; - e.write_slice(msg.as_bytes())?; - } - DhcpOption::RapidCommit => { - e.write_u16(0)?; - } - DhcpOption::UserClass(UserClass { data }) => { - e.write_u16(data.len() as u16)?; - for s in data { - e.write_u16(s.len() as u16)?; - e.write_slice(s)?; - } - } - DhcpOption::VendorClass(VendorClass { num, data }) => { - e.write_u16(4 + data.len() as u16)?; - e.write_u32(*num)?; - for s in data { - e.write_u16(s.len() as u16)?; - e.write_slice(s)?; - } - } - DhcpOption::VendorOpts(VendorOpts { num, opts }) => { - let mut buf = Vec::new(); - let mut opt_enc = Encoder::new(&mut buf); - opts.encode(&mut opt_enc)?; - // buf now has total len - e.write_u16(4 + buf.len() as u16)?; - e.write_u32(*num)?; - e.write_slice(&buf)?; - } - DhcpOption::InterfaceId(id) => { - e.write_u16(id.len() as u16)?; - e.write_slice(id)?; - } - DhcpOption::ReconfMsg(msg_type) => { - e.write_u16(1)?; - e.write_u8((*msg_type).into())?; - } - DhcpOption::ReconfAccept => { - e.write_u16(0)?; - } - DhcpOption::DNSNameServer(addrs) => { - e.write_u16(addrs.len() as u16 * 16)?; - for addr in addrs { - e.write_u128((*addr).into())?; - } - } - DhcpOption::DomainSearchList(names) => { - let mut buf = Vec::new(); - let mut name_encoder = BinEncoder::new(&mut buf); - for name in names { - name.0.emit(&mut name_encoder)?; - } - e.write_u16(buf.len() as u16)?; - e.write_slice(&buf)?; - } - DhcpOption::IAPDPrefix(IAPDPrefix { - preferred_lifetime, - valid_lifetime, - prefix_len, - prefix_ip, - opts, - }) => { - let mut buf = Vec::new(); - let mut opt_enc = Encoder::new(&mut buf); - opts.encode(&mut opt_enc)?; - // buf now has total len - e.write_u16(25 + buf.len() as u16)?; - // write data - e.write_u32(*preferred_lifetime)?; - e.write_u32(*valid_lifetime)?; - e.write_u8(*prefix_len)?; - e.write_u128((*prefix_ip).into())?; - e.write_slice(&buf)?; - } - DhcpOption::Unknown(UnknownOption { data, .. }) => { - e.write_u16(data.len() as u16)?; - e.write_slice(data)?; - } - }; - Ok(()) - } -} - -/// option code type -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum OptionCode { - /// 1 - ClientId, // should duid for this be bytes or string? - /// 2 - ServerId, - /// 3 - IANA, - /// 4 - IATA, - /// 5 - IAAddr, - /// 6 - ORO, - /// 7 - Preference, - /// 8 - ElapsedTime, - /// 9 - RelayMsg, - /// 11 - Authentication, - /// 12 - ServerUnicast, - /// 13 - StatusCode, - /// 14 - RapidCommit, - /// 15 - UserClass, - /// 16 - VendorClass, - /// 17 - VendorOpts, - /// 18 - InterfaceId, - /// 19 - ReconfMsg, - /// 20 - ReconfAccept, - /// 23 - DNSNameServer, - /// 24 - DomainSearchList, - /// 25 - IAPD, - /// 26 - IAPDPrefix, - /// an unknown or unimplemented option type - Unknown(u16), -} - -impl PartialOrd for OptionCode { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for OptionCode { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - u16::from(*self).cmp(&u16::from(*other)) - } -} - -impl From for u16 { - fn from(opt: OptionCode) -> Self { - use OptionCode::*; - match opt { - ClientId => 1, - ServerId => 2, - IANA => 3, - IATA => 4, - IAAddr => 5, - ORO => 6, - Preference => 7, - ElapsedTime => 8, - RelayMsg => 9, - Authentication => 11, - ServerUnicast => 12, - StatusCode => 13, - RapidCommit => 14, - UserClass => 15, - VendorClass => 16, - VendorOpts => 17, - InterfaceId => 18, - ReconfMsg => 19, - ReconfAccept => 20, - DNSNameServer => 23, - DomainSearchList => 24, - IAPD => 25, - IAPDPrefix => 26, - Unknown(n) => n, - } - } -} - -impl From for OptionCode { - fn from(n: u16) -> Self { - use OptionCode::*; - match n { - 1 => ClientId, - 2 => ServerId, - 3 => IANA, - 4 => IATA, - 5 => IAAddr, - 6 => ORO, - 7 => Preference, - 8 => ElapsedTime, - 9 => RelayMsg, - 11 => Authentication, - 12 => ServerUnicast, - 13 => StatusCode, - 14 => RapidCommit, - 15 => UserClass, - 16 => VendorClass, - 17 => VendorOpts, - 18 => InterfaceId, - 19 => ReconfMsg, - 20 => ReconfAccept, - 23 => DNSNameServer, - 24 => DomainSearchList, - 25 => IAPD, - 26 => IAPDPrefix, - _ => Unknown(n), - } - } -} - -impl From<&DhcpOption> for OptionCode { - fn from(opt: &DhcpOption) -> Self { - use DhcpOption::*; - match opt { - ClientId(_) => OptionCode::ClientId, - ServerId(_) => OptionCode::ServerId, - IANA(_) => OptionCode::IANA, - IATA(_) => OptionCode::IATA, - IAAddr(_) => OptionCode::IAAddr, - ORO(_) => OptionCode::ORO, - Preference(_) => OptionCode::Preference, - ElapsedTime(_) => OptionCode::ElapsedTime, - RelayMsg(_) => OptionCode::RelayMsg, - Authentication(_) => OptionCode::Authentication, - ServerUnicast(_) => OptionCode::ServerUnicast, - StatusCode(_) => OptionCode::StatusCode, - RapidCommit => OptionCode::RapidCommit, - UserClass(_) => OptionCode::UserClass, - VendorClass(_) => OptionCode::VendorClass, - VendorOpts(_) => OptionCode::VendorOpts, - InterfaceId(_) => OptionCode::InterfaceId, - ReconfMsg(_) => OptionCode::ReconfMsg, - ReconfAccept => OptionCode::ReconfAccept, - DNSNameServer(_) => OptionCode::DNSNameServer, - DomainSearchList(_) => OptionCode::DomainSearchList, - IAPD(_) => OptionCode::IAPD, - IAPDPrefix(_) => OptionCode::IAPDPrefix, - Unknown(UnknownOption { code, .. }) => OptionCode::Unknown(*code), - } - } -} - -#[inline] -fn first(arr: &[T], f: F) -> Option -where - T: Ord, - F: Fn(&T) -> Ordering, -{ - let mut l = 0; - let mut r = arr.len() - 1; - while l <= r { - let mid = (l + r) >> 1; - // SAFETY: we know it is within the length - let mid_cmp = f(unsafe { arr.get_unchecked(mid) }); - let prev_cmp = if mid > 0 { - f(unsafe { arr.get_unchecked(mid - 1) }) == Ordering::Less - } else { - false - }; - if (mid == 0 || prev_cmp) && mid_cmp == Ordering::Equal { - return Some(mid); - } else if mid_cmp == Ordering::Less { - l = mid + 1; - } else { - r = mid - 1; - } - } - None -} - -#[inline] -fn last(arr: &[T], f: F) -> Option -where - T: Ord, - F: Fn(&T) -> Ordering, -{ - let n = arr.len(); - let mut l = 0; - let mut r = n - 1; - while l <= r { - let mid = (l + r) >> 1; - // SAFETY: we know it is within the length - let mid_cmp = f(unsafe { arr.get_unchecked(mid) }); - let nxt_cmp = if mid < n { - f(unsafe { arr.get_unchecked(mid + 1) }) == Ordering::Greater - } else { - false - }; - if (mid == n - 1 || nxt_cmp) && mid_cmp == Ordering::Equal { - return Some(mid); - } else if mid_cmp == Ordering::Greater { - r = mid - 1; - } else { - l = mid + 1; - } - } - None -} - -#[inline] -fn range_binsearch(arr: &[T], f: F) -> Option> -where - T: Ord, - F: Fn(&T) -> Ordering, -{ - let first = first(arr, &f)?; - let last = last(arr, &f)?; - Some(first..=last) -} - -#[cfg(test)] -mod tests { - use super::*; - #[test] - fn test_range_binsearch() { - let arr = vec![0, 1, 1, 1, 1, 4, 6, 7, 9, 9, 10]; - assert_eq!(Some(1..=4), range_binsearch(&arr, |x| x.cmp(&1))); - - let arr = vec![0, 1, 1, 1, 1, 4, 6, 7, 9, 9, 10]; - assert_eq!(Some(0..=0), range_binsearch(&arr, |x| x.cmp(&0))); - - let arr = vec![0, 1, 1, 1, 1, 4, 6, 7, 9, 9, 10]; - assert_eq!(Some(5..=5), range_binsearch(&arr, |x| x.cmp(&4))); - - let arr = vec![1, 2, 2, 2, 2, 3, 4, 7, 8, 8]; - assert_eq!(Some(8..=9), range_binsearch(&arr, |x| x.cmp(&8))); - - let arr = vec![1, 2, 2, 2, 2, 3, 4, 7, 8, 8]; - assert_eq!(Some(1..=4), range_binsearch(&arr, |x| x.cmp(&2))); - - let arr = vec![1, 2, 2, 2, 2, 3, 4, 7, 8, 8]; - assert_eq!(Some(7..=7), range_binsearch(&arr, |x| x.cmp(&7))); - } -} diff --git a/src/v6/options/auth.rs b/src/v6/options/auth.rs new file mode 100644 index 0000000..a95de7f --- /dev/null +++ b/src/v6/options/auth.rs @@ -0,0 +1,71 @@ +use super::{DecodeResult, EncodeResult, OptionCode}; +use crate::{Decodable, Decoder, Encodable, Encoder}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// Auth +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Auth { + pub proto: u8, + pub algo: u8, + pub rdm: u8, + pub replay_detection: u64, + // 11 + len + pub info: Vec, +} + +impl Decodable for Auth { + fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { + decoder.read::<2>()?; + let len = decoder.read_u16()? as usize; + Ok(Auth { + proto: decoder.read_u8()?, + algo: decoder.read_u8()?, + rdm: decoder.read_u8()?, + replay_detection: decoder.read_u64()?, + info: decoder.read_slice(len - 11)?.to_vec(), + }) + } +} + +impl Encodable for Auth { + fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { + e.write_u16(OptionCode::Auth.into())?; + e.write_u16(11 + self.info.len() as u16)?; + e.write_u8(self.proto)?; + e.write_u8(self.algo)?; + e.write_u8(self.rdm)?; + e.write_u64(self.replay_detection)?; + e.write_slice(&self.info)?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_iata_encode_decode() { + let option = Auth { + proto: 0xC, + algo: 0xB, + rdm: 0xA, + replay_detection: 0xABCD, + info: vec![1, 2, 3], + }; + + let mut encoder = vec![]; + + option.encode(&mut Encoder::new(&mut encoder)).unwrap(); + let decoded = Auth::decode(&mut Decoder::new(&encoder)).unwrap(); + assert_eq!(option, decoded); + + encoder.push(50); + let mut decoder = Decoder::new(&encoder); + let decoded = Auth::decode(&mut decoder).unwrap(); + assert_eq!(option, decoded); + assert_eq!(50, decoder.read_u8().unwrap()); + } +} diff --git a/src/v6/options/clientdata.rs b/src/v6/options/clientdata.rs new file mode 100644 index 0000000..1175333 --- /dev/null +++ b/src/v6/options/clientdata.rs @@ -0,0 +1,76 @@ +use super::{ + option_builder, ClientId, DecodeResult, DhcpOption, EncodeResult, IAAddr, IAPrefix, OptionCode, +}; +use crate::{Decodable, Decoder, Encodable, Encoder}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// Vendor defined options +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ClientData { + pub opts: ClientDataOptions, +} + +impl Decodable for ClientData { + fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { + decoder.read::<2>()?; + let len = decoder.read_u16()?; + let mut decoder = Decoder::new(decoder.read_slice(len.into())?); + + Ok(ClientData { + opts: ClientDataOptions::decode(&mut decoder)?, + }) + } +} + +impl Encodable for ClientData { + fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { + let mut data = vec![]; + let mut enc = Encoder::new(&mut data); + self.opts.encode(&mut enc)?; + e.write_u16(OptionCode::ClientData.into())?; + e.write_u16(data.len() as u16)?; + e.write_slice(&data)?; + Ok(()) + } +} + +//TODO: add ORO reply options +option_builder!( + ClientDataOption, + ClientDataOptions, + IsClientDataOption, + DhcpOption, + ClientId, + IAAddr, + IAPrefix, + CltTime +); + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct CltTime { + ///seconds since server last communicated with the client (on that link) + pub time: u32, +} + +impl Decodable for CltTime { + fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { + decoder.read::<4>()?; + + Ok(CltTime { + time: decoder.read_u32()?, + }) + } +} + +impl Encodable for CltTime { + fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { + e.write_u16(OptionCode::CltTime.into())?; + e.write_u16(4)?; + e.write_u32(self.time)?; + Ok(()) + } +} diff --git a/src/v6/options/clientid.rs b/src/v6/options/clientid.rs new file mode 100644 index 0000000..f607129 --- /dev/null +++ b/src/v6/options/clientid.rs @@ -0,0 +1,59 @@ +use super::{DecodeResult, Duid, EncodeResult, OptionCode}; +use crate::{Decodable, Decoder, Encodable, Encoder}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// Client Identity +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ClientId { + pub id: Duid, +} + +impl Decodable for ClientId { + fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { + decoder.read::<2>()?; + let len = decoder.read_u16()? as usize; + let mut decoder = Decoder::new(decoder.read_slice(len)?); + Ok(ClientId { + id: Duid::decode(&mut decoder)?, + }) + } +} + +impl Encodable for ClientId { + fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { + // write len + let mut buf = Vec::new(); + let mut opt_enc = Encoder::new(&mut buf); + self.id.encode(&mut opt_enc)?; + e.write_u16(OptionCode::ClientId.into())?; + e.write_u16(buf.len() as u16)?; + e.write_slice(&buf)?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_client_id_encode_decode() { + let option = ClientId { + id: Duid::enterprise(1, &[1, 2, 3]), + }; + + let mut encoder = vec![]; + + option.encode(&mut Encoder::new(&mut encoder)).unwrap(); + let decoded = ClientId::decode(&mut Decoder::new(&encoder)).unwrap(); + assert_eq!(option, decoded); + + encoder.push(50); + let mut decoder = Decoder::new(&encoder); + let decoded = ClientId::decode(&mut decoder).unwrap(); + assert_eq!(option, decoded); + assert_eq!(50, decoder.read_u8().unwrap()); + } +} diff --git a/src/v6/options/dnsservers.rs b/src/v6/options/dnsservers.rs new file mode 100644 index 0000000..4c2ab87 --- /dev/null +++ b/src/v6/options/dnsservers.rs @@ -0,0 +1,61 @@ +use std::net::Ipv6Addr; + +use super::{DecodeResult, EncodeResult, OptionCode}; +use crate::{Decodable, Decoder, Encodable, Encoder}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DNSServers { + pub servers: Vec, +} + +impl Decodable for DNSServers { + fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { + decoder.read::<2>()?; + let len = decoder.read_u16()?; + let mut servers = vec![]; + for _ in 0..(len / 16) { + servers.push(decoder.read::<16>()?.into()); + } + + Ok(DNSServers { servers }) + } +} + +impl Encodable for DNSServers { + fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { + e.write_u16(OptionCode::DNSServers.into())?; + e.write_u16((self.servers.len() * 16) as u16)?; + for ip in self.servers.iter() { + e.write_slice(&ip.octets())?; + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_dns_servrs_encode_decode() { + let option = DNSServers { + servers: vec!["FE80:ABCD:EF12::1".parse::().unwrap()], + }; + + let mut encoder = vec![]; + + option.encode(&mut Encoder::new(&mut encoder)).unwrap(); + println!("{:?}", encoder); + let decoded = DNSServers::decode(&mut Decoder::new(&encoder)).unwrap(); + assert_eq!(option, decoded); + + encoder.push(50); + let mut decoder = Decoder::new(&encoder); + let decoded = DNSServers::decode(&mut decoder).unwrap(); + assert_eq!(option, decoded); + assert_eq!(50, decoder.read_u8().unwrap()); + } +} diff --git a/src/v6/options/domainlist.rs b/src/v6/options/domainlist.rs new file mode 100644 index 0000000..c9e3c75 --- /dev/null +++ b/src/v6/options/domainlist.rs @@ -0,0 +1,44 @@ +use trust_dns_proto::{ + rr::Name, + serialize::binary::{BinDecodable, BinDecoder, BinEncodable, BinEncoder}, +}; + +use super::{DecodeResult, Domain, EncodeResult, OptionCode}; +use crate::{Decodable, Decoder, Encodable, Encoder}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DomainList { + pub domains: Vec, +} + +impl Decodable for DomainList { + fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { + decoder.read::<2>()?; + let len = decoder.read_u16()?; + let mut name_decoder = BinDecoder::new(decoder.read_slice(len as usize)?); + let mut names = Vec::new(); + while let Ok(name) = Name::read(&mut name_decoder) { + names.push(Domain(name)); + } + + Ok(DomainList { domains: names }) + } +} + +impl Encodable for DomainList { + fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { + e.write_u16(OptionCode::DomainList.into())?; + let mut buf = Vec::new(); + let mut name_encoder = BinEncoder::new(&mut buf); + for name in self.domains.iter() { + name.0.emit(&mut name_encoder)?; + } + e.write_u16(buf.len() as u16)?; + e.write_slice(&buf)?; + Ok(()) + } +} diff --git a/src/v6/options/elapsedtime.rs b/src/v6/options/elapsedtime.rs new file mode 100644 index 0000000..e49514d --- /dev/null +++ b/src/v6/options/elapsedtime.rs @@ -0,0 +1,52 @@ +use super::{DecodeResult, EncodeResult, OptionCode}; +use crate::{Decodable, Decoder, Encodable, Encoder}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// Time in milliseconds elapsed since the start of negotiation. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct ElapsedTime { + pub time: u16, +} + +impl Decodable for ElapsedTime { + fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { + decoder.read::<2>()?; + let _len = decoder.read_u16()? as usize; + Ok(ElapsedTime { + time: decoder.read_u16()?, + }) + } +} + +impl Encodable for ElapsedTime { + fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { + e.write_u16(OptionCode::ElapsedTime.into())?; + e.write_u16(2)?; + e.write_u16(self.time)?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_server_id_encode_decode() { + let option = ElapsedTime { time: 1 }; + + let mut encoder = vec![]; + + option.encode(&mut Encoder::new(&mut encoder)).unwrap(); + let decoded = ElapsedTime::decode(&mut Decoder::new(&encoder)).unwrap(); + assert_eq!(option, decoded); + + encoder.push(50); + let mut decoder = Decoder::new(&encoder); + let decoded = ElapsedTime::decode(&mut decoder).unwrap(); + assert_eq!(option, decoded); + assert_eq!(50, decoder.read_u8().unwrap()); + } +} diff --git a/src/v6/options/iaaddr.rs b/src/v6/options/iaaddr.rs new file mode 100644 index 0000000..0a24ec0 --- /dev/null +++ b/src/v6/options/iaaddr.rs @@ -0,0 +1,89 @@ +use crate::v6::options::{option_builder, DhcpOption}; +use crate::v6::{DecodeResult, EncodeResult, Ipv6Addr, OptionCode, StatusCode}; +use crate::{Decodable, Decoder, Encodable, Encoder}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// Identity Association Address +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct IAAddr { + pub addr: Ipv6Addr, + pub preferred_life: u32, + pub valid_life: u32, + // 24 + opts.len() + // should this be DhcpOptions ? + // the RFC suggests it 'encapsulates options' + pub opts: IAAddrOptions, +} + +impl Decodable for IAAddr { + fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { + decoder.read::<2>()?; + let len = decoder.read_u16()? as usize; + let mut decoder = Decoder::new(decoder.read_slice(len)?); + Ok(IAAddr { + addr: decoder.read::<16>()?.into(), + preferred_life: decoder.read_u32()?, + valid_life: decoder.read_u32()?, + opts: IAAddrOptions::decode(&mut decoder)?, + }) + } +} + +impl Encodable for IAAddr { + fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { + // write len + let mut buf = Vec::new(); + let mut opt_enc = Encoder::new(&mut buf); + self.opts.encode(&mut opt_enc)?; + e.write_u16(OptionCode::IAAddr.into())?; + // buf now has total len + e.write_u16(24 + buf.len() as u16)?; + // data + e.write_u128((self.addr).into())?; + e.write_u32(self.preferred_life)?; + e.write_u32(self.valid_life)?; + e.write_slice(&buf)?; + Ok(()) + } +} + +option_builder!( + IAAddrOption, + IAAddrOptions, + IsAIIdrOption, + DhcpOption, + StatusCode +); + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_iaaddr_encode_decode() { + let option = IAAddr { + addr: "FE:80::AB".parse().unwrap(), + preferred_life: 0xEF12, + valid_life: 0xABCD, + opts: IAAddrOptions(vec![StatusCode { + status: 0xABCDu16.into(), + msg: "message".into(), + } + .into()]), + }; + + let mut encoder = vec![]; + + option.encode(&mut Encoder::new(&mut encoder)).unwrap(); + let decoded = IAAddr::decode(&mut Decoder::new(&encoder)).unwrap(); + assert_eq!(option, decoded); + + encoder.push(50); + let mut decoder = Decoder::new(&encoder); + let decoded = IAAddr::decode(&mut decoder).unwrap(); + assert_eq!(option, decoded); + assert_eq!(50, decoder.read_u8().unwrap()); + } +} diff --git a/src/v6/options/iana.rs b/src/v6/options/iana.rs new file mode 100644 index 0000000..a6cf86f --- /dev/null +++ b/src/v6/options/iana.rs @@ -0,0 +1,90 @@ +use super::{ + option_builder, DecodeResult, DhcpOption, EncodeResult, IAAddr, OptionCode, StatusCode, +}; +use crate::{Decodable, Decoder, Encodable, Encoder}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// Identity Association for Non-Temporary Addresses +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct IANA { + pub id: u32, + pub t1: u32, + pub t2: u32, + // 12 + opts.len() + pub opts: IANAOptions, +} + +impl Decodable for IANA { + fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { + decoder.read::<2>()?; + let len = decoder.read_u16()? as usize; + let mut decoder = Decoder::new(decoder.read_slice(len)?); + Ok(IANA { + id: decoder.read_u32()?, + t1: decoder.read_u32()?, + t2: decoder.read_u32()?, + opts: IANAOptions::decode(&mut decoder)?, + }) + } +} + +impl Encodable for IANA { + fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { + // write len + let mut buf = Vec::new(); + let mut opt_enc = Encoder::new(&mut buf); + self.opts.encode(&mut opt_enc)?; + // buf now has total len + e.write_u16(OptionCode::IANA.into())?; + e.write_u16(12 + buf.len() as u16)?; + // write data + e.write_u32(self.id)?; + e.write_u32(self.t1)?; + e.write_u32(self.t2)?; + e.write_slice(&buf)?; + Ok(()) + } +} + +option_builder!( + IANAOption, + IANAOptions, + IsIANAOption, + DhcpOption, + IAAddr, + StatusCode +); + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_iana_encode_decode() { + let option = IANA { + id: 0xAABB, + t1: 0xCCDDEEFF, + t2: 0x11223344, + // 12 + opts.len() + opts: IANAOptions(vec![StatusCode { + status: 0xABCDu16.into(), + msg: "message".into(), + } + .into()]), + }; + + let mut encoder = vec![]; + + option.encode(&mut Encoder::new(&mut encoder)).unwrap(); + let decoded = IANA::decode(&mut Decoder::new(&encoder)).unwrap(); + assert_eq!(option, decoded); + + encoder.push(50); + let mut decoder = Decoder::new(&encoder); + let decoded = IANA::decode(&mut decoder).unwrap(); + assert_eq!(option, decoded); + assert_eq!(50, decoder.read_u8().unwrap()); + } +} diff --git a/src/v6/options/iapd.rs b/src/v6/options/iapd.rs new file mode 100644 index 0000000..f11f4bc --- /dev/null +++ b/src/v6/options/iapd.rs @@ -0,0 +1,92 @@ +use super::{ + option_builder, DecodeResult, DhcpOption, EncodeResult, IAPrefix, OptionCode, StatusCode, +}; +use crate::{Decodable, Decoder, Encodable, Encoder}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// Identity Association Prefix Delegation +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct IAPD { + pub id: u32, + pub t1: u32, + pub t2: u32, + // 12 + opts.len() + pub opts: IAPDOptions, +} + +impl Decodable for IAPD { + fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { + decoder.read::<2>()?; + let len = decoder.read_u16()? as usize; + Ok(IAPD { + id: decoder.read_u32()?, + t1: decoder.read_u32()?, + t2: decoder.read_u32()?, + opts: { + let mut dec = Decoder::new(decoder.read_slice(len - 12)?); + IAPDOptions::decode(&mut dec)? + }, + }) + } +} + +impl Encodable for IAPD { + fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { + e.write_u16(OptionCode::IAPD.into())?; + // write len + let mut buf = Vec::new(); + let mut opt_enc = Encoder::new(&mut buf); + self.opts.encode(&mut opt_enc)?; + // buf now has total len + e.write_u16(12 + buf.len() as u16)?; + // write data + e.write_u32(self.id)?; + e.write_u32(self.t1)?; + e.write_u32(self.t2)?; + e.write_slice(&buf)?; + Ok(()) + } +} + +option_builder!( + IAPDOption, + IAPDOptions, + IsIAPDOption, + DhcpOption, + IAPrefix, + StatusCode +); + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_iapd_encode_decode() { + let option = IAPD { + id: 0xAABB, + t1: 0xCCDDEEFF, + t2: 0x11223344, + // 12 + opts.len() + opts: IAPDOptions(vec![StatusCode { + status: 0xABCDu16.into(), + msg: "message".into(), + } + .into()]), + }; + + let mut encoder = vec![]; + + option.encode(&mut Encoder::new(&mut encoder)).unwrap(); + let decoded = IAPD::decode(&mut Decoder::new(&encoder)).unwrap(); + assert_eq!(option, decoded); + + encoder.push(50); + let mut decoder = Decoder::new(&encoder); + let decoded = IAPD::decode(&mut decoder).unwrap(); + assert_eq!(option, decoded); + assert_eq!(50, decoder.read_u8().unwrap()); + } +} diff --git a/src/v6/options/iaprefix.rs b/src/v6/options/iaprefix.rs new file mode 100644 index 0000000..b813239 --- /dev/null +++ b/src/v6/options/iaprefix.rs @@ -0,0 +1,88 @@ +use super::{option_builder, DecodeResult, DhcpOption, EncodeResult, Ipv6Addr, OptionCode}; +use crate::{Decodable, Decoder, Encodable, Encoder}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// Identity Association Prefix Delegation Prefix Option +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct IAPrefix { + pub preferred_lifetime: u32, + pub valid_lifetime: u32, + pub prefix_len: u8, + pub prefix_ip: Ipv6Addr, + // 25 + opts.len() + pub opts: IAPrefixOptions, +} + +impl Decodable for IAPrefix { + fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { + decoder.read::<2>()?; + let len = decoder.read_u16()? as usize; + Ok(IAPrefix { + preferred_lifetime: decoder.read_u32()?, + valid_lifetime: decoder.read_u32()?, + prefix_len: decoder.read_u8()?, + prefix_ip: decoder.read::<16>()?.into(), + opts: { + let mut dec = Decoder::new(decoder.read_slice(len - 25)?); + IAPrefixOptions::decode(&mut dec)? + }, + }) + } +} + +impl Encodable for IAPrefix { + fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { + e.write_u16(OptionCode::IAPrefix.into())?; + // write len + let mut buf = Vec::new(); + let mut opt_enc = Encoder::new(&mut buf); + self.opts.encode(&mut opt_enc)?; + // buf now has total len + e.write_u16(25 + buf.len() as u16)?; + // write data + e.write_u32(self.preferred_lifetime)?; + e.write_u32(self.valid_lifetime)?; + e.write_u8(self.prefix_len)?; + e.write_u128(self.prefix_ip.into())?; + e.write_slice(&buf)?; + Ok(()) + } +} + +option_builder!( + IAPrefixOption, + IAPrefixOptions, + IsIAPrefixOption, + DhcpOption, +); + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_iapd_encode_decode() { + let option = IAPrefix { + preferred_lifetime: 0, + valid_lifetime: 0, + prefix_len: 0, + prefix_ip: "FE80::".parse().unwrap(), + // 12 + opts.len() + opts: IAPrefixOptions(vec![]), + }; + + let mut encoder = vec![]; + + option.encode(&mut Encoder::new(&mut encoder)).unwrap(); + let decoded = IAPrefix::decode(&mut Decoder::new(&encoder)).unwrap(); + assert_eq!(option, decoded); + + encoder.push(50); + let mut decoder = Decoder::new(&encoder); + let decoded = IAPrefix::decode(&mut decoder).unwrap(); + assert_eq!(option, decoded); + assert_eq!(50, decoder.read_u8().unwrap()); + } +} diff --git a/src/v6/options/iata.rs b/src/v6/options/iata.rs new file mode 100644 index 0000000..9d69453 --- /dev/null +++ b/src/v6/options/iata.rs @@ -0,0 +1,82 @@ +use super::{ + option_builder, DecodeResult, DhcpOption, EncodeResult, IAAddr, OptionCode, StatusCode, +}; +use crate::{Decodable, Decoder, Encodable, Encoder}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// Identity Association for Temporary Addresses +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct IATA { + pub id: u32, + // 4 + opts.len() + pub opts: IATAOptions, +} + +impl Decodable for IATA { + fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { + decoder.read::<2>()?; + let len = decoder.read_u16()? as usize; + let mut decoder = Decoder::new(decoder.read_slice(len)?); + Ok(IATA { + id: decoder.read_u32()?, + opts: IATAOptions::decode(&mut decoder)?, + }) + } +} + +impl Encodable for IATA { + fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { + // write len + let mut buf = Vec::new(); + let mut opt_enc = Encoder::new(&mut buf); + self.opts.encode(&mut opt_enc)?; + // buf now has total len + e.write_u16(OptionCode::IATA.into())?; + e.write_u16(4 + buf.len() as u16)?; + // write data + e.write_u32(self.id)?; + e.write_slice(&buf)?; + Ok(()) + } +} + +option_builder!( + IATAOption, + IATAOptions, + IsIATAOption, + DhcpOption, + IAAddr, + StatusCode +); + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_iata_encode_decode() { + let option = IATA { + id: 0, + // 12 + opts.len() + opts: IATAOptions(vec![StatusCode { + status: 0xABCDu16.into(), + msg: "message".into(), + } + .into()]), + }; + + let mut encoder = vec![]; + + option.encode(&mut Encoder::new(&mut encoder)).unwrap(); + let decoded = IATA::decode(&mut Decoder::new(&encoder)).unwrap(); + assert_eq!(option, decoded); + + encoder.push(50); + let mut decoder = Decoder::new(&encoder); + let decoded = IATA::decode(&mut decoder).unwrap(); + assert_eq!(option, decoded); + assert_eq!(50, decoder.read_u8().unwrap()); + } +} diff --git a/src/v6/options/informationrefreshtime.rs b/src/v6/options/informationrefreshtime.rs new file mode 100644 index 0000000..4cc1721 --- /dev/null +++ b/src/v6/options/informationrefreshtime.rs @@ -0,0 +1,29 @@ +use super::{DecodeResult, EncodeResult, OptionCode}; +use crate::{Decodable, Decoder, Encodable, Encoder}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct InformationRefreshTime { + pub value: u32, +} + +impl Decodable for InformationRefreshTime { + fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { + decoder.read::<4>()?; + Ok(InformationRefreshTime { + value: decoder.read_u32()?, + }) + } +} + +impl Encodable for InformationRefreshTime { + fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { + e.write_u16(OptionCode::InformationRefreshTime.into())?; + e.write_u16(4)?; + e.write_u32(self.value)?; + Ok(()) + } +} diff --git a/src/v6/options/interfaceid.rs b/src/v6/options/interfaceid.rs new file mode 100644 index 0000000..2b61736 --- /dev/null +++ b/src/v6/options/interfaceid.rs @@ -0,0 +1,53 @@ +use super::{DecodeResult, EncodeResult, OptionCode}; +use crate::{Decodable, Decoder, Encodable, Encoder}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct InterfaceId { + pub id: Vec, +} + +impl Decodable for InterfaceId { + fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { + decoder.read::<2>()?; + let len = decoder.read_u16()? as usize; + Ok(InterfaceId { + id: decoder.read_slice(len)?.into(), + }) + } +} + +impl Encodable for InterfaceId { + fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { + e.write_u16(OptionCode::InterfaceId.into())?; + e.write_u16(self.id.len() as u16)?; + e.write_slice(&self.id)?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_interface_id_encode_decode() { + let option = InterfaceId { + id: vec![1, 2, 3, 4], + }; + + let mut encoder = vec![]; + + option.encode(&mut Encoder::new(&mut encoder)).unwrap(); + let decoded = InterfaceId::decode(&mut Decoder::new(&encoder)).unwrap(); + assert_eq!(option, decoded); + + encoder.push(50); + let mut decoder = Decoder::new(&encoder); + let decoded = InterfaceId::decode(&mut decoder).unwrap(); + assert_eq!(option, decoded); + assert_eq!(50, decoder.read_u8().unwrap()); + } +} diff --git a/src/v6/options/linkaddress.rs b/src/v6/options/linkaddress.rs new file mode 100644 index 0000000..82ee28e --- /dev/null +++ b/src/v6/options/linkaddress.rs @@ -0,0 +1,30 @@ +use super::{DecodeResult, EncodeResult, Ipv6Addr, OptionCode}; +use crate::{Decodable, Decoder, Encodable, Encoder}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct LinkAddress { + pub link_address: Ipv6Addr, +} + +impl Decodable for LinkAddress { + fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { + decoder.read::<2>()?; + let _len = decoder.read_u16()? as usize; + Ok(LinkAddress { + link_address: decoder.read::<16>()?.into(), + }) + } +} + +impl Encodable for LinkAddress { + fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { + e.write_u16(OptionCode::LinkAddress.into())?; + e.write_u16(16)?; + e.write_u128(self.link_address.into())?; + Ok(()) + } +} diff --git a/src/v6/options/lqclientlink.rs b/src/v6/options/lqclientlink.rs new file mode 100644 index 0000000..c0d0b96 --- /dev/null +++ b/src/v6/options/lqclientlink.rs @@ -0,0 +1,39 @@ +use std::net::Ipv6Addr; + +use super::{DecodeResult, EncodeResult, OptionCode}; +use crate::{Decodable, Decoder, Encodable, Encoder}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// Vendor defined options +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct LqClientLink { + pub link_addresses: Vec, +} + +impl Decodable for LqClientLink { + fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { + decoder.read::<2>()?; + let len = decoder.read_u16()? as usize; + let mut link_addresses = Vec::with_capacity(len / 16); + for _ in 0..(len / 16) { + link_addresses.push(decoder.read::<16>()?.into()); + } + + Ok(LqClientLink { link_addresses }) + } +} + +impl Encodable for LqClientLink { + fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { + e.write_u16(OptionCode::LqClientLink.into())?; + e.write_u16(self.link_addresses.len() as u16 * 16)?; + for address in self.link_addresses.iter() { + e.write_slice(&address.octets())?; + } + + Ok(()) + } +} diff --git a/src/v6/options/lqrelaydata.rs b/src/v6/options/lqrelaydata.rs new file mode 100644 index 0000000..4d6a49f --- /dev/null +++ b/src/v6/options/lqrelaydata.rs @@ -0,0 +1,38 @@ +use std::net::Ipv6Addr; + +use super::{DecodeResult, EncodeResult, OptionCode}; +use crate::{Decodable, Decoder, Encodable, Encoder}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// Vendor defined options +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct LqRelayData { + pub peer_address: Ipv6Addr, + pub relay_message: Vec, +} + +impl Decodable for LqRelayData { + fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { + decoder.read::<2>()?; + let len = decoder.read_u16()? as usize; + let mut decoder = Decoder::new(decoder.read_slice(len)?); + + Ok(LqRelayData { + peer_address: decoder.read::<16>()?.into(), + relay_message: decoder.read_slice(len - 16)?.into(), + }) + } +} + +impl Encodable for LqRelayData { + fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { + e.write_u16(OptionCode::LqRelayData.into())?; + e.write_u16(self.relay_message.len() as u16 + 16)?; + e.write_slice(&self.peer_address.octets())?; + e.write_slice(&self.relay_message)?; + Ok(()) + } +} diff --git a/src/v6/options/maxrt.rs b/src/v6/options/maxrt.rs new file mode 100644 index 0000000..8ac35bb --- /dev/null +++ b/src/v6/options/maxrt.rs @@ -0,0 +1,53 @@ +use super::{DecodeResult, EncodeResult, OptionCode}; +use crate::{Decodable, Decoder, Encodable, Encoder}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct SolMaxRt { + pub value: u32, +} + +impl Decodable for SolMaxRt { + fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { + decoder.read::<4>()?; + Ok(SolMaxRt { + value: decoder.read_u32()?, + }) + } +} + +impl Encodable for SolMaxRt { + fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { + e.write_u16(OptionCode::SolMaxRt.into())?; + e.write_u16(4)?; + e.write_u32(self.value)?; + Ok(()) + } +} + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct InfMaxRt { + pub value: u32, +} + +impl Decodable for InfMaxRt { + fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { + decoder.read::<4>()?; + Ok(InfMaxRt { + value: decoder.read_u32()?, + }) + } +} + +impl Encodable for InfMaxRt { + fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { + e.write_u16(OptionCode::InfMaxRt.into())?; + e.write_u16(4)?; + e.write_u32(self.value)?; + Ok(()) + } +} diff --git a/src/v6/options/mod.rs b/src/v6/options/mod.rs new file mode 100644 index 0000000..44c2b38 --- /dev/null +++ b/src/v6/options/mod.rs @@ -0,0 +1,753 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +//rfc8415 +mod iana; +pub use iana::*; +mod iaaddr; +pub use iaaddr::*; +mod status; +pub use status::*; +mod iapd; +pub use iapd::*; +mod iaprefix; +pub use iaprefix::*; +mod iata; +pub use iata::*; +mod auth; +pub use auth::*; +mod oro; +pub use oro::*; +mod clientid; +pub use clientid::*; +mod serverid; +pub use serverid::*; +mod preference; +pub use preference::*; +mod elapsedtime; +pub use elapsedtime::*; +mod relaymsg; +pub use relaymsg::*; +mod unicast; +pub use unicast::*; +mod interfaceid; +pub use interfaceid::*; +mod rapidcommit; +pub use rapidcommit::*; +mod reconfmsg; +pub use reconfmsg::*; +mod userclass; +pub use userclass::*; +mod vendorclass; +pub use vendorclass::*; +mod vendoropts; +pub use vendoropts::*; +mod maxrt; +pub use maxrt::*; +mod informationrefreshtime; +pub use informationrefreshtime::*; + +//rfc3646 +mod dnsservers; +pub use dnsservers::*; +mod domainlist; +pub use domainlist::*; + +//rfc5007 +mod query; +pub use query::*; +mod clientdata; +pub use clientdata::*; +mod lqrelaydata; +pub use lqrelaydata::*; +mod lqclientlink; +pub use lqclientlink::*; + +//rfc5460 +mod relayid; +pub use relayid::*; + +//rfc6977 +mod linkaddress; +pub use linkaddress::*; + +use std::{cmp::Ordering, net::Ipv6Addr, ops::RangeInclusive}; + +pub use crate::Domain; +use crate::{ + decoder::{Decodable, Decoder}, + encoder::{Encodable, Encoder}, + error::{DecodeResult, EncodeResult}, + v6::{Duid, MessageType, OROCode, OptionCode}, +}; + +//helper macro for implementing sub-options (IANAOptions, ect) +//useage: option_builder!(IANAOption, IANAOptions, IsIANAOption, DhcpOption, IAAddr, StatusCode); +// option_builder!(name , names , isname , master , subname... ); +macro_rules! option_builder{ + ($name: ident, $names: ident, $isname: ident, $mastername: ident, $($subnames: ident),*) => { + pub trait $isname{ + fn code() -> OptionCode; + } + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] + #[derive(Debug, Clone, PartialEq, Eq)] + pub enum $name { + $( + $subnames($subnames), + )* + ///invalid or unknown + Unknown($mastername), + } + $( + impl From<$subnames> for $name { + fn from(sc: $subnames) -> Self{ + $name :: $subnames(sc) + } + } + impl<'a> TryFrom<&'a $name> for &'a $subnames { + type Error = &'static str; + fn try_from(name: &'a $name) -> Result<&'a $subnames, Self::Error>{ + match name{ + $name :: $subnames(opt) => Ok(opt), + _ => Err("$subname is not a $name"), + } + } + } + impl TryFrom<$name> for $subnames { + type Error = &'static str; + fn try_from(name: $name) -> Result<$subnames, Self::Error>{ + match name{ + $name :: $subnames(opt) => Ok(opt), + _ => Err("$subname is not a $name"), + } + } + } + impl $isname for $subnames { + fn code() -> OptionCode{ + OptionCode::$subnames + } + } + )* + impl From<&$name> for $mastername{ + fn from(option: &$name) -> Self{ + match option { + $( + $name :: $subnames(u) => $mastername :: $subnames(u.clone()), + )* + $name::Unknown(other) => other.clone(), + } + } + } + impl TryFrom<&$mastername> for $name{ + type Error=&'static str; + + fn try_from(option: &$mastername) -> Result{ + match option{ + $( + $mastername :: $subnames(u) => Ok($name :: $subnames(u.clone())), + )* + _ => Err("invalid or unknown option"), + } + } + } + impl From<&$name> for OptionCode{ + fn from(option: &$name) -> OptionCode{ + match option{ + $( + $name :: $subnames(_) => OptionCode :: $subnames, + )* + $name :: Unknown(u) => OptionCode::from(u), + + } + } + } + impl Encodable for $name { + fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { + $mastername::from(self).encode(e) + } + } + + impl Decodable for $name { + fn decode(decoder: &mut Decoder<'_>) -> DecodeResult { + let option = $mastername::decode(decoder)?; + match (&option).try_into() { + Ok(n) => Ok(n), + _ => Ok($name::Unknown(option)), + } + } + } + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] + #[derive(Debug, Clone, PartialEq, Eq, Default)] + pub struct $names(Vec<$name>); + impl $names { + /// construct empty $names + pub fn new() -> Self{ + Default::default() + } + /// get the first element matching this type + pub fn get<'a, T: $isname>(&'a self) -> Option<&'a T> where &'a T: TryFrom<&'a $name>{ + use crate::v6::options::first; + use crate::v6::OptionCode; + let first = first(&self.0, |x| OptionCode::from(x).cmp(&T::code()))?; + //unwrap can not fail, it has already been checked. + self.0.get(first).map(|opt| <&T>::try_from(opt).ok().unwrap()) + } + /// get all elements matching this type + pub fn get_all(&self) -> Option<&[$name]>{ + use crate::v6::options::range_binsearch; + use crate::v6::OptionCode; + let range = range_binsearch(&self.0, |x| OptionCode::from(x).cmp(&T::code()))?; + Some(&self.0[range]) + } + /// get the first element matching the type + pub fn get_mut<'a, T: $isname>(&'a mut self) -> Option<&'a mut T> where &'a mut T: TryFrom<&'a mut $name>{ + use crate::v6::options::first; + use crate::v6::OptionCode; + let first = first(&self.0, |x| OptionCode::from(x).cmp(&T::code()))?; + //unwrap can not fail, it has already been checked. + self.0.get_mut(first).map(|opt| <&mut T>::try_from(opt).ok().unwrap()) + } + /// get all elements matching this option + pub fn get_mut_all(&mut self) -> Option<&mut [$name]>{ + use crate::v6::options::range_binsearch; + use crate::v6::OptionCode; + let range = range_binsearch(&self.0, |x| OptionCode::from(x).cmp(&T::code()))?; + Some(&mut self.0[range]) + } + /// remove the first element with a matching type + pub fn remove(&mut self) -> Option where T: TryFrom<$name>{ + use crate::v6::options::first; + use crate::v6::OptionCode; + let first = first(&self.0, |x| OptionCode::from(x).cmp(&T::code()))?; + T::try_from(self.0.remove(first)).ok() + } + pub fn remove_all(&mut self) -> Option + '_> where T: TryFrom<$name>{ + use crate::v6::options::range_binsearch; + use crate::v6::OptionCode; + let range = range_binsearch(&self.0, |x| OptionCode::from(x).cmp(&T::code()))?; + Some(self.0.drain(range).map(|opt| T::try_from(opt).ok().unwrap())) + } + /// insert a new option into the list of opts + pub fn insert>(&mut self, opt: T){ + let opt = opt.into(); + let i = self.0.partition_point(|x| OptionCode::from(x) < OptionCode::from(&opt)); + self.0.insert(i, opt) + } + /// return a mutable ref to an iterator + pub fn iter(&self) -> impl Iterator { + self.0.iter() + } + /// return a mutable ref to an iterator + pub fn iter_mut(&mut self) -> impl Iterator { + self.0.iter_mut() + } + } + impl Encodable for $names { + fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { + self.0.iter().try_for_each(|opt| opt.encode(e)) + } + } + impl Decodable for $names { + fn decode(decoder: &mut Decoder<'_>) -> DecodeResult { + let mut opts = Vec::new(); + while let Ok(opt) = $name::decode(decoder) { + opts.push(opt); + } + Ok($names(opts)) + } + } + impl IntoIterator for $names { + type Item = $name; + type IntoIter = std::vec::IntoIter; + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } + } + impl FromIterator<$name> for $names{ + fn from_iter>(iter: T) -> Self { + let opts = iter.into_iter().collect::>(); + $names(opts) + } + } + }; +} + +pub(crate) use option_builder; + +// server can send multiple IA_NA options to request multiple addresses +// so we must be able to handle multiple of the same option type +// +// TODO: consider HashMap> + +/// +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct DhcpOptions(Vec); +// vec maintains sorted on OptionCode + +impl DhcpOptions { + /// construct empty DhcpOptions + pub fn new() -> Self { + Self::default() + } + /// get the first element matching this option code + pub fn get(&self, code: OptionCode) -> Option<&DhcpOption> { + let first = first(&self.0, |x| OptionCode::from(x).cmp(&code))?; + // get_unchecked? + self.0.get(first) + } + /// get all elements matching this option code + pub fn get_all(&self, code: OptionCode) -> Option<&[DhcpOption]> { + let range = range_binsearch(&self.0, |x| OptionCode::from(x).cmp(&code))?; + Some(&self.0[range]) + } + /// get the first element matching this option code + pub fn get_mut(&mut self, code: OptionCode) -> Option<&mut DhcpOption> { + let first = first(&self.0, |x| OptionCode::from(x).cmp(&code))?; + self.0.get_mut(first) + } + /// get all elements matching this option code + pub fn get_mut_all(&mut self, code: OptionCode) -> Option<&mut [DhcpOption]> { + let range = range_binsearch(&self.0, |x| OptionCode::from(x).cmp(&code))?; + Some(&mut self.0[range]) + } + /// remove the first element with a matching option code + pub fn remove(&mut self, code: OptionCode) -> Option { + let first = first(&self.0, |x| OptionCode::from(x).cmp(&code))?; + Some(self.0.remove(first)) + } + /// remove all elements with a matching option code + pub fn remove_all( + &mut self, + code: OptionCode, + ) -> Option + '_> { + let range = range_binsearch(&self.0, |x| OptionCode::from(x).cmp(&code))?; + Some(self.0.drain(range)) + } + /// insert a new option into the list of opts + pub fn insert(&mut self, opt: DhcpOption) { + let i = self.0.partition_point(|x| x < &opt); + self.0.insert(i, opt) + } + /// return a reference to an iterator + pub fn iter(&self) -> impl Iterator { + self.0.iter() + } + /// return a mutable ref to an iterator + pub fn iter_mut(&mut self) -> impl Iterator { + self.0.iter_mut() + } +} + +impl IntoIterator for DhcpOptions { + type Item = DhcpOption; + + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl FromIterator for DhcpOptions { + fn from_iter>(iter: T) -> Self { + let mut opts = iter.into_iter().collect::>(); + opts.sort_unstable(); + DhcpOptions(opts) + } +} + +/// DHCPv6 option types +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum DhcpOption { + /// 1 - + ClientId(ClientId), + /// 2 - + ServerId(ServerId), + /// 3 - + IANA(IANA), + /// 4 - + IATA(IATA), + /// 5 - + IAAddr(IAAddr), + /// 6 - + ORO(ORO), + /// 7 - + Preference(Preference), + /// 8 - + /// Elapsed time in millis + ElapsedTime(ElapsedTime), + /// 9 - + RelayMsg(RelayMsg), + /// 11 - + Auth(Auth), + /// 12 - + Unicast(Unicast), + /// 13 - + StatusCode(StatusCode), + /// 14 - + RapidCommit(RapidCommit), + /// 15 - + UserClass(UserClass), + /// 16 - + VendorClass(VendorClass), + /// 17 - + VendorOpts(VendorOpts), + /// 18 - + InterfaceId(InterfaceId), + /// 19 - + ReconfMsg(ReconfMsg), + /// 20 - + ReconfAccept(ReconfAccept), + /// 23 - + DNSServers(DNSServers), + /// 24 - + DomainList(DomainList), + /// 25 - + IAPD(IAPD), + /// 26 - + IAPrefix(IAPrefix), + InformationRefreshTime(InformationRefreshTime), + SolMaxRt(SolMaxRt), + InfMaxRt(InfMaxRt), + LqQuery(LqQuery), + ClientData(ClientData), + CltTime(CltTime), + LqRelayData(LqRelayData), + LqClientLink(LqClientLink), + RelayId(RelayId), + LinkAddress(LinkAddress), + /// An unknown or unimplemented option type + Unknown(UnknownOption), +} + +impl PartialOrd for DhcpOption { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for DhcpOption { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + OptionCode::from(self).cmp(&OptionCode::from(other)) + } +} + +/// fallback for options not yet implemented +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct UnknownOption { + code: u16, + data: Vec, +} + +impl UnknownOption { + pub fn new(code: OptionCode, data: Vec) -> Self { + Self { + code: code.into(), + data, + } + } + /// return the option code + pub fn code(&self) -> OptionCode { + self.code.into() + } + /// return the data for this option + pub fn data(&self) -> &[u8] { + &self.data + } + /// consume option into its components + pub fn into_parts(self) -> (OptionCode, Vec) { + (self.code.into(), self.data) + } +} + +impl From<&UnknownOption> for OptionCode { + fn from(opt: &UnknownOption) -> Self { + opt.code.into() + } +} + +impl Decodable for DhcpOptions { + fn decode(decoder: &mut Decoder<'_>) -> DecodeResult { + let mut opts = Vec::new(); + while let Ok(opt) = DhcpOption::decode(decoder) { + opts.push(opt); + } + // sorts by OptionCode + opts.sort_unstable(); + Ok(DhcpOptions(opts)) + } +} + +impl Encodable for DhcpOptions { + fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { + self.0.iter().try_for_each(|opt| opt.encode(e)) + } +} + +impl Decodable for DhcpOption { + fn decode(decoder: &mut Decoder<'_>) -> DecodeResult { + let code = decoder.peek_u16()?.into(); + let tmp = Decoder::new(&decoder.buffer()[2..]); + let len = tmp.peek_u16()? as usize; + + Ok(match code { + OptionCode::ClientId => DhcpOption::ClientId(ClientId::decode(decoder)?), + OptionCode::ServerId => DhcpOption::ServerId(ServerId::decode(decoder)?), + OptionCode::IANA => DhcpOption::IANA(IANA::decode(decoder)?), + OptionCode::IATA => DhcpOption::IATA(IATA::decode(decoder)?), + OptionCode::IAAddr => DhcpOption::IAAddr(IAAddr::decode(decoder)?), + OptionCode::ORO => DhcpOption::ORO(ORO::decode(decoder)?), + OptionCode::Preference => DhcpOption::Preference(Preference::decode(decoder)?), + OptionCode::ElapsedTime => DhcpOption::ElapsedTime(ElapsedTime::decode(decoder)?), + OptionCode::RelayMsg => DhcpOption::RelayMsg(RelayMsg::decode(decoder)?), + OptionCode::Auth => DhcpOption::Auth(Auth::decode(decoder)?), + OptionCode::Unicast => DhcpOption::Unicast(Unicast::decode(decoder)?), + OptionCode::StatusCode => DhcpOption::StatusCode(StatusCode::decode(decoder)?), + OptionCode::RapidCommit => DhcpOption::RapidCommit(RapidCommit::decode(decoder)?), + OptionCode::UserClass => DhcpOption::UserClass(UserClass::decode(decoder)?), + OptionCode::VendorClass => DhcpOption::VendorClass(VendorClass::decode(decoder)?), + OptionCode::VendorOpts => DhcpOption::VendorOpts(VendorOpts::decode(decoder)?), + OptionCode::InterfaceId => DhcpOption::InterfaceId(InterfaceId::decode(decoder)?), + OptionCode::ReconfMsg => DhcpOption::ReconfMsg(ReconfMsg::decode(decoder)?), + OptionCode::ReconfAccept => DhcpOption::ReconfAccept(ReconfAccept::decode(decoder)?), + OptionCode::DNSServers => DhcpOption::DNSServers(DNSServers::decode(decoder)?), + OptionCode::IAPD => DhcpOption::IAPD(IAPD::decode(decoder)?), + OptionCode::IAPrefix => DhcpOption::IAPrefix(IAPrefix::decode(decoder)?), + OptionCode::InfMaxRt => DhcpOption::InfMaxRt(InfMaxRt::decode(decoder)?), + OptionCode::InformationRefreshTime => { + DhcpOption::InformationRefreshTime(InformationRefreshTime::decode(decoder)?) + } + OptionCode::SolMaxRt => DhcpOption::SolMaxRt(SolMaxRt::decode(decoder)?), + OptionCode::DomainList => DhcpOption::DomainList(DomainList::decode(decoder)?), + OptionCode::LqQuery => DhcpOption::LqQuery(LqQuery::decode(decoder)?), + OptionCode::ClientData => DhcpOption::ClientData(ClientData::decode(decoder)?), + OptionCode::CltTime => DhcpOption::CltTime(CltTime::decode(decoder)?), + OptionCode::LqRelayData => DhcpOption::LqRelayData(LqRelayData::decode(decoder)?), + OptionCode::LqClientLink => DhcpOption::LqClientLink(LqClientLink::decode(decoder)?), + OptionCode::RelayId => DhcpOption::RelayId(RelayId::decode(decoder)?), + OptionCode::LinkAddress => DhcpOption::LinkAddress(LinkAddress::decode(decoder)?), + // not yet implemented + OptionCode::Unknown(code) => { + decoder.read_u16()?; + decoder.read_u16()?; + DhcpOption::Unknown(UnknownOption { + code, + data: decoder.read_slice(len)?.to_vec(), + }) + } + unimplemented => { + decoder.read_u16()?; + decoder.read_u16()?; + DhcpOption::Unknown(UnknownOption { + code: unimplemented.into(), + data: decoder.read_slice(len)?.to_vec(), + }) + } + }) + } +} +impl Encodable for DhcpOption { + fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { + let code: OptionCode = self.into(); + match self { + DhcpOption::ClientId(duid) => { + duid.encode(e)?; + } + DhcpOption::ServerId(duid) => { + duid.encode(e)?; + } + DhcpOption::IANA(iana) => { + iana.encode(e)?; + } + DhcpOption::IAPD(iapd) => { + iapd.encode(e)?; + } + DhcpOption::IATA(iata) => { + iata.encode(e)?; + } + DhcpOption::IAAddr(iaaddr) => { + iaaddr.encode(e)?; + } + DhcpOption::ORO(oro) => { + oro.encode(e)?; + } + DhcpOption::Preference(pref) => { + pref.encode(e)?; + } + DhcpOption::ElapsedTime(elapsed) => { + elapsed.encode(e)?; + } + DhcpOption::RelayMsg(msg) => { + msg.encode(e)?; + } + DhcpOption::Auth(auth) => { + auth.encode(e)?; + } + DhcpOption::Unicast(addr) => { + addr.encode(e)?; + } + DhcpOption::StatusCode(status) => { + status.encode(e)?; + } + DhcpOption::RapidCommit(rc) => { + rc.encode(e)?; + } + DhcpOption::UserClass(uc) => { + uc.encode(e)?; + } + DhcpOption::VendorClass(vc) => { + vc.encode(e)?; + } + DhcpOption::VendorOpts(vopts) => { + vopts.encode(e)?; + } + DhcpOption::InterfaceId(id) => { + id.encode(e)?; + } + DhcpOption::ReconfMsg(msg_type) => { + msg_type.encode(e)?; + } + DhcpOption::ReconfAccept(accept) => { + accept.encode(e)?; + } + DhcpOption::SolMaxRt(auth) => { + auth.encode(e)?; + } + DhcpOption::InfMaxRt(auth) => { + auth.encode(e)?; + } + DhcpOption::InformationRefreshTime(auth) => { + auth.encode(e)?; + } + DhcpOption::DNSServers(addrs) => { + addrs.encode(e)?; + } + DhcpOption::DomainList(names) => { + names.encode(e)?; + } + DhcpOption::IAPrefix(iaprefix) => { + iaprefix.encode(e)?; + } + DhcpOption::LqQuery(q) => { + q.encode(e)?; + } + DhcpOption::ClientData(q) => { + q.encode(e)?; + } + DhcpOption::CltTime(q) => { + q.encode(e)?; + } + DhcpOption::LqRelayData(q) => { + q.encode(e)?; + } + DhcpOption::LqClientLink(q) => { + q.encode(e)?; + } + DhcpOption::RelayId(q) => { + q.encode(e)?; + } + DhcpOption::LinkAddress(q) => { + q.encode(e)?; + } + DhcpOption::Unknown(UnknownOption { data, .. }) => { + e.write_u16(code.into())?; + e.write_u16(data.len() as u16)?; + e.write_slice(data)?; + } + }; + Ok(()) + } +} + +#[inline] +pub(crate) fn first(arr: &[T], f: F) -> Option +where + F: Fn(&T) -> Ordering, +{ + let mut l = 0; + let mut r = arr.len() - 1; + while l <= r { + let mid = (l + r) >> 1; + // SAFETY: we know it is within the length + let mid_cmp = f(unsafe { arr.get_unchecked(mid) }); + let prev_cmp = if mid > 0 { + f(unsafe { arr.get_unchecked(mid - 1) }) == Ordering::Less + } else { + false + }; + if (mid == 0 || prev_cmp) && mid_cmp == Ordering::Equal { + return Some(mid); + } else if mid_cmp == Ordering::Less { + l = mid + 1; + } else { + r = mid - 1; + } + } + None +} + +#[inline] +pub(crate) fn last(arr: &[T], f: F) -> Option +where + F: Fn(&T) -> Ordering, +{ + let n = arr.len(); + let mut l = 0; + let mut r = n - 1; + while l <= r { + let mid = (l + r) >> 1; + // SAFETY: we know it is within the length + let mid_cmp = f(unsafe { arr.get_unchecked(mid) }); + let nxt_cmp = if mid < n { + f(unsafe { arr.get_unchecked(mid + 1) }) == Ordering::Greater + } else { + false + }; + if (mid == n - 1 || nxt_cmp) && mid_cmp == Ordering::Equal { + return Some(mid); + } else if mid_cmp == Ordering::Greater { + r = mid - 1; + } else { + l = mid + 1; + } + } + None +} + +#[inline] +pub(crate) fn range_binsearch(arr: &[T], f: F) -> Option> +where + F: Fn(&T) -> Ordering, +{ + let first = first(arr, &f)?; + let last = last(arr, &f)?; + Some(first..=last) +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_range_binsearch() { + let arr = vec![0, 1, 1, 1, 1, 4, 6, 7, 9, 9, 10]; + assert_eq!(Some(1..=4), range_binsearch(&arr, |x| x.cmp(&1))); + + let arr = vec![0, 1, 1, 1, 1, 4, 6, 7, 9, 9, 10]; + assert_eq!(Some(0..=0), range_binsearch(&arr, |x| x.cmp(&0))); + + let arr = vec![0, 1, 1, 1, 1, 4, 6, 7, 9, 9, 10]; + assert_eq!(Some(5..=5), range_binsearch(&arr, |x| x.cmp(&4))); + + let arr = vec![1, 2, 2, 2, 2, 3, 4, 7, 8, 8]; + assert_eq!(Some(8..=9), range_binsearch(&arr, |x| x.cmp(&8))); + + let arr = vec![1, 2, 2, 2, 2, 3, 4, 7, 8, 8]; + assert_eq!(Some(1..=4), range_binsearch(&arr, |x| x.cmp(&2))); + + let arr = vec![1, 2, 2, 2, 2, 3, 4, 7, 8, 8]; + assert_eq!(Some(7..=7), range_binsearch(&arr, |x| x.cmp(&7))); + } +} diff --git a/src/v6/options/oro.rs b/src/v6/options/oro.rs new file mode 100644 index 0000000..c7b14dd --- /dev/null +++ b/src/v6/options/oro.rs @@ -0,0 +1,66 @@ +use super::{DecodeResult, EncodeResult, OROCode, OptionCode}; +use crate::{Decodable, Decoder, Encodable, Encoder}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// Option Request Option +/// +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ORO { + pub opts: Vec, +} + +impl Decodable for ORO { + fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { + decoder.read_u16()?; + let len = decoder.read_u16()? as usize; + Ok(ORO { + opts: { + decoder + .read_slice(len)? + .chunks_exact(2) + // TODO: use .array_chunks::<2>() when stable + .map(|code| OROCode::from(u16::from_be_bytes([code[0], code[1]]))) + .collect() + }, + }) + } +} + +impl Encodable for ORO { + fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { + e.write_u16(OptionCode::ORO.into())?; + // write len + e.write_u16(2 * self.opts.len() as u16)?; + // data + for &code in self.opts.iter() { + e.write_u16(code.into())?; + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_iata_encode_decode() { + let option = ORO { + opts: vec![OROCode::SolMaxRt], + }; + + let mut encoder = vec![]; + + option.encode(&mut Encoder::new(&mut encoder)).unwrap(); + let decoded = ORO::decode(&mut Decoder::new(&encoder)).unwrap(); + assert_eq!(option, decoded); + + encoder.push(50); + let mut decoder = Decoder::new(&encoder); + let decoded = ORO::decode(&mut decoder).unwrap(); + assert_eq!(option, decoded); + assert_eq!(50, decoder.read_u8().unwrap()); + } +} diff --git a/src/v6/options/preference.rs b/src/v6/options/preference.rs new file mode 100644 index 0000000..4c37be7 --- /dev/null +++ b/src/v6/options/preference.rs @@ -0,0 +1,51 @@ +use super::{DecodeResult, EncodeResult, OptionCode}; +use crate::{Decodable, Decoder, Encodable, Encoder}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Preference { + pub pref: u8, +} + +impl Decodable for Preference { + fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { + decoder.read::<2>()?; + let _len = decoder.read_u16()? as usize; + Ok(Preference { + pref: decoder.read_u8()?, + }) + } +} + +impl Encodable for Preference { + fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { + e.write_u16(OptionCode::Preference.into())?; + e.write_u16(1)?; + e.write_u8(self.pref)?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_preference_encode_decode() { + let option = Preference { pref: 1 }; + + let mut encoder = vec![]; + + option.encode(&mut Encoder::new(&mut encoder)).unwrap(); + let decoded = Preference::decode(&mut Decoder::new(&encoder)).unwrap(); + assert_eq!(option, decoded); + + encoder.push(50); + let mut decoder = Decoder::new(&encoder); + let decoded = Preference::decode(&mut decoder).unwrap(); + assert_eq!(option, decoded); + assert_eq!(50, decoder.read_u8().unwrap()); + } +} diff --git a/src/v6/options/query.rs b/src/v6/options/query.rs new file mode 100644 index 0000000..68c7ebe --- /dev/null +++ b/src/v6/options/query.rs @@ -0,0 +1,124 @@ +use std::net::Ipv6Addr; + +use super::{ + option_builder, ClientId, DecodeResult, DhcpOption, EncodeResult, IAAddr, OptionCode, ORO, +}; +use crate::{Decodable, Decoder, Encodable, Encoder}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// Lease Query +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct LqQuery { + pub qtype: QueryType, + pub link_address: Ipv6Addr, + pub opts: LqQueryOptions, +} + +impl Decodable for LqQuery { + fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { + decoder.read::<2>()?; + let len = decoder.read_u16()? as usize; + let mut decoder = Decoder::new(decoder.read_slice(len)?); + let qtype = decoder.read_u8()?.into(); + let link_address = decoder.read::<16>()?.into(); + let opts = LqQueryOptions::decode(&mut decoder)?; + Ok(LqQuery { + qtype, + link_address, + opts, + }) + } +} + +impl Encodable for LqQuery { + fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { + let mut buf = Vec::new(); + let mut opt_enc = Encoder::new(&mut buf); + self.opts.encode(&mut opt_enc)?; + + e.write_u16(OptionCode::LqQuery.into())?; + e.write_u16(buf.len() as u16 + 17)?; + e.write_u8(self.qtype.into())?; + e.write::<16>(self.link_address.octets())?; + e.write_slice(&buf)?; + + Ok(()) + } +} + +option_builder!( + LqQueryOption, + LqQueryOptions, + IsLqQueryOption, + DhcpOption, + IAAddr, + ClientId, + ORO +); + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum QueryType { + QueryByAddress, + QueryByClientID, + QueryByRelayID, + QueryByLinkAaddress, + QueryByRemoteID, + Unknown(u8), +} + +impl From for QueryType { + fn from(qtype: u8) -> Self { + use QueryType::*; + match qtype { + 1 => QueryByAddress, + 2 => QueryByClientID, + 3 => QueryByRelayID, + 4 => QueryByLinkAaddress, + 5 => QueryByRemoteID, + t => Unknown(t), + } + } +} + +impl From for u8 { + fn from(num: QueryType) -> Self { + use QueryType::*; + match num { + QueryByAddress => 1, + QueryByClientID => 2, + QueryByRelayID => 3, + QueryByLinkAaddress => 4, + QueryByRemoteID => 5, + Unknown(t) => t, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_query_option_encode_decode() { + let option = LqQuery { + qtype: 1.into(), + link_address: "0::0".parse().unwrap(), + opts: LqQueryOptions::default(), + }; + + let mut encoder = vec![]; + + option.encode(&mut Encoder::new(&mut encoder)).unwrap(); + let decoded = LqQuery::decode(&mut Decoder::new(&encoder)).unwrap(); + assert_eq!(option, decoded); + + encoder.push(50); + let mut decoder = Decoder::new(&encoder); + let decoded = LqQuery::decode(&mut decoder).unwrap(); + assert_eq!(option, decoded); + assert_eq!(50, decoder.read_u8().unwrap()); + } +} diff --git a/src/v6/options/rapidcommit.rs b/src/v6/options/rapidcommit.rs new file mode 100644 index 0000000..ef8ed44 --- /dev/null +++ b/src/v6/options/rapidcommit.rs @@ -0,0 +1,45 @@ +use super::{DecodeResult, EncodeResult, OptionCode}; +use crate::{Decodable, Decoder, Encodable, Encoder}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct RapidCommit; + +impl Decodable for RapidCommit { + fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { + decoder.read::<4>()?; + Ok(RapidCommit) + } +} + +impl Encodable for RapidCommit { + fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { + e.write_u16(OptionCode::RapidCommit.into())?; + e.write_u16(0)?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_rapid_commit_encode_decode() { + let option = RapidCommit; + + let mut encoder = vec![]; + + option.encode(&mut Encoder::new(&mut encoder)).unwrap(); + let decoded = RapidCommit::decode(&mut Decoder::new(&encoder)).unwrap(); + assert_eq!(option, decoded); + + encoder.push(50); + let mut decoder = Decoder::new(&encoder); + let decoded = RapidCommit::decode(&mut decoder).unwrap(); + assert_eq!(option, decoded); + assert_eq!(50, decoder.read_u8().unwrap()); + } +} diff --git a/src/v6/options/reconfmsg.rs b/src/v6/options/reconfmsg.rs new file mode 100644 index 0000000..d3bd982 --- /dev/null +++ b/src/v6/options/reconfmsg.rs @@ -0,0 +1,87 @@ +use super::{DecodeResult, EncodeResult, MessageType, OptionCode}; +use crate::{Decodable, Decoder, Encodable, Encoder}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// Reconfigure message +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ReconfMsg { + pub msg_type: MessageType, +} + +impl Decodable for ReconfMsg { + fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { + decoder.read::<4>()?; + Ok(ReconfMsg { + msg_type: decoder.read_u8()?.into(), + }) + } +} + +impl Encodable for ReconfMsg { + fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { + e.write_u16(OptionCode::ReconfMsg.into())?; + e.write_u16(1)?; + e.write_u8(self.msg_type.into())?; + Ok(()) + } +} + +/// Identity Association for Non-Temporary Addresses +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ReconfAccept {} + +impl Decodable for ReconfAccept { + fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { + decoder.read::<4>()?; + Ok(ReconfAccept {}) + } +} + +impl Encodable for ReconfAccept { + fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { + e.write_u16(OptionCode::ReconfAccept.into())?; + e.write_u16(0)?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_reconf_msg_encode_decode() { + let option = ReconfMsg { msg_type: 1.into() }; + + let mut encoder = vec![]; + + option.encode(&mut Encoder::new(&mut encoder)).unwrap(); + let decoded = ReconfMsg::decode(&mut Decoder::new(&encoder)).unwrap(); + assert_eq!(option, decoded); + + encoder.push(50); + let mut decoder = Decoder::new(&encoder); + let decoded = ReconfMsg::decode(&mut decoder).unwrap(); + assert_eq!(option, decoded); + assert_eq!(50, decoder.read_u8().unwrap()); + } + #[test] + fn test_reconf_accept_encode_decode() { + let option = ReconfAccept {}; + + let mut encoder = vec![]; + + option.encode(&mut Encoder::new(&mut encoder)).unwrap(); + let decoded = ReconfAccept::decode(&mut Decoder::new(&encoder)).unwrap(); + assert_eq!(option, decoded); + + encoder.push(50); + let mut decoder = Decoder::new(&encoder); + let decoded = ReconfAccept::decode(&mut decoder).unwrap(); + assert_eq!(option, decoded); + assert_eq!(50, decoder.read_u8().unwrap()); + } +} diff --git a/src/v6/options/relayid.rs b/src/v6/options/relayid.rs new file mode 100644 index 0000000..f57e014 --- /dev/null +++ b/src/v6/options/relayid.rs @@ -0,0 +1,36 @@ +use super::{DecodeResult, Duid, EncodeResult, OptionCode}; +use crate::{Decodable, Decoder, Encodable, Encoder}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// Client Identity +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RelayId { + pub id: Duid, +} + +impl Decodable for RelayId { + fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { + decoder.read::<2>()?; + let len = decoder.read_u16()? as usize; + let mut decoder = Decoder::new(decoder.read_slice(len)?); + Ok(RelayId { + id: Duid::decode(&mut decoder)?, + }) + } +} + +impl Encodable for RelayId { + fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { + // write len + let mut buf = Vec::new(); + let mut opt_enc = Encoder::new(&mut buf); + self.id.encode(&mut opt_enc)?; + e.write_u16(OptionCode::RelayId.into())?; + e.write_u16(buf.len() as u16)?; + e.write_slice(&buf)?; + Ok(()) + } +} diff --git a/src/v6/options/relaymsg.rs b/src/v6/options/relaymsg.rs new file mode 100644 index 0000000..7686fe5 --- /dev/null +++ b/src/v6/options/relaymsg.rs @@ -0,0 +1,54 @@ +use super::{DecodeResult, EncodeResult, OptionCode}; +use crate::{Decodable, Decoder, Encodable, Encoder}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RelayMsg { + pub msg: Vec, +} + +impl Decodable for RelayMsg { + fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { + decoder.read::<2>()?; + let len = decoder.read_u16()? as usize; + + Ok(RelayMsg { + msg: decoder.read_slice(len)?.into(), + }) + } +} + +impl Encodable for RelayMsg { + fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { + e.write_u16(OptionCode::RelayMsg.into())?; + e.write_u16(self.msg.len() as u16)?; + e.write_slice(&self.msg)?; + Ok(()) + } +} + +//impl From for Message? + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_server_id_encode_decode() { + let option = RelayMsg { msg: vec![1, 2, 3] }; + + let mut encoder = vec![]; + + option.encode(&mut Encoder::new(&mut encoder)).unwrap(); + let decoded = RelayMsg::decode(&mut Decoder::new(&encoder)).unwrap(); + assert_eq!(option, decoded); + + encoder.push(50); + let mut decoder = Decoder::new(&encoder); + let decoded = RelayMsg::decode(&mut decoder).unwrap(); + assert_eq!(option, decoded); + assert_eq!(50, decoder.read_u8().unwrap()); + } +} diff --git a/src/v6/options/serverid.rs b/src/v6/options/serverid.rs new file mode 100644 index 0000000..a354c1c --- /dev/null +++ b/src/v6/options/serverid.rs @@ -0,0 +1,59 @@ +use super::{DecodeResult, Duid, EncodeResult, OptionCode}; +use crate::{Decodable, Decoder, Encodable, Encoder}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// Server Identity +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ServerId { + pub id: Duid, +} + +impl Decodable for ServerId { + fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { + decoder.read::<2>()?; + let len = decoder.read_u16()? as usize; + let mut decoder = Decoder::new(decoder.read_slice(len)?); + Ok(ServerId { + id: Duid::decode(&mut decoder)?, + }) + } +} + +impl Encodable for ServerId { + fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { + // write len + let mut buf = Vec::new(); + let mut opt_enc = Encoder::new(&mut buf); + self.id.encode(&mut opt_enc)?; + e.write_u16(OptionCode::ServerId.into())?; + e.write_u16(buf.len() as u16)?; + e.write_slice(&buf)?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_server_id_encode_decode() { + let option = ServerId { + id: Duid::enterprise(1, &[1, 2, 3]), + }; + + let mut encoder = vec![]; + + option.encode(&mut Encoder::new(&mut encoder)).unwrap(); + let decoded = ServerId::decode(&mut Decoder::new(&encoder)).unwrap(); + assert_eq!(option, decoded); + + encoder.push(50); + let mut decoder = Decoder::new(&encoder); + let decoded = ServerId::decode(&mut decoder).unwrap(); + assert_eq!(option, decoded); + assert_eq!(50, decoder.read_u8().unwrap()); + } +} diff --git a/src/v6/options/status.rs b/src/v6/options/status.rs new file mode 100644 index 0000000..dbea760 --- /dev/null +++ b/src/v6/options/status.rs @@ -0,0 +1,151 @@ +use crate::v6::{DecodeResult, EncodeResult, OptionCode}; +use crate::{Decodable, Decoder, Encodable, Encoder}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct StatusCode { + pub status: Status, + // 2 + len + pub msg: String, +} + +impl Decodable for StatusCode { + fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { + let _code = decoder.read_u16()?; + let len = decoder.read_u16()? as usize; + Ok(StatusCode { + status: decoder.read_u16()?.into(), + msg: decoder.read_string(len - 2)?, + }) + } +} + +impl Encodable for StatusCode { + fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { + e.write_u16(OptionCode::StatusCode.into())?; + e.write_u16(2 + self.msg.len() as u16)?; + e.write_u16(self.status.into())?; + e.write_slice(self.msg.as_bytes())?; + Ok(()) + } +} + +/// Status code +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Status { + Success, + UnspecFail, + NoAddrsAvail, + NoBinding, + NotOnLink, + UseMulticast, + NoPrefixAvail, + UnknownQueryType, + MalformedQuery, + NotConfigured, + NotAllowed, + QueryTerminated, + DataMissing, + CatchUpComplete, + NotSupported, + TLSConnectionRefused, + AddressInUse, + ConfigurationConflict, + MissingBindingInformation, + OutdatedBindingInformation, + ServerShuttingDown, + DNSUpdateNotSupported, + ExcessiveTimeSkew, + /// unknown/unimplemented message type + Unknown(u16), +} + +impl From for Status { + fn from(n: u16) -> Self { + use Status::*; + match n { + 0 => Success, + 1 => UnspecFail, + 2 => NoAddrsAvail, + 3 => NoBinding, + 4 => NotOnLink, + 5 => UseMulticast, + 6 => NoPrefixAvail, + 7 => UnknownQueryType, + 8 => MalformedQuery, + 9 => NotConfigured, + 10 => NotAllowed, + 11 => QueryTerminated, + 12 => DataMissing, + 13 => CatchUpComplete, + 14 => NotSupported, + 15 => TLSConnectionRefused, + 16 => AddressInUse, + 17 => ConfigurationConflict, + 18 => MissingBindingInformation, + 19 => OutdatedBindingInformation, + 20 => ServerShuttingDown, + 21 => DNSUpdateNotSupported, + 22 => ExcessiveTimeSkew, + _ => Unknown(n), + } + } +} +impl From for u16 { + fn from(n: Status) -> Self { + use Status::*; + match n { + Success => 0, + UnspecFail => 1, + NoAddrsAvail => 2, + NoBinding => 3, + NotOnLink => 4, + UseMulticast => 5, + NoPrefixAvail => 6, + UnknownQueryType => 7, + MalformedQuery => 8, + NotConfigured => 9, + NotAllowed => 10, + QueryTerminated => 11, + DataMissing => 12, + CatchUpComplete => 13, + NotSupported => 14, + TLSConnectionRefused => 15, + AddressInUse => 16, + ConfigurationConflict => 17, + MissingBindingInformation => 18, + OutdatedBindingInformation => 19, + ServerShuttingDown => 20, + DNSUpdateNotSupported => 21, + ExcessiveTimeSkew => 22, + Unknown(n) => n, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_status_code_encode_decode() { + let sc = StatusCode { + status: 0xABCDu16.into(), + msg: "message".into(), + }; + let mut encoder = vec![]; + + sc.encode(&mut Encoder::new(&mut encoder)).unwrap(); + let decoded = StatusCode::decode(&mut Decoder::new(&encoder)).unwrap(); + assert_eq!(sc, decoded); + + encoder.push(50); + let mut decoder = Decoder::new(&encoder); + let decoded = StatusCode::decode(&mut decoder).unwrap(); + assert_eq!(sc, decoded); + assert_eq!(50, decoder.read_u8().unwrap()); + } +} diff --git a/src/v6/options/unicast.rs b/src/v6/options/unicast.rs new file mode 100644 index 0000000..aaeb22e --- /dev/null +++ b/src/v6/options/unicast.rs @@ -0,0 +1,53 @@ +use super::{DecodeResult, EncodeResult, Ipv6Addr, OptionCode}; +use crate::{Decodable, Decoder, Encodable, Encoder}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct Unicast { + pub server_address: Ipv6Addr, +} + +impl Decodable for Unicast { + fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { + decoder.read::<2>()?; + let _len = decoder.read_u16()? as usize; + Ok(Unicast { + server_address: decoder.read::<16>()?.into(), + }) + } +} + +impl Encodable for Unicast { + fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { + e.write_u16(OptionCode::Unicast.into())?; + e.write_u16(16)?; + e.write_u128(self.server_address.into())?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_server_id_encode_decode() { + let option = Unicast { + server_address: "FE80::".parse().unwrap(), + }; + + let mut encoder = vec![]; + + option.encode(&mut Encoder::new(&mut encoder)).unwrap(); + let decoded = Unicast::decode(&mut Decoder::new(&encoder)).unwrap(); + assert_eq!(option, decoded); + + encoder.push(50); + let mut decoder = Decoder::new(&encoder); + let decoded = Unicast::decode(&mut decoder).unwrap(); + assert_eq!(option, decoded); + assert_eq!(50, decoder.read_u8().unwrap()); + } +} diff --git a/src/v6/options/userclass.rs b/src/v6/options/userclass.rs new file mode 100644 index 0000000..ff4e28e --- /dev/null +++ b/src/v6/options/userclass.rs @@ -0,0 +1,94 @@ +use super::{DecodeResult, EncodeResult, OptionCode}; +use crate::{Decodable, Decoder, Encodable, Encoder}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct UserClass { + pub data: Vec, +} + +impl Decodable for UserClass { + fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { + decoder.read::<2>()?; + let len = decoder.read_u16()?; + let mut data = vec![]; + let mut decoder = Decoder::new(decoder.read_slice(len as usize)?); + let mut remaining_len = len; + while remaining_len > 0 { + let len = decoder.peek_u16()?; + data.push(UserClassData::decode(&mut decoder)?); + remaining_len -= len + 2; + } + Ok(UserClass { data }) + } +} + +impl Encodable for UserClass { + fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { + e.write_u16(OptionCode::UserClass.into())?; + let mut data = vec![]; + let mut dataenc = Encoder::new(&mut data); + for ucd in self.data.iter() { + ucd.encode(&mut dataenc)?; + } + e.write_u16(data.len() as u16)?; + e.write_slice(&data)?; + Ok(()) + } +} + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct UserClassData { + pub data: Vec, +} + +impl Decodable for UserClassData { + fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { + let len = decoder.read_u16()?; + Ok(UserClassData { + data: decoder.read_slice(len.into())?.into(), + }) + } +} + +impl Encodable for UserClassData { + fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { + e.write_u16(self.data.len() as u16)?; + e.write_slice(&self.data)?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_userclass_encode_decode() { + let option = UserClass { + data: vec![ + UserClassData { + data: vec![1, 2, 3, 4], + }, + UserClassData { data: vec![1] }, + UserClassData { data: vec![1, 2] }, + ], + }; + + let mut encoder = vec![]; + + option.encode(&mut Encoder::new(&mut encoder)).unwrap(); + + let decoded = UserClass::decode(&mut Decoder::new(&encoder)).unwrap(); + assert_eq!(option, decoded); + + encoder.push(50); + let mut decoder = Decoder::new(&encoder); + let decoded = UserClass::decode(&mut decoder).unwrap(); + assert_eq!(option, decoded); + assert_eq!(50, decoder.read_u8().unwrap()); + } +} diff --git a/src/v6/options/vendorclass.rs b/src/v6/options/vendorclass.rs new file mode 100644 index 0000000..ed9ee95 --- /dev/null +++ b/src/v6/options/vendorclass.rs @@ -0,0 +1,94 @@ +use super::{DecodeResult, EncodeResult, OptionCode}; +use crate::{Decodable, Decoder, Encodable, Encoder}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct VendorClass { + pub data: Vec, +} + +impl Decodable for VendorClass { + fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { + decoder.read::<2>()?; + let len = decoder.read_u16()?; + let mut data = vec![]; + let mut decoder = Decoder::new(decoder.read_slice(len as usize)?); + let mut remaining_len = len; + while remaining_len > 0 { + let len = decoder.peek_u16()?; + data.push(VendorClassData::decode(&mut decoder)?); + remaining_len -= len + 2; + } + Ok(VendorClass { data }) + } +} + +impl Encodable for VendorClass { + fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { + e.write_u16(OptionCode::VendorClass.into())?; + let mut data = vec![]; + let mut dataenc = Encoder::new(&mut data); + for ucd in self.data.iter() { + ucd.encode(&mut dataenc)?; + } + e.write_u16(data.len() as u16)?; + e.write_slice(&data)?; + Ok(()) + } +} + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct VendorClassData { + pub data: Vec, +} + +impl Decodable for VendorClassData { + fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { + let len = decoder.read_u16()?; + Ok(VendorClassData { + data: decoder.read_slice(len.into())?.into(), + }) + } +} + +impl Encodable for VendorClassData { + fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { + e.write_u16(self.data.len() as u16)?; + e.write_slice(&self.data)?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_vendorclass_encode_decode() { + let option = VendorClass { + data: vec![ + VendorClassData { + data: vec![1, 2, 3, 4], + }, + VendorClassData { data: vec![1] }, + VendorClassData { data: vec![1, 2] }, + ], + }; + + let mut encoder = vec![]; + + option.encode(&mut Encoder::new(&mut encoder)).unwrap(); + + let decoded = VendorClass::decode(&mut Decoder::new(&encoder)).unwrap(); + assert_eq!(option, decoded); + + encoder.push(50); + let mut decoder = Decoder::new(&encoder); + let decoded = VendorClass::decode(&mut decoder).unwrap(); + assert_eq!(option, decoded); + assert_eq!(50, decoder.read_u8().unwrap()); + } +} diff --git a/src/v6/options/vendoropts.rs b/src/v6/options/vendoropts.rs new file mode 100644 index 0000000..e391097 --- /dev/null +++ b/src/v6/options/vendoropts.rs @@ -0,0 +1,113 @@ +use super::{DecodeResult, EncodeResult, OptionCode}; +use crate::{Decodable, Decoder, Encodable, Encoder}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// Vendor defined options +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct VendorOpts { + pub enterprise_number: u32, + pub opts: Vec, +} + +impl Decodable for VendorOpts { + fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { + decoder.read::<2>()?; + let len = decoder.read_u16()?; + let enterprise_number = decoder.read_u32()?; + let mut opts = vec![]; + let mut used_len = 4; + while used_len < len { + let opt = VendorOption::decode(decoder)?; + used_len += opt.len() + 4; + opts.push(opt); + } + Ok(VendorOpts { + enterprise_number, + opts, + }) + } +} + +impl Encodable for VendorOpts { + fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { + let mut data = vec![]; + let mut enc = Encoder::new(&mut data); + for opt in self.opts.iter() { + opt.encode(&mut enc)?; + } + e.write_u16(OptionCode::VendorOpts.into())?; + e.write_u16(data.len() as u16 + 4)?; + e.write_u32(self.enterprise_number)?; + e.write_slice(&data)?; + Ok(()) + } +} + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct VendorOption { + pub code: u16, + pub data: Vec, +} + +impl VendorOption { + fn len(&self) -> u16 { + self.data.len() as u16 + } +} + +impl Decodable for VendorOption { + fn decode(decoder: &'_ mut Decoder<'_>) -> DecodeResult { + let code = decoder.read_u16()?; + let len = decoder.read_u16()?; + Ok(VendorOption { + code, + data: decoder.read_slice(len.into())?.into(), + }) + } +} + +impl Encodable for VendorOption { + fn encode(&self, e: &'_ mut Encoder<'_>) -> EncodeResult<()> { + e.write_u16(self.code)?; + e.write_u16(self.data.len() as u16)?; + e.write_slice(&self.data)?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_vendoropts_encode_decode() { + let option = VendorOpts { + enterprise_number: 0xABCD, + opts: vec![ + VendorOption { + code: 0xABCD, + data: vec![1, 2], + }, + VendorOption { + code: 0xACBD, + data: vec![1, 2, 3], + }, + ], + }; + + let mut encoder = vec![]; + + option.encode(&mut Encoder::new(&mut encoder)).unwrap(); + let decoded = VendorOpts::decode(&mut Decoder::new(&encoder)).unwrap(); + assert_eq!(option, decoded); + + encoder.push(50); + let mut decoder = Decoder::new(&encoder); + let decoded = VendorOpts::decode(&mut decoder).unwrap(); + assert_eq!(option, decoded); + assert_eq!(50, decoder.read_u8().unwrap()); + } +} diff --git a/src/v6/oro_codes.rs b/src/v6/oro_codes.rs new file mode 100644 index 0000000..d0cc9f9 --- /dev/null +++ b/src/v6/oro_codes.rs @@ -0,0 +1,233 @@ +///Valid Option Codes for ORO +///https://datatracker.ietf.org/doc/html/rfc8415#section-24 + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use crate::v6::OptionCode; +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum OROCode { + ///Optional + VendorOpts, + SipServerD, + SipServerA, + DNSServers, + DomainList, + NisServers, + NispServers, + NisDomainName, + NispDomainName, + SntpServers, + ///Required for Information-request + InformationRefreshTime, + BcmcsServerD, + BcmcsServerA, + GeoconfCivic, + ClientFqdn, + PanaAgent, + NewPosixTimezone, + NewTzdbTimezone, + Mip6Hnidf, + Mip6Vdinf, + V6Lost, + CapwapAcV6, + Ipv6AddressMoS, + Ipv6FQDNMoS, + NtpServer, + V6AccessDomain, + SipUaCsList, + OptBootfileUrl, + OptBootfileParam, + Nii, + Geolocation, + AftrName, + ErpLocalDomainName, + PdExclude, + Mip6Idinf, + Mip6Udinf, + Mip6Hnp, + Mip6Haa, + Mip6Haf, + RdnssSelection, + KrbPrincipalName, + KrbRealmName, + KrbDefaultRealmName, + KrbKdc, + ///Required for Solicit + SolMaxRt, + ///Required for Information-request + InfMaxRt, + Addrsel, + AddrselTable, + V6PcpServer, + Dhcp4ODhcp6Server, + S46ContMape, + S46ContMapt, + S46ContLw, + _4Rd, + _4RdMapRule, + _4RdNonMapRule, + DhcpCaptivePortal, + MplParameters, + S46Priority, + V6Prefix64, + Ipv6AddressANDSF, + ///Avalible for future codes. + Unknown(u16), +} + +impl From for u16 { + fn from(opt: OROCode) -> Self { + OptionCode::from(opt).into() + } +} + +//should this be a TryFrom? +impl From for OROCode { + fn from(opt: u16) -> Self { + OptionCode::from(opt) + .try_into() + .unwrap_or(OROCode::Unknown(opt)) + } +} + +impl TryFrom for OROCode { + type Error = &'static str; + fn try_from(opt: OptionCode) -> Result { + match opt { + OptionCode::VendorOpts => Ok(OROCode::VendorOpts), + OptionCode::SipServerD => Ok(OROCode::SipServerD), + OptionCode::SipServerA => Ok(OROCode::SipServerA), + OptionCode::DNSServers => Ok(OROCode::DNSServers), + OptionCode::DomainList => Ok(OROCode::DomainList), + OptionCode::NisServers => Ok(OROCode::NisServers), + OptionCode::NispServers => Ok(OROCode::NispServers), + OptionCode::NisDomainName => Ok(OROCode::NisDomainName), + OptionCode::NispDomainName => Ok(OROCode::NispDomainName), + OptionCode::SntpServers => Ok(OROCode::SntpServers), + OptionCode::InformationRefreshTime => Ok(OROCode::InformationRefreshTime), + OptionCode::BcmcsServerD => Ok(OROCode::BcmcsServerD), + OptionCode::BcmcsServerA => Ok(OROCode::BcmcsServerA), + OptionCode::GeoconfCivic => Ok(OROCode::GeoconfCivic), + OptionCode::ClientFqdn => Ok(OROCode::ClientFqdn), + OptionCode::PanaAgent => Ok(OROCode::PanaAgent), + OptionCode::NewPosixTimezone => Ok(OROCode::NewPosixTimezone), + OptionCode::NewTzdbTimezone => Ok(OROCode::NewTzdbTimezone), + OptionCode::Mip6Hnidf => Ok(OROCode::Mip6Hnidf), + OptionCode::Mip6Vdinf => Ok(OROCode::Mip6Vdinf), + OptionCode::V6Lost => Ok(OROCode::V6Lost), + OptionCode::CapwapAcV6 => Ok(OROCode::CapwapAcV6), + OptionCode::Ipv6AddressMoS => Ok(OROCode::Ipv6AddressMoS), + OptionCode::Ipv6FQDNMoS => Ok(OROCode::Ipv6FQDNMoS), + OptionCode::NtpServer => Ok(OROCode::NtpServer), + OptionCode::V6AccessDomain => Ok(OROCode::V6AccessDomain), + OptionCode::SipUaCsList => Ok(OROCode::SipUaCsList), + OptionCode::OptBootfileUrl => Ok(OROCode::OptBootfileUrl), + OptionCode::OptBootfileParam => Ok(OROCode::OptBootfileParam), + OptionCode::Nii => Ok(OROCode::Nii), + OptionCode::Geolocation => Ok(OROCode::Geolocation), + OptionCode::AftrName => Ok(OROCode::AftrName), + OptionCode::ErpLocalDomainName => Ok(OROCode::ErpLocalDomainName), + OptionCode::PdExclude => Ok(OROCode::PdExclude), + OptionCode::Mip6Idinf => Ok(OROCode::Mip6Idinf), + OptionCode::Mip6Udinf => Ok(OROCode::Mip6Udinf), + OptionCode::Mip6Hnp => Ok(OROCode::Mip6Hnp), + OptionCode::Mip6Haa => Ok(OROCode::Mip6Haa), + OptionCode::Mip6Haf => Ok(OROCode::Mip6Haf), + OptionCode::RdnssSelection => Ok(OROCode::RdnssSelection), + OptionCode::KrbPrincipalName => Ok(OROCode::KrbPrincipalName), + OptionCode::KrbRealmName => Ok(OROCode::KrbRealmName), + OptionCode::KrbDefaultRealmName => Ok(OROCode::KrbDefaultRealmName), + OptionCode::KrbKdc => Ok(OROCode::KrbKdc), + OptionCode::SolMaxRt => Ok(OROCode::SolMaxRt), + OptionCode::InfMaxRt => Ok(OROCode::InfMaxRt), + OptionCode::Addrsel => Ok(OROCode::Addrsel), + OptionCode::AddrselTable => Ok(OROCode::AddrselTable), + OptionCode::V6PcpServer => Ok(OROCode::V6PcpServer), + OptionCode::Dhcp4ODhcp6Server => Ok(OROCode::Dhcp4ODhcp6Server), + OptionCode::S46ContMape => Ok(OROCode::S46ContMape), + OptionCode::S46ContMapt => Ok(OROCode::S46ContMapt), + OptionCode::S46ContLw => Ok(OROCode::S46ContLw), + OptionCode::_4Rd => Ok(OROCode::_4Rd), + OptionCode::_4RdMapRule => Ok(OROCode::_4RdMapRule), + OptionCode::_4RdNonMapRule => Ok(OROCode::_4RdNonMapRule), + OptionCode::DhcpCaptivePortal => Ok(OROCode::DhcpCaptivePortal), + OptionCode::MplParameters => Ok(OROCode::MplParameters), + OptionCode::S46Priority => Ok(OROCode::S46Priority), + OptionCode::V6Prefix64 => Ok(OROCode::V6Prefix64), + OptionCode::Ipv6AddressANDSF => Ok(OROCode::Ipv6AddressANDSF), + OptionCode::Unknown(u16) => Ok(OROCode::Unknown(u16)), + _ => Err("conversion error, is not a valid OROCode"), + } + } +} + +impl From for OptionCode { + fn from(opt: OROCode) -> OptionCode { + match opt { + OROCode::VendorOpts => OptionCode::VendorOpts, + OROCode::SipServerD => OptionCode::SipServerD, + OROCode::SipServerA => OptionCode::SipServerA, + OROCode::DNSServers => OptionCode::DNSServers, + OROCode::DomainList => OptionCode::DomainList, + OROCode::NisServers => OptionCode::NisServers, + OROCode::NispServers => OptionCode::NispServers, + OROCode::NisDomainName => OptionCode::NisDomainName, + OROCode::NispDomainName => OptionCode::NispDomainName, + OROCode::SntpServers => OptionCode::SntpServers, + OROCode::InformationRefreshTime => OptionCode::InformationRefreshTime, + OROCode::BcmcsServerD => OptionCode::BcmcsServerD, + OROCode::BcmcsServerA => OptionCode::BcmcsServerA, + OROCode::GeoconfCivic => OptionCode::GeoconfCivic, + OROCode::ClientFqdn => OptionCode::ClientFqdn, + OROCode::PanaAgent => OptionCode::PanaAgent, + OROCode::NewPosixTimezone => OptionCode::NewPosixTimezone, + OROCode::NewTzdbTimezone => OptionCode::NewTzdbTimezone, + OROCode::Mip6Hnidf => OptionCode::Mip6Hnidf, + OROCode::Mip6Vdinf => OptionCode::Mip6Vdinf, + OROCode::V6Lost => OptionCode::V6Lost, + OROCode::CapwapAcV6 => OptionCode::CapwapAcV6, + OROCode::Ipv6AddressMoS => OptionCode::Ipv6AddressMoS, + OROCode::Ipv6FQDNMoS => OptionCode::Ipv6FQDNMoS, + OROCode::NtpServer => OptionCode::NtpServer, + OROCode::V6AccessDomain => OptionCode::V6AccessDomain, + OROCode::SipUaCsList => OptionCode::SipUaCsList, + OROCode::OptBootfileUrl => OptionCode::OptBootfileUrl, + OROCode::OptBootfileParam => OptionCode::OptBootfileParam, + OROCode::Nii => OptionCode::Nii, + OROCode::Geolocation => OptionCode::Geolocation, + OROCode::AftrName => OptionCode::AftrName, + OROCode::ErpLocalDomainName => OptionCode::ErpLocalDomainName, + OROCode::PdExclude => OptionCode::PdExclude, + OROCode::Mip6Idinf => OptionCode::Mip6Idinf, + OROCode::Mip6Udinf => OptionCode::Mip6Udinf, + OROCode::Mip6Hnp => OptionCode::Mip6Hnp, + OROCode::Mip6Haa => OptionCode::Mip6Haa, + OROCode::Mip6Haf => OptionCode::Mip6Haf, + OROCode::RdnssSelection => OptionCode::RdnssSelection, + OROCode::KrbPrincipalName => OptionCode::KrbPrincipalName, + OROCode::KrbRealmName => OptionCode::KrbRealmName, + OROCode::KrbDefaultRealmName => OptionCode::KrbDefaultRealmName, + OROCode::KrbKdc => OptionCode::KrbKdc, + OROCode::SolMaxRt => OptionCode::SolMaxRt, + OROCode::InfMaxRt => OptionCode::InfMaxRt, + OROCode::Addrsel => OptionCode::Addrsel, + OROCode::AddrselTable => OptionCode::AddrselTable, + OROCode::V6PcpServer => OptionCode::V6PcpServer, + OROCode::Dhcp4ODhcp6Server => OptionCode::Dhcp4ODhcp6Server, + OROCode::S46ContMape => OptionCode::S46ContMape, + OROCode::S46ContMapt => OptionCode::S46ContMapt, + OROCode::S46ContLw => OptionCode::S46ContLw, + OROCode::_4Rd => OptionCode::_4Rd, + OROCode::_4RdMapRule => OptionCode::_4RdMapRule, + OROCode::_4RdNonMapRule => OptionCode::_4RdNonMapRule, + OROCode::DhcpCaptivePortal => OptionCode::DhcpCaptivePortal, + OROCode::MplParameters => OptionCode::MplParameters, + OROCode::S46Priority => OptionCode::S46Priority, + OROCode::V6Prefix64 => OptionCode::V6Prefix64, + OROCode::Ipv6AddressANDSF => OptionCode::Ipv6AddressANDSF, + OROCode::Unknown(u16) => OptionCode::Unknown(u16), + } + } +}