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#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
62pub struct TransactionId([u8; 12]);
63
64impl TransactionId {
65 pub fn new(v: [u8; 12]) -> Self {
67 Self(v)
68 }
69
70 pub fn random() -> Self {
72 Self(rand::random())
73 }
74}
75
76#[derive(Debug)]
78pub enum IsStunMessageInfo {
79 TooShort,
82
83 No,
85
86 Yes { len: usize },
89
90 YesIncomplete { needed: usize },
93}
94
95pub 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}