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
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, usize), // (offset, index)
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, 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 /// # Note
244 /// If the author that you provide is longer than 32,
245 /// it may truncated.
246 pub fn set_author(&mut self, author: &str) {
247 self.author = author.to_string();
248 }
249
250 /// Set up the program name.
251 ///
252 /// # Note
253 /// If the author that you provide is longer than 32,
254 /// it may truncated.
255 pub fn set_name(&mut self, name: &str) {
256 self.name = name.to_string();
257 }
258
259 /// Set the mode of this program.
260 pub fn set_mode(&mut self, mode: ExecMode) {
261 self.mode = mode;
262 }
263
264 /// Set the min version.
265 pub fn set_min(&mut self, min: [u16; 3]) {
266 self.min = min;
267 }
268
269 /// Set the max version.
270 pub fn set_max(&mut self, max: [u16; 3]) {
271 self.max = max;
272 }
273
274 /// Append a section and specify its name.
275 ///
276 /// # Arguments
277 /// - `data`: The data that you want to append;
278 /// - `name`: The section name;
279 /// - `is_loadable`: Assign is this loadable section or not;
280 /// - `is_execable`: Assign is this executable section or not;
281 /// - `entry`: The offset of the entry point, pass `None` if no entry point.
282 ///
283 /// # Errors
284 /// This will return error once these happened:
285 /// - Provide an entry address which is unloadable or unexecable;
286 ///
287 /// # Note
288 /// - If you try to provide a name which is over than 16 bytes, it may truncated;
289 /// - If you provide the entry offset for multiple times, once you invoke `build()`, it will
290 /// use that latest set one.
291 pub fn append(
292 &mut self,
293 data: &'a [u8],
294 name: &str,
295 is_loadable: bool,
296 is_execable: bool,
297 entry: Option<u32>,
298 ) -> Result<(), Error> {
299 // Check: Is entry is Some(...) within unloadable & unexecable
300 if entry.is_some() && !(is_execable && is_loadable) {
301 return Err(Error::ExecutableCorrupted);
302 }
303
304 let section = InnerSections {
305 secinfo: Section {
306 name: str_to_array(name),
307 is_loadable,
308 is_execable,
309 base: HEADER_SIZE as u32, // Will replace during building...
310 length: data.len() as u32,
311 _reserved: [0; 6],
312 },
313 data,
314 };
315 self.sections.push(section);
316
317 // Set entry if Some(...)...
318 if let Some(ent_offset) = entry {
319 let sec_index = self.sections.len() - 1;
320 self.entry = (ent_offset, sec_index);
321 }
322 Ok(())
323 }
324
325 /// Build the whole file to a valid exec format.
326 ///
327 /// Will return error if no section was appended.
328 pub fn build(self) -> Result<Vec<u8>, Error> {
329 // Check: Is section list empty
330 if self.sections.is_empty() {
331 return Err(Error::NoSections);
332 }
333
334 // Create up a data...
335 let mut data: Vec<u8> = Vec::new();
336
337 // Then create up a header and push into data...
338 {
339 // Calculate the absolute offset of entry...
340 let sections = self.sections.len();
341 let entry = {
342 let metalen = HEADER_SIZE + sections * SECTION_SIZE;
343 let datasum: usize = self.sections
344 .iter()
345 .map(|s| s.data.len())
346 .sum();
347 (metalen + datasum) as u32 + self.entry.0
348 };
349 let header = Header {
350 min: self.min,
351 max: self.max,
352 entry: entry,
353 mode: self.mode,
354 author: str_to_array(self.author.as_str()),
355 name: str_to_array(self.name.as_str()),
356 sections: sections as u16,
357 ..Default::default()
358 }
359 .to_array();
360 data.extend_from_slice(&header);
361 }
362
363 // And each section info...
364 let mut cnt = 0;
365 for section in &self.sections {
366 let mut secinfo = section.secinfo;
367
368 // Update base...
369 secinfo.base += (self.sections.len() * SECTION_SIZE + cnt) as u32;
370
371 // Push...
372 data.extend_from_slice(&secinfo.to_array());
373 cnt += section.data.len();
374 }
375
376 // And each section's data...
377 for section in &self.sections {
378 data.extend_from_slice(§ion.data);
379 }
380
381 // Return
382 Ok(data)
383 }
384}
385
386/// Internal section form.
387#[derive(Debug, Clone, Copy)]
388struct InnerSections<'a> {
389 pub secinfo: Section,
390 pub data: &'a [u8],
391}
392
393/// The error type of parsing header.
394#[repr(C)]
395#[derive(Debug, Clone, Copy, PartialEq, Eq)]
396pub enum Error {
397 /// The executable is not valid
398 ///
399 /// Will appear if magic is not correct.
400 NotValidExecutable,
401
402 /// The section which is corrupted.
403 ///
404 /// Will appear if:
405 /// - The buffer size is lower than specified length;
406 /// - Append an unexecable and unloadable section within an entry address (`Builder` only).
407 ExecutableCorrupted,
408
409 /// An unknown character in UTF-8 was found in
410 /// parsing arrays
411 ///
412 /// May appear in converting slice to `&str`.
413 UnknownCharacter,
414
415 /// The argument which gives is too long.
416 ///
417 /// For example, if a field, which require at most 16 bytes, but you gave
418 /// 17 bytes, it will return this error.
419 ArgsTooLong,
420
421 /// No sections in the current executable.
422 ///
423 /// Will appear if you try to build without any appending.
424 NoSections,
425}