qred 0.1.2

A fast, light-footprint native QR code generator with custom color styling.
Documentation
use crate::gf28::GF28;

pub struct ReedSolomon<'a> {
    gf: &'a GF28,
}

impl<'a> ReedSolomon<'a> {
    pub fn new(gf: &'a GF28) ->  Self {
        Self { gf }
    }

    // Generator polynomial g(x) of degree ec_count
    pub fn generator_polynomial(&self, ec_count: usize) -> Vec<u8> {
        let mut poly = vec![1u8];   // g(x) = 1

        for i in 0..ec_count {
            let alpha_i = self.gf.exp(i);
            let mut next_poly = vec![0u8; poly.len() + 1];

            for (j, &coeff) in poly.iter().enumerate() {
                next_poly[j] ^= self.gf.mul(coeff, 1);
                next_poly[j+1] ^= self.gf.mul(coeff, alpha_i);
            }

            poly = next_poly
        }

        poly
    }

    // Generate error correction codewords
    pub fn generate_ec_bytes(&self, data: &[u8], ec_count: usize) -> Vec<u8> {
        let poly = self.generator_polynomial(ec_count);
        let mut ec_buf = vec![0u8; ec_count];

        for &byte in data {
            let feedback = ec_buf[0] ^ byte;

            // shift left
            ec_buf.copy_within(1.., 0);
            ec_buf[ec_count-1] = 0;

            if feedback != 0 {
                for i in 0..ec_count {
                    ec_buf[i] ^= self.gf.mul(poly[i+1], feedback);
                }
            }
        }

        ec_buf
    }
}


// Interleaving blocks and EC codewords
pub struct BlockSpec {
    pub total_data_codewords: usize,
    pub ec_codewords_per_block: usize,
    pub g1_blocks: usize,
    pub g1_data_per_block: usize,
    pub g2_blocks: usize,
    pub g2_data_per_block: usize,
}

pub fn interleave_blocks(data: &[u8], block_spec: &BlockSpec, rs: &ReedSolomon) -> Vec<u8> {
    let mut data_blocks: Vec<Vec<u8>> = Vec::new();
    let mut ec_blocks: Vec<Vec<u8>> = Vec::new();
    let mut cursor = 0;

    // Extract group 1 blocks
    for _ in 0..block_spec.g1_blocks {
        let end = cursor + block_spec.g1_data_per_block;
        let block_data = data[cursor..end].to_vec();
        let ec_data = rs.generate_ec_bytes(&block_data, block_spec.ec_codewords_per_block);

        data_blocks.push(block_data);
        ec_blocks.push(ec_data);
        cursor = end;
    }

    // Extract group 2 blocks
    for _ in 0..block_spec.g2_blocks {
        let end = cursor + block_spec.g2_data_per_block;
        let block_data = data[cursor..end].to_vec();
        let ec_data = rs.generate_ec_bytes(&block_data, block_spec.ec_codewords_per_block);

        data_blocks.push(block_data);
        ec_blocks.push(ec_data);
        cursor = end;
    }

    let mut result = Vec::with_capacity(data.len() + ec_blocks.len() * block_spec.ec_codewords_per_block);

    // Interleave data bytes
    let max_data_len = block_spec.g1_data_per_block.max(block_spec.g2_data_per_block);
    for i in 0..max_data_len {
        for block in &data_blocks {
            if i < block.len() {
                result.push(block[i]);
            }
        }
    }

    // Interleave EC bytes
    for i in 0..block_spec.ec_codewords_per_block {
        for block in &ec_blocks {
            result.push(block[i]);
        }
    }

    result
}


// TESTS
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_reed_solomon() {
        let gf = GF28::new();
        let rs = ReedSolomon::new(&gf);

        // let data = [
        //     0x20, 0x41, 0xCD, 0x45, 0x29, 0xDC, 0x2E, 0x80, 0xEC, 0x11, 0xEC, 0x11, 0xEC, 0x11,
        //     0xEC, 0x11,
        // ];
        let data = [32, 91, 11, 120, 209, 114, 220, 77, 67, 64, 236, 17, 236, 17, 236, 17];
        // let expected = [42, 159, 74, 221, 244, 169, 239, 150, 138, 70];
        let expected = [196, 35, 39, 119, 235, 215, 231, 226, 93, 23];

        let ec_bytes = rs.generate_ec_bytes(&data, 10);
        assert_eq!(ec_bytes, expected);
    }

    #[test]
    fn test_interleave_v1() {
        let gf = GF28::new();
        let rs = ReedSolomon::new(&gf);

        let data = [32, 91, 11, 120, 209, 114, 220, 77, 67, 64, 236, 17, 236, 17, 236, 17];
        
        let spec = BlockSpec {
            total_data_codewords: 16,
            ec_codewords_per_block: 10,
            g1_blocks: 1,
            g1_data_per_block: 16,
            g2_blocks: 0,
            g2_data_per_block: 0,
        };

        let interleaved = interleave_blocks(&data, &spec, &rs);

        // 16 data bytes + 10 EC bytes = 26 bytes total
        assert_eq!(interleaved.len(), 26);
        // Tail end must match our EC test vector
        assert_eq!(&interleaved[16..], &[196, 35, 39, 119, 235, 215, 231, 226, 93, 23]);
    }

    #[test]
    fn test_interleave_v5_q() {
        let gf = GF28::new();
        let rs = ReedSolomon::new(&gf);

        // Simulated 62 data codewords (0, 1, 2, ..., 61)
        let data: Vec<u8> = (0..62).collect();

        // Version 5-Q spec: 4 blocks total
        // Group 1: 2 blocks of 16 data bytes
        // Group 2: 2 blocks of 15 data bytes
        // EC per block: 18 bytes
        let spec = BlockSpec {
            total_data_codewords: 62,
            ec_codewords_per_block: 18,
            g1_blocks: 2,
            g1_data_per_block: 16,
            g2_blocks: 2,
            g2_data_per_block: 15,
        };

        let result = interleave_blocks(&data, &spec, &rs);

        // Total expected length: 62 data + (4 blocks * 18 EC) = 134 bytes
        assert_eq!(result.len(), 134);

        // --- Verify Interleaved Data Ordering ---
        // Byte 0: 1st byte of Block 1 (data[0] = 0)
        // Byte 1: 1st byte of Block 2 (data[16] = 16)
        // Byte 2: 1st byte of Block 3 (data[32] = 32)
        // Byte 3: 1st byte of Block 4 (data[47] = 47)
        assert_eq!(result[0], 0);
        assert_eq!(result[1], 16);
        assert_eq!(result[2], 32);
        assert_eq!(result[3], 47);

        // Byte 4: 2nd byte of Block 1 (data[1] = 1)
        // Byte 5: 2nd byte of Block 2 (data[17] = 17)
        assert_eq!(result[4], 1);
        assert_eq!(result[5], 17);

        // At index 60 (the 16th byte slot):
        // Blocks 3 & 4 only have 15 bytes, so only Blocks 1 & 2 contribute here.
        assert_eq!(result[60], 15); // 16th byte of Block 1
        assert_eq!(result[61], 31); // 16th byte of Block 2

        // --- Verify Interleaved EC Ordering starts at byte index 62 ---
        // Block 1 EC for [0..16] with 18 EC bytes
        let ec1 = rs.generate_ec_bytes(&data[0..16], 18);
        // Block 2 EC for [16..32]
        let ec2 = rs.generate_ec_bytes(&data[16..32], 18);
        // Block 3 EC for [32..47]
        let ec3 = rs.generate_ec_bytes(&data[32..47], 18);
        // Block 4 EC for [47..62]
        let ec4 = rs.generate_ec_bytes(&data[47..62], 18);

        // First 4 interleaved EC bytes
        assert_eq!(result[62], ec1[0]);
        assert_eq!(result[63], ec2[0]);
        assert_eq!(result[64], ec3[0]);
        assert_eq!(result[65], ec4[0]);

        // Second 4 interleaved EC bytes
        assert_eq!(result[66], ec1[1]);
        assert_eq!(result[67], ec2[1]);
        assert_eq!(result[68], ec3[1]);
        assert_eq!(result[69], ec4[1]);
    }
}