ezk_stun_types/
lib.rs

1#![warn(unreachable_pub)]
2
3use byteorder::ReadBytesExt;
4use std::io::{self, Cursor};
5use std::num::TryFromIntError;
6use std::str::Utf8Error;
7
8pub mod attributes;
9mod builder;
10mod header;
11mod parse;
12
13pub use builder::MessageBuilder;
14pub use header::{Class, MessageHead, Method};
15pub use parse::{AttrSpan, Message};
16
17type NE = byteorder::NetworkEndian;
18
19const COOKIE: u32 = 0x2112A442;
20
21#[derive(Debug, thiserror::Error)]
22pub enum Error {
23    #[error("invalid input data, {0}")]
24    InvalidData(&'static str),
25    #[error("failed to convert integer")]
26    TryFromInt(#[from] TryFromIntError),
27    #[error(transparent)]
28    Utf8(#[from] Utf8Error),
29}
30
31impl From<io::Error> for Error {
32    fn from(e: io::Error) -> Self {
33        match e.kind() {
34            io::ErrorKind::UnexpectedEof => Self::InvalidData("buffer seems incomplete"),
35            _ => Self::InvalidData("failed to read from buffer"),
36        }
37    }
38}
39
40fn padding_u16(n: u16) -> u16 {
41    match n % 4 {
42        0 => 0,
43        1 => 3,
44        2 => 2,
45        3 => 1,
46        _ => unreachable!(),
47    }
48}
49
50fn padding_usize(n: usize) -> usize {
51    match n % 4 {
52        0 => 0,
53        1 => 3,
54        2 => 2,
55        3 => 1,
56        _ => unreachable!(),
57    }
58}
59
60/// 96 bit STUN transaction id
61#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
62pub struct TransactionId([u8; 12]);
63
64impl TransactionId {
65    /// Create a new transaction id from the given value
66    pub fn new(v: [u8; 12]) -> Self {
67        Self(v)
68    }
69
70    /// Generate a new random transaction id
71    pub fn random() -> Self {
72        Self(rand::random())
73    }
74}
75
76/// Return value of [`is_stun_message`]
77#[derive(Debug)]
78pub enum IsStunMessageInfo {
79    /// Message is shorter than 20 bytes (STUN message header length),
80    /// making it impossible to check.
81    TooShort,
82
83    /// Buffer does not contain a STUN message.
84    No,
85
86    /// Buffer contains a STUN message.
87    /// Variant contains length of the message.
88    Yes { len: usize },
89
90    /// Buffer contains a STUN message, but its incomplete.
91    /// Variant contains the needed amount of bytes message.
92    YesIncomplete { needed: usize },
93}
94
95/// Inspect the given input to find out if it contains a STUN message.
96///
97/// Does not perform any kind of searching, to detect the
98/// STUN message it must begin at the start of the input.
99pub fn is_stun_message(i: &[u8]) -> IsStunMessageInfo {
100    if i.len() < 20 {
101        return IsStunMessageInfo::TooShort;
102    }
103
104    let mut cursor = Cursor::new(i);
105
106    let head = cursor.read_u32::<NE>().unwrap();
107    let head = MessageHead(head);
108
109    if head.z() != 0 {
110        return IsStunMessageInfo::No;
111    }
112
113    let cookie = cursor.read_u32::<NE>().unwrap();
114
115    if cookie != COOKIE {
116        return IsStunMessageInfo::No;
117    }
118
119    let expected_msg_len = head.len() as usize + 20;
120
121    if i.len() < expected_msg_len {
122        let needed = expected_msg_len - i.len();
123        IsStunMessageInfo::YesIncomplete { needed }
124    } else {
125        IsStunMessageInfo::Yes {
126            len: expected_msg_len,
127        }
128    }
129}