Skip to main content

proka_exec/
lib.rs

1//! # `proka-exec`
2//!
3//! [![Rust Nightly](https://img.shields.io/badge/rust-nightly-orange?style=flat-square&logo=rust)](https://www.rust-lang.org/)
4//! [![License: GPLv3](https://img.shields.io/badge/License-GPLv3-yellow.svg?style=flat-square)](https://opensource.org/license/gpl-3.0)
5//! [![GitHub Stars](https://img.shields.io/github/stars/RainSTR-Studio/proka-exec?style=flat-square)](https://github.com/RainSTR-Studio/proka-exec/stargazers)
6//! [![GitHub Issues](https://img.shields.io/github/issues/RainSTR-Studio/proka-exec?style=flat-square)](https://github.com/RainSTR-Studio/proka-exec/issues)
7//! [![GitHub Pull Requests](https://img.shields.io/github/issues-pr/RainSTR-Studio/proka-exec?style=flat-square)](https://github.com/RainSTR-Studio/proka-exec/pulls)
8//! [![Documentation](https://img.shields.io/badge/docs-prokadoc-brightgreen?style=flat-square)](https://prokadoc.pages.dev/)
9//!
10//! Copyright (C) 2026 RainSTR Studio. Licensed under GNU GPLv3.
11//!
12//! ---
13//!
14//! ## Introduction
15//! This crate provides the definitions of headers, section
16//! entrys, and some utils to help you parse the executable
17//! easily.
18//!
19//! ## Steps to use this crate
20//! Before you parse it, you should do these steps:
21//!
22//! - Read the executable file content;
23//! - Make this file's content to a slice (`&'static [u8]`)
24//! - Use [`Parser`] to parse the executable.
25//!
26//! After this, you can do further operations through this parser by
27//! calling its functions.
28//!
29//! ### Note
30//! If you want to do minimal reading, you can just read the header and
31//! section table, other content can be read later;
32//!
33//! Make sure you have read the header and each sections, and they are **NOT** optional!!!
34//!
35//! # LICENSE
36//! This crate is under license [GPL-v3](https://github.com/RainSTR-Studio/proka-exec/blob/main/LICENSE),
37//! and you must follow its rules.
38//!
39//! See [LICENSE](https://github.com/RainSTR-Studio/proka-exec/blob/main/LICENSE) file for more details.
40//!
41//! ## MSRV
42//! This crate's MSRV is `1.85.0` stable.
43#![no_std]
44
45// Alloc features...
46#[cfg(feature = "alloc")]
47extern crate alloc;
48
49pub mod header;
50pub mod sections;
51pub mod utils;
52
53#[cfg(feature = "alloc")]
54use alloc::{
55    string::{String, ToString},
56    vec::Vec,
57};
58use header::{ExecMode, Header};
59use sections::{Section, SectionIter};
60pub use utils::*;
61
62/// The header size.
63pub const HEADER_SIZE: usize = core::mem::size_of::<Header>();
64
65/// The section entry size
66pub const SECTION_SIZE: usize = core::mem::size_of::<Section>();
67
68/// The parser of the proka executable.
69///
70/// # Usage
71/// To use this parser, you must put an slice into the initializations.
72///
73/// If the content of the proka executable is in memory, the best way
74/// is to use `core::slice::from_raw_parts`.
75#[derive(Debug, Clone, Copy)]
76pub struct Parser<'a> {
77    buf: &'a [u8],
78    header: Header,
79    total_sections: u16,
80}
81
82impl<'a> Parser<'a> {
83    /// Initialize the parser by passing a slice.
84    ///
85    /// This is the recommended way to initialize this parser, because it will
86    /// help you do all checks and return error if something wrong, so you can
87    /// leave everything about parsing to us :)
88    ///
89    /// # Note
90    /// If this crate is used on the kernel-side, you must first map the memory
91    /// that the slice points to before invoking this function.
92    pub fn init(buf: &'a [u8]) -> Result<Self, Error> {
93        let header_raw = &buf[0..HEADER_SIZE]; // Header length
94        let header = unsafe { *(header_raw.as_ptr() as *const Header) };
95
96        // Check: Validate is this correct executable
97        if !header.validate() {
98            return Err(Error::NotValidExecutable);
99        }
100
101        // Check: Is the buffer contains all sections
102        let len = HEADER_SIZE + header.sections as usize * SECTION_SIZE;
103        if buf.len() < len {
104            return Err(Error::ExecutableCorrupted);
105        }
106
107        // SAFETY: Already check all staff and able to do initialization
108        unsafe { Ok(Self::init_unchecked(buf)) }
109    }
110
111    /// Initialize the parser by passing a slice without checking.
112    ///
113    /// # Safety
114    /// You must ensure these if you invoke this function:
115    ///
116    ///  - The slice's content is a valid proka executable (match the magic);
117    ///  - The slice must contain the header and all section tables.
118    ///
119    /// # Note
120    /// Use this function to initialize is **NOT** recommended, because it might
121    /// cause some problems while parsing this header.
122    pub unsafe fn init_unchecked(buf: &'a [u8]) -> Self {
123        let header_raw = &buf[0..HEADER_SIZE];
124        let header = unsafe { *(header_raw.as_ptr() as *const Header) };
125
126        Self {
127            buf,
128            header,
129            total_sections: header.sections,
130        }
131    }
132
133    /// Do more validation after initialization.
134    ///
135    /// # Content
136    /// This will validates:
137    ///
138    ///  - Is the header min >= max;
139    ///  - Is each section's base correct;
140    ///  - Is the section's length not zeroed;
141    ///  - Is section base out of length;
142    ///  - Is entry_off is over than section length.
143    pub fn validate(&self) -> bool {
144        // Check: Is header's min > max
145        let minimal = self.header.min;
146        let maximum = self.header.max;
147        for (&min, &max) in minimal.iter().zip(maximum.iter()) {
148            if min > max {
149                return false;
150            }
151        }
152
153        // Check: Is each section's base and length correct
154        let min_base = HEADER_SIZE + self.header.sections as usize * SECTION_SIZE;
155        for (index, section) in self.sections().enumerate() {
156            let base_off = section.base as usize;
157            let len = section.length as usize;
158            let entry_sec = self.header.entry_sec as usize;
159
160            if base_off < min_base
161                || base_off + len > self.buf.len()
162                || len == 0
163                || !section.validate()
164            {
165                return false;
166            }
167
168            // Also at here, we'd like to check the entry_off
169            // overflow or not.
170            if index == entry_sec {
171                let entry_off = self.header.entry_off as usize;
172                if entry_off > len {
173                    return false;
174                }
175            }
176        }
177
178        // All's fine :)
179        true
180    }
181
182    /// Get the content from specified sections.
183    ///
184    /// # Arguments
185    ///  - `secname`: The name of the section
186    ///
187    /// # Returns
188    /// `Option<&'static [u8]>`: The content of this section, return `None` if this section not exist.
189    pub fn get_section_content(&self, secname: &str) -> Option<&'a [u8]> {
190        // Iterate all sections...
191        for section in self.sections() {
192            let name = section.name;
193            if str_to_array(secname) == name {
194                // Get its base and length
195                let base = section.base as usize;
196                let length = section.length as usize;
197                let content = &self.buf[base..base + length];
198                return Some(content);
199            }
200        }
201
202        None
203    }
204
205    /// Get the header in this buffer.
206    #[inline]
207    pub fn header(&self) -> Header {
208        self.header
209    }
210
211    /// Get each section table.
212    pub fn sections(&self) -> SectionIter<'_> {
213        SectionIter::new(self.buf, self.total_sections, 0)
214    }
215}
216
217/// The builder of the proka executable.
218#[derive(Debug, Clone)]
219#[cfg(feature = "alloc")]
220pub struct Builder<'a> {
221    min: [u16; 3],
222    max: [u16; 3],
223    entry: (u32, usize), // (offset, index)
224    author: String,
225    name: String,
226    mode: ExecMode,
227    sections: Vec<InnerSections<'a>>,
228}
229
230#[cfg(feature = "alloc")]
231impl Default for Builder<'_> {
232    fn default() -> Self {
233        Self::new()
234    }
235}
236
237#[cfg(feature = "alloc")]
238impl<'a> Builder<'a> {
239    /// Create up a empty builder.
240    pub fn new() -> Self {
241        Self {
242            min: [0; 3],
243            max: [0; 3],
244            entry: (0, 0),
245            author: String::new(),
246            name: String::new(),
247            mode: ExecMode::UserApp,
248            sections: Vec::new(),
249        }
250    }
251
252    /// Set up the author.
253    ///
254    /// # Note
255    /// If the author that you provide is longer than 32,
256    /// it may truncated.
257    pub fn set_author(&mut self, author: &str) {
258        self.author = author.to_string();
259    }
260
261    /// Set up the program name.
262    ///
263    /// # Note
264    /// If the author that you provide is longer than 32,
265    /// it may truncated.
266    pub fn set_name(&mut self, name: &str) {
267        self.name = name.to_string();
268    }
269
270    /// Set the mode of this program.
271    pub fn set_mode(&mut self, mode: ExecMode) {
272        self.mode = mode;
273    }
274
275    /// Set the min version.
276    pub fn set_min(&mut self, min: [u16; 3]) {
277        self.min = min;
278    }
279
280    /// Set the max version.
281    pub fn set_max(&mut self, max: [u16; 3]) {
282        self.max = max;
283    }
284
285    /// Append a section and specify its name.
286    ///
287    /// # Arguments
288    ///  - `data`: The data that you want to append;
289    ///  - `name`: The section name;
290    ///  - `is_loadable`: Assign is this loadable section or not;
291    ///  - `is_execable`: Assign is this executable section or not;
292    ///  - `entry`: The offset of the entry point, pass `None` if no entry point.
293    ///
294    /// # Errors
295    /// This will return error once these happened:
296    ///  - Provide an entry address which is unloadable or unexecable;
297    ///
298    /// # Note
299    ///  - If you try to provide a name which is over than 16 bytes, it may truncated;
300    ///  - If you provide the entry offset for multiple times, once you invoke `build()`, it will
301    ///    use that latest set one.
302    pub fn append(
303        &mut self,
304        data: &'a [u8],
305        name: &str,
306        is_loadable: bool,
307        is_execable: bool,
308        entry: Option<u32>,
309    ) -> Result<(), Error> {
310        // Check: Is entry is Some(...) within unloadable & unexecable
311        if entry.is_some() && !(is_execable && is_loadable) {
312            return Err(Error::ExecutableCorrupted);
313        }
314
315        let section = InnerSections {
316            secinfo: Section {
317                name: str_to_array(name),
318                is_loadable,
319                is_execable,
320                base: 0,    // Will replace during building...
321                length: data.len() as u32,
322                _reserved: [0; 6],
323            },
324            data,
325        };
326        self.sections.push(section);
327
328        // Set entry if Some(...)...
329        if let Some(ent_offset) = entry {
330            let sec_index = self.sections.len() - 1;
331            self.entry = (ent_offset, sec_index);
332        }
333        Ok(())
334    }
335
336    /// Build the whole file to a valid exec format.
337    ///
338    /// Will return error if no section was appended.
339    pub fn build(self) -> Result<Vec<u8>, Error> {
340        // Check: Is section list empty
341        if self.sections.is_empty() {
342            return Err(Error::NoSections);
343        }
344
345        // Create up a data...
346        let mut data: Vec<u8> = Vec::new();
347
348        // Then create up a header and push into data...
349        {
350            let header = Header {
351                min: self.min,
352                max: self.max,
353                entry_off: self.entry.0,
354                entry_sec: self.entry.1 as u16,
355                mode: self.mode,
356                author: str_to_array(self.author.as_str()),
357                name: str_to_array(self.name.as_str()),
358                sections: self.sections.len() as u16,
359                ..Default::default()
360            }
361            .to_array();
362            data.extend_from_slice(&header);
363        }
364
365        // And each section info...
366        let mut cnt = 0;
367        for section in &self.sections {
368            let mut secinfo = section.secinfo;
369
370            // Update base...
371            secinfo.base = (HEADER_SIZE + self.sections.len() * SECTION_SIZE + cnt) as u32;
372
373            // Push...
374            data.extend_from_slice(&secinfo.to_array());
375            cnt += section.data.len();
376        }
377
378        // And each section's data...
379        for section in &self.sections {
380            data.extend_from_slice(section.data);
381        }
382
383        // Return
384        Ok(data)
385    }
386}
387
388/// Internal section form.
389#[derive(Debug, Clone, Copy)]
390struct InnerSections<'a> {
391    pub secinfo: Section,
392    pub data: &'a [u8],
393}
394
395/// The error type of parsing header.
396#[repr(C)]
397#[derive(Debug, Clone, Copy, PartialEq, Eq)]
398pub enum Error {
399    /// The executable is not valid
400    ///
401    /// Will appear if magic is not correct.
402    NotValidExecutable,
403
404    /// The section which is corrupted.
405    ///
406    /// Will appear if:  
407    ///  - The buffer size is lower than specified length;
408    ///  - Append an unexecable and unloadable section within an entry address (`Builder` only).
409    ExecutableCorrupted,
410
411    /// An unknown character in UTF-8 was found in
412    /// parsing arrays
413    ///
414    /// May appear in converting slice to `&str`.
415    UnknownCharacter,
416
417    /// The argument which gives is too long.
418    ///
419    /// For example, if a field, which require at most 16 bytes, but you gave
420    /// 17 bytes, it will return this error.
421    ArgsTooLong,
422
423    /// No sections in the current executable.
424    ///
425    /// Will appear if you try to build without any appending.
426    NoSections,
427}