ieee1212_config_rom/
lib.rs

1// SPDX-License-Identifier: MIT
2// Copyright (c) 2020 Takashi Sakamoto
3
4#![doc = include_str!("../README.md")]
5
6mod entry;
7mod leaf;
8
9pub use {entry::*, leaf::*};
10
11use std::convert::TryFrom;
12
13/// The structure to express content of configuration ROM in IEEE 1212.
14///
15/// The structure implements std::convert::TryFrom<&[u8]> to parse raw data of configuration ROM,
16/// aligned to big-endian. The structure refers to content of the raw data, thus has the same
17/// lifetime of the raw data.
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct ConfigRom<'a> {
20    /// The content of bus information block.
21    pub bus_info: &'a [u8],
22    /// The directory entries in root directory block.
23    pub root: Vec<Entry<'a>>,
24}
25
26/// The error to parse Configuration ROM.
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub enum ConfigRomParseError {
29    /// The error to parse bus information block.
30    BusInfo(
31        /// The cause of error.
32        BusInfoParseError,
33    ),
34    /// The error to parse root directory entry.
35    RootDirectory(
36        /// The start offset of root directory.
37        usize,
38        /// The index of root directory entry.
39        usize,
40        /// The cause of error to parse block referred by the root directory entry.
41        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/// The error to parse bus information block.
63#[derive(Debug, Clone, PartialEq, Eq)]
64pub enum BusInfoParseError {
65    /// The start offset is beyond boundary.
66    OffsetBeyondBoundary(
67        /// The offset of block.
68        usize,
69    ),
70    /// The detected length is invalid.
71    InvalidLength(
72        /// The length of content.
73        usize,
74    ),
75    /// The content is beyond boundary.
76    ContentBeyondBoundary(
77        /// The start offset of content.
78        usize,
79        /// The length of content.
80        usize,
81    ),
82    /// The error to parse directory entry.
83    Directory(
84        /// The start offset of directory.
85        usize,
86        /// The index of directory entry.
87        usize,
88        /// The cause of error to parse block referred by the directory entry.
89        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/// The error to parse block for leaf or directory.
119#[derive(Debug, Clone, PartialEq, Eq)]
120pub enum BlockParseError {
121    /// The start offset is beyond boundary.
122    OffsetBeyondBoundary(
123        /// The offset of block.
124        usize,
125    ),
126    /// The detected length is invalid.
127    InvalidLength(
128        /// The length of content.
129        usize,
130    ),
131    /// The content is beyond boundary.
132    ContentBeyondBoundary(
133        /// The start offset of content.
134        usize,
135        /// The length of content.
136        usize,
137    ),
138    /// The error to parse directory entry.
139    Directory(
140        /// The start offset of directory.
141        usize,
142        /// The index of directory entry.
143        usize,
144        /// The cause of error.
145        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                // NOTE: The maximum value of value field in directory entry is 0x00ffffff. The
247                // maximum value multipled by 4 is within 0x0fffffff, therefore no need to detect
248                // error.
249                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            // NOTE: The field of key has two bits, thus it can not be over 0x03.
271            _ => 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}