1#![doc = include_str!("../README.md")]
5
6mod entry;
7mod leaf;
8
9pub use {entry::*, leaf::*};
10
11use std::convert::TryFrom;
12
13#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct ConfigRom<'a> {
20 pub bus_info: &'a [u8],
22 pub root: Vec<Entry<'a>>,
24}
25
26#[derive(Debug, Clone, PartialEq, Eq)]
28pub enum ConfigRomParseError {
29 BusInfo(
31 BusInfoParseError,
33 ),
34 RootDirectory(
36 usize,
38 usize,
40 BlockParseError,
42 ),
43}
44
45impl std::fmt::Display for ConfigRomParseError {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 match self {
48 Self::BusInfo(err) => {
49 write!(f, "Detect ill-formed bus info: {}", err)
50 }
51 Self::RootDirectory(pos, entry_index, err) => {
52 write!(
53 f,
54 "Detect ill-formed root directory, pos {}, entry {}: {}",
55 pos, entry_index, err
56 )
57 }
58 }
59 }
60}
61
62#[derive(Debug, Clone, PartialEq, Eq)]
64pub enum BusInfoParseError {
65 OffsetBeyondBoundary(
67 usize,
69 ),
70 InvalidLength(
72 usize,
74 ),
75 ContentBeyondBoundary(
77 usize,
79 usize,
81 ),
82 Directory(
84 usize,
86 usize,
88 Box<BlockParseError>,
90 ),
91}
92
93impl std::fmt::Display for BusInfoParseError {
94 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95 match self {
96 Self::OffsetBeyondBoundary(offset) => write!(f, "offset {}", offset),
97 Self::InvalidLength(length) => {
98 write!(f, "invalid length {}", length)
99 }
100 Self::ContentBeyondBoundary(offset, length) => {
101 write!(
102 f,
103 "content beyond boundary, offset {}, length {}",
104 offset, length
105 )
106 }
107 Self::Directory(pos, entry_index, err) => {
108 write!(
109 f,
110 "ill-formed directory, pos: {}, entry {}: {}",
111 pos, entry_index, err
112 )
113 }
114 }
115 }
116}
117
118#[derive(Debug, Clone, PartialEq, Eq)]
120pub enum BlockParseError {
121 OffsetBeyondBoundary(
123 usize,
125 ),
126 InvalidLength(
128 usize,
130 ),
131 ContentBeyondBoundary(
133 usize,
135 usize,
137 ),
138 Directory(
140 usize,
142 usize,
144 Box<BlockParseError>,
146 ),
147}
148
149impl std::fmt::Display for BlockParseError {
150 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151 match self {
152 Self::OffsetBeyondBoundary(offset) => {
153 write!(f, "offset {}", offset)
154 }
155 Self::InvalidLength(length) => {
156 write!(f, "invalid length {}", length)
157 }
158 Self::ContentBeyondBoundary(offset, length) => {
159 write!(
160 f,
161 "content beyond boundary, offset {}, length {}",
162 offset, length
163 )
164 }
165 Self::Directory(pos, entry_index, err) => {
166 write!(
167 f,
168 "ill-formed directory, pos: {}, entry {}: {}",
169 pos, entry_index, err
170 )
171 }
172 }
173 }
174}
175
176fn detect_block(raw: &[u8], pos: usize, offset: usize) -> Result<(usize, usize), BlockParseError> {
177 let mut start_offset = pos + offset;
178 if start_offset > raw.len() {
179 Err(BlockParseError::OffsetBeyondBoundary(start_offset))
180 } else {
181 let doublet = [raw[start_offset], raw[start_offset + 1]];
182 let length = 4 * u16::from_be_bytes(doublet) as usize;
183 if length < 4 {
184 Err(BlockParseError::InvalidLength(length))
185 } else {
186 start_offset += 4;
187 if start_offset + length > raw.len() {
188 Err(BlockParseError::ContentBeyondBoundary(start_offset, length))
189 } else {
190 Ok((start_offset, length))
191 }
192 }
193 }
194}
195
196impl<'a> TryFrom<&'a [u8]> for ConfigRom<'a> {
197 type Error = ConfigRomParseError;
198
199 fn try_from(raw: &'a [u8]) -> Result<Self, Self::Error> {
200 let mut pos = 0;
201
202 let bus_info_length = 4 * raw[pos] as usize;
203 pos += 4;
204
205 if pos + bus_info_length > raw.len() {
206 Err(Self::Error::BusInfo(BusInfoParseError::InvalidLength(
207 bus_info_length,
208 )))
209 } else {
210 let bus_info = &raw[pos..(pos + bus_info_length)];
211 pos += bus_info_length;
212
213 detect_block(raw, pos, 0)
214 .and_then(|(start_offset, length)| {
215 get_directory_entry_list(raw, start_offset, length)
216 .map(|root| ConfigRom { bus_info, root })
217 })
218 .map_err(|err| Self::Error::RootDirectory(pos, 0, err))
219 }
220 }
221}
222
223const ENTRY_KEY_IMMEDIATE: u8 = 0;
224const ENTRY_KEY_CSR_OFFSET: u8 = 1;
225const ENTRY_KEY_LEAF: u8 = 2;
226const ENTRY_KEY_DIRECTORY: u8 = 3;
227
228fn get_directory_entry_list<'a>(
229 raw: &'a [u8],
230 content_pos: usize,
231 content_length: usize,
232) -> Result<Vec<Entry<'a>>, BlockParseError> {
233 let mut entries = Vec::new();
234
235 let mut pos = content_pos;
236
237 while pos < content_pos + content_length {
238 let entry_type = raw[pos] >> 6;
239 let key = raw[pos] & 0x3f;
240 let quadlet = [0, raw[pos + 1], raw[pos + 2], raw[pos + 3]];
241 let value = u32::from_be_bytes(quadlet);
242
243 match entry_type {
244 ENTRY_KEY_IMMEDIATE => Ok(EntryData::Immediate(value)),
245 ENTRY_KEY_CSR_OFFSET => {
246 let offset = 0xfffff0000000 + (4 * value as usize);
250 Ok(EntryData::CsrOffset(offset))
251 }
252 ENTRY_KEY_LEAF => {
253 let offset = 4 * value as usize;
254 detect_block(raw, pos, offset)
255 .map_err(|err| BlockParseError::Directory(pos, offset, Box::new(err)))
256 .map(|(start_offset, length)| {
257 let leaf = &raw[start_offset..(start_offset + length)];
258 EntryData::Leaf(leaf)
259 })
260 }
261 ENTRY_KEY_DIRECTORY => {
262 let offset = 4 * value as usize;
263 detect_block(raw, pos, offset)
264 .and_then(|(start_offset, length)| {
265 get_directory_entry_list(raw, start_offset, length)
266 .map(|entries| EntryData::Directory(entries))
267 })
268 .map_err(|err| BlockParseError::Directory(pos, offset, Box::new(err)))
269 }
270 _ => unreachable!(),
272 }
273 .map(|entry_data| {
274 entries.push(Entry {
275 key: KeyType::from(key),
276 data: entry_data,
277 });
278 pos += 4;
279 })?;
280 }
281
282 Ok(entries)
283}