stun_rs/
context.rs

1use crate::attributes::stun::{Fingerprint, MessageIntegrity, MessageIntegritySha256};
2use crate::attributes::{AsVerifiable, EncodeAttributeValue, Unknown};
3use crate::common::{check_buffer_boundaries, fill_padding_value, padding, DEFAULT_PADDING_VALUE};
4use crate::error::{
5    StunAttributeError, StunDecodeError, StunEncodeError, StunError, StunErrorLevel, StunErrorType,
6    StunMessageError,
7};
8use crate::raw::{
9    get_input_text, RawAttributes, RawMessage, ATTRIBUTE_HEADER_SIZE, MESSAGE_HEADER_SIZE,
10};
11use crate::registry::get_handler;
12use crate::types::MAGIC_COOKIE;
13use crate::{
14    AttributeType, Decode, Encode, MessageType, StunAttributeType, StunMessageBuilder,
15    TransactionId,
16};
17use crate::{HMACKey, StunAttribute, StunMessage};
18use byteorder::{BigEndian, ByteOrder};
19use fallible_iterator::{FallibleIterator, IntoFallibleIterator};
20use std::convert::{TryFrom, TryInto};
21
22/// Builder class used to construct [`DecoderContext`] objects
23#[derive(Debug, Default)]
24pub struct DecoderContextBuilder(DecoderContext);
25
26impl DecoderContextBuilder {
27    /// Configure the builder to use a key to decode messages
28    pub fn with_key(mut self, key: HMACKey) -> Self {
29        self.0.key = Some(key);
30        self
31    }
32
33    /// Whether this decoder will validate attributes.
34    pub fn with_validation(mut self) -> Self {
35        self.0.validation = true;
36        self
37    }
38
39    /// If raw data belonging to unknown attributes must be stored. When this option is
40    /// enabled, the [Unknown](crate::attributes::Unknown) attribute will keep the raw
41    /// data value whenever an unknown attribute is decoded.
42    pub fn with_unknown_data(mut self) -> Self {
43        self.0.unknown_data = true;
44        self
45    }
46
47    /// If agents should ignore attributes that follow MESSAGE-INTEGRITY,
48    /// with the exception of the MESSAGE-INTEGRITY-SHA256 and FINGERPRINT attributes.
49    /// STUN states that agents MUST ignore those attributes, use this flag if you want
50    /// to change this behavior: to decode all attributes event if they follow one of the
51    /// above mentioned attributes.
52    pub fn not_ignore(mut self) -> Self {
53        self.0.not_ignore = true;
54        self
55    }
56
57    /// Builds a [`DecoderContext`]
58    pub fn build(self) -> DecoderContext {
59        self.0
60    }
61}
62
63#[derive(Debug, Default)]
64pub(crate) struct AttributeDecoderContext<'a> {
65    ctx: Option<DecoderContext>,
66    decoded_msg: &'a [u8],
67    raw_value: &'a [u8],
68}
69
70impl<'a> AttributeDecoderContext<'a> {
71    pub(crate) fn new(
72        ctx: Option<DecoderContext>,
73        decoded_msg: &'a [u8],
74        raw_value: &'a [u8],
75    ) -> Self {
76        Self {
77            ctx,
78            decoded_msg,
79            raw_value,
80        }
81    }
82    pub fn context(&self) -> Option<DecoderContext> {
83        self.ctx.clone()
84    }
85
86    pub fn decoded_message(&self) -> &[u8] {
87        self.decoded_msg
88    }
89
90    pub fn raw_value(&self) -> &[u8] {
91        self.raw_value
92    }
93}
94
95/// Context used to decode STUN messages
96#[derive(Debug, Default, PartialEq, Eq, Clone)]
97pub struct DecoderContext {
98    key: Option<HMACKey>,
99    validation: bool,
100    unknown_data: bool,
101    not_ignore: bool,
102}
103
104impl DecoderContext {
105    /// Key used for integrity hashes
106    pub fn key(&self) -> Option<&HMACKey> {
107        self.key.as_ref()
108    }
109
110    /// Whether validation is required to decoding
111    pub fn validate(&self) -> bool {
112        self.validation
113    }
114
115    /// Whether unknown attributes should keep the attribute data or not
116    pub fn with_unknown_data(&self) -> bool {
117        self.unknown_data
118    }
119}
120
121/// Builder class used to create a stun [`MessageDecoder`]
122#[derive(Debug, Default)]
123pub struct MessageDecoderBuilder(MessageDecoder);
124impl MessageDecoderBuilder {
125    /// Adds a context to the builder
126    pub fn with_context(mut self, ctx: DecoderContext) -> Self {
127        self.0.ctx = Some(ctx);
128        self
129    }
130
131    /// Builds a [`MessageDecoder`]
132    pub fn build(self) -> MessageDecoder {
133        self.0
134    }
135}
136
137/// Class used to decode STUN messages
138#[derive(Debug, Default, Clone)]
139pub struct MessageDecoder {
140    ctx: Option<DecoderContext>,
141}
142
143fn validate_attribute(
144    attr: &StunAttribute,
145    ctx: &Option<DecoderContext>,
146    buffer: &[u8],
147) -> Result<(), StunError> {
148    match ctx.as_ref() {
149        Some(ctx) => {
150            if ctx.validate() {
151                match attr.as_verifiable_ref() {
152                    Some(verifiable) => {
153                        let input = get_input_text(buffer, attr.attribute_type().as_u16())?;
154                        verifiable.verify(&input, ctx).then_some(()).ok_or_else(|| {
155                            StunError::new(
156                                StunErrorType::ValidationFailed,
157                                "Attribute validation failed",
158                            )
159                        })
160                    }
161                    None => Ok(()),
162                }
163            } else {
164                // No validations required
165                Ok(())
166            }
167        }
168        None => Ok(()),
169    }
170}
171
172#[derive(Debug, Default)]
173struct AttributeFilter {
174    message_integrity: bool,
175    message_integrity_sha256: bool,
176    fingerprint: bool,
177}
178
179fn ignore_attribute(f: &mut AttributeFilter, attr_type: AttributeType) -> bool {
180    if !f.message_integrity && attr_type == MessageIntegrity::get_type() {
181        if f.message_integrity_sha256 || f.fingerprint {
182            // MessageIntegrity comes behind of MessageIntegritySha256
183            // or Fingerprint or both
184            return true;
185        } else {
186            f.message_integrity = true;
187            return false;
188        }
189    }
190
191    if !f.message_integrity_sha256 && attr_type == MessageIntegritySha256::get_type() {
192        if f.fingerprint {
193            // MessageIntegritySha256 might come behind of MessageIntegrity
194            // but not after of Fingerprint
195            return true;
196        } else {
197            f.message_integrity_sha256 = true;
198            return false;
199        }
200    }
201
202    if !f.fingerprint && attr_type == Fingerprint::get_type() {
203        f.message_integrity_sha256 = true;
204        return false;
205    }
206
207    // If MessageIntegrity or  MessageIntegritySha256 or Fingerprint
208    // if processed, this attribute must be ignored
209    f.message_integrity || f.message_integrity_sha256 || f.fingerprint
210}
211
212impl MessageDecoder {
213    /// Decodes the STUN raw buffer
214    /// # Arguments:
215    /// - `buffer` - Raw buffer containing the STUN message
216    /// # Returns:
217    /// A tuple with [`StunMessage`] itself and the size consumed to decode the message,
218    /// or an error describing the problem if the message could not be decoded.
219    pub fn decode(&self, buffer: &[u8]) -> Result<(StunMessage, usize), StunDecodeError> {
220        let (raw_msg, size) = RawMessage::decode(buffer)
221            .map_err(|error| StunDecodeError(StunErrorLevel::Message(StunMessageError(error))))?;
222        let msg_type = MessageType::from(raw_msg.header.msg_type);
223        let mut builder = StunMessageBuilder::new(msg_type.method(), msg_type.class())
224            .with_transaction_id(TransactionId::from(raw_msg.header.transaction_id));
225
226        // Parse raw attributes
227        let attributes = RawAttributes::from(raw_msg.attributes);
228        let mut iter = attributes.into_fallible_iter();
229        let mut index = MESSAGE_HEADER_SIZE;
230        let mut position = 0;
231
232        let mut filter = AttributeFilter::default();
233        let ignore = match self.ctx.as_ref() {
234            Some(ctx) => !ctx.not_ignore,
235            None => true,
236        };
237
238        while let Some(raw_attr) = iter.next().map_err(|error| {
239            StunDecodeError(StunErrorLevel::Attribute(StunAttributeError {
240                attr_type: None,
241                position,
242                error,
243            }))
244        })? {
245            let ctx =
246                AttributeDecoderContext::new(self.ctx.clone(), &buffer[0..index], raw_attr.value);
247            let attr_type: AttributeType = raw_attr.attr_type.into();
248            let (attr, _) = match get_handler(attr_type) {
249                Some(handler) => handler(ctx).map_err(|error| {
250                    StunDecodeError(StunErrorLevel::Attribute(StunAttributeError {
251                        attr_type: Some(attr_type),
252                        position,
253                        error,
254                    }))
255                })?,
256                None => (
257                    Unknown::new(
258                        attr_type,
259                        match self.ctx.as_ref() {
260                            Some(ctx) => ctx.with_unknown_data().then_some(raw_attr.value),
261                            None => None,
262                        },
263                    )
264                    .into(),
265                    raw_attr.value.len(),
266                ),
267            };
268
269            if !ignore_attribute(&mut filter, attr_type) || !ignore {
270                validate_attribute(&attr, &self.ctx, buffer).map_err(|error| {
271                    StunDecodeError(StunErrorLevel::Attribute(StunAttributeError {
272                        attr_type: Some(attr_type),
273                        position,
274                        error,
275                    }))
276                })?;
277
278                builder = builder.with_attribute(attr);
279            }
280
281            index = MESSAGE_HEADER_SIZE + iter.pos();
282            position += 1;
283        }
284
285        Ok((builder.build(), size))
286    }
287
288    /// Gets the context associated to this decoder
289    pub fn get_context(&self) -> Option<&DecoderContext> {
290        self.ctx.as_ref()
291    }
292}
293
294#[cfg(feature = "experiments")]
295/// Custom padding used to encode a message. This feature required to enable
296/// the flag `experiments`
297#[derive(Debug, PartialEq, Eq, Clone)]
298pub enum StunPadding {
299    /// custom
300    Custom(u8),
301    /// random padding
302    Random,
303}
304
305/// Builder class used to construct [`EncoderContext`] objects
306#[derive(Debug, Default)]
307pub struct EncoderContextBuilder(EncoderContext);
308impl EncoderContextBuilder {
309    #[cfg(feature = "experiments")]
310    /// Configure the STUN context to use a custom padding. The STUN
311    /// specification states that the padding bits MUST be set
312    /// to zero on sending and MUST be ignored by the receiver.
313    /// Nevertheless, it could be useful to use a custom padding
314    /// for debugging purposes. For example, the STUN Test Vectors
315    /// [`RFC5769`](https://datatracker.ietf.org/doc/html/RFC5769)
316    /// uses buffers with non zero padding and we can set this
317    /// feature on to check that buffers generated by the library
318    /// are identical when they are compared byte to byte.
319    pub fn with_custom_padding(mut self, padding: StunPadding) -> Self {
320        self.0.padding = Some(padding);
321        self
322    }
323
324    /// Builds a [`EncoderContext`]
325    pub fn build(self) -> EncoderContext {
326        self.0
327    }
328}
329
330/// Context used to decode STUN messages that requires special
331/// treatment like `CRC` or integrity validations
332#[derive(Debug, Default, PartialEq, Eq, Clone)]
333pub struct EncoderContext {
334    #[cfg(feature = "experiments")]
335    padding: Option<StunPadding>,
336}
337
338impl EncoderContext {
339    #[cfg(feature = "experiments")]
340    /// Padding used when encoding a message
341    pub fn padding(&self) -> u8 {
342        self.padding
343            .as_ref()
344            .map_or(DEFAULT_PADDING_VALUE, |padding| match padding {
345                StunPadding::Random => {
346                    use rand::Rng;
347                    let mut rng = rand::rng();
348                    rng.random()
349                }
350                StunPadding::Custom(v) => *v,
351            })
352    }
353
354    #[cfg(not(feature = "experiments"))]
355    /// Padding used when encoding a message
356    pub fn padding(&self) -> u8 {
357        DEFAULT_PADDING_VALUE
358    }
359}
360
361/// Builder class used to create a stun [`MessageEncoder`]
362#[derive(Debug, Default)]
363pub struct MessageEncoderBuilder(MessageEncoder);
364impl MessageEncoderBuilder {
365    /// Adds a context to the builder
366    pub fn with_context(mut self, ctx: EncoderContext) -> Self {
367        self.0.ctx = Some(ctx);
368        self
369    }
370
371    /// Builds a [`MessageEncoder`]
372    pub fn build(self) -> MessageEncoder {
373        self.0
374    }
375}
376
377#[derive(Debug, Default)]
378pub(crate) struct AttributeEncoderContext<'a> {
379    ctx: Option<EncoderContext>,
380    encoded_msg: &'a [u8],
381    raw_value: &'a mut [u8],
382}
383
384impl<'a> AttributeEncoderContext<'a> {
385    pub(crate) fn new(
386        ctx: Option<EncoderContext>,
387        encoded_msg: &'a [u8],
388        raw_value: &'a mut [u8],
389    ) -> Self {
390        Self {
391            ctx,
392            encoded_msg,
393            raw_value,
394        }
395    }
396    pub fn context(&self) -> Option<EncoderContext> {
397        self.ctx.clone()
398    }
399
400    pub fn encoded_message(&self) -> &'a [u8] {
401        self.encoded_msg
402    }
403
404    pub fn raw_value(&self) -> &[u8] {
405        self.raw_value
406    }
407
408    pub fn raw_value_mut(&mut self) -> &mut [u8] {
409        self.raw_value
410    }
411}
412
413/// Class used to encode STUN messages
414#[derive(Debug, Default, Clone)]
415pub struct MessageEncoder {
416    ctx: Option<EncoderContext>,
417}
418
419impl MessageEncoder {
420    /// Encodes a STUN message.
421    /// # Arguments:
422    /// - `buffer` - Output buffer
423    /// - `msg` - The STUN message.
424    /// # Returns:
425    /// The size in bytes taken to encode the `msg` or a [`StunEncodeError`] describing
426    /// the error if the message could not be encoded.
427    pub fn encode(&self, buffer: &mut [u8], msg: &StunMessage) -> Result<usize, StunEncodeError> {
428        check_buffer_boundaries(buffer, MESSAGE_HEADER_SIZE)
429            .map_err(|error| StunEncodeError(StunErrorLevel::Message(StunMessageError(error))))?;
430
431        MessageType::new(msg.method(), msg.class())
432            .encode(buffer)
433            .map_err(|error| StunEncodeError(StunErrorLevel::Message(StunMessageError(error))))?;
434
435        let mut length = 0;
436        BigEndian::write_u16(&mut buffer[2..4], length);
437        BigEndian::write_u32(&mut buffer[4..8], MAGIC_COOKIE.as_u32());
438        buffer[8..20].copy_from_slice(msg.transaction_id().as_bytes());
439
440        for (position, attr) in msg.attributes().iter().enumerate() {
441            let coded_index = length + MESSAGE_HEADER_SIZE as u16;
442            let (raw_msg, attributes) = buffer.split_at_mut(coded_index.into());
443
444            // Encode attribute
445            // Check we have room for attribute type and length
446            check_buffer_boundaries(attributes, ATTRIBUTE_HEADER_SIZE).map_err(|error| {
447                StunEncodeError(StunErrorLevel::Attribute(StunAttributeError {
448                    attr_type: Some(attr.attribute_type()),
449                    position,
450                    error,
451                }))
452            })?;
453            // Encode value size
454            let attr_ctx = AttributeEncoderContext::new(
455                self.ctx.clone(),
456                raw_msg,
457                &mut attributes[ATTRIBUTE_HEADER_SIZE..],
458            );
459            let value_size = attr.encode(attr_ctx).map_err(|error| {
460                StunEncodeError(StunErrorLevel::Attribute(StunAttributeError {
461                    attr_type: Some(attr.attribute_type()),
462                    position,
463                    error,
464                }))
465            })?;
466
467            // Encode attribute headers
468            BigEndian::write_u16(&mut attributes[..2], attr.attribute_type().into());
469            BigEndian::write_u16(
470                &mut attributes[2..4],
471                value_size.try_into().map_err(|error| {
472                    StunEncodeError(StunErrorLevel::Attribute(StunAttributeError {
473                        attr_type: Some(attr.attribute_type()),
474                        position,
475                        error: StunError::from_error(StunErrorType::InvalidParam, Box::new(error)),
476                    }))
477                })?,
478            );
479
480            let attr_size = ATTRIBUTE_HEADER_SIZE + value_size;
481
482            // calculate padding
483            let padding_size = padding(value_size);
484            let padding_value = match self.ctx.as_ref() {
485                Some(ctx) => ctx.padding(),
486                _ => DEFAULT_PADDING_VALUE,
487            };
488            // Padding goes after headers and attribute value
489            fill_padding_value(&mut attributes[attr_size..], padding_size, padding_value).map_err(
490                |error| {
491                    StunEncodeError(StunErrorLevel::Attribute(StunAttributeError {
492                        attr_type: Some(attr.attribute_type()),
493                        position,
494                        error,
495                    }))
496                },
497            )?;
498
499            // Update length taking into account padding
500            length += u16::try_from(attr_size + padding_size).map_err(|error| {
501                StunEncodeError(StunErrorLevel::Attribute(StunAttributeError {
502                    attr_type: Some(attr.attribute_type()),
503                    position,
504                    error: StunError::from_error(StunErrorType::InvalidParam, Box::new(error)),
505                }))
506            })?;
507            BigEndian::write_u16(&mut raw_msg[2..4], length);
508
509            // Post process (only attribute value)
510            let coded_value =
511                &mut attributes[ATTRIBUTE_HEADER_SIZE..ATTRIBUTE_HEADER_SIZE + value_size];
512            let ctx = AttributeEncoderContext::new(None, raw_msg, coded_value);
513            attr.post_encode(ctx).map_err(|error| {
514                StunEncodeError(StunErrorLevel::Attribute(StunAttributeError {
515                    attr_type: Some(attr.attribute_type()),
516                    position,
517                    error,
518                }))
519            })?;
520        }
521
522        Ok((length + MESSAGE_HEADER_SIZE as u16).into())
523    }
524}
525
526#[cfg(test)]
527mod tests {
528    use super::*;
529    use crate::attributes::stun::{Software, UserName, XorMappedAddress};
530    use crate::methods::BINDING;
531    use crate::MessageClass;
532    use std::net::{IpAddr, Ipv4Addr};
533
534    #[test]
535    fn test_ignore_attribute() {
536        let mut filter = AttributeFilter::default();
537        assert!(!ignore_attribute(&mut filter, XorMappedAddress::get_type()));
538        assert!(!ignore_attribute(&mut filter, MessageIntegrity::get_type()));
539        assert!(ignore_attribute(&mut filter, UserName::get_type()));
540        assert!(!ignore_attribute(
541            &mut filter,
542            MessageIntegritySha256::get_type()
543        ));
544        assert!(ignore_attribute(&mut filter, Software::get_type()));
545        assert!(!ignore_attribute(&mut filter, Fingerprint::get_type()));
546        assert!(ignore_attribute(&mut filter, Software::get_type()));
547
548        let mut filter = AttributeFilter::default();
549        assert!(!ignore_attribute(&mut filter, Software::get_type()));
550        assert!(!ignore_attribute(
551            &mut filter,
552            MessageIntegritySha256::get_type()
553        ));
554        assert!(ignore_attribute(&mut filter, UserName::get_type()));
555        assert!(ignore_attribute(&mut filter, MessageIntegrity::get_type()));
556        assert!(!ignore_attribute(&mut filter, Fingerprint::get_type()));
557        assert!(ignore_attribute(&mut filter, UserName::get_type()));
558
559        let mut filter = AttributeFilter::default();
560        assert!(!ignore_attribute(&mut filter, XorMappedAddress::get_type()));
561        assert!(!ignore_attribute(&mut filter, Fingerprint::get_type()));
562        assert!(ignore_attribute(&mut filter, UserName::get_type()));
563        assert!(ignore_attribute(
564            &mut filter,
565            MessageIntegritySha256::get_type()
566        ));
567        assert!(ignore_attribute(&mut filter, MessageIntegrity::get_type()));
568    }
569
570    #[test]
571    fn message_decoder() {
572        // This response uses the following parameter:
573        // Password: `VOkJxbRl1RmTxUk/WvJxBt` (without quotes)
574        // Software name: "test vector" (without quotes)
575        // Mapped address: 192.0.2.1 port 32853
576        let sample_ipv4_response = [
577            0x01, 0x01, 0x00, 0x3c, // Response type and message length
578            0x21, 0x12, 0xa4, 0x42, // Magic cookie
579            0xb7, 0xe7, 0xa7, 0x01, // }
580            0xbc, 0x34, 0xd6, 0x86, // }  Transaction ID
581            0xfa, 0x87, 0xdf, 0xae, // }
582            0x80, 0x22, 0x00, 0x0b, // SOFTWARE attribute header
583            0x74, 0x65, 0x73, 0x74, // }
584            0x20, 0x76, 0x65, 0x63, // }  UTF-8 server name
585            0x74, 0x6f, 0x72, 0x20, // }
586            0x00, 0x20, 0x00, 0x08, // XOR-MAPPED-ADDRESS attribute header
587            0x00, 0x01, 0xa1, 0x47, // Address family (IPv4) and xor'd mapped port number
588            0xe1, 0x12, 0xa6, 0x43, // Xor'd mapped IPv4 address
589            0x00, 0x08, 0x00, 0x14, // MESSAGE-INTEGRITY header
590            0x2b, 0x91, 0xf5, 0x99, // }
591            0xfd, 0x9e, 0x90, 0xc3, // }
592            0x8c, 0x74, 0x89, 0xf9, // } HMAC-SHA1 fingerprint
593            0x2a, 0xf9, 0xba, 0x53, // }
594            0xf0, 0x6b, 0xe7, 0xd7, // }
595            0x80, 0x28, 0x00, 0x04, // FINGERPRINT attribute header
596            0xc0, 0x7d, 0x4c, 0x96, // Reserved for CRC32 fingerprint
597        ];
598
599        // Create a context with a custom key that require validation of attribures
600        let ctx = DecoderContextBuilder::default()
601            .with_key(
602                HMACKey::new_short_term("VOkJxbRl1RmTxUk/WvJxBt")
603                    .expect("Can not create new_short_term credential"),
604            )
605            .with_validation()
606            .build();
607
608        // create a decoder that uses our custom context
609        let decoder = MessageDecoderBuilder::default().with_context(ctx).build();
610        assert!(decoder.get_context().is_some());
611
612        let (msg, size) = decoder
613            .decode(&sample_ipv4_response)
614            .expect("Unable to decode buffer");
615        assert_eq!(size, sample_ipv4_response.len());
616
617        // Check message method is a BINDING response
618        assert_eq!(msg.method(), BINDING);
619        assert_eq!(msg.class(), MessageClass::SuccessResponse);
620
621        let software = msg.get::<Software>().unwrap().expect_software();
622        assert_eq!(software.as_str(), "test vector");
623
624        let xor_addr = msg
625            .get::<XorMappedAddress>()
626            .unwrap()
627            .expect_xor_mapped_address();
628        let socket = xor_addr.socket_address();
629        assert_eq!(socket.ip(), IpAddr::V4(Ipv4Addr::new(192, 0, 2, 1)));
630        assert_eq!(socket.port(), 32853);
631        assert!(socket.is_ipv4());
632    }
633
634    #[cfg(feature = "experiments")]
635    #[test]
636    fn message_encoder_custom_padding() {
637        let padding_value = 0x02;
638        let mut buffer: [u8; 30] = [0x0; 30];
639
640        // Create a SUN request message with a random transaction ID.
641        let msg = StunMessageBuilder::new(BINDING, MessageClass::Request)
642            .with_attribute(UserName::try_from("TT").unwrap())
643            .build();
644
645        // Use a context configured to use custom padding
646        let ctx = EncoderContextBuilder::default()
647            .with_custom_padding(StunPadding::Custom(padding_value))
648            .build();
649
650        // Create a encoder that uses our custom context
651        let encoder = MessageEncoderBuilder::default().with_context(ctx).build();
652        let size = encoder
653            .encode(&mut buffer, &msg)
654            .expect("Could not encode value");
655        assert_eq!(size, 28);
656
657        // Check username value
658        assert_eq!(buffer[24], 0x54); // 'T' ascii value
659        assert_eq!(buffer[25], 0x54); // 'T' ascii value
660        assert_eq!(buffer[26], padding_value); // custom padding
661        assert_eq!(buffer[27], padding_value); // custom padding
662
663        // Remaining buffer must be untouched
664        assert_eq!(buffer[28], 0x00);
665        assert_eq!(buffer[29], 0x00);
666    }
667
668    #[test]
669    fn message_encoder_default_padding() {
670        let padding_value = 0x00;
671        let mut buffer: [u8; 30] = [0x0; 30];
672
673        // Create a SUN request message with a random transaction ID.
674        let msg = StunMessageBuilder::new(BINDING, MessageClass::Request)
675            .with_attribute(UserName::try_from("TT").unwrap())
676            .build();
677
678        // Use a context configured to use custom padding
679        let ctx = EncoderContextBuilder::default().build();
680
681        // Create a encoder that uses our custom context
682        let encoder = MessageEncoderBuilder::default().with_context(ctx).build();
683        let size = encoder
684            .encode(&mut buffer, &msg)
685            .expect("Could not encode value");
686        assert_eq!(size, 28);
687
688        // Check username value
689        assert_eq!(buffer[24], 0x54); // 'T' ascii value
690        assert_eq!(buffer[25], 0x54); // 'T' ascii value
691        assert_eq!(buffer[26], padding_value); // custom padding
692        assert_eq!(buffer[27], padding_value); // custom padding
693
694        // Remaining buffer must be untouched
695        assert_eq!(buffer[28], 0x00);
696        assert_eq!(buffer[29], 0x00);
697    }
698}