radius_rust/protocol/
radius_packet.rs

1//! RADIUS Packet implementation
2
3
4use super::dictionary::{ Dictionary, SupportedAttributeTypes };
5use super::error::RadiusError;
6use crate::tools::{
7    bytes_to_integer,
8    bytes_to_integer64,
9    bytes_to_interfaceid_string,
10    bytes_to_ipv4_string,
11    bytes_to_ipv6_string,
12    bytes_to_timestamp,
13    u16_from_be_bytes
14};
15
16use hmac::{ Hmac, Mac };
17use md5::Md5;
18
19use rand::{ thread_rng, Rng };
20use rand::distributions::{ Distribution, Uniform };
21
22
23use std::convert::TryInto;
24use std::fmt;
25
26
27type HmacMd5 = Hmac<Md5>;
28
29
30#[derive(PartialEq, Eq, Hash)]
31/// Allowed types of RADIUS messages/packets
32///
33/// Mainly used in RADIUS Server implementation to distinguish between sockets and functions, that should
34/// process RADIUS packets
35pub enum RadiusMsgType {
36    /// Authentication packet
37    AUTH,
38    /// Accounting packet
39    ACCT,
40    /// Change of Authorisation packet
41    COA
42}
43
44impl fmt::Display for RadiusMsgType {
45    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46        match *self {
47            RadiusMsgType::AUTH => f.write_str("Auth"),
48            RadiusMsgType::ACCT => f.write_str("Acct"),
49            RadiusMsgType::COA  => f.write_str("CoA"),
50        }
51    }
52}
53
54
55#[derive(Debug, Clone, PartialEq)]
56/// Contains all supported Codes of RADIUS message/packet
57/// as defined in RFC 2865 & RFC 3576
58pub enum TypeCode {
59    /// AccessRequest      = 1
60    AccessRequest,
61    /// AccessAccept       = 2
62    AccessAccept,
63    /// AccessReject       = 3
64    AccessReject,
65    /// AccountingRequest  = 4
66    AccountingRequest,
67    /// AccountingResponse = 5
68    AccountingResponse,
69    /// AccessChallenge    = 11
70    AccessChallenge,
71    /// StatusServer       = 12
72    StatusServer,
73    /// StatusClient       = 13
74    StatusClient,
75    /// DisconnectRequest  = 40
76    DisconnectRequest,
77    /// DisconnectACK      = 41
78    DisconnectACK,
79    /// DisconnectNAK      = 42
80    DisconnectNAK,
81    /// CoARequest         = 43
82    CoARequest,
83    /// CoAACK             = 44
84    CoAACK,
85    /// CoANAK             = 45
86    CoANAK
87}
88
89impl TypeCode {
90    /// Convert integer(u8) value into corresponding TypeCode enum
91    pub fn from_u8(code: u8) -> Result<TypeCode, RadiusError> {
92        match code {
93            1u8  => Ok(TypeCode::AccessRequest),
94            2u8  => Ok(TypeCode::AccessAccept),
95            3u8  => Ok(TypeCode::AccessReject),
96            4u8  => Ok(TypeCode::AccountingRequest),
97            5u8  => Ok(TypeCode::AccountingResponse),
98            11u8 => Ok(TypeCode::AccessChallenge),
99            12u8 => Ok(TypeCode::StatusServer),
100            13u8 => Ok(TypeCode::StatusClient),
101            40u8 => Ok(TypeCode::DisconnectRequest),
102            41u8 => Ok(TypeCode::DisconnectACK),
103            42u8 => Ok(TypeCode::DisconnectNAK),
104            43u8 => Ok(TypeCode::CoARequest ),
105            44u8 => Ok(TypeCode::CoAACK),
106            45u8 => Ok(TypeCode::CoANAK),
107            _ => Err( RadiusError::UnsupportedTypeCodeError { error: format!("Unknown RADIUS code: {}", code) }),
108        }
109    }
110
111    /// Convert TypeCode enum value into corresponding integer(u8)
112    pub fn to_u8(&self) -> u8 {
113        match self {
114            TypeCode::AccessRequest      => 1u8,
115            TypeCode::AccessAccept       => 2u8,
116            TypeCode::AccessReject       => 3u8,
117            TypeCode::AccountingRequest  => 4u8,
118            TypeCode::AccountingResponse => 5u8,
119            TypeCode::AccessChallenge    => 11u8,
120            TypeCode::StatusServer       => 12u8,
121            TypeCode::StatusClient       => 13u8,
122            TypeCode::DisconnectRequest  => 40u8,
123            TypeCode::DisconnectACK      => 41u8,
124            TypeCode::DisconnectNAK      => 42u8,
125            TypeCode::CoARequest         => 43u8,
126            TypeCode::CoAACK             => 44u8,
127            TypeCode::CoANAK             => 45u8
128        }
129    }
130}
131
132
133#[derive(Debug, PartialEq)]
134/// Represents an attribute, which would be sent to RADIUS Server/client as a part of RadiusPacket
135pub struct RadiusAttribute {
136    id:    u8,
137    name:  String,
138    value: Vec<u8>
139}
140
141impl RadiusAttribute {
142    /// Creates RadiusAttribute with given name
143    ///
144    /// Returns None, if ATTRIBUTE with such name is not found in Dictionary
145    pub fn create_by_name(dictionary: &Dictionary, attribute_name: &str, value: Vec<u8>) -> Option<RadiusAttribute> {
146        dictionary.attributes().iter().find(|&attr| attr.name() == attribute_name).map(|attr| RadiusAttribute {
147            id:    attr.code(),
148            name:  attr.name().to_string(),
149            value
150        })
151    }
152
153    /// Creates RadiusAttribute with given id
154    ///
155    /// Returns None, if ATTRIBUTE with such id is not found in Dictionary
156    pub fn create_by_id(dictionary: &Dictionary, attribute_code: u8, value: Vec<u8>) -> Option<RadiusAttribute> {
157        dictionary.attributes().iter().find(|&attr| attr.code() == attribute_code).map(|attr| RadiusAttribute {
158            id:    attribute_code,
159            name:  attr.name().to_string(),
160            value
161        })
162    }
163
164    /// Overriddes RadiusAttribute value
165    ///
166    /// Mainly used when building Message-Authenticator
167    pub fn override_value(&mut self, new_value: Vec<u8>) {
168        self.value = new_value
169    }
170
171    /// Returns RadiusAttribute id
172    pub fn id(&self) -> u8 {
173        self.id
174    }
175
176    /// Returns RadiusAttribute value
177    pub fn value(&self) -> &[u8] {
178        &self.value
179    }
180
181    /// Returns RadiusAttribute name
182    pub fn name(&self) -> &str {
183        &self.name
184    }
185
186    /// Verifies RadiusAttribute value, based on the ATTRIBUTE code type
187    pub fn verify_original_value(&self, allowed_type: &Option<SupportedAttributeTypes>) -> Result<(), RadiusError> {
188        match allowed_type {
189            Some(SupportedAttributeTypes::AsciiString) => {
190                match String::from_utf8(self.value().to_vec()) {
191                    Ok(_) => Ok(()),
192                    _     => Err( RadiusError::MalformedAttributeError {error: String::from("invalid ASCII(Text) bytes")} )
193                }
194            },
195            Some(SupportedAttributeTypes::ByteString)  => {
196                // We cannot verify original value as it is a binary string
197                Ok(())
198            },
199            Some(SupportedAttributeTypes::Concat) => {
200                // Behaves similar to ByteString but allowed to be longer than 253 octets
201                Ok(())
202            },
203            Some(SupportedAttributeTypes::Integer)     => {
204                match self.value().try_into() {
205                    Ok(value) => {
206                        bytes_to_integer(value);
207                        Ok(())
208                    },
209                    _         => Err( RadiusError::MalformedAttributeError {error: String::from("invalid Integer bytes")} )
210                }
211            },
212            Some(SupportedAttributeTypes::Integer64)   => {
213                match self.value().try_into() {
214                    Ok(value) => {
215                        bytes_to_integer64(value);
216                        Ok(())
217                    },
218                    _         => Err( RadiusError::MalformedAttributeError {error: String::from("invalid Integer64 bytes")} )
219                }
220            },
221            Some(SupportedAttributeTypes::Date)        => {
222                match self.value().try_into() {
223                    Ok(value) => {
224                        bytes_to_timestamp(value);
225                        Ok(())
226                    },
227                    _         => Err( RadiusError::MalformedAttributeError {error: String::from("invalid Date bytes")} )
228                }
229            },
230            Some(SupportedAttributeTypes::IPv4Addr)    => {
231                match bytes_to_ipv4_string(self.value()) {
232                    Ok(_) => Ok(()),
233                    _     => Err( RadiusError::MalformedAttributeError {error: String::from("invalid IPv4 bytes")} )
234                }
235            },
236            Some(SupportedAttributeTypes::IPv4Prefix)  => {
237                match bytes_to_ipv4_string(self.value()) {
238                    Ok(_) => Ok(()),
239                    _     => Err( RadiusError::MalformedAttributeError {error: String::from("invalid IPv4Prefix bytes")} )
240                }
241            },
242            Some(SupportedAttributeTypes::IPv6Addr)    => {
243                match bytes_to_ipv6_string(self.value()) {
244                    Ok(_) => Ok(()),
245                    _     => Err( RadiusError::MalformedAttributeError {error: String::from("invalid IPv6 bytes")} )
246                }
247            },
248            Some(SupportedAttributeTypes::IPv6Prefix)  => {
249                match bytes_to_ipv6_string(self.value()) {
250                    Ok(_) => Ok(()),
251                    _     => Err( RadiusError::MalformedAttributeError {error: String::from("invalid IPv6Prefix bytes")} )
252                }
253            },
254            Some(SupportedAttributeTypes::InterfaceId) => {
255                match bytes_to_interfaceid_string(self.value()) {
256                    Ok(_) => Ok(()),
257                    _     => Err( RadiusError::MalformedAttributeError {error: String::from("invalid InterfaceId bytes")} )
258                }
259            },
260            _                                          => Err( RadiusError::MalformedAttributeError {error: String::from("unsupported attribute code type")} )
261        }
262    }
263
264    /// Returns RadiusAttribute value, if the attribute is dictionary's ATTRIBUTE with code type
265    /// string, ipaddr, ipv4addr, ipv4prefix, ipv6addr or ipv6prefix
266    pub fn original_string_value(&self, allowed_type: &Option<SupportedAttributeTypes>) -> Result<String, RadiusError> {
267        match allowed_type {
268            Some(SupportedAttributeTypes::AsciiString) => {
269                match String::from_utf8(self.value().to_vec()) {
270                    Ok(value) => Ok(value),
271                    _         => Err( RadiusError::MalformedAttributeError {error: String::from("invalid ASCII bytes")} )
272                }
273            },
274            Some(SupportedAttributeTypes::IPv4Addr)    => {
275                match bytes_to_ipv4_string(self.value()) {
276                    Ok(value) => Ok(value),
277                    _         => Err( RadiusError::MalformedAttributeError {error: String::from("invalid IPv4 bytes")} )
278                }
279            },
280            Some(SupportedAttributeTypes::IPv4Prefix)  => {
281                match bytes_to_ipv4_string(self.value()) {
282                    Ok(value) => Ok(value),
283                    _         => Err( RadiusError::MalformedAttributeError {error: String::from("invalid IPv4Prefix bytes")} )
284                }
285            },
286            Some(SupportedAttributeTypes::IPv6Addr)    => {
287                match bytes_to_ipv6_string(self.value()) {
288                    Ok(value) => Ok(value),
289                    _         => Err( RadiusError::MalformedAttributeError {error: String::from("invalid IPv6 bytes")} )
290                }
291            },
292            Some(SupportedAttributeTypes::IPv6Prefix)  => {
293                match bytes_to_ipv6_string(self.value()) {
294                    Ok(value) => Ok(value),
295                    _         => Err( RadiusError::MalformedAttributeError {error: String::from("invalid IPv6 bytes")} )
296                }
297            },
298            Some(SupportedAttributeTypes::InterfaceId) => {
299                match bytes_to_interfaceid_string(self.value()) {
300                    Ok(value) => Ok(value),
301                    _         => Err( RadiusError::MalformedAttributeError {error: String::from("invalid InterfaceId bytes")} )
302                }
303            },
304            _                                          => Err( RadiusError::MalformedAttributeError {error: String::from("not a String data type")} )
305        }
306    }
307
308    /// Returns RadiusAttribute value, if the attribute is dictionary's ATTRIBUTE with code type
309    /// integer or date
310    pub fn original_integer_value(&self, allowed_type: &Option<SupportedAttributeTypes>) -> Result<u32, RadiusError> {
311        match allowed_type {
312            Some(SupportedAttributeTypes::Integer) => {
313                match self.value().try_into() {
314                    Ok(value) => Ok(bytes_to_integer(value)),
315                    _         => Err( RadiusError::MalformedAttributeError {error: String::from("invalid Integer bytes")} )
316                }
317            } ,
318            Some(SupportedAttributeTypes::Date)    => {
319                match self.value().try_into() {
320                    Ok(value) => Ok(bytes_to_timestamp(value)),
321                    _         => Err( RadiusError::MalformedAttributeError {error: String::from("invalid Date bytes")} )
322                }
323            },
324            _                                      => Err( RadiusError::MalformedAttributeError {error: String::from("not an Integer data type")} )
325        }
326    }
327
328    /// Returns RadiusAttribute value, if the attribute is dictionary's ATTRIBUTE with code type
329    /// integer64
330    pub fn original_integer64_value(&self, allowed_type: &Option<SupportedAttributeTypes>) -> Result<u64, RadiusError> {
331        match allowed_type {
332            Some(SupportedAttributeTypes::Integer64) => {
333                match self.value().try_into() {
334                    Ok(value) => Ok(bytes_to_integer64(value)),
335                    _         => Err( RadiusError::MalformedAttributeError {error: String::from("invalid Integer64 bytes")} )
336                }
337            },
338            _                                        => Err( RadiusError::MalformedAttributeError {error: String::from("not an Integer data type")} )
339        }
340    }
341
342    fn to_bytes(&self) -> Vec<u8> {
343        /*
344         *
345         *         0               1              2
346         0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
347         +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
348         |     Type      |    Length     |  Value ...
349         +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
350         *  Taken from https://tools.ietf.org/html/rfc2865#page-23
351         */
352        [ &[self.id], &[(2 + self.value.len()) as u8], self.value.as_slice() ].concat()
353    }
354}
355
356
357#[derive(Debug, PartialEq)]
358/// Represents RADIUS packet
359pub struct RadiusPacket {
360    id:            u8,
361    code:          TypeCode,
362    authenticator: Vec<u8>,
363    attributes:    Vec<RadiusAttribute>
364}
365
366impl RadiusPacket {
367    /// Initialises RADIUS packet with random ID and authenticator
368    pub fn initialise_packet(code: TypeCode) -> RadiusPacket {
369        RadiusPacket {
370            id:            RadiusPacket::create_id(),
371            code,
372            authenticator: RadiusPacket::create_authenticator(),
373            attributes:    Vec::new()
374        }
375    }
376
377    /// Initialises RADIUS packet from raw bytes
378    pub fn initialise_packet_from_bytes(dictionary: &Dictionary, bytes: &[u8]) -> Result<RadiusPacket, RadiusError> {
379        if bytes.len() < 20 || bytes.len() > 4096 {
380            return Err( RadiusError::MalformedPacketError {error: String::from("packet length should be of size between 20 and 4096 octets")} )
381        }
382
383        let code           = TypeCode::from_u8(bytes[0])?;
384        let id             = bytes[1];
385        let packet_len     = u16_from_be_bytes(&bytes[2..4]) as usize;
386        let authenticator  = bytes[4..20].to_vec();
387        let mut attributes = Vec::new();
388
389        if packet_len > bytes.len() {
390            return Err( RadiusError::MalformedPacketError {error:format!("defined packet length: [{}] is greater than actual packet length received: [{}]", packet_len, bytes.len())} )
391        }
392
393        let mut last_index = 20;
394
395        while last_index != packet_len {
396            let attr_id     = bytes[last_index];
397            let attr_length = bytes[last_index + 1] as usize;
398
399            if attr_length == 0 {
400                return Err( RadiusError::MalformedPacketError {error:format!("attribute with ID: {} has invalid length 0", attr_id)} )
401            }
402
403            let attr_value  = &bytes[(last_index + 2)..=(last_index + attr_length - 1)];
404
405            match RadiusAttribute::create_by_id(dictionary, attr_id, attr_value.to_vec()) {
406                Some(attr) => {
407                    attributes.push(attr);
408                    last_index += attr_length;
409                },
410                _          => return Err( RadiusError::MalformedPacketError {error:format!("attribute with ID: {} is not found in dictionary", attr_id)} )
411            }
412        }
413
414        let mut packet = RadiusPacket{
415            id,
416            code,
417            authenticator,
418            attributes:    Vec::new()
419        };
420        packet.set_attributes(attributes);
421
422        Ok(packet)
423    }
424
425    /// Sets attrbiutes
426    pub fn set_attributes(&mut self, attributes: Vec<RadiusAttribute>) {
427        self.attributes = attributes;
428    }
429
430    /// Overrides RadiusPacket id
431    pub fn override_id(&mut self, new_id: u8) {
432        self.id = new_id
433    }
434
435    /// Overrides RadiusPacket authenticator
436    pub fn override_authenticator(&mut self, new_authenticator: Vec<u8>) {
437        self.authenticator = new_authenticator
438    }
439
440    /// Overrides RadiusPacket Message-Authenticator
441    ///
442    /// Note: would fail if RadiusPacket has no Message-Authenticator attribute defined
443    pub fn override_message_authenticator(&mut self, new_message_authenticator: Vec<u8>) -> Result<(), RadiusError> {
444        match self.attributes.iter_mut().find(|attr| attr.name() == "Message-Authenticator") {
445            Some(attr) => {
446                attr.override_value(new_message_authenticator);
447                Ok(())
448            },
449            _          => Err( RadiusError::MalformedPacketError {error:String::from("Message-Authenticator attribute not found in packet")} )
450        }
451    }
452
453    /// Generates HMAC-MD5 hash for Message-Authenticator attribute
454    ///
455    /// Note 1: this function assumes that RadiusAttribute Message-Authenticator already exists in RadiusPacket
456    /// Note 2: Message-Authenticator in RadiusPacket would be overwritten when this function is called
457    pub fn generate_message_authenticator(&mut self, secret: &str) -> Result<(), RadiusError> {
458        // Step 1. Set Message-Authenticator to an array of 16 zeros in the RadiusPacket
459        let zeroed_authenticator = [0; 16];
460        self.override_message_authenticator(zeroed_authenticator.to_vec())?;
461
462        // Step 2. Calculate HMAC-MD5 for the entire RadiusPacket
463        let mut hash = HmacMd5::new_from_slice(secret.as_bytes()).map_err(|error| RadiusError::MalformedPacketError { error: error.to_string() })?;
464        hash.update(&self.to_bytes());
465
466        // Step 3. Set Message-Authenticator to the result of Step 2
467        self.override_message_authenticator(hash.finalize().into_bytes().to_vec())?;
468
469        Ok(())
470    }
471
472    /// Returns Message-Authenticator value, if exists in RadiusPacket
473    pub fn message_authenticator(&self) -> Result<&[u8], RadiusError> {
474        match self.attributes.iter().find(|attr| attr.name() == "Message-Authenticator") {
475            Some(attr) => {
476                Ok(attr.value())
477            },
478            _          => Err( RadiusError::MalformedPacketError {error: String::from("Message-Authenticator attribute not found in packet")} )
479        }
480    }
481
482    /// Returns RadiusPacket id
483    pub fn id(&self) -> u8 {
484        self.id
485    }
486
487    /// Returns RadiusPacket authenticator
488    pub fn authenticator(&self) -> &[u8] {
489        &self.authenticator
490    }
491
492    /// Returns RadiusPacket code
493    pub fn code(&self) -> &TypeCode {
494        &self.code
495    }
496
497    /// Returns RadiusPacket attributes
498    pub fn attributes(&self) -> &[RadiusAttribute] {
499        &self.attributes
500    }
501
502    /// Returns RadiusAttribute with given name
503    pub fn attribute_by_name(&self, name: &str) -> Option<&RadiusAttribute> {
504        self.attributes.iter().find(|&attr| attr.name() == name)
505    }
506
507    /// Returns RadiusAttribute with given id
508    pub fn attribute_by_id(&self, id: u8) -> Option<&RadiusAttribute> {
509        self.attributes.iter().find(|&attr| attr.id() == id)
510    }
511
512    /// Converts RadiusPacket into ready-to-be-sent bytes vector
513    pub fn to_bytes(&mut self) -> Vec<u8> {
514        /* Prepare packet for a transmission to server/client
515         *
516         *          0               1               2         3
517         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
518         +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
519         |     Code      |  Identifier   |            Length             |
520         +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
521         |                                                               |
522         |                         Authenticator                         |
523         |                                                               |
524         |                                                               |
525         +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
526         |  Attributes ...
527         +-+-+-+-+-+-+-+-+-+-+-+-+-
528         * Taken from https://tools.ietf.org/html/rfc2865#page-14
529         *
530         */
531
532        let mut packet_bytes = Vec::new();
533        let mut packet_attr  = Vec::new();
534
535        if self.authenticator.is_empty() {
536            self.authenticator = Self::create_authenticator();
537        }
538
539        for attr in self.attributes.iter() {
540            packet_attr.extend(&attr.to_bytes());
541        }
542
543        packet_bytes.push(self.code.to_u8());
544        packet_bytes.push(self.id);
545        packet_bytes.append(&mut Self::packet_length_to_bytes(((20 + packet_attr.len()) as u16).to_be()).to_vec());
546        packet_bytes.append(&mut self.authenticator.as_slice().to_vec());
547        packet_bytes.append(&mut packet_attr);
548
549        packet_bytes
550    }
551
552    fn create_id() -> u8 {
553        thread_rng().gen_range(0u8..=255u8)
554    }
555
556    fn create_authenticator() -> Vec<u8> {
557        let allowed_values             = Uniform::from(0u8..=255u8);
558        let mut rng                    = thread_rng();
559        let mut authenticator: Vec<u8> = Vec::with_capacity(16);
560
561        for _ in 0..16 {
562            authenticator.push(allowed_values.sample(&mut rng))
563        }
564
565        authenticator
566    }
567
568    fn packet_length_to_bytes(length: u16) -> [u8; 2] {
569        RadiusPacket::u16_to_u8(length)
570    }
571
572    fn u16_to_u8(u16_data: u16) -> [u8;2] {
573        [u16_data as u8, (u16_data >> 8) as u8]
574    }
575}
576
577#[cfg(test)]
578mod tests {
579    use crate::tools::{ integer_to_bytes, ipv4_string_to_bytes};
580    use super::*;
581
582    #[test]
583    fn test_radius_attribute_create_by_name() {
584        let dictionary_path = "./dict_examples/test_dictionary_dict";
585        let dict            = Dictionary::from_file(dictionary_path).unwrap();
586
587        let expected = RadiusAttribute {
588            id:    1,
589            name:  String::from("User-Name"),
590            value: vec![1,2,3]
591        };
592
593        assert_eq!(Some(expected), RadiusAttribute::create_by_name(&dict, "User-Name", vec![1,2,3]));
594    }
595    #[test]
596    fn test_radius_attribute_create_by_name_non_existing() {
597        let dictionary_path = "./dict_examples/test_dictionary_dict";
598        let dict            = Dictionary::from_file(dictionary_path).unwrap();
599
600        assert_eq!(None, RadiusAttribute::create_by_name(&dict, "Non-Existing", vec![1,2,3]));
601    }
602
603    #[test]
604    fn test_radius_attribute_create_by_id() {
605        let dictionary_path = "./dict_examples/test_dictionary_dict";
606        let dict            = Dictionary::from_file(dictionary_path).unwrap();
607
608        let expected = RadiusAttribute {
609            id:    5,
610            name:  String::from("NAS-Port-Id"),
611            value: vec![1,2,3]
612        };
613
614        assert_eq!(Some(expected), RadiusAttribute::create_by_id(&dict, 5, vec![1,2,3]));
615    }
616    #[test]
617    fn test_radius_attribute_create_by_id_non_existing() {
618        let dictionary_path = "./dict_examples/test_dictionary_dict";
619        let dict            = Dictionary::from_file(dictionary_path).unwrap();
620
621        assert_eq!(None, RadiusAttribute::create_by_id(&dict, 205, vec![1,2,3]));
622    }
623
624    #[test]
625    fn test_initialise_packet_from_bytes() {
626        let dictionary_path = "./dict_examples/integration_dict";
627        let dict            = Dictionary::from_file(dictionary_path).unwrap();
628
629        let nas_ip_addr_bytes    = ipv4_string_to_bytes("192.168.1.10").unwrap();
630        let framed_ip_addr_bytes = ipv4_string_to_bytes("10.0.0.100").unwrap();
631        let attributes          = vec![
632            RadiusAttribute::create_by_name(&dict, "NAS-IP-Address",     nas_ip_addr_bytes).unwrap(),
633            RadiusAttribute::create_by_name(&dict, "NAS-Port-Id",        integer_to_bytes(0)).unwrap(),
634            RadiusAttribute::create_by_name(&dict, "NAS-Identifier",     String::from("trillian").into_bytes()).unwrap(),
635            RadiusAttribute::create_by_name(&dict, "Called-Station-Id",  String::from("00-04-5F-00-0F-D1").into_bytes()).unwrap(),
636            RadiusAttribute::create_by_name(&dict, "Calling-Station-Id", String::from("00-01-24-80-B3-9C").into_bytes()).unwrap(),
637            RadiusAttribute::create_by_name(&dict, "Framed-IP-Address",  framed_ip_addr_bytes).unwrap()
638        ];
639        let authenticator       = vec![215, 189, 213, 172, 57, 94, 141, 70, 134, 121, 101, 57, 187, 220, 227, 73];
640        let mut expected_packet = RadiusPacket::initialise_packet(TypeCode::AccountingRequest);
641        expected_packet.set_attributes(attributes);
642        expected_packet.override_id(43);
643        expected_packet.override_authenticator(authenticator);
644
645        let bytes             = [4, 43, 0, 86, 215, 189, 213, 172, 57, 94, 141, 70, 134, 121, 101, 57, 187, 220, 227, 73, 4, 6, 192, 168, 1, 10, 5, 6, 0, 0, 0, 0, 32, 10, 116, 114, 105, 108, 108, 105, 97, 110, 30, 19, 48, 48, 45, 48, 52, 45, 53, 70, 45, 48, 48, 45, 48, 70, 45, 68, 49, 31, 19, 48, 48, 45, 48, 49, 45, 50, 52, 45, 56, 48, 45, 66, 51, 45, 57, 67, 8, 6, 10, 0, 0, 100];
646        let packet_from_bytes = RadiusPacket::initialise_packet_from_bytes(&dict, &bytes).unwrap();
647
648        assert_eq!(expected_packet, packet_from_bytes);
649    }
650
651    #[test]
652    fn test_initialise_packet_from_bytes_invalid_length() {
653        let dictionary_path = "./dict_examples/integration_dict";
654        let dict            = Dictionary::from_file(dictionary_path).unwrap();
655
656        let bytes             = [4, 43, 0, 26, 215, 189, 213, 172, 57, 94, 141, 70, 134, 121, 101, 57, 187, 220, 227, 73];
657
658
659        match RadiusPacket::initialise_packet_from_bytes(&dict, &bytes) {
660            Err(err) => assert_eq!(String::from("Radius packet is malformed: defined packet length: [26] is greater than actual packet length received: [20]"), err.to_string()),
661            _        => assert!(false)
662        }
663    }
664
665    #[test]
666    fn test_initialise_packet_from_bytes_invalid_length_less_than_20() {
667        let dictionary_path = "./dict_examples/integration_dict";
668        let dict            = Dictionary::from_file(dictionary_path).unwrap();
669
670        let bytes             = [4, 43, 0, 19, 215, 189, 213, 172, 57, 94, 141, 70, 134, 121, 101, 57, 187, 220, 227];
671
672        match RadiusPacket::initialise_packet_from_bytes(&dict, &bytes) {
673            Err(err) => assert_eq!(String::from("Radius packet is malformed: packet length should be of size between 20 and 4096 octets"), err.to_string()),
674            _        => assert!(false)
675        }
676    }
677
678    #[test]
679    fn test_initialise_packet_from_bytes_invalid_length_greater_than_4096() {
680        let dictionary_path = "./dict_examples/integration_dict";
681        let dict            = Dictionary::from_file(dictionary_path).unwrap();
682
683        let bytes             = [0; 4097];
684
685        match RadiusPacket::initialise_packet_from_bytes(&dict, &bytes) {
686            Err(err) => assert_eq!(String::from("Radius packet is malformed: packet length should be of size between 20 and 4096 octets"), err.to_string()),
687            _        => assert!(false)
688        }
689    }
690
691    #[test]
692    fn test_initialise_packet_from_bytes_invalid_attr_length() {
693        let dictionary_path = "./dict_examples/integration_dict";
694        let dict            = Dictionary::from_file(dictionary_path).unwrap();
695
696        let bytes             = [4, 43, 0, 26, 215, 189, 213, 172, 57, 94, 141, 70, 134, 121, 101, 57, 187, 220, 227, 73, 4, 0, 192, 168, 1, 10];
697
698        match RadiusPacket::initialise_packet_from_bytes(&dict, &bytes) {
699            Err(err) => assert_eq!(String::from("Radius packet is malformed: attribute with ID: 4 has invalid length 0"), err.to_string()),
700            _        => assert!(false)
701        }
702    }
703
704    #[test]
705    fn test_initialise_packet_from_bytes_missing_attr() {
706        let dictionary_path = "./dict_examples/integration_dict";
707        let dict            = Dictionary::from_file(dictionary_path).unwrap();
708
709        let bytes             = [4, 43, 0, 26, 215, 189, 213, 172, 57, 94, 141, 70, 134, 121, 101, 57, 187, 220, 227, 73, 234, 6, 192, 168, 1, 10];
710
711        match RadiusPacket::initialise_packet_from_bytes(&dict, &bytes) {
712            Err(err) => assert_eq!(String::from("Radius packet is malformed: attribute with ID: 234 is not found in dictionary"), err.to_string()),
713            _        => assert!(false)
714        }
715    }
716
717    #[test]
718    fn test_radius_packet_override_id() {
719        let attributes: Vec<RadiusAttribute> = Vec::with_capacity(1);
720        let new_id: u8                       = 50;
721
722        let mut packet = RadiusPacket::initialise_packet(TypeCode::AccessRequest);
723        packet.set_attributes(attributes);
724        packet.override_id(new_id);
725
726        assert_eq!(new_id, packet.id());
727    }
728    #[test]
729    fn test_radius_packet_override_authenticator() {
730        let attributes: Vec<RadiusAttribute> = Vec::with_capacity(1);
731        let new_authenticator: Vec<u8>       = vec![0, 25, 100, 56, 13];
732
733        let mut packet = RadiusPacket::initialise_packet(TypeCode::AccessRequest);
734        packet.set_attributes(attributes);
735        packet.override_authenticator(new_authenticator.to_vec());
736
737        assert_eq!(new_authenticator, packet.authenticator());
738    }
739    #[test]
740    fn test_radius_packet_to_bytes() {
741        let attributes: Vec<RadiusAttribute> = Vec::with_capacity(1);
742        let new_id: u8                       = 50;
743        let new_authenticator: Vec<u8>       = vec![0, 25, 100, 56, 13, 0, 67, 34, 39, 12, 88, 153, 0, 1, 2, 3];
744
745        let exepcted_bytes = vec![1, 50, 0, 20, 0, 25, 100, 56, 13, 0, 67, 34, 39, 12, 88, 153, 0, 1, 2, 3];
746        let mut packet = RadiusPacket::initialise_packet(TypeCode::AccessRequest);
747        packet.set_attributes(attributes);
748        packet.override_id(new_id);
749        packet.override_authenticator(new_authenticator);
750
751        assert_eq!(exepcted_bytes, packet.to_bytes());
752    }
753
754    #[test]
755    fn test_override_message_authenticator_fail() {
756        let dictionary_path = "./dict_examples/integration_dict";
757        let dict            = Dictionary::from_file(dictionary_path).unwrap();
758
759        let nas_ip_addr_bytes    = ipv4_string_to_bytes("192.168.1.10").unwrap();
760        let framed_ip_addr_bytes = ipv4_string_to_bytes("10.0.0.100").unwrap();
761        let attributes          = vec![
762            RadiusAttribute::create_by_name(&dict, "NAS-IP-Address",     nas_ip_addr_bytes).unwrap(),
763            RadiusAttribute::create_by_name(&dict, "NAS-Port-Id",        integer_to_bytes(0)).unwrap(),
764            RadiusAttribute::create_by_name(&dict, "NAS-Identifier",     String::from("trillian").into_bytes()).unwrap(),
765            RadiusAttribute::create_by_name(&dict, "Called-Station-Id",  String::from("00-04-5F-00-0F-D1").into_bytes()).unwrap(),
766            RadiusAttribute::create_by_name(&dict, "Calling-Station-Id", String::from("00-01-24-80-B3-9C").into_bytes()).unwrap(),
767            RadiusAttribute::create_by_name(&dict, "Framed-IP-Address",  framed_ip_addr_bytes).unwrap()
768        ];
769
770        let new_message_authenticator = vec![1, 50, 0, 20, 0, 25, 100, 56, 13, 0, 67, 34, 39, 12, 88, 153];
771        let mut packet = RadiusPacket::initialise_packet(TypeCode::AccountingRequest);
772        packet.set_attributes(attributes);
773
774        match packet.override_message_authenticator(new_message_authenticator) {
775            Err(err) => assert_eq!(String::from("Radius packet is malformed: Message-Authenticator attribute not found in packet"), err.to_string()),
776            _        => assert!(false)
777        }
778    }
779
780    #[test]
781    fn test_override_message_authenticator_success() {
782        let dictionary_path = "./dict_examples/integration_dict";
783        let dict            = Dictionary::from_file(dictionary_path).unwrap();
784
785        let attributes = vec![
786            RadiusAttribute::create_by_name(&dict, "Calling-Station-Id",    String::from("00-01-24-80-B3-9C").into_bytes()).unwrap(),
787            RadiusAttribute::create_by_name(&dict, "Message-Authenticator", vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]).unwrap()
788        ];
789
790        let mut packet                 = RadiusPacket::initialise_packet(TypeCode::AccessRequest);
791        let new_message_authenticator  = vec![1, 50, 0, 20, 0, 25, 100, 56, 13, 0, 67, 34, 39, 12, 88, 153];
792        let new_id: u8                 = 50;
793        let new_authenticator: Vec<u8> = vec![0, 25, 100, 56, 13, 0, 67, 34, 39, 12, 88, 153, 0, 1, 2, 3];
794
795        packet.set_attributes(attributes);
796        packet.override_id(new_id);
797        packet.override_authenticator(new_authenticator);
798
799        let expected_packet_bytes: Vec<u8> = vec![1, 50, 0, 57, 0, 25, 100, 56, 13, 0, 67, 34, 39, 12, 88, 153, 0, 1, 2, 3, 31, 19, 48, 48, 45, 48, 49, 45, 50, 52, 45, 56, 48, 45, 66, 51, 45, 57, 67, 80, 18, 1, 50, 0, 20, 0, 25, 100, 56, 13, 0, 67, 34, 39, 12, 88, 153];
800
801        match packet.override_message_authenticator(new_message_authenticator) {
802            Err(_) => assert!(false),
803            _      => assert_eq!(expected_packet_bytes, packet.to_bytes())
804        }
805    }
806
807    #[test]
808    fn test_generate_message_authenticator_success() {
809        let expected_message_authenticator = vec![85, 134, 2, 170, 83, 101, 202, 79, 109, 163, 59, 12, 66, 170, 183, 220];
810
811        let dictionary_path = "./dict_examples/integration_dict";
812        let dict            = Dictionary::from_file(dictionary_path).unwrap();
813        let secret          = "secret";
814        let mut packet      = RadiusPacket::initialise_packet(TypeCode::AccessRequest);
815
816        let attributes        = vec![
817            RadiusAttribute::create_by_name(&dict, "User-Name",             String::from("testing").into_bytes()).unwrap(),
818            RadiusAttribute::create_by_name(&dict, "Message-Authenticator", [0;16].to_vec()).unwrap()
819        ];
820        let new_authenticator = vec![152, 137, 115, 14, 56, 250, 103, 56, 57, 57, 104, 246, 226, 80, 71, 167];
821        let new_id: u8        = 220;
822
823        packet.set_attributes(attributes);
824        packet.override_id(new_id);
825        packet.override_authenticator(new_authenticator);
826
827        packet.generate_message_authenticator(&secret).unwrap();
828
829        assert_eq!(expected_message_authenticator, packet.message_authenticator().unwrap());
830    }
831}