use crate::codec::{StunError, TransactionId, MAGIC_COOKIE};
use crate::credential::nonce::NONCE_LEN;
use crate::credential::wire::{
base64url_encode, write_attr, write_header, ATTR_ERROR_CODE, ATTR_NONCE, ATTR_REALM,
BINDING_ERROR, ERR_BAD_REQUEST, ERR_STALE_NONCE, ERR_UNAUTHENTICATED, REALM,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Challenge {
pub code: u16,
pub realm: Option<String>,
pub nonce: Option<Vec<u8>>,
}
fn reason_phrase(code: u16) -> &'static str {
match code {
ERR_UNAUTHENTICATED => "Unauthenticated",
ERR_STALE_NONCE => "Stale Nonce",
ERR_BAD_REQUEST => "Bad Request",
other => panic!(
"encode_challenge: unsupported error code {other} (caller error, not wire input)"
),
}
}
pub fn encode_challenge(
txid: &TransactionId,
code: u16,
nonce: Option<&[u8; NONCE_LEN]>,
) -> Vec<u8> {
let reason = reason_phrase(code);
let mut error_code_value = Vec::with_capacity(4 + reason.len());
error_code_value.extend_from_slice(&[0, 0, (code / 100) as u8, (code % 100) as u8]);
error_code_value.extend_from_slice(reason.as_bytes());
let mut attrs = Vec::new();
write_attr(&mut attrs, ATTR_ERROR_CODE, &error_code_value);
if let Some(raw_nonce) = nonce {
write_attr(&mut attrs, ATTR_REALM, REALM.as_bytes());
let encoded = base64url_encode(raw_nonce);
write_attr(&mut attrs, ATTR_NONCE, encoded.as_bytes());
}
let mut msg = Vec::with_capacity(20 + attrs.len());
write_header(&mut msg, BINDING_ERROR, attrs.len() as u16, txid);
msg.extend_from_slice(&attrs);
msg
}
pub fn parse_challenge(msg: &[u8], expected_txid: &TransactionId) -> Result<Challenge, StunError> {
if msg.len() < 20 {
return Err(StunError::Truncated);
}
let msg_type = u16::from_be_bytes([msg[0], msg[1]]);
let msg_len = u16::from_be_bytes([msg[2], msg[3]]) as usize;
let cookie = u32::from_be_bytes([msg[4], msg[5], msg[6], msg[7]]);
if cookie != MAGIC_COOKIE {
return Err(StunError::BadMagicCookie);
}
if msg_type != BINDING_ERROR {
return Err(StunError::UnexpectedType(msg_type));
}
let txid: TransactionId = msg[8..20].try_into().map_err(|_| StunError::Truncated)?;
if &txid != expected_txid {
return Err(StunError::TransactionIdMismatch);
}
if msg.len() < 20 + msg_len {
return Err(StunError::Truncated);
}
let area = &msg[20..20 + msg_len];
let mut code: u16 = 0;
let mut realm: Option<String> = None;
let mut nonce: Option<Vec<u8>> = None;
let mut off = 0usize;
while off + 4 <= area.len() {
let attr_type = u16::from_be_bytes([area[off], area[off + 1]]);
let attr_len = u16::from_be_bytes([area[off + 2], area[off + 3]]) as usize;
let val_start = off + 4;
let val_end = val_start + attr_len;
if val_end > area.len() {
break; }
let value = &area[val_start..val_end];
match attr_type {
ATTR_ERROR_CODE if value.len() >= 4 => {
code = 100 * value[2] as u16 + value[3] as u16;
}
ATTR_REALM => {
if let Ok(s) = std::str::from_utf8(value) {
realm = Some(s.to_string());
}
}
ATTR_NONCE => {
nonce = Some(value.to_vec());
}
_ => {}
}
off = val_end + ((4 - (attr_len % 4)) % 4);
}
Ok(Challenge { code, realm, nonce })
}