| 1 | use crate::{Error, bytes::Cursor}; |
| 2 | use std::ops::Range; |
| 3 | |
| 4 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| 5 | pub enum IdStream { |
| 6 | Objects, |
| 7 | ObjectSpaces, |
| 8 | Contexts, |
| 9 | } |
| 10 | |
| 11 | #[derive(Debug, PartialEq, Eq)] |
| 12 | pub enum Value<'a> { |
| 13 | NoData, |
| 14 | Bytes(&'a [u8]), |
| 15 | References { |
| 16 | stream: IdStream, |
| 17 | compact_ids: &'a [u8], |
| 18 | }, |
| 19 | Sets(Range<usize>), |
| 20 | } |
| 21 | |
| 22 | #[derive(Debug, PartialEq, Eq)] |
| 23 | pub struct Property<'a> { |
| 24 | pub id: u32, |
| 25 | pub value: Value<'a>, |
| 26 | } |
| 27 | |
| 28 | /// Set zero is the root; nested sets are ranges of indices in this arena. |
| 29 | #[derive(Debug, PartialEq, Eq)] |
| 30 | pub struct PropertySets<'a> { |
| 31 | pub sets: Vec<Vec<Property<'a>>>, |
| 32 | pub(crate) padding: &'a [u8], |
| 33 | pub(crate) root_ids: &'a [u8], |
| 34 | } |
| 35 | |
| 36 | enum Work<'a> { |
| 37 | Set(usize), |
| 38 | Fields(usize, &'a [u8]), |
| 39 | } |
| 40 | |
| 41 | impl<'a> PropertySets<'a> { |
| 42 | pub fn parse(bytes: &'a [u8]) -> Result<Self, Error> { |
| 43 | let mut c = Cursor { bytes, offset: 0 }; |
| 44 | let mut streams = reference_streams(&mut c)?; |
| 45 | let mut sets = vec![Vec::new()]; |
| 46 | let mut root_ids = &bytes[..0]; |
| 47 | let mut work = vec![Work::Set(0)]; |
| 48 | while let Some(item) = work.pop() { |
| 49 | match item { |
| 50 | Work::Set(index) => { |
| 51 | let count = usize::from(u16::from_le_bytes(c.read()?)); |
| 52 | let ids = c.take(count * 4)?; |
| 53 | if index == 0 { |
| 54 | root_ids = ids; |
| 55 | } |
| 56 | work.push(Work::Fields(index, ids)); |
| 57 | } |
| 58 | Work::Fields(index, ids) => { |
| 59 | let Some((first, rest)) = ids.split_first_chunk::<4>() else { |
| 60 | continue; |
| 61 | }; |
| 62 | let id = u32::from_le_bytes(*first); |
| 63 | let kind = (id >> 26) & 0x1f; |
| 64 | work.push(Work::Fields(index, rest)); |
| 65 | let value = match kind { |
| 66 | 1 | 2 => Value::NoData, |
| 67 | 3..=6 => Value::Bytes(c.take(1 << (kind - 3))?), |
| 68 | 7 => { |
| 69 | let count = u32::from_le_bytes(c.read()?); |
| 70 | if count >= 0x40000000 { |
| 71 | return Err(Error { |
| 72 | offset: c.offset - 4, |
| 73 | message: "Property data exceeds the format length limit", |
| 74 | }); |
| 75 | } |
| 76 | Value::Bytes(c.take(usize::try_from(count).unwrap())?) |
| 77 | } |
| 78 | 8..=13 => { |
| 79 | let count = if kind & 1 != 0 { |
| 80 | u32::from_le_bytes(c.read()?) |
| 81 | } else { |
| 82 | 1 |
| 83 | }; |
| 84 | let stream_index = usize::try_from((kind - 8) / 2).unwrap(); |
| 85 | let count = usize::try_from(count) |
| 86 | .ok() |
| 87 | .and_then(|count| count.checked_mul(4)) |
| 88 | .ok_or(Error { |
| 89 | offset: c.offset, |
| 90 | message: "Reference count exceeds address space", |
| 91 | })?; |
| 92 | let compact_ids = streams[stream_index].take(count)?; |
| 93 | let stream = match stream_index { |
| 94 | 0 => IdStream::Objects, |
| 95 | 1 => IdStream::ObjectSpaces, |
| 96 | _ => IdStream::Contexts, |
| 97 | }; |
| 98 | Value::References { |
| 99 | stream, |
| 100 | compact_ids, |
| 101 | } |
| 102 | } |
| 103 | 16 | 17 => { |
| 104 | let count = if kind == 16 { |
| 105 | let count = u32::from_le_bytes(c.read()?); |
| 106 | if count != 0 { |
| 107 | let element = u32::from_le_bytes(c.read()?); |
| 108 | if (element >> 26) & 0x1f != 17 { |
| 109 | return Err(Error { |
| 110 | offset: c.offset - 4, |
| 111 | message: "Property array contains a non-set element type", |
| 112 | }); |
| 113 | } |
| 114 | } |
| 115 | usize::try_from(count).map_err(|_| Error { |
| 116 | offset: c.offset, |
| 117 | message: "Property-set count exceeds address space", |
| 118 | })? |
| 119 | } else { |
| 120 | 1 |
| 121 | }; |
| 122 | let start = sets.len(); |
| 123 | let end = start |
| 124 | .checked_add(count) |
| 125 | .filter(|end| *end <= bytes.len() / 2) |
| 126 | .ok_or(Error { |
| 127 | offset: c.offset, |
| 128 | message: "Property-set count exceeds the available data", |
| 129 | })?; |
| 130 | sets.resize_with(end, Vec::new); |
| 131 | for child in (start..end).rev() { |
| 132 | work.push(Work::Set(child)); |
| 133 | } |
| 134 | Value::Sets(start..end) |
| 135 | } |
| 136 | _ => { |
| 137 | return Err(Error { |
| 138 | offset: c.offset, |
| 139 | message: "Unsupported property type", |
| 140 | }); |
| 141 | } |
| 142 | }; |
| 143 | sets[index].push(Property { id, value }); |
| 144 | } |
| 145 | } |
| 146 | } |
| 147 | for stream in streams { |
| 148 | if !stream.bytes.is_empty() { |
| 149 | return Err(Error { |
| 150 | offset: stream.offset, |
| 151 | message: "Unconsumed property references", |
| 152 | }); |
| 153 | } |
| 154 | } |
| 155 | if !c.bytes.is_empty() && (c.bytes.len() > 7 || !bytes.len().is_multiple_of(8)) { |
| 156 | return Err(Error { |
| 157 | offset: c.offset, |
| 158 | message: "Trailing data exceeds property-set padding", |
| 159 | }); |
| 160 | } |
| 161 | Ok(Self { |
| 162 | sets, |
| 163 | padding: c.bytes, |
| 164 | root_ids, |
| 165 | }) |
| 166 | } |
| 167 | } |
| 168 | |
| 169 | pub(crate) fn reference_streams<'a>(c: &mut Cursor<'a>) -> Result<[Cursor<'a>; 3], Error> { |
| 170 | let mut streams = std::array::from_fn::<_, 3, _>(|_| Cursor { |
| 171 | bytes: &[], |
| 172 | offset: 0, |
| 173 | }); |
| 174 | let mut extended = false; |
| 175 | for (index, stream) in streams.iter_mut().enumerate() { |
| 176 | let header = u32::from_le_bytes(c.read()?); |
| 177 | let count = usize::try_from(header & 0xffffff).unwrap(); |
| 178 | if (index > 0 && header & 0x80000000 != 0) |
| 179 | || (index == 0 && header & 0xc0000000 == 0xc0000000) |
| 180 | || (index == 1 && (header & 0x40000000 != 0) != extended) |
| 181 | || (index == 2 && header & 0x40000000 != 0) |
| 182 | { |
| 183 | return Err(Error { |
| 184 | offset: c.offset - 4, |
| 185 | message: "Inconsistent property reference-stream flags", |
| 186 | }); |
| 187 | } |
| 188 | let offset = c.offset; |
| 189 | *stream = Cursor { |
| 190 | bytes: c.take(count * 4)?, |
| 191 | offset, |
| 192 | }; |
| 193 | if index == 0 { |
| 194 | extended = header & 0x40000000 != 0; |
| 195 | if header & 0x80000000 != 0 { |
| 196 | break; |
| 197 | } |
| 198 | } else if index == 1 && !extended { |
| 199 | break; |
| 200 | } |
| 201 | } |
| 202 | Ok(streams) |
| 203 | } |