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