266 lines
7.5 KiB
Zig
266 lines
7.5 KiB
Zig
// 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-023)
|
|
//!
|
|
//! 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;
|
|
}
|