#![no_std]
#[doc(hidden)]
pub const BUFFER_SIZE: usize = match option_env!("VER_STUB_BUFFER_SIZE") {
Some(s) => match u16::from_str_radix(s, 10) {
Ok(n) => n as usize,
Err(_) => panic!("VER_STUB_BUFFER_SIZE must be a valid u16 integer (0-65535)"),
},
None => 512,
};
#[doc(hidden)]
pub const fn header_size(num_members: usize) -> usize {
1 + num_members * 2
}
const _: () = assert!(
header_size(Member::COUNT) <= 32,
"header_size(Member::COUNT) exceeds 32, these asserts must be updated"
);
const _: () = assert!(
BUFFER_SIZE > 32,
"VER_STUB_BUFFER_SIZE must be greater than 32"
);
#[cfg(target_os = "macos")]
pub const SECTION_NAME: &str = "__TEXT,ver_stub";
#[cfg(not(target_os = "macos"))]
pub const SECTION_NAME: &str = "ver_stub";
#[cfg_attr(target_os = "macos", unsafe(link_section = "__TEXT,ver_stub"))]
#[cfg_attr(not(target_os = "macos"), unsafe(link_section = "ver_stub"))]
#[used]
static BUFFER: [u8; BUFFER_SIZE] = [0u8; BUFFER_SIZE];
#[doc(hidden)]
#[repr(u16)]
#[derive(Clone, Copy)]
pub enum Member {
GitSha = 0,
GitDescribe = 1,
GitBranch = 2,
GitCommitTimestamp = 3,
GitCommitDate = 4,
GitCommitMsg = 5,
BuildTimestamp = 6,
BuildDate = 7,
Custom = 8,
}
impl Member {
#[doc(hidden)]
pub const COUNT: usize = 9;
#[doc(hidden)]
pub fn get_from_buffer<'a>(&self, buffer: &'a [u8; BUFFER_SIZE]) -> Option<&'a str> {
let idx = *self as usize;
Self::get_idx_from_buffer(idx, buffer)
}
#[doc(hidden)]
pub fn get_idx_from_buffer(idx: usize, buffer: &[u8; BUFFER_SIZE]) -> Option<&str> {
let actual_num_members = Self::read_buffer_byte(buffer, 0) as usize;
if actual_num_members == 0 {
return None;
}
if idx >= actual_num_members {
return None;
}
let actual_header_size = header_size(actual_num_members);
let end_offset_pos = 1 + idx * 2;
let end = actual_header_size + Self::read_buffer_u16(buffer, end_offset_pos) as usize;
let start = if idx == 0 {
actual_header_size
} else {
let prev_end_pos = 1 + (idx - 1) * 2;
actual_header_size + Self::read_buffer_u16(buffer, prev_end_pos) as usize
};
if start == end {
return None;
}
if end < start {
panic!(
"ver-stub: invalid range for {:?}: start={}, end={}",
idx, start, end
);
}
if end > BUFFER_SIZE {
panic!(
"ver-stub: end offset {} exceeds buffer size {} for {:?}",
end, BUFFER_SIZE, idx
);
}
let bytes = core::hint::black_box(&buffer[start..end]);
match core::str::from_utf8(bytes) {
Ok(s) => Some(s),
Err(e) => panic!("ver-stub: invalid UTF-8 for {:?}: {:?}", idx, e),
}
}
fn read_buffer_u16(buffer: &[u8; BUFFER_SIZE], offset: usize) -> u16 {
let lo = Self::read_buffer_byte(buffer, offset) as u16;
let hi = Self::read_buffer_byte(buffer, offset + 1) as u16;
lo | (hi << 8)
}
#[inline(never)]
fn read_buffer_byte(buffer: &[u8; BUFFER_SIZE], offset: usize) -> u8 {
assert!(
offset < BUFFER_SIZE,
"ver-stub: invalid section data, {offset} >= {BUFFER_SIZE} is out of bounds"
);
unsafe { core::ptr::read_volatile(buffer.as_ptr().add(offset)) }
}
}
pub fn git_sha() -> Option<&'static str> {
Member::GitSha.get_from_buffer(&BUFFER)
}
pub fn git_describe() -> Option<&'static str> {
Member::GitDescribe.get_from_buffer(&BUFFER)
}
pub fn git_branch() -> Option<&'static str> {
Member::GitBranch.get_from_buffer(&BUFFER)
}
pub fn git_commit_timestamp() -> Option<&'static str> {
Member::GitCommitTimestamp.get_from_buffer(&BUFFER)
}
pub fn git_commit_date() -> Option<&'static str> {
Member::GitCommitDate.get_from_buffer(&BUFFER)
}
pub fn git_commit_msg() -> Option<&'static str> {
Member::GitCommitMsg.get_from_buffer(&BUFFER)
}
pub fn build_timestamp() -> Option<&'static str> {
Member::BuildTimestamp.get_from_buffer(&BUFFER)
}
pub fn build_date() -> Option<&'static str> {
Member::BuildDate.get_from_buffer(&BUFFER)
}
pub fn custom() -> Option<&'static str> {
Member::Custom.get_from_buffer(&BUFFER)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_zeroes() {
let buffer = [0u8; BUFFER_SIZE];
for idx in 0..Member::COUNT {
assert!(Member::get_idx_from_buffer(idx, &buffer).is_none());
}
}
#[test]
#[should_panic = "exceeds buffer size"]
fn test_ones() {
let buffer = [255u8; BUFFER_SIZE];
Member::get_idx_from_buffer(0, &buffer);
}
#[test]
fn test_one_element() {
let mut buffer = [0u8; BUFFER_SIZE];
buffer[0..7].copy_from_slice(&[1u8, 4u8, 0u8, b'a', b's', b'd', b'f']);
assert_eq!(Member::GitSha.get_from_buffer(&buffer).unwrap(), "asdf");
for idx in 1..Member::COUNT {
assert!(Member::get_idx_from_buffer(idx, &buffer).is_none());
}
buffer[0..11].copy_from_slice(&[3u8, 4u8, 0u8, 4u8, 0u8, 4u8, 0u8, b'a', b's', b'd', b'f']);
assert_eq!(Member::GitSha.get_from_buffer(&buffer).unwrap(), "asdf");
for idx in 1..Member::COUNT {
assert!(Member::get_idx_from_buffer(idx, &buffer).is_none());
}
}
#[test]
#[should_panic = "invalid range"]
fn test_invalid_range() {
let mut buffer = [0u8; BUFFER_SIZE];
buffer[0..9].copy_from_slice(&[2u8, 4u8, 0u8, 0u8, 0u8, b'a', b's', b'd', b'f']);
Member::GitDescribe.get_from_buffer(&buffer);
}
#[test]
fn test_two_elements() {
let mut buffer = [0u8; BUFFER_SIZE];
buffer[0..17].copy_from_slice(&[
3u8, 4u8, 0u8, 4u8, 0u8, 10u8, 0u8, b'a', b's', b'd', b'f', b'm', b'a', b's', b't',
b'e', b'r',
]);
assert_eq!(Member::GitSha.get_from_buffer(&buffer).unwrap(), "asdf");
assert!(Member::GitDescribe.get_from_buffer(&buffer).is_none());
assert_eq!(
Member::GitBranch.get_from_buffer(&buffer).unwrap(),
"master"
);
for idx in 3..Member::COUNT {
assert!(Member::get_idx_from_buffer(idx, &buffer).is_none());
}
buffer[3] = 5u8;
assert_eq!(Member::GitSha.get_from_buffer(&buffer).unwrap(), "asdf");
assert_eq!(Member::GitDescribe.get_from_buffer(&buffer).unwrap(), "m");
assert_eq!(Member::GitBranch.get_from_buffer(&buffer).unwrap(), "aster");
for idx in 3..Member::COUNT {
assert!(Member::get_idx_from_buffer(idx, &buffer).is_none());
}
}
#[test]
#[should_panic = "exceeds buffer size"]
fn test_127s() {
let buffer = [127u8; BUFFER_SIZE];
Member::GitSha.get_from_buffer(&buffer);
}
#[test]
#[should_panic = "invalid UTF-8"]
fn test_invalid_utf8() {
let mut buffer = [0u8; BUFFER_SIZE];
buffer[0..5].copy_from_slice(&[1u8, 2u8, 0u8, 255u8, 255u8]);
Member::GitSha.get_from_buffer(&buffer);
}
}