use async_trait::async_trait;
use super::common::TokenParser;
use crate::core::TdsResult;
use crate::io::packet_reader::TdsPacketReader;
use crate::io::token_stream::ParserContext;
use crate::token::tokens::{SessionStateEntry, SessionStateToken, Tokens};
const MAX_SESSION_STATE_TOKEN_BYTES: u32 = 1024 * 1024;
#[derive(Debug, Default)]
pub(crate) struct SessionStateTokenParser;
#[async_trait]
impl<T> TokenParser<T> for SessionStateTokenParser
where
T: TdsPacketReader + Send + Sync,
{
async fn parse(&self, reader: &mut T, _context: &ParserContext) -> TdsResult<Tokens> {
let total_length = reader.read_uint32().await?;
if total_length > MAX_SESSION_STATE_TOKEN_BYTES {
return Err(crate::error::Error::ProtocolError(format!(
"SESSIONSTATE token length too large: {total_length} bytes \
(max: {MAX_SESSION_STATE_TOKEN_BYTES} bytes). Possible DoS attack."
)));
}
if total_length < 5 {
return Err(crate::error::Error::ProtocolError(format!(
"SESSIONSTATE token too short: {total_length} bytes (minimum 5)"
)));
}
let sequence_number = reader.read_uint32().await?;
let status = reader.read_byte().await?;
let mut bytes_read: u32 = 5; let mut states = Vec::new();
while bytes_read < total_length {
if bytes_read + 1 > total_length {
return Err(crate::error::Error::ProtocolError(
"SESSIONSTATE: unexpected end of token reading state_id".to_string(),
));
}
let state_id = reader.read_byte().await?;
bytes_read += 1;
if bytes_read + 1 > total_length {
return Err(crate::error::Error::ProtocolError(
"SESSIONSTATE: unexpected end of token reading state_len".to_string(),
));
}
let len_byte = reader.read_byte().await?;
bytes_read += 1;
let state_len: u32 = if len_byte == 0xFF {
if bytes_read + 4 > total_length {
return Err(crate::error::Error::ProtocolError(
"SESSIONSTATE: unexpected end of token reading extended state_len"
.to_string(),
));
}
let extended_len = reader.read_uint32().await?;
bytes_read += 4;
extended_len
} else {
len_byte as u32
};
let remaining = total_length - bytes_read;
if state_len > remaining {
return Err(crate::error::Error::ProtocolError(format!(
"SESSIONSTATE: state data length {state_len} exceeds remaining \
token bytes ({remaining} remaining)"
)));
}
let mut data = vec![0u8; state_len as usize];
if state_len > 0 {
reader.read_bytes(&mut data).await?;
}
bytes_read += state_len;
states.push(SessionStateEntry {
state_id,
recoverable: (status & 0x01) != 0,
data,
});
}
Ok(Tokens::from(SessionStateToken {
sequence_number,
status,
states,
}))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::token::parsers::common::test_utils::MockReader;
fn build_session_state_bytes(
sequence_number: u32,
status: u8,
state_entries: &[(u8, &[u8])],
) -> Vec<u8> {
let mut payload = Vec::new();
payload.extend_from_slice(&sequence_number.to_le_bytes());
payload.push(status);
for &(state_id, data) in state_entries {
payload.push(state_id);
if data.len() < 0xFF {
payload.push(data.len() as u8);
} else {
payload.push(0xFF);
payload.extend_from_slice(&(data.len() as u32).to_le_bytes());
}
payload.extend_from_slice(data);
}
let total_len = payload.len() as u32;
let mut result = Vec::new();
result.extend_from_slice(&total_len.to_le_bytes());
result.extend_from_slice(&payload);
result
}
#[tokio::test]
async fn parse_single_recoverable_state() {
let bytes = build_session_state_bytes(1, 0x01, &[(5, &[0xAA, 0xBB])]);
let mut reader = MockReader::new(bytes);
let parser = SessionStateTokenParser;
let context = ParserContext::default();
let token = parser.parse(&mut reader, &context).await.unwrap();
match token {
Tokens::SessionState(t) => {
assert_eq!(t.sequence_number, 1);
assert_eq!(t.status, 0x01);
assert_eq!(t.states.len(), 1);
assert_eq!(t.states[0].state_id, 5);
assert!(t.states[0].recoverable);
assert_eq!(t.states[0].data, vec![0xAA, 0xBB]);
}
_ => panic!("Expected SessionState token"),
}
}
#[tokio::test]
async fn parse_unrecoverable_state() {
let bytes = build_session_state_bytes(2, 0x00, &[(10, &[0x01])]);
let mut reader = MockReader::new(bytes);
let parser = SessionStateTokenParser;
let context = ParserContext::default();
let token = parser.parse(&mut reader, &context).await.unwrap();
match token {
Tokens::SessionState(t) => {
assert!(!t.states[0].recoverable);
}
_ => panic!("Expected SessionState token"),
}
}
#[tokio::test]
async fn parse_multiple_states() {
let bytes =
build_session_state_bytes(3, 0x01, &[(0, &[0x01]), (1, &[0x02, 0x03]), (255, &[])]);
let mut reader = MockReader::new(bytes);
let parser = SessionStateTokenParser;
let context = ParserContext::default();
let token = parser.parse(&mut reader, &context).await.unwrap();
match token {
Tokens::SessionState(t) => {
assert_eq!(t.states.len(), 3);
assert_eq!(t.states[0].state_id, 0);
assert_eq!(t.states[0].data, vec![0x01]);
assert_eq!(t.states[1].state_id, 1);
assert_eq!(t.states[1].data, vec![0x02, 0x03]);
assert_eq!(t.states[2].state_id, 255);
assert!(t.states[2].data.is_empty());
}
_ => panic!("Expected SessionState token"),
}
}
#[tokio::test]
async fn parse_master_disabled_sequence() {
let bytes = build_session_state_bytes(u32::MAX, 0x00, &[(0, &[0x01])]);
let mut reader = MockReader::new(bytes);
let parser = SessionStateTokenParser;
let context = ParserContext::default();
let token = parser.parse(&mut reader, &context).await.unwrap();
match token {
Tokens::SessionState(t) => {
assert_eq!(t.sequence_number, u32::MAX);
}
_ => panic!("Expected SessionState token"),
}
}
#[tokio::test]
async fn parse_extended_length_state() {
let large_data = vec![0x42u8; 300];
let bytes = build_session_state_bytes(1, 0x01, &[(7, &large_data)]);
let mut reader = MockReader::new(bytes);
let parser = SessionStateTokenParser;
let context = ParserContext::default();
let token = parser.parse(&mut reader, &context).await.unwrap();
match token {
Tokens::SessionState(t) => {
assert_eq!(t.states.len(), 1);
assert_eq!(t.states[0].state_id, 7);
assert_eq!(t.states[0].data.len(), 300);
}
_ => panic!("Expected SessionState token"),
}
}
#[tokio::test]
async fn reject_oversized_token() {
let mut bytes = Vec::new();
let huge_len: u32 = MAX_SESSION_STATE_TOKEN_BYTES + 1;
bytes.extend_from_slice(&huge_len.to_le_bytes());
let mut reader = MockReader::new(bytes);
let parser = SessionStateTokenParser;
let context = ParserContext::default();
let result = parser.parse(&mut reader, &context).await;
assert!(result.is_err());
let err_msg = format!("{}", result.unwrap_err());
assert!(err_msg.contains("too large"));
}
#[tokio::test]
async fn reject_token_too_short() {
let mut bytes = Vec::new();
bytes.extend_from_slice(&3u32.to_le_bytes());
bytes.extend_from_slice(&[0x00, 0x00, 0x00]); let mut reader = MockReader::new(bytes);
let parser = SessionStateTokenParser;
let context = ParserContext::default();
let result = parser.parse(&mut reader, &context).await;
assert!(result.is_err());
let err_msg = format!("{}", result.unwrap_err());
assert!(err_msg.contains("too short"));
}
#[tokio::test]
async fn reject_extended_length_overflow() {
let mut bytes = Vec::new();
let total_len: u32 = 11; bytes.extend_from_slice(&total_len.to_le_bytes());
bytes.extend_from_slice(&0u32.to_le_bytes()); bytes.push(0x00); bytes.push(0x00); bytes.push(0xFF); bytes.extend_from_slice(&u32::MAX.to_le_bytes()); let mut reader = MockReader::new(bytes);
let parser = SessionStateTokenParser;
let context = ParserContext::default();
let result = parser.parse(&mut reader, &context).await;
assert!(result.is_err());
let err_msg = format!("{}", result.unwrap_err());
assert!(err_msg.contains("exceeds remaining"));
}
}