dig_stun/credential/
response.rs1use crate::codec::{StunError, TransactionId, MAGIC_COOKIE};
6use crate::credential::nonce::NONCE_LEN;
7use crate::credential::wire::{
8 base64url_encode, write_attr, write_header, ATTR_ERROR_CODE, ATTR_NONCE, ATTR_REALM,
9 BINDING_ERROR, ERR_BAD_REQUEST, ERR_STALE_NONCE, ERR_UNAUTHENTICATED, REALM,
10};
11
12#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct Challenge {
18 pub code: u16,
20 pub realm: Option<String>,
22 pub nonce: Option<Vec<u8>>,
24}
25
26fn reason_phrase(code: u16) -> &'static str {
28 match code {
29 ERR_UNAUTHENTICATED => "Unauthenticated",
30 ERR_STALE_NONCE => "Stale Nonce",
31 ERR_BAD_REQUEST => "Bad Request",
32 other => panic!(
33 "encode_challenge: unsupported error code {other} (caller error, not wire input)"
34 ),
35 }
36}
37
38pub fn encode_challenge(
47 txid: &TransactionId,
48 code: u16,
49 nonce: Option<&[u8; NONCE_LEN]>,
50) -> Vec<u8> {
51 let reason = reason_phrase(code);
52 let mut error_code_value = Vec::with_capacity(4 + reason.len());
53 error_code_value.extend_from_slice(&[0, 0, (code / 100) as u8, (code % 100) as u8]);
54 error_code_value.extend_from_slice(reason.as_bytes());
55
56 let mut attrs = Vec::new();
57 write_attr(&mut attrs, ATTR_ERROR_CODE, &error_code_value);
58 if let Some(raw_nonce) = nonce {
59 write_attr(&mut attrs, ATTR_REALM, REALM.as_bytes());
60 let encoded = base64url_encode(raw_nonce);
61 write_attr(&mut attrs, ATTR_NONCE, encoded.as_bytes());
62 }
63
64 let mut msg = Vec::with_capacity(20 + attrs.len());
65 write_header(&mut msg, BINDING_ERROR, attrs.len() as u16, txid);
66 msg.extend_from_slice(&attrs);
67 msg
68}
69
70pub fn parse_challenge(msg: &[u8], expected_txid: &TransactionId) -> Result<Challenge, StunError> {
81 if msg.len() < 20 {
82 return Err(StunError::Truncated);
83 }
84 let msg_type = u16::from_be_bytes([msg[0], msg[1]]);
85 let msg_len = u16::from_be_bytes([msg[2], msg[3]]) as usize;
86 let cookie = u32::from_be_bytes([msg[4], msg[5], msg[6], msg[7]]);
87 if cookie != MAGIC_COOKIE {
88 return Err(StunError::BadMagicCookie);
89 }
90 if msg_type != BINDING_ERROR {
91 return Err(StunError::UnexpectedType(msg_type));
92 }
93 let txid: TransactionId = msg[8..20].try_into().map_err(|_| StunError::Truncated)?;
94 if &txid != expected_txid {
95 return Err(StunError::TransactionIdMismatch);
96 }
97 if msg.len() < 20 + msg_len {
98 return Err(StunError::Truncated);
99 }
100
101 let area = &msg[20..20 + msg_len];
102 let mut code: u16 = 0;
103 let mut realm: Option<String> = None;
104 let mut nonce: Option<Vec<u8>> = None;
105
106 let mut off = 0usize;
107 while off + 4 <= area.len() {
108 let attr_type = u16::from_be_bytes([area[off], area[off + 1]]);
109 let attr_len = u16::from_be_bytes([area[off + 2], area[off + 3]]) as usize;
110 let val_start = off + 4;
111 let val_end = val_start + attr_len;
112 if val_end > area.len() {
113 break; }
115 let value = &area[val_start..val_end];
116 match attr_type {
117 ATTR_ERROR_CODE if value.len() >= 4 => {
118 code = 100 * value[2] as u16 + value[3] as u16;
119 }
120 ATTR_REALM => {
121 if let Ok(s) = std::str::from_utf8(value) {
122 realm = Some(s.to_string());
123 }
124 }
125 ATTR_NONCE => {
126 nonce = Some(value.to_vec());
127 }
128 _ => {}
129 }
130 off = val_end + ((4 - (attr_len % 4)) % 4);
131 }
132
133 Ok(Challenge { code, realm, nonce })
134}