//! Provides CRC-32/ISCSI and CRC-32/ISO-HDLC calculations using a fusion of native CLMUL
//! instructions and native CRC calculation instructions on aarch64.
//!
//! https://dougallj.wordpress.com/2022/05/22/faster-crc32-on-the-apple-m1/
//!
//! Converted to Rust from the original C code generated by https://github.com/corsix/fast-crc32/
//! with the help of Claude.ai using:
//!
//! ./generate -i neon -p crc32c -a v12e_v1
//! ./generate -i neon_eor3 -p crc32 -a v9s3x2e_s3
//! ./generate -i neon -p crc32 -a v12e_v1
//!
//! Modified as necessary for this Rust implementation.
//!
//! MIT licensed.

#![cfg(target_arch = "aarch64")]

use std::arch::aarch64::*;

/// Safe wrapper for CRC32 iSCSI calculation
#[inline]
#[cfg(target_feature = "sha3")]
pub fn crc32_iscsi(crc: u32, data: &[u8]) -> u32 {
    unsafe {
        const LARGE_BUFFER_THRESHOLD: usize = 1024;

        // Select implementation based on buffer size
        if data.len() <= LARGE_BUFFER_THRESHOLD {
            crc32_iscsi_v12e_v1(crc, data.as_ptr(), data.len())
        } else {
            crc32_iscsi_eor3_v9s3x2e_s3(crc, data.as_ptr(), data.len())
        }
    }
}

#[inline]
#[cfg(not(target_feature = "sha3"))]
pub fn crc32_iscsi(crc: u32, data: &[u8]) -> u32 {
    unsafe { crc32_iscsi_v12e_v1(crc, data.as_ptr(), data.len()) }
}

/// Safe wrapper for CRC32 ISO-HDLC calculation
#[inline]
#[cfg(target_feature = "sha3")]
pub fn crc32_iso_hdlc(crc: u32, data: &[u8]) -> u32 {
    unsafe {
        const LARGE_BUFFER_THRESHOLD: usize = 1024;

        // Select implementation based on buffer size
        if data.len() <= LARGE_BUFFER_THRESHOLD {
            crc32_iso_hdlc_v12e_v1(crc, data.as_ptr(), data.len())
        } else {
            crc32_iso_hdlc_eor3_v9s3x2e_s3(crc, data.as_ptr(), data.len())
        }
    }
}

#[inline]
#[cfg(not(target_feature = "sha3"))]
pub fn crc32_iso_hdlc(crc: u32, data: &[u8]) -> u32 {
    unsafe { crc32_iso_hdlc_v12e_v1(crc, data.as_ptr(), data.len()) }
}

#[inline]
#[cfg(target_feature = "sha3")]
#[target_feature(enable = "aes")]
unsafe fn clmul_lo_eor3(a: uint64x2_t, b: uint64x2_t) -> uint64x2_t {
    // Polynomial multiply low parts - convert u128 result to uint64x2_t
    let result = vmull_p64(vgetq_lane_u64(a, 0), vgetq_lane_u64(b, 0));
    vreinterpretq_u64_p128(result)
}

#[inline]
#[cfg(target_feature = "sha3")]
#[target_feature(enable = "aes")]
unsafe fn clmul_hi_eor3(a: uint64x2_t, b: uint64x2_t) -> uint64x2_t {
    // Polynomial multiply high parts - convert u128 result to uint64x2_t
    let result = vmull_p64(vgetq_lane_u64(a, 1), vgetq_lane_u64(b, 1));
    vreinterpretq_u64_p128(result)
}

#[inline]
#[cfg(target_feature = "sha3")]
#[target_feature(enable = "aes")]
unsafe fn clmul_scalar(a: u32, b: u32) -> uint64x2_t {
    // Polynomial multiply scalars - convert u128 result to uint64x2_t
    let result = vmull_p64(a as u64, b as u64);
    vreinterpretq_u64_p128(result)
}

// x^n mod P, in log(n) time
#[inline]
#[cfg(target_feature = "sha3")]
#[target_feature(enable = "aes")]
unsafe fn xnmodp_crc32_iscsi(mut n: u64) -> u32 {
    let mut stack = !1u64;
    let mut acc: u32;
    let mut low: u32;

    while n > 191 {
        stack = (stack << 1) + (n & 1);
        n = (n >> 1) - 16;
    }
    stack = !stack;
    acc = 0x80000000u32 >> (n & 31);
    n >>= 5;

    while n > 0 {
        // ARM CRC32 instruction
        acc = unsafe { __crc32cw(acc, 0) };
        n -= 1;
    }

    while {
        low = (stack & 1) as u32;
        stack >>= 1;
        stack != 0
    } {
        unsafe {
            // Convert to polynomial type and square it
            let x = vreinterpret_p8_u64(vmov_n_u64(acc as u64));
            let squared = vmull_p8(x, x);
            let y = vgetq_lane_u64(vreinterpretq_u64_p16(squared), 0);
            acc = __crc32cd(0, y << low);
        }
    }
    acc
}

#[inline]
#[cfg(target_feature = "sha3")]
#[target_feature(enable = "aes")]
unsafe fn crc_shift_iscsi(crc: u32, nbytes: usize) -> uint64x2_t {
    clmul_scalar(crc, xnmodp_crc32_iscsi((nbytes * 8 - 33) as u64))
}

// x^n mod P, in log(n) time
#[inline]
#[cfg(target_feature = "sha3")]
#[target_feature(enable = "aes")]
unsafe fn xnmodp_iso_hdlc(mut n: u64) -> u32 {
    let mut stack = !1u64;
    let mut acc: u32;
    let mut low: u32;

    while n > 191 {
        stack = (stack << 1) + (n & 1);
        n = (n >> 1) - 16;
    }
    stack = !stack;
    acc = 0x80000000u32 >> (n & 31);
    n >>= 5;

    while n > 0 {
        // ARM CRC32 instruction (ISO-HDLC uses standard CRC32, not CRC32C)
        acc = unsafe { __crc32w(acc, 0) };
        n -= 1;
    }

    while {
        low = (stack & 1) as u32;
        stack >>= 1;
        stack != 0
    } {
        unsafe {
            // Convert to polynomial type and square it
            let x = vreinterpret_p8_u64(vmov_n_u64(acc as u64));
            let squared = vmull_p8(x, x);
            let y = vgetq_lane_u64(vreinterpretq_u64_p16(squared), 0);
            acc = __crc32d(0, y << low);
        }
    }
    acc
}

#[inline]
#[cfg(target_feature = "sha3")]
#[target_feature(enable = "aes")]
unsafe fn crc_shift_iso_hdlc(crc: u32, nbytes: usize) -> uint64x2_t {
    clmul_scalar(crc, xnmodp_iso_hdlc((nbytes * 8 - 33) as u64))
}

/// Converted to Rust from the original C code generated by https://github.com/corsix/fast-crc32/
/// using:
///
/// ./generate -i neon_eor3 -p crc32c -a v9s3x2e_s3
#[inline]
#[cfg(target_feature = "sha3")]
#[target_feature(enable = "aes,sha3")]
unsafe fn crc32_iscsi_eor3_v9s3x2e_s3(mut crc0: u32, mut buf: *const u8, mut len: usize) -> u32 {
    // Align to 8-byte boundary
    while len > 0 && (buf as usize & 7) != 0 {
        crc0 = __crc32cb(crc0, *buf);
        buf = buf.add(1);
        len -= 1;
    }

    // Handle 8-byte alignment
    if (buf as usize & 8) != 0 && len >= 8 {
        crc0 = __crc32cd(crc0, *(buf as *const u64));
        buf = buf.add(8);
        len -= 8;
    }

    if len >= 192 {
        let end = buf.add(len);
        let blk = len / 192;
        let klen = blk * 16;
        let buf2 = buf.add(klen * 3);
        let limit = buf.add(klen).sub(32);
        let mut crc1 = 0u32;
        let mut crc2 = 0u32;

        // First vector chunk
        let mut x0 = vld1q_u64(buf2 as *const u64);
        let mut x1 = vld1q_u64(buf2.add(16) as *const u64);
        let mut x2 = vld1q_u64(buf2.add(32) as *const u64);
        let mut x3 = vld1q_u64(buf2.add(48) as *const u64);
        let mut x4 = vld1q_u64(buf2.add(64) as *const u64);
        let mut x5 = vld1q_u64(buf2.add(80) as *const u64);
        let mut x6 = vld1q_u64(buf2.add(96) as *const u64);
        let mut x7 = vld1q_u64(buf2.add(112) as *const u64);
        let mut x8 = vld1q_u64(buf2.add(128) as *const u64);

        let k_vals: [u64; 2] = [0x7e908048, 0xc96cfdc0];
        let mut k = vld1q_u64(k_vals.as_ptr());
        let mut buf2 = buf2.add(144);

        // Main loop
        while buf <= limit {
            let y0 = clmul_lo_eor3(x0, k);
            x0 = clmul_hi_eor3(x0, k);
            let y1 = clmul_lo_eor3(x1, k);
            x1 = clmul_hi_eor3(x1, k);
            let y2 = clmul_lo_eor3(x2, k);
            x2 = clmul_hi_eor3(x2, k);
            let y3 = clmul_lo_eor3(x3, k);
            x3 = clmul_hi_eor3(x3, k);
            let y4 = clmul_lo_eor3(x4, k);
            x4 = clmul_hi_eor3(x4, k);
            let y5 = clmul_lo_eor3(x5, k);
            x5 = clmul_hi_eor3(x5, k);
            let y6 = clmul_lo_eor3(x6, k);
            x6 = clmul_hi_eor3(x6, k);
            let y7 = clmul_lo_eor3(x7, k);
            x7 = clmul_hi_eor3(x7, k);
            let y8 = clmul_lo_eor3(x8, k);
            x8 = clmul_hi_eor3(x8, k);

            x0 = veor3q_u64(x0, y0, vld1q_u64(buf2 as *const u64));
            x1 = veor3q_u64(x1, y1, vld1q_u64(buf2.add(16) as *const u64));
            x2 = veor3q_u64(x2, y2, vld1q_u64(buf2.add(32) as *const u64));
            x3 = veor3q_u64(x3, y3, vld1q_u64(buf2.add(48) as *const u64));
            x4 = veor3q_u64(x4, y4, vld1q_u64(buf2.add(64) as *const u64));
            x5 = veor3q_u64(x5, y5, vld1q_u64(buf2.add(80) as *const u64));
            x6 = veor3q_u64(x6, y6, vld1q_u64(buf2.add(96) as *const u64));
            x7 = veor3q_u64(x7, y7, vld1q_u64(buf2.add(112) as *const u64));
            x8 = veor3q_u64(x8, y8, vld1q_u64(buf2.add(128) as *const u64));

            crc0 = __crc32cd(crc0, *(buf as *const u64));
            crc1 = __crc32cd(crc1, *(buf.add(klen) as *const u64));
            crc2 = __crc32cd(crc2, *(buf.add(klen * 2) as *const u64));
            crc0 = __crc32cd(crc0, *(buf.add(8) as *const u64));
            crc1 = __crc32cd(crc1, *(buf.add(klen + 8) as *const u64));
            crc2 = __crc32cd(crc2, *(buf.add(klen * 2 + 8) as *const u64));

            buf = buf.add(16);
            buf2 = buf2.add(144);
        }

        // Reduce x0 ... x8 to just x0
        let k_vals: [u64; 2] = [0xf20c0dfe, 0x493c7d27];
        k = vld1q_u64(k_vals.as_ptr());

        let y0 = clmul_lo_eor3(x0, k);
        x0 = clmul_hi_eor3(x0, k);
        x0 = veor3q_u64(x0, y0, x1);
        x1 = x2;
        x2 = x3;
        x3 = x4;
        x4 = x5;
        x5 = x6;
        x6 = x7;
        x7 = x8;

        let y0 = clmul_lo_eor3(x0, k);
        x0 = clmul_hi_eor3(x0, k);
        let y2 = clmul_lo_eor3(x2, k);
        x2 = clmul_hi_eor3(x2, k);
        let y4 = clmul_lo_eor3(x4, k);
        x4 = clmul_hi_eor3(x4, k);
        let y6 = clmul_lo_eor3(x6, k);
        x6 = clmul_hi_eor3(x6, k);

        x0 = veor3q_u64(x0, y0, x1);
        x2 = veor3q_u64(x2, y2, x3);
        x4 = veor3q_u64(x4, y4, x5);
        x6 = veor3q_u64(x6, y6, x7);

        let k_vals: [u64; 2] = [0x3da6d0cb, 0xba4fc28e];
        k = vld1q_u64(k_vals.as_ptr());

        let y0 = clmul_lo_eor3(x0, k);
        x0 = clmul_hi_eor3(x0, k);
        let y4 = clmul_lo_eor3(x4, k);
        x4 = clmul_hi_eor3(x4, k);

        x0 = veor3q_u64(x0, y0, x2);
        x4 = veor3q_u64(x4, y4, x6);

        let k_vals: [u64; 2] = [0x740eef02, 0x9e4addf8];
        k = vld1q_u64(k_vals.as_ptr());

        let y0 = clmul_lo_eor3(x0, k);
        x0 = clmul_hi_eor3(x0, k);
        x0 = veor3q_u64(x0, y0, x4);

        // Final scalar chunk
        crc0 = __crc32cd(crc0, *(buf as *const u64));
        crc1 = __crc32cd(crc1, *(buf.add(klen) as *const u64));
        crc2 = __crc32cd(crc2, *(buf.add(klen * 2) as *const u64));
        crc0 = __crc32cd(crc0, *(buf.add(8) as *const u64));
        crc1 = __crc32cd(crc1, *(buf.add(klen + 8) as *const u64));
        crc2 = __crc32cd(crc2, *(buf.add(klen * 2 + 8) as *const u64));

        let vc0 = crc_shift_iscsi(crc0, klen * 2 + blk * 144);
        let vc1 = crc_shift_iscsi(crc1, klen + blk * 144);
        let vc2 = crc_shift_iscsi(crc2, blk * 144);
        let vc = vgetq_lane_u64(veor3q_u64(vc0, vc1, vc2), 0);

        // Reduce 128 bits to 32 bits, and multiply by x^32
        crc0 = __crc32cd(0, vgetq_lane_u64(x0, 0));
        crc0 = __crc32cd(crc0, vc ^ vgetq_lane_u64(x0, 1));

        buf = buf2;
        len = end.offset_from(buf) as usize;
    }

    if len >= 32 {
        let klen = ((len - 8) / 24) * 8;
        let mut crc1 = 0u32;
        let mut crc2 = 0u32;

        // Main loop
        loop {
            crc0 = __crc32cd(crc0, *(buf as *const u64));
            crc1 = __crc32cd(crc1, *(buf.add(klen) as *const u64));
            crc2 = __crc32cd(crc2, *(buf.add(klen * 2) as *const u64));
            buf = buf.add(8);
            len -= 24;
            if len < 32 {
                break;
            }
        }

        let vc0 = crc_shift_iscsi(crc0, klen * 2 + 8);
        let vc1 = crc_shift_iscsi(crc1, klen + 8);
        let vc = vgetq_lane_u64(veorq_u64(vc0, vc1), 0);

        // Final 8 bytes
        buf = buf.add(klen * 2);
        crc0 = crc2;
        crc0 = __crc32cd(crc0, *(buf as *const u64) ^ vc);
        buf = buf.add(8);
        len -= 8;
    }

    while len >= 8 {
        crc0 = __crc32cd(crc0, *(buf as *const u64));
        buf = buf.add(8);
        len -= 8;
    }

    while len > 0 {
        crc0 = __crc32cb(crc0, *buf);
        buf = buf.add(1);
        len -= 1;
    }

    crc0
}

#[inline]
#[target_feature(enable = "aes")]
unsafe fn clmul_lo_e(a: uint64x2_t, b: uint64x2_t, c: uint64x2_t) -> uint64x2_t {
    // Polynomial multiply low parts and XOR with c
    let mul_result = vmull_p64(vgetq_lane_u64(a, 0), vgetq_lane_u64(b, 0));
    let mul_vec = vreinterpretq_u64_p128(mul_result);
    veorq_u64(mul_vec, c)
}

#[inline]
#[target_feature(enable = "aes")]
unsafe fn clmul_hi_e(a: uint64x2_t, b: uint64x2_t, c: uint64x2_t) -> uint64x2_t {
    // Polynomial multiply high parts and XOR with c
    let mul_result = vmull_p64(vgetq_lane_u64(a, 1), vgetq_lane_u64(b, 1));
    let mul_vec = vreinterpretq_u64_p128(mul_result);
    veorq_u64(mul_vec, c)
}

/// Converted to Rust from the original C code generated by https://github.com/corsix/fast-crc32/
/// using:
///
/// ./generate -i neon -p crc32c -a v12e_v1
#[inline]
#[target_feature(enable = "aes")]
unsafe fn crc32_iscsi_v12e_v1(mut crc0: u32, mut buf: *const u8, mut len: usize) -> u32 {
    // Align to 8-byte boundary
    while len > 0 && (buf as usize & 7) != 0 {
        crc0 = __crc32cb(crc0, *buf);
        buf = buf.add(1);
        len -= 1;
    }

    // Handle 8-byte alignment
    if (buf as usize & 8) != 0 && len >= 8 {
        crc0 = __crc32cd(crc0, *(buf as *const u64));
        buf = buf.add(8);
        len -= 8;
    }

    if len >= 192 {
        let end = buf.add(len);
        let limit = buf.add(len - 192);

        // First vector chunk
        let mut x0 = vld1q_u64(buf as *const u64);
        let mut x1 = vld1q_u64(buf.add(16) as *const u64);
        let mut x2 = vld1q_u64(buf.add(32) as *const u64);
        let mut x3 = vld1q_u64(buf.add(48) as *const u64);
        let mut x4 = vld1q_u64(buf.add(64) as *const u64);
        let mut x5 = vld1q_u64(buf.add(80) as *const u64);
        let mut x6 = vld1q_u64(buf.add(96) as *const u64);
        let mut x7 = vld1q_u64(buf.add(112) as *const u64);
        let mut x8 = vld1q_u64(buf.add(128) as *const u64);
        let mut x9 = vld1q_u64(buf.add(144) as *const u64);
        let mut x10 = vld1q_u64(buf.add(160) as *const u64);
        let mut x11 = vld1q_u64(buf.add(176) as *const u64);

        let k_vals: [u64; 2] = [0xa87ab8a8, 0xab7aff2a];
        let mut k = vld1q_u64(k_vals.as_ptr());

        // Create CRC vector and XOR with first vector
        let crc_vec = vsetq_lane_u64(crc0 as u64, vmovq_n_u64(0), 0);
        x0 = veorq_u64(crc_vec, x0);
        buf = buf.add(192);

        // Main loop
        while buf <= limit {
            let y0 = clmul_lo_e(x0, k, vld1q_u64(buf as *const u64));
            x0 = clmul_hi_e(x0, k, y0);
            let y1 = clmul_lo_e(x1, k, vld1q_u64(buf.add(16) as *const u64));
            x1 = clmul_hi_e(x1, k, y1);
            let y2 = clmul_lo_e(x2, k, vld1q_u64(buf.add(32) as *const u64));
            x2 = clmul_hi_e(x2, k, y2);
            let y3 = clmul_lo_e(x3, k, vld1q_u64(buf.add(48) as *const u64));
            x3 = clmul_hi_e(x3, k, y3);
            let y4 = clmul_lo_e(x4, k, vld1q_u64(buf.add(64) as *const u64));
            x4 = clmul_hi_e(x4, k, y4);
            let y5 = clmul_lo_e(x5, k, vld1q_u64(buf.add(80) as *const u64));
            x5 = clmul_hi_e(x5, k, y5);
            let y6 = clmul_lo_e(x6, k, vld1q_u64(buf.add(96) as *const u64));
            x6 = clmul_hi_e(x6, k, y6);
            let y7 = clmul_lo_e(x7, k, vld1q_u64(buf.add(112) as *const u64));
            x7 = clmul_hi_e(x7, k, y7);
            let y8 = clmul_lo_e(x8, k, vld1q_u64(buf.add(128) as *const u64));
            x8 = clmul_hi_e(x8, k, y8);
            let y9 = clmul_lo_e(x9, k, vld1q_u64(buf.add(144) as *const u64));
            x9 = clmul_hi_e(x9, k, y9);
            let y10 = clmul_lo_e(x10, k, vld1q_u64(buf.add(160) as *const u64));
            x10 = clmul_hi_e(x10, k, y10);
            let y11 = clmul_lo_e(x11, k, vld1q_u64(buf.add(176) as *const u64));
            x11 = clmul_hi_e(x11, k, y11);
            buf = buf.add(192);
        }

        // Reduce x0 ... x11 to just x0
        let k_vals: [u64; 2] = [0xf20c0dfe, 0x493c7d27];
        k = vld1q_u64(k_vals.as_ptr());

        let y0 = clmul_lo_e(x0, k, x1);
        x0 = clmul_hi_e(x0, k, y0);
        let y2 = clmul_lo_e(x2, k, x3);
        x2 = clmul_hi_e(x2, k, y2);
        let y4 = clmul_lo_e(x4, k, x5);
        x4 = clmul_hi_e(x4, k, y4);
        let y6 = clmul_lo_e(x6, k, x7);
        x6 = clmul_hi_e(x6, k, y6);
        let y8 = clmul_lo_e(x8, k, x9);
        x8 = clmul_hi_e(x8, k, y8);
        let y10 = clmul_lo_e(x10, k, x11);
        x10 = clmul_hi_e(x10, k, y10);

        let k_vals: [u64; 2] = [0x3da6d0cb, 0xba4fc28e];
        k = vld1q_u64(k_vals.as_ptr());

        let y0 = clmul_lo_e(x0, k, x2);
        x0 = clmul_hi_e(x0, k, y0);
        let y4 = clmul_lo_e(x4, k, x6);
        x4 = clmul_hi_e(x4, k, y4);
        let y8 = clmul_lo_e(x8, k, x10);
        x8 = clmul_hi_e(x8, k, y8);

        let k_vals: [u64; 2] = [0x740eef02, 0x9e4addf8];
        k = vld1q_u64(k_vals.as_ptr());

        let y0 = clmul_lo_e(x0, k, x4);
        x0 = clmul_hi_e(x0, k, y0);
        x4 = x8;
        let y0 = clmul_lo_e(x0, k, x4);
        x0 = clmul_hi_e(x0, k, y0);

        // Reduce 128 bits to 32 bits, and multiply by x^32
        crc0 = __crc32cd(0, vgetq_lane_u64(x0, 0));
        crc0 = __crc32cd(crc0, vgetq_lane_u64(x0, 1));
        len = end.offset_from(buf) as usize;
    }

    if len >= 16 {
        // First vector chunk
        let mut x0 = vld1q_u64(buf as *const u64);

        let k_vals: [u64; 2] = [0xf20c0dfe, 0x493c7d27];
        let k = vld1q_u64(k_vals.as_ptr());

        // Create CRC vector and XOR with first vector
        let crc_vec = vsetq_lane_u64(crc0 as u64, vmovq_n_u64(0), 0);
        x0 = veorq_u64(crc_vec, x0);
        buf = buf.add(16);
        len -= 16;

        // Main loop
        while len >= 16 {
            let y0 = clmul_lo_e(x0, k, vld1q_u64(buf as *const u64));
            x0 = clmul_hi_e(x0, k, y0);
            buf = buf.add(16);
            len -= 16;
        }

        // Reduce 128 bits to 32 bits, and multiply by x^32
        crc0 = __crc32cd(0, vgetq_lane_u64(x0, 0));
        crc0 = __crc32cd(crc0, vgetq_lane_u64(x0, 1));
    }

    while len >= 8 {
        crc0 = __crc32cd(crc0, *(buf as *const u64));
        buf = buf.add(8);
        len -= 8;
    }

    while len > 0 {
        crc0 = __crc32cb(crc0, *buf);
        buf = buf.add(1);
        len -= 1;
    }

    crc0
}

/// Converted to Rust from the original C code generated by https://github.com/corsix/fast-crc32/
/// using:
///
/// ./generate -i neon_eor3 -p crc32 -a v9s3x2e_s3
#[inline]
#[cfg(target_feature = "sha3")]
#[target_feature(enable = "aes,sha3")]
unsafe fn crc32_iso_hdlc_eor3_v9s3x2e_s3(mut crc0: u32, mut buf: *const u8, mut len: usize) -> u32 {
    // Align to 8-byte boundary
    while len > 0 && (buf as usize & 7) != 0 {
        crc0 = __crc32b(crc0, *buf);
        buf = buf.add(1);
        len -= 1;
    }

    // Handle 8-byte alignment
    if (buf as usize & 8) != 0 && len >= 8 {
        crc0 = __crc32d(crc0, *(buf as *const u64));
        buf = buf.add(8);
        len -= 8;
    }

    if len >= 192 {
        let end = buf.add(len);
        let blk = len / 192;
        let klen = blk * 16;
        let buf2 = buf.add(klen * 3);
        let limit = buf.add(klen).sub(32);
        let mut crc1 = 0u32;
        let mut crc2 = 0u32;

        // First vector chunk
        let mut x0 = vld1q_u64(buf2 as *const u64);
        let mut x1 = vld1q_u64(buf2.add(16) as *const u64);
        let mut x2 = vld1q_u64(buf2.add(32) as *const u64);
        let mut x3 = vld1q_u64(buf2.add(48) as *const u64);
        let mut x4 = vld1q_u64(buf2.add(64) as *const u64);
        let mut x5 = vld1q_u64(buf2.add(80) as *const u64);
        let mut x6 = vld1q_u64(buf2.add(96) as *const u64);
        let mut x7 = vld1q_u64(buf2.add(112) as *const u64);
        let mut x8 = vld1q_u64(buf2.add(128) as *const u64);

        // ISO-HDLC specific constants
        let k_vals: [u64; 2] = [0x26b70c3d, 0x3f41287a];
        let mut k = vld1q_u64(k_vals.as_ptr());
        let mut buf2 = buf2.add(144);

        // Main loop
        while buf <= limit {
            let y0 = clmul_lo_eor3(x0, k);
            x0 = clmul_hi_eor3(x0, k);
            let y1 = clmul_lo_eor3(x1, k);
            x1 = clmul_hi_eor3(x1, k);
            let y2 = clmul_lo_eor3(x2, k);
            x2 = clmul_hi_eor3(x2, k);
            let y3 = clmul_lo_eor3(x3, k);
            x3 = clmul_hi_eor3(x3, k);
            let y4 = clmul_lo_eor3(x4, k);
            x4 = clmul_hi_eor3(x4, k);
            let y5 = clmul_lo_eor3(x5, k);
            x5 = clmul_hi_eor3(x5, k);
            let y6 = clmul_lo_eor3(x6, k);
            x6 = clmul_hi_eor3(x6, k);
            let y7 = clmul_lo_eor3(x7, k);
            x7 = clmul_hi_eor3(x7, k);
            let y8 = clmul_lo_eor3(x8, k);
            x8 = clmul_hi_eor3(x8, k);

            x0 = veor3q_u64(x0, y0, vld1q_u64(buf2 as *const u64));
            x1 = veor3q_u64(x1, y1, vld1q_u64(buf2.add(16) as *const u64));
            x2 = veor3q_u64(x2, y2, vld1q_u64(buf2.add(32) as *const u64));
            x3 = veor3q_u64(x3, y3, vld1q_u64(buf2.add(48) as *const u64));
            x4 = veor3q_u64(x4, y4, vld1q_u64(buf2.add(64) as *const u64));
            x5 = veor3q_u64(x5, y5, vld1q_u64(buf2.add(80) as *const u64));
            x6 = veor3q_u64(x6, y6, vld1q_u64(buf2.add(96) as *const u64));
            x7 = veor3q_u64(x7, y7, vld1q_u64(buf2.add(112) as *const u64));
            x8 = veor3q_u64(x8, y8, vld1q_u64(buf2.add(128) as *const u64));

            crc0 = __crc32d(crc0, *(buf as *const u64));
            crc1 = __crc32d(crc1, *(buf.add(klen) as *const u64));
            crc2 = __crc32d(crc2, *(buf.add(klen * 2) as *const u64));
            crc0 = __crc32d(crc0, *(buf.add(8) as *const u64));
            crc1 = __crc32d(crc1, *(buf.add(klen + 8) as *const u64));
            crc2 = __crc32d(crc2, *(buf.add(klen * 2 + 8) as *const u64));

            buf = buf.add(16);
            buf2 = buf2.add(144);
        }

        // Reduce x0 ... x8 to just x0
        let k_vals: [u64; 2] = [0xae689191, 0xccaa009e];
        k = vld1q_u64(k_vals.as_ptr());

        let y0 = clmul_lo_eor3(x0, k);
        x0 = clmul_hi_eor3(x0, k);
        x0 = veor3q_u64(x0, y0, x1);
        x1 = x2;
        x2 = x3;
        x3 = x4;
        x4 = x5;
        x5 = x6;
        x6 = x7;
        x7 = x8;

        let y0 = clmul_lo_eor3(x0, k);
        x0 = clmul_hi_eor3(x0, k);
        let y2 = clmul_lo_eor3(x2, k);
        x2 = clmul_hi_eor3(x2, k);
        let y4 = clmul_lo_eor3(x4, k);
        x4 = clmul_hi_eor3(x4, k);
        let y6 = clmul_lo_eor3(x6, k);
        x6 = clmul_hi_eor3(x6, k);

        x0 = veor3q_u64(x0, y0, x1);
        x2 = veor3q_u64(x2, y2, x3);
        x4 = veor3q_u64(x4, y4, x5);
        x6 = veor3q_u64(x6, y6, x7);

        let k_vals: [u64; 2] = [0xf1da05aa, 0x81256527];
        k = vld1q_u64(k_vals.as_ptr());

        let y0 = clmul_lo_eor3(x0, k);
        x0 = clmul_hi_eor3(x0, k);
        let y4 = clmul_lo_eor3(x4, k);
        x4 = clmul_hi_eor3(x4, k);

        x0 = veor3q_u64(x0, y0, x2);
        x4 = veor3q_u64(x4, y4, x6);

        let k_vals: [u64; 2] = [0x8f352d95, 0x1d9513d7];
        k = vld1q_u64(k_vals.as_ptr());

        let y0 = clmul_lo_eor3(x0, k);
        x0 = clmul_hi_eor3(x0, k);
        x0 = veor3q_u64(x0, y0, x4);

        // Final scalar chunk
        crc0 = __crc32d(crc0, *(buf as *const u64));
        crc1 = __crc32d(crc1, *(buf.add(klen) as *const u64));
        crc2 = __crc32d(crc2, *(buf.add(klen * 2) as *const u64));
        crc0 = __crc32d(crc0, *(buf.add(8) as *const u64));
        crc1 = __crc32d(crc1, *(buf.add(klen + 8) as *const u64));
        crc2 = __crc32d(crc2, *(buf.add(klen * 2 + 8) as *const u64));

        let vc0 = crc_shift_iso_hdlc(crc0, klen * 2 + blk * 144);
        let vc1 = crc_shift_iso_hdlc(crc1, klen + blk * 144);
        let vc2 = crc_shift_iso_hdlc(crc2, blk * 144);
        let vc = vgetq_lane_u64(veor3q_u64(vc0, vc1, vc2), 0);

        // Reduce 128 bits to 32 bits, and multiply by x^32
        crc0 = __crc32d(0, vgetq_lane_u64(x0, 0));
        crc0 = __crc32d(crc0, vc ^ vgetq_lane_u64(x0, 1));

        buf = buf2;
        len = end.offset_from(buf) as usize;
    }

    if len >= 32 {
        let klen = ((len - 8) / 24) * 8;
        let mut crc1 = 0u32;
        let mut crc2 = 0u32;

        // Main loop
        loop {
            crc0 = __crc32d(crc0, *(buf as *const u64));
            crc1 = __crc32d(crc1, *(buf.add(klen) as *const u64));
            crc2 = __crc32d(crc2, *(buf.add(klen * 2) as *const u64));
            buf = buf.add(8);
            len -= 24;
            if len < 32 {
                break;
            }
        }

        let vc0 = crc_shift_iso_hdlc(crc0, klen * 2 + 8);
        let vc1 = crc_shift_iso_hdlc(crc1, klen + 8);
        let vc = vgetq_lane_u64(veorq_u64(vc0, vc1), 0);

        // Final 8 bytes
        buf = buf.add(klen * 2);
        crc0 = crc2;
        crc0 = __crc32d(crc0, *(buf as *const u64) ^ vc);
        buf = buf.add(8);
        len -= 8;
    }

    while len >= 8 {
        crc0 = __crc32d(crc0, *(buf as *const u64));
        buf = buf.add(8);
        len -= 8;
    }

    while len > 0 {
        crc0 = __crc32b(crc0, *buf);
        buf = buf.add(1);
        len -= 1;
    }

    crc0
}

/// Converted to Rust from the original C code generated by https://github.com/corsix/fast-crc32/
/// using:
///
/// ./generate -i neon -p crc32 -a v12e_v1
#[inline]
#[target_feature(enable = "aes")]
unsafe fn crc32_iso_hdlc_v12e_v1(mut crc0: u32, mut buf: *const u8, mut len: usize) -> u32 {
    // Align to 8-byte boundary
    while len > 0 && (buf as usize & 7) != 0 {
        crc0 = __crc32b(crc0, *buf);
        buf = buf.add(1);
        len -= 1;
    }

    // Handle 8-byte alignment
    if (buf as usize & 8) != 0 && len >= 8 {
        crc0 = __crc32d(crc0, *(buf as *const u64));
        buf = buf.add(8);
        len -= 8;
    }

    if len >= 192 {
        let end = buf.add(len);
        let limit = buf.add(len - 192);

        // First vector chunk
        let mut x0 = vld1q_u64(buf as *const u64);
        let mut x1 = vld1q_u64(buf.add(16) as *const u64);
        let mut x2 = vld1q_u64(buf.add(32) as *const u64);
        let mut x3 = vld1q_u64(buf.add(48) as *const u64);
        let mut x4 = vld1q_u64(buf.add(64) as *const u64);
        let mut x5 = vld1q_u64(buf.add(80) as *const u64);
        let mut x6 = vld1q_u64(buf.add(96) as *const u64);
        let mut x7 = vld1q_u64(buf.add(112) as *const u64);
        let mut x8 = vld1q_u64(buf.add(128) as *const u64);
        let mut x9 = vld1q_u64(buf.add(144) as *const u64);
        let mut x10 = vld1q_u64(buf.add(160) as *const u64);
        let mut x11 = vld1q_u64(buf.add(176) as *const u64);

        // ISO-HDLC specific constants for small implementation
        let k_vals: [u64; 2] = [0x596c8d81, 0xf5e48c85];
        let mut k = vld1q_u64(k_vals.as_ptr());

        // Create CRC vector and XOR with first vector
        let crc_vec = vsetq_lane_u64(crc0 as u64, vmovq_n_u64(0), 0);
        x0 = veorq_u64(crc_vec, x0);
        buf = buf.add(192);

        // Main loop
        while buf <= limit {
            let y0 = clmul_lo_e(x0, k, vld1q_u64(buf as *const u64));
            x0 = clmul_hi_e(x0, k, y0);
            let y1 = clmul_lo_e(x1, k, vld1q_u64(buf.add(16) as *const u64));
            x1 = clmul_hi_e(x1, k, y1);
            let y2 = clmul_lo_e(x2, k, vld1q_u64(buf.add(32) as *const u64));
            x2 = clmul_hi_e(x2, k, y2);
            let y3 = clmul_lo_e(x3, k, vld1q_u64(buf.add(48) as *const u64));
            x3 = clmul_hi_e(x3, k, y3);
            let y4 = clmul_lo_e(x4, k, vld1q_u64(buf.add(64) as *const u64));
            x4 = clmul_hi_e(x4, k, y4);
            let y5 = clmul_lo_e(x5, k, vld1q_u64(buf.add(80) as *const u64));
            x5 = clmul_hi_e(x5, k, y5);
            let y6 = clmul_lo_e(x6, k, vld1q_u64(buf.add(96) as *const u64));
            x6 = clmul_hi_e(x6, k, y6);
            let y7 = clmul_lo_e(x7, k, vld1q_u64(buf.add(112) as *const u64));
            x7 = clmul_hi_e(x7, k, y7);
            let y8 = clmul_lo_e(x8, k, vld1q_u64(buf.add(128) as *const u64));
            x8 = clmul_hi_e(x8, k, y8);
            let y9 = clmul_lo_e(x9, k, vld1q_u64(buf.add(144) as *const u64));
            x9 = clmul_hi_e(x9, k, y9);
            let y10 = clmul_lo_e(x10, k, vld1q_u64(buf.add(160) as *const u64));
            x10 = clmul_hi_e(x10, k, y10);
            let y11 = clmul_lo_e(x11, k, vld1q_u64(buf.add(176) as *const u64));
            x11 = clmul_hi_e(x11, k, y11);
            buf = buf.add(192);
        }

        // Reduce x0 ... x11 to just x0
        let k_vals: [u64; 2] = [0xae689191, 0xccaa009e];
        k = vld1q_u64(k_vals.as_ptr());

        let y0 = clmul_lo_e(x0, k, x1);
        x0 = clmul_hi_e(x0, k, y0);
        let y2 = clmul_lo_e(x2, k, x3);
        x2 = clmul_hi_e(x2, k, y2);
        let y4 = clmul_lo_e(x4, k, x5);
        x4 = clmul_hi_e(x4, k, y4);
        let y6 = clmul_lo_e(x6, k, x7);
        x6 = clmul_hi_e(x6, k, y6);
        let y8 = clmul_lo_e(x8, k, x9);
        x8 = clmul_hi_e(x8, k, y8);
        let y10 = clmul_lo_e(x10, k, x11);
        x10 = clmul_hi_e(x10, k, y10);

        let k_vals: [u64; 2] = [0xf1da05aa, 0x81256527];
        k = vld1q_u64(k_vals.as_ptr());

        let y0 = clmul_lo_e(x0, k, x2);
        x0 = clmul_hi_e(x0, k, y0);
        let y4 = clmul_lo_e(x4, k, x6);
        x4 = clmul_hi_e(x4, k, y4);
        let y8 = clmul_lo_e(x8, k, x10);
        x8 = clmul_hi_e(x8, k, y8);

        let k_vals: [u64; 2] = [0x8f352d95, 0x1d9513d7];
        k = vld1q_u64(k_vals.as_ptr());

        let y0 = clmul_lo_e(x0, k, x4);
        x0 = clmul_hi_e(x0, k, y0);
        x4 = x8;
        let y0 = clmul_lo_e(x0, k, x4);
        x0 = clmul_hi_e(x0, k, y0);

        // Reduce 128 bits to 32 bits, and multiply by x^32
        crc0 = __crc32d(0, vgetq_lane_u64(x0, 0));
        crc0 = __crc32d(crc0, vgetq_lane_u64(x0, 1));
        len = end.offset_from(buf) as usize;
    }

    if len >= 16 {
        // First vector chunk
        let mut x0 = vld1q_u64(buf as *const u64);

        let k_vals: [u64; 2] = [0xae689191, 0xccaa009e];
        let k = vld1q_u64(k_vals.as_ptr());

        // Create CRC vector and XOR with first vector
        let crc_vec = vsetq_lane_u64(crc0 as u64, vmovq_n_u64(0), 0);
        x0 = veorq_u64(crc_vec, x0);
        buf = buf.add(16);
        len -= 16;

        // Main loop
        while len >= 16 {
            let y0 = clmul_lo_e(x0, k, vld1q_u64(buf as *const u64));
            x0 = clmul_hi_e(x0, k, y0);
            buf = buf.add(16);
            len -= 16;
        }

        // Reduce 128 bits to 32 bits, and multiply by x^32
        crc0 = __crc32d(0, vgetq_lane_u64(x0, 0));
        crc0 = __crc32d(crc0, vgetq_lane_u64(x0, 1));
    }

    while len >= 8 {
        crc0 = __crc32d(crc0, *(buf as *const u64));
        buf = buf.add(8);
        len -= 8;
    }

    while len > 0 {
        crc0 = __crc32b(crc0, *buf);
        buf = buf.add(1);
        len -= 1;
    }

    crc0
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::test::consts::TEST_CHECK_STRING;
    use crc::{Crc, Table};
    use rand::{rng, Rng};

    const RUST_CRC32_ISO_HDLC: Crc<u32, Table<16>> =
        Crc::<u32, Table<16>>::new(&crc::CRC_32_ISO_HDLC);

    const RUST_CRC32_ISCSI: Crc<u32, Table<16>> = Crc::<u32, Table<16>>::new(&crc::CRC_32_ISCSI);

    #[test]
    fn test_crc32_iso_hdlc_check() {
        assert_eq!(
            crc32_iso_hdlc(0xffffffff, TEST_CHECK_STRING) ^ 0xffffffff,
            0xcbf43926
        );
    }

    #[test]
    fn test_crc32_iso_hdlc_small_all_lengths() {
        for len in 1..=255 {
            crc32_iso_hdlc_random(len)
        }
    }

    #[test]
    fn test_crc32_iso_hdlc_medium_lengths() {
        // Test each length from 256 to 1024, which should fold and include handling remainders
        for len in 256..=1024 {
            crc32_iso_hdlc_random(len)
        }
    }

    #[test]
    fn test_crc32_iso_hdlc_large_lengths() {
        // Test 1 MiB just before, at, and just after the folding boundaries
        for len in 1048575..1048577 {
            crc32_iso_hdlc_random(len)
        }
    }

    #[test]
    fn test_crc32_iscsi_check() {
        assert_eq!(
            crc32_iscsi(0xffffffff, TEST_CHECK_STRING) ^ 0xffffffff,
            0xe3069283
        );
    }

    #[test]
    fn test_crc32_iscsi_small_all_lengths() {
        for len in 1..=255 {
            crc32_iscsi_random(len);
        }
    }

    #[test]
    fn test_crc32_iscsi_medium_lengths() {
        // Test each length from 256 to 1024, which should fold and include handling remainders
        for len in 256..=1024 {
            crc32_iscsi_random(len);
        }
    }

    #[test]
    fn test_crc32_iscsi_large_lengths() {
        // Test 1 MiB just before, at, and just after the folding boundaries
        for len in 1048575..1048577 {
            crc32_iscsi_random(len);
        }
    }

    #[cfg(target_feature = "sha3")]
    fn crc32_iso_hdlc_random(len: usize) {
        let mut data = vec![0u8; len];
        rng().fill(&mut data[..]);

        let checksum = RUST_CRC32_ISO_HDLC.checksum(&data);

        assert_eq!(crc32_iso_hdlc(0xffffffff, &data) ^ 0xffffffff, checksum);

        unsafe {
            assert_eq!(
                crc32_iso_hdlc_eor3_v9s3x2e_s3(0xffffffff, data.as_ptr(), data.len()) ^ 0xffffffff,
                checksum
            );

            assert_eq!(
                crc32_iso_hdlc_v12e_v1(0xffffffff, data.as_ptr(), data.len()) ^ 0xffffffff,
                checksum
            );
        }
    }

    #[cfg(not(target_feature = "sha3"))]
    fn crc32_iso_hdlc_random(len: usize) {
        let mut data = vec![0u8; len];
        rng().fill(&mut data[..]);

        let checksum = RUST_CRC32_ISO_HDLC.checksum(&data);

        assert_eq!(crc32_iso_hdlc(0xffffffff, &data) ^ 0xffffffff, checksum);

        unsafe {
            assert_eq!(
                crc32_iso_hdlc_v12e_v1(0xffffffff, data.as_ptr(), data.len()) ^ 0xffffffff,
                checksum
            );
        }
    }

    #[cfg(target_feature = "sha3")]
    fn crc32_iscsi_random(len: usize) {
        let mut data = vec![0u8; len];
        rng().fill(&mut data[..]);

        let checksum = RUST_CRC32_ISCSI.checksum(&data);

        assert_eq!(crc32_iscsi(0xffffffff, &data) ^ 0xffffffff, checksum);

        unsafe {
            assert_eq!(
                crc32_iscsi_eor3_v9s3x2e_s3(0xffffffff, data.as_ptr(), data.len()) ^ 0xffffffff,
                checksum
            );

            assert_eq!(
                crc32_iscsi_v12e_v1(0xffffffff, data.as_ptr(), data.len()) ^ 0xffffffff,
                checksum
            );
        }
    }

    #[cfg(not(target_feature = "sha3"))]
    fn crc32_iscsi_random(len: usize) {
        let mut data = vec![0u8; len];
        rng().fill(&mut data[..]);

        let checksum = RUST_CRC32_ISCSI.checksum(&data);

        assert_eq!(crc32_iscsi(0xffffffff, &data) ^ 0xffffffff, checksum);

        unsafe {
            assert_eq!(
                crc32_iscsi_v12e_v1(0xffffffff, data.as_ptr(), data.len()) ^ 0xffffffff,
                checksum
            );
        }
    }
}
