use crate::CoreError;
pub(crate) trait DisplayTailRead {
fn read_i32(&self) -> Result<i32, CoreError>;
fn read_float(&self) -> Result<f32, CoreError>;
fn read_int64(&self) -> Result<i64, CoreError>;
fn read_bool(&self) -> Result<bool, CoreError>;
fn read_string(&self) -> Result<Option<String>, CoreError>;
fn skip_int_array(&self) -> Result<(), CoreError>;
}
pub(crate) fn parse_display_state_tail(r: &impl DisplayTailRead) -> Result<i32, CoreError> {
let n_app_modes = r.read_i32()?.max(0) as usize;
for _ in 0..n_app_modes {
let _mode = r.read_i32()?; r.read_i32()?; r.read_i32()?; let _refresh = r.read_float()?;
let alt = r.read_i32()?.max(0) as usize;
for _ in 0..alt {
let _f = r.read_float()?;
}
let hdr = r.read_i32()?.max(0) as usize;
for _ in 0..hdr {
r.read_i32()?;
}
}
r.read_i32()?; let n_color_modes = r.read_i32()?.max(0) as usize;
for _ in 0..n_color_modes {
r.read_i32()?;
}
if r.read_string()?.is_some() {
r.skip_int_array()?; r.read_float()?; r.read_float()?; r.read_float()?; }
r.read_bool()?; r.read_bool()?; r.read_i32()?; r.read_float()?; r.read_float()?; r.read_int64()?; r.read_int64()?; r.read_i32() }
#[cfg(test)]
mod tests {
use super::*;
struct TailCursor {
buf: Vec<u8>,
pos: std::cell::Cell<usize>,
}
impl TailCursor {
fn take(&self, n: usize) -> Result<&[u8], CoreError> {
let pos = self.pos.get();
if pos + n > self.buf.len() {
return Err(CoreError::binder(-1, "cursor:past_end"));
}
self.pos.set(pos + n);
Ok(&self.buf[pos..pos + n])
}
}
impl DisplayTailRead for TailCursor {
fn read_i32(&self) -> Result<i32, CoreError> {
let b: &[u8] = self.take(4)?;
Ok(i32::from_le_bytes(b.try_into().unwrap()))
}
fn read_float(&self) -> Result<f32, CoreError> {
let b: &[u8] = self.take(4)?;
Ok(f32::from_le_bytes(b.try_into().unwrap()))
}
fn read_int64(&self) -> Result<i64, CoreError> {
let b: &[u8] = self.take(8)?;
Ok(i64::from_le_bytes(b.try_into().unwrap()))
}
fn read_bool(&self) -> Result<bool, CoreError> {
Ok(self.read_i32()? != 0)
}
fn read_string(&self) -> Result<Option<String>, CoreError> {
let len16 = self.read_i32()?;
if len16 < 0 {
return Ok(None);
}
let padded = ((len16 as usize + 1) * 2).next_multiple_of(4);
let bytes = self.take(padded)?;
let mut u16s = Vec::with_capacity(len16 as usize);
for pair in bytes[..len16 as usize * 2].chunks_exact(2) {
u16s.push(u16::from_le_bytes([pair[0], pair[1]]));
}
Ok(Some(String::from_utf16_lossy(&u16s)))
}
fn skip_int_array(&self) -> Result<(), CoreError> {
let n = self.read_i32()?.max(0) as usize;
for _ in 0..n {
self.read_i32()?;
}
Ok(())
}
}
fn i32b(v: i32) -> Vec<u8> {
v.to_le_bytes().to_vec()
}
fn f32b(v: f32) -> Vec<u8> {
v.to_le_bytes().to_vec()
}
fn i64b(v: i64) -> Vec<u8> {
v.to_le_bytes().to_vec()
}
fn str16(s: &str) -> Vec<u8> {
let mut b = i32b(s.len() as i32);
for c in s.chars() {
b.extend((c as u16).to_le_bytes());
}
b.extend(0u16.to_le_bytes());
let payload = (s.len() + 1) * 2;
b.extend(std::iter::repeat_n(
0u8,
payload.next_multiple_of(4) - payload,
));
b
}
fn tail_prefix() -> Vec<u8> {
let mut b = Vec::new();
b.extend(i32b(2));
for (id, refresh) in [(1u32, 60.0f32), (2, 90.0)] {
b.extend(i32b(id as i32));
b.extend(i32b(1080));
b.extend(i32b(2400));
b.extend(f32b(refresh));
b.extend(i32b(1));
b.extend(f32b(refresh));
b.extend(i32b(0));
}
b.extend(i32b(3)); b.extend(i32b(2)); b.extend(i32b(2));
b.extend(i32b(3));
b.extend(str16("android.view.Display$HdrCapabilities"));
b.extend(i32b(1));
b.extend(i32b(1)); b.extend(f32b(1000.0));
b.extend(f32b(540.0));
b.extend(f32b(0.01));
b.extend(i32b(1)); b.extend(i32b(0)); b.extend(i32b(420)); b.extend(f32b(420.0)); b.extend(f32b(420.0)); b.extend(i64b(1_000_000)); b.extend(i64b(1_000_000)); b
}
#[test]
fn tail_walk_decodes_state_on() {
let mut b = tail_prefix();
b.extend(i32b(2)); let c = TailCursor {
buf: b,
pos: std::cell::Cell::new(0),
};
assert_eq!(parse_display_state_tail(&c).unwrap(), 2);
assert_eq!(c.pos.get(), c.buf.len());
}
#[test]
fn tail_walk_decodes_state_doze() {
let mut b = tail_prefix();
b.extend(i32b(3)); let c = TailCursor {
buf: b,
pos: std::cell::Cell::new(0),
};
assert_eq!(parse_display_state_tail(&c).unwrap(), 3);
}
#[test]
fn tail_walk_decodes_null_hdr_capabilities() {
let mut b = tail_prefix();
let probe = str16("android.view.Display$HdrCapabilities");
let pos = b
.windows(probe.len())
.position(|w| w == probe.as_slice())
.unwrap();
let body = 4 + 4 + 3 * 4;
b.drain(pos..pos + probe.len() + body);
b.splice(pos..pos, i32b(-1));
b.extend(i32b(1)); let c = TailCursor {
buf: b,
pos: std::cell::Cell::new(0),
};
assert_eq!(parse_display_state_tail(&c).unwrap(), 1);
}
#[test]
fn tail_walk_fails_closed_on_truncation() {
let mut b = tail_prefix();
b.extend(i32b(2));
b.pop(); let c = TailCursor {
buf: b,
pos: std::cell::Cell::new(0),
};
let err = parse_display_state_tail(&c).unwrap_err();
assert!(err.to_string().contains("cursor"), "err: {err}");
}
#[test]
fn tail_walk_fails_closed_on_empty_input() {
let c = TailCursor {
buf: Vec::new(),
pos: std::cell::Cell::new(0),
};
assert!(parse_display_state_tail(&c).is_err());
}
}