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