proka_exec/lib.rs
1//! # `proka-exec`
2//!
3//! [](https://www.rust-lang.org/)
4//! [](https://opensource.org/license/gpl-3.0)
5//! [](https://github.com/RainSTR-Studio/proka-exec/stargazers)
6//! [](https://github.com/RainSTR-Studio/proka-exec/issues)
7//! [](https://github.com/RainSTR-Studio/proka-exec/pulls)
8//! [](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;
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 header in this buffer.
163 #[inline]
164 pub fn header(&self) -> Header {
165 self.header
166 }
167
168 /// Get each section table.
169 #[allow(private_interfaces)]
170 pub fn sections(&self) -> SectionIter {
171 SectionIter::new(self.buf, self.total_sections, 0)
172 }
173}
174
175/// The error type of parsing header.
176#[repr(C)]
177#[derive(Debug, Clone, Copy, PartialEq, Eq)]
178pub enum Error {
179 /// The executable is not valid
180 ///
181 /// Will appear if magic is not correct.
182 NotValidExecutable,
183
184 /// The executable is corrupted.
185 ///
186 /// Will appear if the buffer size is lower than specified
187 /// length.
188 ExecutableCorrupted,
189
190 /// An unknown character in UTF-8 was found in
191 /// parsing arrays
192 ///
193 /// May appear in converting slice to `&str`.
194 UnknownCharacter,
195}