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 index, section metadata,
16//! and some utils to help you parse the executable easily.
17//! 
18//! ## Structures of this executable
19//! This executable is structured as follows:
20//! - PKE headers - Records the basic information of this executable;
21//! - Section index - Records the section header's offset and section name's length;
22//! - Section metadata- Records the section flags, data offset and its length;
23//! - Data - The binary content.
24//! 
25//! We can use this picture to explain their segmented structure:
26//! 
27//! `[Headers] [Section Index] [Section Metadata] [Data]`
28//! 
29//! Simultaneously, the `[Section Metadata]` can be separated as follows:
30//! 
31//! `[Section Headers] [Section Name]`
32//! 
33//! In the picture above, the `[Section Headers]`'s length is fixed, which is recorded in [`SECTION_HDR_SIZE`]; 
34//! The section name is different - It's dynamic, so you can store almost infinite words in it!
35//!
36//! ## Steps to use this crate
37//! Before you parse it, you should do these steps:
38//!
39//! - Read the executable file content;
40//! - Make this file's content to a slice (`&[u8]`)
41//! - Use [`Parser`] to parse the executable.
42//!
43//! After this, you can do further operations through this parser by
44//! calling its functions.
45//!
46//! ## Example Usage
47//! ```rust, ignore
48//! use proka_exec::Parser;
49//! use std::path::PathBuf;
50//!
51//! let file = PathBuf::from("example.pke");
52//! let content = std::fs::read(file).expect("Failed to read file");
53//! let parser = Parser::init(&content).expect("Failed to parse parser");
54//! 
55//! // More API see below
56//! ```
57//!
58//! # LICENSE
59//! This crate is under license [GPL-v3](https://github.com/RainSTR-Studio/proka-exec/blob/main/LICENSE),
60//! and you must follow its rules.
61//!
62//! See [LICENSE](https://github.com/RainSTR-Studio/proka-exec/blob/main/LICENSE) file for more details.
63//!
64//! ## MSRV
65//! This crate's MSRV is `1.85.0` stable.
66#![no_std]
67
68// Alloc features...
69#[cfg(feature = "alloc")]
70extern crate alloc;
71
72pub mod header;
73pub mod sections;
74pub mod utils;
75
76#[cfg(feature = "alloc")]
77use alloc::{
78    string::{String, ToString},
79    vec::Vec,
80};
81use header::HeaderError;
82use header::{ExecMode, Header};
83use sections::{SectionError, SectionHdr, SectionIndex, SectionTable};
84pub use utils::*;
85
86#[cfg(feature = "alloc")]
87use crate::sections::SectionFlag;
88
89/// Generic result type in this crate
90pub type Result<T> = core::result::Result<T, Error>;
91
92/// The header size.
93pub const HEADER_SIZE: usize = core::mem::size_of::<Header>();
94
95/// The section header size.
96pub const SECTION_HDR_SIZE: usize = core::mem::size_of::<SectionHdr>();
97
98/// The section entry size
99pub const SECTION_INDEX_SIZE: usize = core::mem::size_of::<SectionIndex>();
100
101/// The error type of parsing header.
102#[repr(C)]
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104pub enum Error {
105    /// Section inner error.
106    ///
107    /// See [`SectionError`] for more details.
108    SectionError(SectionError),
109
110    /// Header inner error.
111    ///
112    /// See [`HeaderError`] for more details.
113    HeaderError(HeaderError),
114
115    /// The executable is not valid
116    ///
117    /// Will appear if magic is not correct.
118    NotValidExecutable,
119
120    /// The section which is corrupted.
121    ///
122    /// Will appear if:  
123    ///  - The buffer size is lower than specified length;
124    ///  - Append an unexecable and unloadable section within an entry address (`Builder` only).
125    ExecutableCorrupted,
126
127    /// The version that was written in file is incorrect.
128    ///
129    /// Will appear if:
130    ///  - The max version is lower than the min version;
131    ///  - Passing a max version which is lower than min version (`Builder` only).
132    ///
133    /// # Arguments
134    ///  - 0: The min version;
135    ///  - 1: The max version.
136    VersionIncorrect([u16; 3], [u16; 3]),
137
138    /// An unknown character in UTF-8 was found in
139    /// parsing arrays
140    ///
141    /// May appear in converting slice to `&str`.
142    UnknownCharacter,
143
144    /// The
145
146    /// No sections in the current executable.
147    ///
148    /// Will appear if you try to build without any appending.
149    NoSections,
150}
151
152/// The parser of the proka executable.
153///
154/// # Usage
155/// To use this parser, you must put an slice into the initializations.
156///
157/// If the content of the proka executable is in memory, the best way
158/// is to use `core::slice::from_raw_parts`.
159#[derive(Debug, Clone, Copy)]
160pub struct Parser<'a> {
161    buf: &'a [u8],
162    header: Header,
163    total_sections: u16,
164}
165
166impl<'a> Parser<'a> {
167    /// Initialize the parser by passing a slice.
168    ///
169    /// This is the recommended way to initialize this parser, because it will
170    /// help you do all checks and return error if something wrong, so you can
171    /// leave everything about parsing to us :)
172    ///
173    /// # Note
174    /// If this crate is used on the kernel-side, you must first map the memory
175    /// that the slice points to before invoking this function.
176    pub fn init(buf: &'a [u8]) -> Result<Self> {
177        let header_raw = &buf[0..HEADER_SIZE]; // Header length
178        let header = unsafe { *(header_raw.as_ptr() as *const Header) };
179
180        // Check: Validate is this correct executable
181        if header.validate().is_err() {
182            return Err(Error::NotValidExecutable);
183        }
184
185        // Check: Is section count = 0?
186        if header.sections == 0 {
187            return Err(Error::NoSections);
188        }
189
190        // Check: Is the buffer contains all sections
191        let offset = HEADER_SIZE + (header.sections as usize - 1) * SECTION_INDEX_SIZE;
192        let index_content = &buf[offset..offset + SECTION_INDEX_SIZE];
193        let index = unsafe { *(index_content.as_ptr() as *const SectionIndex) };
194        let len = (index.base + index.name_len) as usize + SECTION_HDR_SIZE;
195        if buf.len() < len {
196            return Err(Error::ExecutableCorrupted);
197        }
198
199        // SAFETY: Already check all staff and able to do initialization
200        unsafe { Ok(Self::init_unchecked(buf)) }
201    }
202
203    /// Initialize the parser by passing a slice without checking.
204    ///
205    /// # Safety
206    /// You must ensure these if you invoke this function:
207    ///
208    ///  - The slice's content is a valid proka executable (match the magic);
209    ///  - The slice must contain the header and all section tables.
210    ///
211    /// # Note
212    /// Use this function to initialize is **NOT** recommended, because it might
213    /// cause some problems while parsing this header.
214    pub unsafe fn init_unchecked(buf: &'a [u8]) -> Self {
215        let header_raw = &buf[0..HEADER_SIZE];
216        let header = unsafe { *(header_raw.as_ptr() as *const Header) };
217
218        Self {
219            buf,
220            header,
221            total_sections: header.sections,
222        }
223    }
224
225    /// Do more validation after initialization.
226    ///
227    /// # Content
228    /// This will validates:
229    ///
230    ///  - Is the header min >= max;
231    ///  - Is each section's base correct;
232    ///  - Is the section's length not zeroed;
233    ///  - Is section base out of length;
234    ///  - Is entry_off is over than section length.
235    pub fn validate(&self) -> Result<()> {
236        // Check: Is header's min > max
237        let minimal = self.header.min;
238        let maximum = self.header.max;
239        for (&min, &max) in minimal.iter().zip(maximum.iter()) {
240            if min > max {
241                return Err(Error::VersionIncorrect(minimal, maximum));
242            }
243        }
244
245        // Check: Is each section's base and length correct (section check)
246        let min_base = HEADER_SIZE + self.header.sections as usize * SECTION_HDR_SIZE;
247        for (index, section_index) in self.sections().enumerate() {
248            let section = self.sections().get_hdr_secindex(section_index);
249            let base_off = section.base as usize;
250            let len = section.size as usize;
251            let entry_sec = self.header.entry_sec as usize;
252
253            // Check: Is section base in metadata range
254            if base_off < min_base {
255                return Err(Error::SectionError(SectionError::BaseError(
256                    base_off as u32,
257                )));
258            }
259
260            // Check: Is section length not zeroed
261            if len == 0 {
262                return Err(Error::SectionError(SectionError::LengthError));
263            }
264
265            // Check: Is section entry_off out of range
266            if index == entry_sec {
267                let entry_off = self.header.entry_off as usize;
268                if entry_off > len {
269                    return Err(Error::SectionError(SectionError::EntryOffsetOutOfRange(
270                        entry_off as u32,
271                        len as u32,
272                    )));
273                }
274            }
275        }
276
277        // All's fine :)
278        Ok(())
279    }
280
281    /// Get the content from specified sections.
282    ///
283    /// # Arguments
284    ///  - `secname`: The name of the section
285    ///
286    /// # Returns
287    /// `Option<&'static [u8]>`: The content of this section, return `None` if this section not exist.
288    pub fn get_section_content(&self, secname: &str) -> Option<&'a [u8]> {
289        // Iterate all sections...
290        for section_index in self.sections() {
291            let table = self.sections();
292            let name = table.get_name_secindex(section_index);
293            let section = table.get_hdr_secindex(section_index);
294            if secname == name {
295                // Get its base and length
296                let base = section.base as usize;
297                let length = section.size as usize;
298                let content = &self.buf[base..base + length];
299                return Some(content);
300            }
301        }
302
303        None
304    }
305
306    /// Get the header in this buffer.
307    #[inline]
308    pub fn header(&self) -> Header {
309        self.header
310    }
311
312    /// Get each section table.
313    pub fn sections(&self) -> SectionTable<'_> {
314        SectionTable::new(self.buf, self.total_sections)
315    }
316}
317
318/// The builder of the proka executable.
319#[derive(Debug, Clone)]
320#[cfg(feature = "alloc")]
321pub struct Builder<'a> {
322    min: [u16; 3],
323    max: [u16; 3],
324    entry: (u32, usize), // (offset, index)
325    author: String,
326    name: String,
327    mode: ExecMode,
328    sections: Vec<InnerSections<'a>>,
329}
330
331#[cfg(feature = "alloc")]
332impl Default for Builder<'_> {
333    fn default() -> Self {
334        Self::new()
335    }
336}
337
338#[cfg(feature = "alloc")]
339impl<'a> Builder<'a> {
340    /// Create up a empty builder.
341    pub fn new() -> Self {
342        Self {
343            min: [0; 3],
344            max: [0; 3],
345            entry: (0, 0),
346            author: String::new(),
347            name: String::new(),
348            mode: ExecMode::UserApp,
349            sections: Vec::new(),
350        }
351    }
352
353    /// Set up the author.
354    ///
355    /// # Note
356    /// If the author that you provide is longer than 32,
357    /// it may truncated.
358    pub fn set_author(&mut self, author: &str) {
359        self.author = author.to_string();
360    }
361
362    /// Set up the program name.
363    ///
364    /// # Note
365    /// If the author that you provide is longer than 32,
366    /// it may truncated.
367    pub fn set_name(&mut self, name: &str) {
368        self.name = name.to_string();
369    }
370
371    /// Set the mode of this program.
372    pub fn set_mode(&mut self, mode: ExecMode) {
373        self.mode = mode;
374    }
375
376    /// Set the min version.
377    pub fn set_min(&mut self, min: [u16; 3]) {
378        self.min = min;
379    }
380
381    /// Set the max version.
382    pub fn set_max(&mut self, max: [u16; 3]) {
383        self.max = max;
384    }
385
386    /// Append a section and specify its name.
387    ///
388    /// # Arguments
389    ///  - `data`: The data that you want to append;
390    ///  - `name`: The section name;
391    ///  - `is_loadable`: Assign is this loadable section or not;
392    ///  - `is_execable`: Assign is this executable section or not;
393    ///  - `entry`: The offset of the entry point, pass `None` if no entry point.
394    ///
395    /// # Errors
396    /// This will return error once these happened:
397    ///  - Provide an entry address which is unloadable or unexecable;
398    ///
399    /// # Note
400    ///  - If you try to provide a name which is over than 16 bytes, it may truncated;
401    ///  - If you provide the entry offset for multiple times, once you invoke `build()`, it will
402    ///    use that latest set one.
403    pub fn append(
404        &mut self,
405        data: &'a [u8],
406        name: &'a str,
407        is_loadable: bool,
408        is_execable: bool,
409        entry: Option<u32>,
410    ) -> Result<()> {
411        // Check: Is entry is Some(...) within unloadable & unexecable
412        if entry.is_some() && !(is_execable && is_loadable) {
413            return Err(Error::ExecutableCorrupted);
414        }
415
416        let flag = match (is_loadable, is_execable) {
417            (true, true) => SectionFlag::LOADABLE | SectionFlag::EXECABLE,
418            (true, false) => SectionFlag::LOADABLE,
419            (false, true) => SectionFlag::EXECABLE,
420            (false, false) => SectionFlag::empty(),
421        };
422
423        let section = InnerSections {
424            secinfo: SectionHdr {
425                flag,
426                _pad1: [0; 3],
427                base: 0, // Will replace during building...
428                size: data.len() as u32,
429                _pad2: [0; 4],
430            },
431            secindex: SectionIndex {
432                base: 0, // Will replace during building...
433                name_len: name.len() as u32,
434            },
435            name,
436            data,
437        };
438        self.sections.push(section);
439
440        // Set entry if Some(...)...
441        if let Some(ent_offset) = entry {
442            let sec_index = self.sections.len() - 1;
443            self.entry = (ent_offset, sec_index);
444        }
445        Ok(())
446    }
447
448    /// Build the whole file to a valid exec format.
449    ///
450    /// Will return error if no section was appended.
451    pub fn build(self) -> Result<Vec<u8>> {
452        // Check: Is section list empty
453        if self.sections.is_empty() {
454            return Err(Error::NoSections);
455        }
456
457        // Check: Is min version lower than max version
458        for (&min, &max) in self.min.iter().zip(self.max.iter()) {
459            if min > max {
460                return Err(Error::VersionIncorrect(self.min, self.max));
461            }
462        }
463
464        // Create up a data...
465        let mut data: Vec<u8> = Vec::new();
466
467        // Then create up a header and push into data...
468        {
469            let header = Header {
470                min: self.min,
471                max: self.max,
472                entry_off: self.entry.0,
473                entry_sec: self.entry.1 as u16,
474                mode: self.mode,
475                author: str_to_array(self.author.as_str()),
476                name: str_to_array(self.name.as_str()),
477                sections: self.sections.len() as u16,
478                ..Default::default()
479            }
480            .to_array();
481            data.extend_from_slice(&header);
482        }
483
484        // And section index...
485        let mut cnt = 0;
486        for section in &self.sections {
487            let mut secindex = section.secindex;
488            secindex.base = (HEADER_SIZE + self.sections.len() * SECTION_INDEX_SIZE + cnt) as u32;
489            data.extend_from_slice(&secindex.to_array());
490            cnt += SECTION_HDR_SIZE + secindex.name_len as usize;
491        }
492
493        // And each section info...
494        for section in &self.sections {
495            let mut secinfo = section.secinfo;
496
497            // Update base...
498            // Note: The `cnt` does not empty, which means that is already store the whole section index.
499            secinfo.base = (HEADER_SIZE + self.sections.len() * SECTION_INDEX_SIZE + cnt) as u32;
500
501            // Push...
502            data.extend_from_slice(&secinfo.to_array());
503            data.extend_from_slice(section.name.as_bytes());
504            cnt += section.data.len();
505        }
506
507        // And each section's data...
508        for section in &self.sections {
509            data.extend_from_slice(section.data);
510        }
511
512        // Return
513        Ok(data)
514    }
515}
516
517/// Internal section form.
518#[derive(Debug, Clone, Copy)]
519struct InnerSections<'a> {
520    pub secinfo: SectionHdr,
521    pub secindex: SectionIndex,
522    pub name: &'a str,
523    pub data: &'a [u8],
524}