// SPDX-License-Identifier: LCL-1.0 // Copyright (c) 2026 Markus Maiwald // Stewardship: Self Sovereign Society Foundation // // This file is part of the Nexus Commonwealth. // See legal/LICENSE_COMMONWEALTH.md for license terms. //! Rumpk HAL: Reed-Solomon RAM Block Device (SPEC-103) //! //! Provides ECC-protected RAM storage using Reed-Solomon GF(2^8). //! This is the "Cortex" - Space-Grade resilient memory. const std = @import("std"); // ========================================================= // Galois Field GF(2^8) Arithmetic // ========================================================= // Primitive polynomial: x^8 + x^4 + x^3 + x^2 + 1 (0x11D) const GF_PRIM: u16 = 0x11D; // Precomputed log and antilog tables for GF(2^8) var gf_log: [256]u8 = undefined; var gf_exp: [512]u8 = undefined; var gf_initialized: bool = false; fn gf_init() void { if (gf_initialized) return; var x: u16 = 1; for (0..255) |i| { gf_exp[i] = @truncate(x); gf_log[@as(usize, @as(u8, @truncate(x)))] = @truncate(i); x <<= 1; if (x & 0x100 != 0) { x ^= GF_PRIM; } } // Extend exp table for easier modulo for (255..512) |i| { gf_exp[i] = gf_exp[i - 255]; } gf_log[0] = 0; // Undefined, but avoid issues gf_initialized = true; } fn gf_mul(a: u8, b: u8) u8 { if (a == 0 or b == 0) return 0; const log_a = gf_log[a]; const log_b = gf_log[b]; return gf_exp[@as(u16, log_a) + @as(u16, log_b)]; } fn gf_div(a: u8, b: u8) u8 { if (b == 0) return 0; // Error: division by zero if (a == 0) return 0; const log_a = gf_log[a]; const log_b = gf_log[b]; return gf_exp[@as(u16, log_a) + 255 - @as(u16, log_b)]; } fn gf_pow(a: u8, n: u8) u8 { if (n == 0) return 1; if (a == 0) return 0; const log_a: u16 = gf_log[a]; return gf_exp[(log_a * @as(u16, n)) % 255]; } fn gf_inv(a: u8) u8 { return gf_exp[255 - @as(u16, gf_log[a])]; } // ========================================================= // Reed-Solomon Codec // ========================================================= pub const SECTOR_SIZE: usize = 512; pub const PARITY_SIZE: usize = 32; // Can correct 16 byte errors pub const TOTAL_SIZE: usize = SECTOR_SIZE + PARITY_SIZE; // Generator polynomial coefficients (precomputed for t=16) var rs_generator: [PARITY_SIZE + 1]u8 = undefined; var rs_generator_initialized: bool = false; fn rs_init_generator() void { if (rs_generator_initialized) return; gf_init(); // Build generator polynomial: prod(x - alpha^i) for i = 0..PARITY_SIZE-1 rs_generator[0] = 1; for (1..PARITY_SIZE + 1) |i| { rs_generator[i] = 0; } for (0..PARITY_SIZE) |i| { const alpha_i = gf_pow(2, @truncate(i)); // Multiply by (x - alpha^i) var j: usize = PARITY_SIZE; while (j > 0) : (j -= 1) { rs_generator[j] = rs_generator[j - 1] ^ gf_mul(rs_generator[j], alpha_i); } rs_generator[0] = gf_mul(rs_generator[0], alpha_i); } rs_generator_initialized = true; } /// Encode data with Reed-Solomon parity pub fn rs_encode(data: []const u8, parity: *[PARITY_SIZE]u8) void { rs_init_generator(); // Initialize parity to zero for (parity) |*p| p.* = 0; // Polynomial division for (data) |byte| { const feedback = byte ^ parity[0]; // Shift parity register for (0..PARITY_SIZE - 1) |i| { parity[i] = parity[i + 1] ^ gf_mul(rs_generator[PARITY_SIZE - 1 - i], feedback); } parity[PARITY_SIZE - 1] = gf_mul(rs_generator[0], feedback); } } /// Calculate syndromes for error detection fn rs_syndromes(data: []const u8, parity: []const u8) [PARITY_SIZE]u8 { var syndromes: [PARITY_SIZE]u8 = undefined; for (0..PARITY_SIZE) |i| { const alpha_i = gf_pow(2, @truncate(i)); var s: u8 = 0; // Evaluate polynomial at alpha^i for (data) |byte| { s = gf_mul(s, alpha_i) ^ byte; } for (parity) |byte| { s = gf_mul(s, alpha_i) ^ byte; } syndromes[i] = s; } return syndromes; } /// Check if all syndromes are zero (no errors) fn rs_check_syndromes(syndromes: []const u8) bool { for (syndromes) |s| { if (s != 0) return false; } return true; } /// Decode and repair data using Reed-Solomon /// Returns true if data is valid (or was repaired), false if uncorrectable pub fn rs_decode(data: []u8, parity: []u8) bool { rs_init_generator(); const syndromes = rs_syndromes(data, parity); // No errors if (rs_check_syndromes(&syndromes)) return true; // TODO: Implement Berlekamp-Massey algorithm for error location // TODO: Implement Forney algorithm for error correction // For MVP, we detect but don't yet repair multi-byte errors // Simple single-byte error correction (simplified) // This is a placeholder - full RS decoding is complex return false; // Could not repair } // ========================================================= // RAM Block Device Interface // ========================================================= var ram_base: [*]u8 = undefined; var ram_size: usize = 0; var ram_initialized: bool = false; /// Initialize the RAM block device with a memory region export fn ram_blk_init(base: [*]u8, size: usize) void { gf_init(); rs_init_generator(); ram_base = base; ram_size = size; ram_initialized = true; } /// Get the number of sectors available export fn ram_blk_sector_count() u32 { if (!ram_initialized) return 0; return @truncate(ram_size / TOTAL_SIZE); } /// Write a sector with ECC protection export fn ram_blk_write(sector: u32, data: [*]const u8) i32 { if (!ram_initialized) return -1; const offset = @as(usize, sector) * TOTAL_SIZE; if (offset + TOTAL_SIZE > ram_size) return -1; // Calculate parity var parity: [PARITY_SIZE]u8 = undefined; rs_encode(data[0..SECTOR_SIZE], &parity); // Write data + parity const dest = ram_base + offset; @memcpy(dest[0..SECTOR_SIZE], data[0..SECTOR_SIZE]); @memcpy(dest[SECTOR_SIZE..TOTAL_SIZE], &parity); return SECTOR_SIZE; } /// Read a sector with ECC verification/repair export fn ram_blk_read(sector: u32, data: [*]u8) i32 { if (!ram_initialized) return -1; const offset = @as(usize, sector) * TOTAL_SIZE; if (offset + TOTAL_SIZE > ram_size) return -1; const src = ram_base + offset; // Read data + parity @memcpy(data[0..SECTOR_SIZE], src[0..SECTOR_SIZE]); var parity: [PARITY_SIZE]u8 = undefined; @memcpy(&parity, src[SECTOR_SIZE..TOTAL_SIZE]); // Verify and attempt repair var data_slice: [SECTOR_SIZE]u8 = undefined; @memcpy(&data_slice, data[0..SECTOR_SIZE]); if (!rs_decode(&data_slice, &parity)) { // TODO: Log the uncorrectable error return -2; // ECC_ERROR } // Copy repaired data back @memcpy(data[0..SECTOR_SIZE], &data_slice); return SECTOR_SIZE; } /// Scrub a sector (read and verify, repair if needed) export fn ram_blk_scrub(sector: u32) i32 { var temp: [SECTOR_SIZE]u8 = undefined; const result = ram_blk_read(sector, &temp); if (result == SECTOR_SIZE) { // Re-write to fix any corrected errors return ram_blk_write(sector, &temp); } return result; } /// Scrub all sectors (patrol scrubbing) export fn ram_blk_scrub_all() u32 { var errors: u32 = 0; const count = ram_blk_sector_count(); for (0..count) |i| { if (ram_blk_scrub(@truncate(i)) < 0) { errors += 1; } } return errors; }