use crate::{Error, SqlReadBytes};
use byteorder::{LittleEndian, ReadBytesExt};
use futures_util::io::AsyncReadExt;
use std::io::{Cursor, Read};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SessionStateValue {
pub id: u8,
pub value: Vec<u8>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TokenSessionState {
pub seq_no: u32,
pub status: u8,
pub states: Vec<SessionStateValue>,
}
impl TokenSessionState {
pub fn is_recoverable(&self) -> bool {
self.status & 0x01 != 0
}
fn parse(bytes: Vec<u8>) -> crate::Result<Self> {
let mut buf = Cursor::new(bytes);
let seq_no = buf.read_u32::<LittleEndian>()?;
let status = buf.read_u8()?;
let mut states = Vec::new();
let total = buf.get_ref().len() as u64;
while buf.position() < total {
let id = buf.read_u8()?;
let short_len = buf.read_u8()?;
let state_len = if short_len == 0xFF {
buf.read_u32::<LittleEndian>()? as usize
} else {
short_len as usize
};
let remaining = total - buf.position();
if state_len as u64 > remaining {
return Err(Error::Protocol(
format!(
"SESSIONSTATE entry length {state_len} exceeds the {remaining} bytes remaining in the token"
)
.into(),
));
}
let mut value = vec![0u8; state_len];
buf.read_exact(&mut value)?;
states.push(SessionStateValue { id, value });
}
Ok(TokenSessionState {
seq_no,
status,
states,
})
}
pub(crate) async fn decode<R>(src: &mut R) -> crate::Result<Self>
where
R: SqlReadBytes + Unpin,
{
let len = src.read_u32_le().await? as usize;
if len > super::MAX_TOKEN_BODY {
return Err(Error::Protocol(
format!("SESSIONSTATE token length {len} exceeds the maximum").into(),
));
}
let mut bytes = vec![0u8; len];
src.read_exact(&mut bytes[0..len]).await?;
if bytes.len() < 5 {
return Err(Error::Protocol(
"SESSIONSTATE token too short to contain SeqNo and Status".into(),
));
}
Self::parse(bytes)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_two_state_values() {
let mut body = Vec::new();
body.extend_from_slice(&1u32.to_le_bytes()); body.push(0x01);
body.push(0x00);
body.push(0x03);
body.extend_from_slice(&[0xAA, 0xBB, 0xCC]);
body.push(0x07);
body.push(0x01);
body.push(0x42);
let token = TokenSessionState::parse(body).unwrap();
assert_eq!(token.seq_no, 1);
assert_eq!(token.status, 0x01);
assert!(token.is_recoverable());
assert_eq!(token.states.len(), 2);
assert_eq!(token.states[0].id, 0);
assert_eq!(token.states[0].value, vec![0xAA, 0xBB, 0xCC]);
assert_eq!(token.states[1].id, 7);
assert_eq!(token.states[1].value, vec![0x42]);
}
#[test]
fn parse_long_state_length() {
let mut body = Vec::new();
body.extend_from_slice(&0xFFFF_FFFFu32.to_le_bytes()); body.push(0x00);
body.push(0x02); body.push(0xFF); body.extend_from_slice(&300u32.to_le_bytes()); body.extend_from_slice(&vec![0x5A; 300]);
let token = TokenSessionState::parse(body).unwrap();
assert_eq!(token.seq_no, 0xFFFF_FFFF);
assert!(!token.is_recoverable());
assert_eq!(token.states.len(), 1);
assert_eq!(token.states[0].id, 2);
assert_eq!(token.states[0].value.len(), 300);
assert!(token.states[0].value.iter().all(|&b| b == 0x5A));
}
#[test]
fn parse_rejects_oversized_state_len() {
let mut body = Vec::new();
body.extend_from_slice(&1u32.to_le_bytes()); body.push(0x00); body.push(0x01); body.push(0xFF); body.extend_from_slice(&0xFFFF_FFF0u32.to_le_bytes());
let err = TokenSessionState::parse(body).expect_err("oversized StateLen must be rejected");
assert!(matches!(err, Error::Protocol(_)));
}
}