#![allow(unsafe_code)]
use crate::error::Result;
use crate::{SplitMix64, adler32, crc32, fnv1a64, sha256};
fn borrow<'a>(data: *const u8, len: usize) -> &'a [u8] {
if data.is_null() {
&[]
} else {
unsafe { core::slice::from_raw_parts(data, len) }
}
}
fn borrow_mut<'a>(out: *mut u64, count: usize) -> &'a mut [u64] {
if out.is_null() {
&mut []
} else {
unsafe { core::slice::from_raw_parts_mut(out, count) }
}
}
pub const PITH_OK: i32 = 0;
pub const PITH_E_INVALID: i32 = -1;
pub const PITH_E_REJECTED: i32 = -2;
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pith_digest_sha256(data: *const u8, len: usize, out: *mut u8) -> i32 {
if out.is_null() || (data.is_null() && len > 0) {
return PITH_E_INVALID;
}
let bytes = borrow(data, len);
match sha256(bytes) {
Ok(digest) => {
unsafe { core::ptr::copy_nonoverlapping(digest.as_bytes().as_ptr(), out, 32) };
PITH_OK
}
Err(_) => PITH_E_REJECTED,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pith_digest_crc32(data: *const u8, len: usize, out: *mut u32) -> i32 {
if out.is_null() || (data.is_null() && len > 0) {
return PITH_E_INVALID;
}
let bytes = borrow(data, len);
unsafe { *out = crc32(bytes) };
PITH_OK
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pith_digest_adler32(data: *const u8, len: usize, out: *mut u32) -> i32 {
if out.is_null() || (data.is_null() && len > 0) {
return PITH_E_INVALID;
}
let bytes = borrow(data, len);
unsafe { *out = adler32(bytes) };
PITH_OK
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pith_digest_fnv1a64(data: *const u8, len: usize, out: *mut u64) -> i32 {
if out.is_null() || (data.is_null() && len > 0) {
return PITH_E_INVALID;
}
let bytes = borrow(data, len);
unsafe { *out = fnv1a64(bytes) };
PITH_OK
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pith_digest_splitmix64_fill(
seed: u64,
out: *mut u64,
count: usize,
) -> i32 {
if out.is_null() && count > 0 {
return PITH_E_INVALID;
}
let slots = borrow_mut(out, count);
let mut rng = SplitMix64::new(seed);
for slot in slots {
*slot = rng.next_u64();
}
PITH_OK
}
fn borrow_mut_bytes<'a>(out: *mut u8, cap: usize) -> &'a mut [u8] {
if out.is_null() {
&mut []
} else {
unsafe { core::slice::from_raw_parts_mut(out, cap) }
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pith_digest_sha1(data: *const u8, len: usize, out: *mut u8) -> i32 {
if out.is_null() || (data.is_null() && len > 0) {
return PITH_E_INVALID;
}
let bytes = borrow(data, len);
match crate::sha1(bytes) {
Ok(digest) => {
unsafe { core::ptr::copy_nonoverlapping(digest.as_bytes().as_ptr(), out, 20) };
PITH_OK
}
Err(_) => PITH_E_REJECTED,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pith_digest_sha512(data: *const u8, len: usize, out: *mut u8) -> i32 {
if out.is_null() || (data.is_null() && len > 0) {
return PITH_E_INVALID;
}
let bytes = borrow(data, len);
let digest = crate::sha512(bytes);
unsafe { core::ptr::copy_nonoverlapping(digest.as_bytes().as_ptr(), out, 64) };
PITH_OK
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pith_digest_hmac_sha1(
key: *const u8,
key_len: usize,
data: *const u8,
len: usize,
out: *mut u8,
) -> i32 {
unsafe {
hmac_export(key, key_len, data, len, out, |k, d| {
crate::hmac_sha1(k, d).map(|digest| *digest.as_bytes())
})
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pith_digest_hmac_sha256(
key: *const u8,
key_len: usize,
data: *const u8,
len: usize,
out: *mut u8,
) -> i32 {
unsafe {
hmac_export(key, key_len, data, len, out, |k, d| {
crate::hmac_sha256(k, d).map(|digest| *digest.as_bytes())
})
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pith_digest_hmac_sha512(
key: *const u8,
key_len: usize,
data: *const u8,
len: usize,
out: *mut u8,
) -> i32 {
unsafe {
hmac_export(key, key_len, data, len, out, |k, d| {
Ok(*crate::hmac_sha512(k, d).as_bytes())
})
}
}
unsafe fn hmac_export<const N: usize>(
key: *const u8,
key_len: usize,
data: *const u8,
len: usize,
out: *mut u8,
hmac: impl FnOnce(&[u8], &[u8]) -> Result<[u8; N]>,
) -> i32 {
if out.is_null() || (key.is_null() && key_len > 0) || (data.is_null() && len > 0) {
return PITH_E_INVALID;
}
let key_bytes = borrow(key, key_len);
let data_bytes = borrow(data, len);
match hmac(key_bytes, data_bytes) {
Ok(digest) => {
unsafe { core::ptr::copy_nonoverlapping(digest.as_ptr(), out, N) };
PITH_OK
}
Err(_) => PITH_E_REJECTED,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pith_digest_xxh64(
data: *const u8,
len: usize,
seed: u64,
out: *mut u64,
) -> i32 {
if out.is_null() || (data.is_null() && len > 0) {
return PITH_E_INVALID;
}
let bytes = borrow(data, len);
unsafe { *out = crate::xxh64(bytes, seed) };
PITH_OK
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pith_digest_murmur3_x64_128(
data: *const u8,
len: usize,
seed: u32,
out: *mut u8,
) -> i32 {
if out.is_null() || (data.is_null() && len > 0) {
return PITH_E_INVALID;
}
let bytes = borrow(data, len);
let digest = crate::murmur3_x64_128(bytes, seed);
unsafe { core::ptr::copy_nonoverlapping(digest.as_bytes().as_ptr(), out, 16) };
PITH_OK
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pith_digest_crc32c(data: *const u8, len: usize, out: *mut u32) -> i32 {
if out.is_null() || (data.is_null() && len > 0) {
return PITH_E_INVALID;
}
let bytes = borrow(data, len);
unsafe { *out = crate::crc32c(bytes) };
PITH_OK
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pith_digest_base64_encode(
data: *const u8,
len: usize,
out: *mut u8,
cap: usize,
out_len: *mut usize,
) -> i32 {
if out_len.is_null() || out.is_null() || (data.is_null() && len > 0) {
return PITH_E_INVALID;
}
let bytes = borrow(data, len);
let buffer = borrow_mut_bytes(out, cap);
match crate::base64::base64_encode(bytes, buffer) {
Ok(()) => {
unsafe { *out_len = crate::base64::encoded_len(len) };
PITH_OK
}
Err(_) => PITH_E_REJECTED,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pith_digest_base64_decode(
data: *const u8,
len: usize,
out: *mut u8,
cap: usize,
out_len: *mut usize,
) -> i32 {
if out_len.is_null() || out.is_null() || (data.is_null() && len > 0) {
return PITH_E_INVALID;
}
let bytes = borrow(data, len);
let buffer = borrow_mut_bytes(out, cap);
match crate::base64::base64_decode(bytes, buffer) {
Ok(written) => {
unsafe { *out_len = written };
PITH_OK
}
Err(_) => PITH_E_REJECTED,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pith_digest_xoshiro256_fill(
seed: u64,
out: *mut u64,
count: usize,
) -> i32 {
if out.is_null() && count > 0 {
return PITH_E_INVALID;
}
let slots = borrow_mut(out, count);
let mut rng = crate::Xoshiro256StarStar::from_seed(seed);
for slot in slots {
*slot = rng.next_u64();
}
PITH_OK
}
#[cfg(test)]
mod tests {
use super::{
PITH_E_INVALID, PITH_OK, pith_digest_adler32, pith_digest_crc32, pith_digest_fnv1a64,
pith_digest_sha256, pith_digest_splitmix64_fill,
};
use crate::sha256;
#[test]
fn ffi_sha256_reproduces_the_empty_digest() {
let mut out = [0u8; 32];
let status = unsafe { pith_digest_sha256(b"abc".as_ptr(), 3, out.as_mut_ptr()) };
assert_eq!(status, PITH_OK);
let expect = sha256(b"abc").expect("within size limit");
assert_eq!(out, *expect.as_bytes());
}
#[test]
fn ffi_checksums_reproduce_published_vectors() {
let mut c = 0u32;
assert_eq!(
unsafe { pith_digest_crc32(b"123456789".as_ptr(), 9, &mut c) },
PITH_OK
);
assert_eq!(c, 0xcbf4_3926);
let mut a = 0u32;
assert_eq!(
unsafe { pith_digest_adler32(b"Wikipedia".as_ptr(), 9, &mut a) },
PITH_OK
);
assert_eq!(a, 0x11e6_0398);
let mut f = 0u64;
assert_eq!(
unsafe { pith_digest_fnv1a64(b"foobar".as_ptr(), 6, &mut f) },
PITH_OK
);
assert_eq!(f, 0x8594_4171_f739_67e8);
}
#[test]
fn ffi_splitmix64_fill_reproduces_the_paper_sequence() {
let mut out = [0u64; 2];
assert_eq!(
unsafe { pith_digest_splitmix64_fill(0, out.as_mut_ptr(), 2) },
PITH_OK
);
assert_eq!(out, [0xe220_a839_7b1d_cdaf, 0x6e78_9e6a_a1b9_65f4]);
let mut zero: [u64; 0] = [];
assert_eq!(
unsafe { pith_digest_splitmix64_fill(0, zero.as_mut_ptr(), 0) },
PITH_OK
);
assert_eq!(
unsafe { pith_digest_splitmix64_fill(0, core::ptr::null_mut(), 1) },
PITH_E_INVALID
);
}
#[test]
fn ffi_null_data_with_zero_len_is_the_empty_preimage() {
let mut out = [0u8; 32];
let status = unsafe { pith_digest_sha256(core::ptr::null(), 0, out.as_mut_ptr()) };
assert_eq!(status, PITH_OK);
let expect = sha256(b"").expect("within size limit");
assert_eq!(out, *expect.as_bytes());
let mut c = 0u32;
assert_eq!(
unsafe { pith_digest_crc32(core::ptr::null(), 0, &mut c) },
PITH_OK
);
assert_eq!(c, 0);
}
#[test]
fn ffi_null_data_with_length_is_refused() {
let mut out = [0u8; 32];
let status = unsafe { pith_digest_sha256(core::ptr::null(), 1, out.as_mut_ptr()) };
assert_eq!(status, PITH_E_INVALID);
}
#[test]
fn ffi_null_out_slots_are_refused() {
let data = b"x";
unsafe {
assert_eq!(
pith_digest_sha256(data.as_ptr(), 1, core::ptr::null_mut()),
PITH_E_INVALID
);
assert_eq!(
pith_digest_crc32(data.as_ptr(), 1, core::ptr::null_mut()),
PITH_E_INVALID
);
assert_eq!(
pith_digest_adler32(data.as_ptr(), 1, core::ptr::null_mut()),
PITH_E_INVALID
);
assert_eq!(
pith_digest_fnv1a64(data.as_ptr(), 1, core::ptr::null_mut()),
PITH_E_INVALID
);
}
}
}