crc_fast/
ffi.rs

1// Copyright 2025 Don MacAskill. Licensed under MIT or Apache-2.0.
2
3//! FFI bindings for the Rust library
4//!
5//! This module provides a C-compatible interface for the Rust library, allowing
6//! C programs to use the library's functionality.
7
8#![cfg(any(target_arch = "aarch64", target_arch = "x86_64", target_arch = "x86"))]
9
10use crate::CrcAlgorithm;
11use crate::CrcParams;
12use crate::{get_calculator_target, Digest};
13use std::collections::HashMap;
14use std::ffi::CStr;
15use std::os::raw::c_char;
16use std::slice;
17use std::sync::Mutex;
18use std::sync::OnceLock;
19
20// Global storage for stable key pointers to ensure they remain valid across FFI boundary
21static STABLE_KEY_STORAGE: OnceLock<Mutex<HashMap<u64, Box<[u64]>>>> = OnceLock::new();
22
23/// Creates a stable pointer to the keys for FFI usage.
24/// The keys are stored in global memory to ensure the pointer remains valid.
25fn create_stable_key_pointer(keys: &crate::CrcKeysStorage) -> (*const u64, u32) {
26    let storage = STABLE_KEY_STORAGE.get_or_init(|| Mutex::new(HashMap::new()));
27
28    // Create a unique hash for this key set to avoid duplicates
29    let key_hash = match keys {
30        crate::CrcKeysStorage::KeysFold256(keys) => {
31            let mut hasher = std::collections::hash_map::DefaultHasher::new();
32            use std::hash::{Hash, Hasher};
33            keys.hash(&mut hasher);
34            hasher.finish()
35        }
36        crate::CrcKeysStorage::KeysFutureTest(keys) => {
37            let mut hasher = std::collections::hash_map::DefaultHasher::new();
38            use std::hash::{Hash, Hasher};
39            keys.hash(&mut hasher);
40            hasher.finish()
41        }
42    };
43
44    let mut storage_map = storage.lock().unwrap();
45
46    // Check if we already have this key set stored
47    if let Some(stored_keys) = storage_map.get(&key_hash) {
48        return (stored_keys.as_ptr(), stored_keys.len() as u32);
49    }
50
51    // Store the keys in stable memory
52    let key_vec: Vec<u64> = match keys {
53        crate::CrcKeysStorage::KeysFold256(keys) => keys.to_vec(),
54        crate::CrcKeysStorage::KeysFutureTest(keys) => keys.to_vec(),
55    };
56
57    let boxed_keys = key_vec.into_boxed_slice();
58    let ptr = boxed_keys.as_ptr();
59    let count = boxed_keys.len() as u32;
60
61    storage_map.insert(key_hash, boxed_keys);
62
63    (ptr, count)
64}
65
66/// A handle to the Digest object
67#[repr(C)]
68pub struct CrcFastDigestHandle(*mut Digest);
69
70/// The supported CRC algorithms
71#[repr(C)]
72pub enum CrcFastAlgorithm {
73    Crc32Aixm,
74    Crc32Autosar,
75    Crc32Base91D,
76    Crc32Bzip2,
77    Crc32CdRomEdc,
78    Crc32Cksum,
79    Crc32Custom,
80    Crc32Iscsi,
81    Crc32IsoHdlc,
82    Crc32Jamcrc,
83    Crc32Mef,
84    Crc32Mpeg2,
85    Crc32Xfer,
86    Crc64Custom,
87    Crc64Ecma182,
88    Crc64GoIso,
89    Crc64Ms,
90    Crc64Nvme,
91    Crc64Redis,
92    Crc64We,
93    Crc64Xz,
94}
95
96// Convert from FFI enum to internal enum
97impl From<CrcFastAlgorithm> for CrcAlgorithm {
98    fn from(value: CrcFastAlgorithm) -> Self {
99        match value {
100            CrcFastAlgorithm::Crc32Aixm => CrcAlgorithm::Crc32Aixm,
101            CrcFastAlgorithm::Crc32Autosar => CrcAlgorithm::Crc32Autosar,
102            CrcFastAlgorithm::Crc32Base91D => CrcAlgorithm::Crc32Base91D,
103            CrcFastAlgorithm::Crc32Bzip2 => CrcAlgorithm::Crc32Bzip2,
104            CrcFastAlgorithm::Crc32CdRomEdc => CrcAlgorithm::Crc32CdRomEdc,
105            CrcFastAlgorithm::Crc32Cksum => CrcAlgorithm::Crc32Cksum,
106            CrcFastAlgorithm::Crc32Custom => CrcAlgorithm::Crc32Custom,
107            CrcFastAlgorithm::Crc32Iscsi => CrcAlgorithm::Crc32Iscsi,
108            CrcFastAlgorithm::Crc32IsoHdlc => CrcAlgorithm::Crc32IsoHdlc,
109            CrcFastAlgorithm::Crc32Jamcrc => CrcAlgorithm::Crc32Jamcrc,
110            CrcFastAlgorithm::Crc32Mef => CrcAlgorithm::Crc32Mef,
111            CrcFastAlgorithm::Crc32Mpeg2 => CrcAlgorithm::Crc32Mpeg2,
112            CrcFastAlgorithm::Crc32Xfer => CrcAlgorithm::Crc32Xfer,
113            CrcFastAlgorithm::Crc64Custom => CrcAlgorithm::Crc64Custom,
114            CrcFastAlgorithm::Crc64Ecma182 => CrcAlgorithm::Crc64Ecma182,
115            CrcFastAlgorithm::Crc64GoIso => CrcAlgorithm::Crc64GoIso,
116            CrcFastAlgorithm::Crc64Ms => CrcAlgorithm::Crc64Ms,
117            CrcFastAlgorithm::Crc64Nvme => CrcAlgorithm::Crc64Nvme,
118            CrcFastAlgorithm::Crc64Redis => CrcAlgorithm::Crc64Redis,
119            CrcFastAlgorithm::Crc64We => CrcAlgorithm::Crc64We,
120            CrcFastAlgorithm::Crc64Xz => CrcAlgorithm::Crc64Xz,
121        }
122    }
123}
124
125/// Custom CRC parameters
126#[repr(C)]
127pub struct CrcFastParams {
128    pub algorithm: CrcFastAlgorithm,
129    pub width: u8,
130    pub poly: u64,
131    pub init: u64,
132    pub refin: bool,
133    pub refout: bool,
134    pub xorout: u64,
135    pub check: u64,
136    pub key_count: u32,
137    pub keys: *const u64,
138}
139
140// Convert from FFI struct to internal struct
141impl From<CrcFastParams> for CrcParams {
142    fn from(value: CrcFastParams) -> Self {
143        // Convert C array back to appropriate CrcKeysStorage
144        let keys = unsafe { std::slice::from_raw_parts(value.keys, value.key_count as usize) };
145
146        let storage = match value.key_count {
147            23 => crate::CrcKeysStorage::from_keys_fold_256(
148                keys.try_into().expect("Invalid key count for fold_256"),
149            ),
150            25 => crate::CrcKeysStorage::from_keys_fold_future_test(
151                keys.try_into().expect("Invalid key count for future_test"),
152            ),
153            _ => panic!("Unsupported key count: {}", value.key_count),
154        };
155
156        CrcParams {
157            algorithm: value.algorithm.into(),
158            name: "custom", // C interface doesn't need the name field
159            width: value.width,
160            poly: value.poly,
161            init: value.init,
162            refin: value.refin,
163            refout: value.refout,
164            xorout: value.xorout,
165            check: value.check,
166            keys: storage,
167        }
168    }
169}
170
171// Convert from internal struct to FFI struct
172impl From<CrcParams> for CrcFastParams {
173    fn from(params: CrcParams) -> Self {
174        // Create stable key pointer for FFI usage
175        let (keys_ptr, key_count) = create_stable_key_pointer(&params.keys);
176
177        CrcFastParams {
178            algorithm: match params.algorithm {
179                CrcAlgorithm::Crc32Aixm => CrcFastAlgorithm::Crc32Aixm,
180                CrcAlgorithm::Crc32Autosar => CrcFastAlgorithm::Crc32Autosar,
181                CrcAlgorithm::Crc32Base91D => CrcFastAlgorithm::Crc32Base91D,
182                CrcAlgorithm::Crc32Bzip2 => CrcFastAlgorithm::Crc32Bzip2,
183                CrcAlgorithm::Crc32CdRomEdc => CrcFastAlgorithm::Crc32CdRomEdc,
184                CrcAlgorithm::Crc32Cksum => CrcFastAlgorithm::Crc32Cksum,
185                CrcAlgorithm::Crc32Custom => CrcFastAlgorithm::Crc32Custom,
186                CrcAlgorithm::Crc32Iscsi => CrcFastAlgorithm::Crc32Iscsi,
187                CrcAlgorithm::Crc32IsoHdlc => CrcFastAlgorithm::Crc32IsoHdlc,
188                CrcAlgorithm::Crc32Jamcrc => CrcFastAlgorithm::Crc32Jamcrc,
189                CrcAlgorithm::Crc32Mef => CrcFastAlgorithm::Crc32Mef,
190                CrcAlgorithm::Crc32Mpeg2 => CrcFastAlgorithm::Crc32Mpeg2,
191                CrcAlgorithm::Crc32Xfer => CrcFastAlgorithm::Crc32Xfer,
192                CrcAlgorithm::Crc64Custom => CrcFastAlgorithm::Crc64Custom,
193                CrcAlgorithm::Crc64Ecma182 => CrcFastAlgorithm::Crc64Ecma182,
194                CrcAlgorithm::Crc64GoIso => CrcFastAlgorithm::Crc64GoIso,
195                CrcAlgorithm::Crc64Ms => CrcFastAlgorithm::Crc64Ms,
196                CrcAlgorithm::Crc64Nvme => CrcFastAlgorithm::Crc64Nvme,
197                CrcAlgorithm::Crc64Redis => CrcFastAlgorithm::Crc64Redis,
198                CrcAlgorithm::Crc64We => CrcFastAlgorithm::Crc64We,
199                CrcAlgorithm::Crc64Xz => CrcFastAlgorithm::Crc64Xz,
200            },
201            width: params.width,
202            poly: params.poly,
203            init: params.init,
204            refin: params.refin,
205            refout: params.refout,
206            xorout: params.xorout,
207            check: params.check,
208            key_count,
209            keys: keys_ptr,
210        }
211    }
212}
213
214/// Creates a new Digest to compute CRC checksums using algorithm
215#[no_mangle]
216pub extern "C" fn crc_fast_digest_new(algorithm: CrcFastAlgorithm) -> *mut CrcFastDigestHandle {
217    let digest = Box::new(Digest::new(algorithm.into()));
218    let handle = Box::new(CrcFastDigestHandle(Box::into_raw(digest)));
219    Box::into_raw(handle)
220}
221
222/// Creates a new Digest with a custom initial state
223#[no_mangle]
224pub extern "C" fn crc_fast_digest_new_with_init_state(
225    algorithm: CrcFastAlgorithm,
226    init_state: u64,
227) -> *mut CrcFastDigestHandle {
228    let digest = Box::new(Digest::new_with_init_state(algorithm.into(), init_state));
229    let handle = Box::new(CrcFastDigestHandle(Box::into_raw(digest)));
230    Box::into_raw(handle)
231}
232
233/// Creates a new Digest to compute CRC checksums using custom parameters
234#[no_mangle]
235pub extern "C" fn crc_fast_digest_new_with_params(
236    params: CrcFastParams,
237) -> *mut CrcFastDigestHandle {
238    let digest = Box::new(Digest::new_with_params(params.into()));
239    let handle = Box::new(CrcFastDigestHandle(Box::into_raw(digest)));
240    Box::into_raw(handle)
241}
242
243/// Updates the Digest with data
244#[no_mangle]
245pub extern "C" fn crc_fast_digest_update(
246    handle: *mut CrcFastDigestHandle,
247    data: *const c_char,
248    len: usize,
249) {
250    if handle.is_null() || data.is_null() {
251        return;
252    }
253
254    unsafe {
255        let digest = &mut *(*handle).0;
256
257        #[allow(clippy::unnecessary_cast)]
258        let bytes = slice::from_raw_parts(data as *const u8, len);
259        digest.update(bytes);
260    }
261}
262
263/// Calculates the CRC checksum for data that's been written to the Digest
264#[no_mangle]
265pub extern "C" fn crc_fast_digest_finalize(handle: *mut CrcFastDigestHandle) -> u64 {
266    if handle.is_null() {
267        return 0;
268    }
269
270    unsafe {
271        let digest = &*(*handle).0;
272        digest.finalize()
273    }
274}
275
276/// Free the Digest resources without finalizing
277#[no_mangle]
278pub extern "C" fn crc_fast_digest_free(handle: *mut CrcFastDigestHandle) {
279    if handle.is_null() {
280        return;
281    }
282
283    unsafe {
284        let handle = Box::from_raw(handle);
285        let _ = Box::from_raw(handle.0); // This drops the digest
286    }
287}
288
289/// Reset the Digest state
290#[no_mangle]
291pub extern "C" fn crc_fast_digest_reset(handle: *mut CrcFastDigestHandle) {
292    if handle.is_null() {
293        return;
294    }
295
296    unsafe {
297        let digest = &mut *(*handle).0;
298
299        digest.reset();
300    }
301}
302
303/// Finalize and reset the Digest in one operation
304#[no_mangle]
305pub extern "C" fn crc_fast_digest_finalize_reset(handle: *mut CrcFastDigestHandle) -> u64 {
306    if handle.is_null() {
307        return 0;
308    }
309
310    unsafe {
311        let digest = &mut *(*handle).0;
312
313        digest.finalize_reset()
314    }
315}
316
317/// Combine two Digest checksums
318#[no_mangle]
319pub extern "C" fn crc_fast_digest_combine(
320    handle1: *mut CrcFastDigestHandle,
321    handle2: *mut CrcFastDigestHandle,
322) {
323    if handle1.is_null() || handle2.is_null() {
324        return;
325    }
326
327    unsafe {
328        let digest1 = &mut *(*handle1).0;
329        let digest2 = &*(*handle2).0;
330        digest1.combine(digest2);
331    }
332}
333
334/// Gets the amount of data processed by the Digest so far
335#[no_mangle]
336pub extern "C" fn crc_fast_digest_get_amount(handle: *mut CrcFastDigestHandle) -> u64 {
337    if handle.is_null() {
338        return 0;
339    }
340
341    unsafe {
342        let digest = &*(*handle).0;
343        digest.get_amount()
344    }
345}
346
347/// Gets the current state of the Digest
348#[no_mangle]
349pub extern "C" fn crc_fast_digest_get_state(handle: *mut CrcFastDigestHandle) -> u64 {
350    if handle.is_null() {
351        return 0;
352    }
353    unsafe {
354        let digest = &*(*handle).0;
355        digest.get_state()
356    }
357}
358
359/// Helper method to calculate a CRC checksum directly for a string using algorithm
360#[no_mangle]
361pub extern "C" fn crc_fast_checksum(
362    algorithm: CrcFastAlgorithm,
363    data: *const c_char,
364    len: usize,
365) -> u64 {
366    if data.is_null() {
367        return 0;
368    }
369    unsafe {
370        #[allow(clippy::unnecessary_cast)]
371        let bytes = slice::from_raw_parts(data as *const u8, len);
372        crate::checksum(algorithm.into(), bytes)
373    }
374}
375
376/// Helper method to calculate a CRC checksum directly for data using custom parameters
377#[no_mangle]
378pub extern "C" fn crc_fast_checksum_with_params(
379    params: CrcFastParams,
380    data: *const c_char,
381    len: usize,
382) -> u64 {
383    if data.is_null() {
384        return 0;
385    }
386    unsafe {
387        #[allow(clippy::unnecessary_cast)]
388        let bytes = slice::from_raw_parts(data as *const u8, len);
389        crate::checksum_with_params(params.into(), bytes)
390    }
391}
392
393/// Helper method to just calculate a CRC checksum directly for a file using algorithm
394#[no_mangle]
395pub extern "C" fn crc_fast_checksum_file(
396    algorithm: CrcFastAlgorithm,
397    path_ptr: *const u8,
398    path_len: usize,
399) -> u64 {
400    if path_ptr.is_null() {
401        return 0;
402    }
403
404    unsafe {
405        crate::checksum_file(
406            algorithm.into(),
407            &convert_to_string(path_ptr, path_len),
408            None,
409        )
410        .unwrap()
411    }
412}
413
414/// Helper method to calculate a CRC checksum directly for a file using custom parameters
415#[no_mangle]
416pub extern "C" fn crc_fast_checksum_file_with_params(
417    params: CrcFastParams,
418    path_ptr: *const u8,
419    path_len: usize,
420) -> u64 {
421    if path_ptr.is_null() {
422        return 0;
423    }
424
425    unsafe {
426        crate::checksum_file_with_params(
427            params.into(),
428            &convert_to_string(path_ptr, path_len),
429            None,
430        )
431        .unwrap_or(0) // Return 0 on error instead of panicking
432    }
433}
434
435/// Combine two CRC checksums using algorithm
436#[no_mangle]
437pub extern "C" fn crc_fast_checksum_combine(
438    algorithm: CrcFastAlgorithm,
439    checksum1: u64,
440    checksum2: u64,
441    checksum2_len: u64,
442) -> u64 {
443    crate::checksum_combine(algorithm.into(), checksum1, checksum2, checksum2_len)
444}
445
446/// Combine two CRC checksums using custom parameters
447#[no_mangle]
448pub extern "C" fn crc_fast_checksum_combine_with_params(
449    params: CrcFastParams,
450    checksum1: u64,
451    checksum2: u64,
452    checksum2_len: u64,
453) -> u64 {
454    crate::checksum_combine_with_params(params.into(), checksum1, checksum2, checksum2_len)
455}
456
457/// Returns the custom CRC parameters for a given set of Rocksoft CRC parameters
458#[no_mangle]
459pub extern "C" fn crc_fast_get_custom_params(
460    name_ptr: *const c_char,
461    width: u8,
462    poly: u64,
463    init: u64,
464    reflected: bool,
465    xorout: u64,
466    check: u64,
467) -> CrcFastParams {
468    let name = if name_ptr.is_null() {
469        "custom"
470    } else {
471        unsafe { CStr::from_ptr(name_ptr).to_str().unwrap_or("custom") }
472    };
473
474    // Get the custom params from the library
475    let params = CrcParams::new(
476        // We need to use a static string for the name field
477        Box::leak(name.to_string().into_boxed_str()),
478        width,
479        poly,
480        init,
481        reflected,
482        xorout,
483        check,
484    );
485
486    // Create stable key pointer for FFI usage
487    let (keys_ptr, key_count) = create_stable_key_pointer(&params.keys);
488
489    // Convert to FFI struct
490    CrcFastParams {
491        algorithm: match width {
492            32 => CrcFastAlgorithm::Crc32Custom,
493            64 => CrcFastAlgorithm::Crc64Custom,
494            _ => panic!("Unsupported width: {width}",),
495        },
496        width: params.width,
497        poly: params.poly,
498        init: params.init,
499        refin: params.refin,
500        refout: params.refout,
501        xorout: params.xorout,
502        check: params.check,
503        key_count,
504        keys: keys_ptr,
505    }
506}
507
508/// Gets the target build properties (CPU architecture and fine-tuning parameters) for this algorithm
509#[no_mangle]
510pub extern "C" fn crc_fast_get_calculator_target(algorithm: CrcFastAlgorithm) -> *const c_char {
511    let target = get_calculator_target(algorithm.into());
512
513    std::ffi::CString::new(target).unwrap().into_raw()
514}
515
516/// Gets the version of this library
517#[no_mangle]
518pub extern "C" fn crc_fast_get_version() -> *const libc::c_char {
519    const VERSION: &CStr =
520        match CStr::from_bytes_with_nul(concat!(env!("CARGO_PKG_VERSION"), "\0").as_bytes()) {
521            Ok(version) => version,
522            Err(_) => panic!("package version contains null bytes??"),
523        };
524
525    VERSION.as_ptr()
526}
527
528unsafe fn convert_to_string(data: *const u8, len: usize) -> String {
529    if data.is_null() {
530        return String::new();
531    }
532
533    // Safely construct string slice from raw parts
534    match std::str::from_utf8(slice::from_raw_parts(data, len)) {
535        Ok(s) => s.to_string(),
536        Err(_) => panic!("Invalid UTF-8 string"),
537    }
538}