crc32fast_lib/
lib.rs

1// Copyright 2024 Don MacAskill. Licensed under MIT.
2
3//! `crc32fast-lib`
4//! ===============
5//!
6//! Fast, SIMD-accelerated
7//! [CRC-32/ISO-HDLC](https://reveng.sourceforge.io/crc-catalogue/all.htm#crc.cat.crc-32-iso-hdlc)
8//! (aka `crc32`) checksum computation in Rust exposed as a C-compatible shared library.
9//!
10//! Results in a dramatic performance improvement. For example, when
11//! [using it via FFI in PHP](https://github.com/awesomized/crc-fast-php), it's >10X faster than
12//! PHP's native [crc32](https://www.php.net/crc32) implementation.
13//!
14//! ## Usage
15//!
16//! ### PHP example
17//!
18//! ```php
19//! $hasher = $ffi->hasher_new();
20//! $ffi->hasher_write($hasher, 'hello world!', 12);
21//! $checksum = $ffi->hasher_finalize($hasher); // 0x03b4c26d
22//! ```
23//!
24
25use crc32fast::Hasher;
26use std::os::raw::c_char;
27use std::slice;
28
29/// Opaque type for C for use in FFI
30#[repr(C)]
31pub struct HasherHandle(*mut Hasher);
32
33/// Creates a new Hasher to compute CRC32 checksums
34#[no_mangle]
35pub extern "C" fn hasher_new() -> *mut HasherHandle {
36    let hasher = Box::new(Hasher::new());
37    let handle = Box::new(HasherHandle(Box::into_raw(hasher)));
38    Box::into_raw(handle)
39}
40
41/// Writes data to the Hasher
42///
43/// # Safety
44///
45/// Uses unsafe method calls
46#[no_mangle]
47pub unsafe extern "C" fn hasher_write(handle: *mut HasherHandle, data: *const c_char, len: usize) {
48    if handle.is_null() || data.is_null() {
49        return;
50    }
51
52    let hasher = &mut *(*handle).0;
53    let bytes = slice::from_raw_parts(data as *const u8, len);
54    hasher.update(bytes);
55}
56
57/// Calculates the CRC32 checksum for data that's been written to the Hasher
58///
59/// # Safety
60///
61/// Uses unsafe method calls
62#[no_mangle]
63pub unsafe extern "C" fn hasher_finalize(handle: *mut HasherHandle) -> u32 {
64    if handle.is_null() {
65        return 0;
66    }
67
68    let handle = Box::from_raw(handle);
69    let hasher = Box::from_raw(handle.0);
70    hasher.finalize()
71}
72
73/// Helper method to just calculate a CRC32 checksum directly for a string
74#[no_mangle]
75pub extern "C" fn crc32_hash(data: *const c_char, len: usize) -> u32 {
76    if data.is_null() {
77        return 0;
78    }
79
80    unsafe {
81        let bytes = slice::from_raw_parts(data as *const u8, len);
82        crc32fast::hash(bytes)
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89    use std::ptr;
90
91    #[test]
92    fn test_hasher_lifecycle() {
93        unsafe {
94            let handle = hasher_new();
95            assert!(!handle.is_null(), "Hasher creation failed");
96
97            let data = b"123456789";
98            hasher_write(handle, data.as_ptr() as *const c_char, data.len());
99
100            let sum = hasher_finalize(handle);
101            assert_eq!(sum, 0xCBF43926, "CRC32 calculation incorrect");
102        }
103    }
104
105    #[test]
106    fn test_simple_hash() {
107        let data = b"123456789";
108        let sum = crc32_hash(data.as_ptr() as *const c_char, data.len());
109        assert_eq!(sum, 0xCBF43926, "Direct hash calculation incorrect");
110    }
111
112    #[test]
113    fn test_null_handling() {
114        unsafe {
115            hasher_write(ptr::null_mut(), b"test".as_ptr() as *const c_char, 4);
116            assert_eq!(
117                hasher_finalize(ptr::null_mut()),
118                0,
119                "Null handle should return 0"
120            );
121            assert_eq!(crc32_hash(ptr::null(), 0), 0, "Null data should return 0");
122        }
123    }
124
125    #[test]
126    fn test_empty_data() {
127        let sum = crc32_hash(b"".as_ptr() as *const c_char, 0);
128        assert_eq!(sum, 0, "Empty data should produce 0");
129    }
130
131    #[test]
132    fn test_incremental_update() {
133        unsafe {
134            let handle = hasher_new();
135
136            // Write data incrementally
137            let data = "hello world";
138            for byte in data.bytes() {
139                hasher_write(handle, &byte as *const u8 as *const c_char, 1);
140            }
141
142            let sum = hasher_finalize(handle);
143            let direct_sum = crc32_hash(data.as_ptr() as *const c_char, data.len());
144            assert_eq!(sum, direct_sum, "Incremental update failed");
145        }
146    }
147}