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. All rights reserved.
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
45pub mod header;
46pub mod sections;
47pub mod utils;
48
49use header::{Header, ExecMode};
50use sections::{Section, SectionIter};
51pub use utils::*;
52
53/// The header size.
54pub const HEADER_SIZE: usize = core::mem::size_of::<Header>();
55
56/// The section entry size
57pub const SECTION_SIZE: usize = core::mem::size_of::<Section>();
58
59/// The parser of the proka executable.
60///
61/// # Usage
62/// To use this parser, you must put an slice into the initializations.
63///
64/// If the content of the proka executable is in memory, the best way
65/// is to use `core::slice::from_raw_parts`.
66#[derive(Debug, Clone, Copy)]
67pub struct Parser {
68    buf: &'static [u8],
69    header: Header,
70    total_sections: u16,
71}
72
73impl Parser {
74    /// Initialize the parser by passing a slice without checking.
75    ///
76    /// # Safety
77    /// You must ensure these if you invoke this function:
78    ///
79    ///  - The slice's content is a valid proka executable (match the magic);
80    ///  - The slice must contain the header and all section tables.
81    ///
82    /// # Note
83    /// Use this function to initialize is **NOT** recommended, because it might  
84    /// cause some problems while parsing this header.
85    pub unsafe fn init_unchecked(buf: &'static [u8]) -> Self {
86        let header_raw = &buf[0..HEADER_SIZE];
87        let header = unsafe { *(header_raw.as_ptr() as *const Header) };
88
89        Self {
90            buf, 
91            header,
92            total_sections: header.sections,
93        }
94    }
95
96    /// Initialize the parser by passing a slice.
97    ///
98    /// This is the recommended way to initialize this parser, because it will 
99    /// help you do all checks and return error if something wrong, so you can 
100    /// leave everything about parsing to us :)
101    ///
102    /// # Note
103    /// If this crate is used on the kernel-side, you must first map the memory
104    /// that the slice points to before invoking this function.
105    pub fn init(buf: &'static [u8]) -> Result<Self, Error> {
106        let header_raw = &buf[0..HEADER_SIZE]; // Header length
107        let header = unsafe { *(header_raw.as_ptr() as *const Header) };
108
109        // Check: Validate is this correct executable
110        if !header.validate() {
111            return Err(Error::NotValidExecutable);
112        }
113
114        // Check: Is the buffer contains all sections
115        let len = HEADER_SIZE + header.sections as usize * SECTION_SIZE;
116        if buf.len() < len {
117            return Err(Error::ExecutableCorrupted);
118        }
119
120        // SAFETY: Already check all staff and able to do initialization
121        unsafe { Ok(Self::init_unchecked(buf)) }
122    }
123
124    /// Do more validation after initialization.
125    ///
126    /// # Content
127    /// This will validates:
128    ///
129    ///  - Is the header min >= max;
130    ///  - Is each section's base correct;
131    ///  - Is the section's length not zeroed.
132    ///  - Is section base out of length.
133    pub fn validate(&self) -> bool {
134        // Check: Is header's min > max
135        let minimal = self.header.min;
136        let maximum = self.header.max;
137        for (&min, &max) in minimal.iter().zip(maximum.iter()) {
138            if min > max {
139                return false;
140            }
141        }
142
143        // Check: Is each section's base and length correct
144        let min_base = HEADER_SIZE + self.header.sections as usize * SECTION_SIZE;
145        for section in self.sections() {
146            let base_off = section.base as usize;
147            let len = section.length as usize;
148
149            if base_off < min_base
150                || base_off + len > self.buf.len()
151                || len == 0
152                || !section.validate()
153            {
154                return false;
155            }
156        }
157
158        // All's fine :)
159        true
160    }
161
162    /// Get the content from specified sections.
163    ///
164    /// # Arguments
165    ///  - secname: The name of the section
166    /// 
167    /// # Returns
168    /// Option<&'static [u8]>: The content of this section, return `None` if this section not exist.
169    pub fn get_section_content(&self, secname: &str) -> Option<&'static [u8]> {
170        // Iterate all sections...
171        for section in self.sections() {
172            let name = section.name;
173            if str_to_array(secname) == name {
174                // Get its base and length
175                let base = section.base as usize;
176                let length = section.length as usize;
177                let content = &self.buf[base..base + length];
178                return Some(content);
179            }
180        }
181         
182        None
183    }
184
185    /// Get the header in this buffer.
186    #[inline]
187    pub fn header(&self) -> Header {
188        self.header
189    }
190
191    /// Get each section table.
192    pub fn sections(&self) -> SectionIter {
193        SectionIter::new(self.buf, self.total_sections, 0)
194    }
195}
196
197/// The builder of the proka executable.
198#[derive(Default, Debug, Clone)]
199pub struct Builder {
200    author: [u8; 32],
201    name: [u8; 32],
202    mode: ExecMode,
203}
204
205impl Builder {
206    /// Create up a empty builder.
207    pub fn new() -> Self {
208        Self::default()
209    }
210
211    /// Set up the author 
212    pub fn set_author(&mut self, author: &str) -> Result<(), Error> {
213        // Check: Is length over than header's author length.
214        if author.len() > 32 {
215            return Err(Error::ArgsTooLong);
216        }
217
218        self.author = str_to_array(author);
219        Ok(())
220    }
221
222    /// Set up the program name.
223    pub fn set_name(&mut self, name: &str) -> Result<(), Error> {
224        // Check: Is length overflow
225        if name.len() > 32 {
226            return Err(Error::ArgsTooLong);
227        }
228
229        self.name = str_to_array(name);
230        Ok(())
231    }
232
233    /// Set the mode of this program
234    pub fn set_mode(&mut self, mode: ExecMode) {
235        self.mode = mode;
236    }
237}
238
239/// The error type of parsing header.
240#[repr(C)]
241#[derive(Debug, Clone, Copy, PartialEq, Eq)]
242pub enum Error {
243    /// The executable is not valid
244    ///
245    /// Will appear if magic is not correct.
246    NotValidExecutable,
247
248    /// The executable is corrupted.
249    ///
250    /// Will appear if the buffer size is lower than specified
251    /// length.
252    ExecutableCorrupted,
253
254    /// An unknown character in UTF-8 was found in
255    /// parsing arrays
256    ///
257    /// May appear in converting slice to `&str`.
258    UnknownCharacter,
259
260    /// The argument which gives is too long.
261    ///
262    /// For example, if a field, which require at most 16 bytes, but you gave
263    /// 17 bytes, it will return this error.
264    ArgsTooLong,
265}