1use crate::{Error, bytes::Cursor};
2use std::ops::Range;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum IdStream {
6 Objects,
7 ObjectSpaces,
8 Contexts,
9}
10
11#[derive(Debug, PartialEq, Eq)]
12pub 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)]
23pub 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)]
30pub 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
36enum Work<'a> {
37 Set(usize),
38 Fields(usize, &'a [u8]),
39}
40
41impl<'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
169pub(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}