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
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 without checking.
84    ///
85    /// # Safety
86    /// You must ensure these if you invoke this function:
87    ///
88    ///  - The slice's content is a valid proka executable (match the magic);
89    ///  - The slice must contain the header and all section tables.
90    ///
91    /// # Note
92    /// Use this function to initialize is **NOT** recommended, because it might  
93    /// cause some problems while parsing this header.
94    pub unsafe fn init_unchecked(buf: &'a [u8]) -> Self {
95        let header_raw = &buf[0..HEADER_SIZE];
96        let header = unsafe { *(header_raw.as_ptr() as *const Header) };
97
98        Self {
99            buf,
100            header,
101            total_sections: header.sections,
102        }
103    }
104
105    /// Initialize the parser by passing a slice.
106    ///
107    /// This is the recommended way to initialize this parser, because it will
108    /// help you do all checks and return error if something wrong, so you can
109    /// leave everything about parsing to us :)
110    ///
111    /// # Note
112    /// If this crate is used on the kernel-side, you must first map the memory
113    /// that the slice points to before invoking this function.
114    pub fn init(buf: &'a [u8]) -> Result<Self, Error> {
115        let header_raw = &buf[0..HEADER_SIZE]; // Header length
116        let header = unsafe { *(header_raw.as_ptr() as *const Header) };
117
118        // Check: Validate is this correct executable
119        if !header.validate() {
120            return Err(Error::NotValidExecutable);
121        }
122
123        // Check: Is the buffer contains all sections
124        let len = HEADER_SIZE + header.sections as usize * SECTION_SIZE;
125        if buf.len() < len {
126            return Err(Error::ExecutableCorrupted);
127        }
128
129        // SAFETY: Already check all staff and able to do initialization
130        unsafe { Ok(Self::init_unchecked(buf)) }
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    pub fn validate(&self) -> bool {
143        // Check: Is header's min > max
144        let minimal = self.header.min;
145        let maximum = self.header.max;
146        for (&min, &max) in minimal.iter().zip(maximum.iter()) {
147            if min > max {
148                return false;
149            }
150        }
151
152        // Check: Is each section's base and length correct
153        let min_base = HEADER_SIZE + self.header.sections as usize * SECTION_SIZE;
154        for section in self.sections() {
155            let base_off = section.base as usize;
156            let len = section.length as usize;
157
158            if base_off < min_base
159                || base_off + len > self.buf.len()
160                || len == 0
161                || !section.validate()
162            {
163                return false;
164            }
165        }
166
167        // All's fine :)
168        true
169    }
170
171    /// Get the content from specified sections.
172    ///
173    /// # Arguments
174    ///  - `secname`: The name of the section
175    ///
176    /// # Returns
177    /// `Option<&'static [u8]>`: The content of this section, return `None` if this section not exist.
178    pub fn get_section_content(&self, secname: &str) -> Option<&'a [u8]> {
179        // Iterate all sections...
180        for section in self.sections() {
181            let name = section.name;
182            if str_to_array(secname) == name {
183                // Get its base and length
184                let base = section.base as usize;
185                let length = section.length as usize;
186                let content = &self.buf[base..base + length];
187                return Some(content);
188            }
189        }
190
191        None
192    }
193
194    /// Get the header in this buffer.
195    #[inline]
196    pub fn header(&self) -> Header {
197        self.header
198    }
199
200    /// Get each section table.
201    pub fn sections(&self) -> SectionIter<'_> {
202        SectionIter::new(self.buf, self.total_sections, 0)
203    }
204}
205
206/// The builder of the proka executable.
207#[derive(Debug, Clone)]
208#[cfg(feature = "alloc")]
209pub struct Builder<'a> {
210    min: [u16; 3],
211    max: [u16; 3],
212    entry: u32,
213    author: String,
214    name: String,
215    mode: ExecMode,
216    sections: Vec<InnerSections<'a>>,
217}
218
219#[cfg(feature = "alloc")]
220impl Default for Builder<'_> {
221    fn default() -> Self {
222        Self::new()
223    }
224}
225
226#[cfg(feature = "alloc")]
227impl<'a> Builder<'a> {
228    /// Create up a empty builder.
229    pub fn new() -> Self {
230        Self {
231            min: [0; 3],
232            max: [0; 3],
233            entry: 0,
234            author: String::new(),
235            name: String::new(),
236            mode: ExecMode::UserApp,
237            sections: Vec::new(),
238        }
239    }
240
241    /// Set up the author.
242    ///
243    /// Will return error if length is > 32.
244    pub fn set_author(&mut self, author: &str) -> Result<(), Error> {
245        // Check: Is length over than header's author length.
246        if author.len() > 32 {
247            return Err(Error::ArgsTooLong);
248        }
249
250        self.author = author.to_string();
251        Ok(())
252    }
253
254    /// Set up the program name.
255    ///
256    /// Will return error if length is > 32.
257    pub fn set_name(&mut self, name: &str) -> Result<(), Error> {
258        // Check: Is length overflow
259        if name.len() > 32 {
260            return Err(Error::ArgsTooLong);
261        }
262
263        self.name = name.to_string();
264        Ok(())
265    }
266
267    /// Set the mode of this program.
268    pub fn set_mode(&mut self, mode: ExecMode) {
269        self.mode = mode;
270    }
271
272    /// Set the min version.
273    pub fn set_min(&mut self, min: [u16; 3]) {
274        self.min = min;
275    }
276
277    /// Set the max version.
278    pub fn set_max(&mut self, max: [u16; 3]) {
279        self.max = max;
280    }
281
282    /// Append a section and specify its name.
283    ///
284    /// # Arguments
285    ///  - `data`: The data that you want to append;
286    ///  - `name`: The section name;
287    ///  - `is_loadable`: Assign is this loadable section or not;
288    ///  - `is_execable`: Assign is this executable section or not;
289    pub fn append(&mut self, data: &'a [u8], name: &str, is_loadable: bool, is_execable: bool) {
290        let section = InnerSections {
291            secinfo: Section {
292                name: str_to_array(name),
293                is_loadable,
294                is_execable,
295                base: HEADER_SIZE as u32, // Will replace during building...
296                length: data.len() as u32,
297                _reserved: [0; 6],
298            },
299            data,
300        };
301        self.sections.push(section);
302    }
303
304    /// Build the whole file to a valid exec format.
305    ///
306    /// Will return error if no section was appended.
307    pub fn build(self) -> Result<Vec<u8>, Error> {
308        // Check: Is section list empty
309        if self.sections.is_empty() {
310            return Err(Error::NoSections);
311        }
312
313        // Create up a data...
314        let mut data: Vec<u8> = Vec::new();
315
316        // Then create up a header and push into data...
317        {
318            let header = Header {
319                min: self.min,
320                max: self.max,
321                entry: self.entry,
322                mode: self.mode,
323                author: str_to_array(self.author.as_str()),
324                name: str_to_array(self.name.as_str()),
325                sections: self.sections.len() as u16,
326                ..Default::default()
327            }
328            .to_array();
329            data.extend_from_slice(&header);
330        }
331
332        // And each section info...
333        let mut cnt = 0;
334        for section in &self.sections {
335            let mut secinfo = section.secinfo;
336
337            // Update base...
338            secinfo.base += (self.sections.len() * SECTION_SIZE + cnt) as u32;
339
340            // Push...
341            data.extend_from_slice(&secinfo.to_array());
342            cnt += section.data.len();
343        }
344
345        // And each section's data...
346        for section in &self.sections {
347            data.extend_from_slice(&section.data);
348        }
349
350        // Return
351        Ok(data)
352    }
353}
354
355/// Internal section form.
356#[derive(Debug, Clone, Copy)]
357struct InnerSections<'a> {
358    pub secinfo: Section,
359    pub data: &'a [u8],
360}
361
362/// The error type of parsing header.
363#[repr(C)]
364#[derive(Debug, Clone, Copy, PartialEq, Eq)]
365pub enum Error {
366    /// The executable is not valid
367    ///
368    /// Will appear if magic is not correct.
369    NotValidExecutable,
370
371    /// The section which is corrupted.
372    ///
373    /// Will appear if the buffer size is lower than specified
374    /// length.
375    ExecutableCorrupted,
376
377    /// An unknown character in UTF-8 was found in
378    /// parsing arrays
379    ///
380    /// May appear in converting slice to `&str`.
381    UnknownCharacter,
382
383    /// The argument which gives is too long.
384    ///
385    /// For example, if a field, which require at most 16 bytes, but you gave
386    /// 17 bytes, it will return this error.
387    ArgsTooLong,
388
389    /// No sections in the current executable.
390    NoSections,
391}