///|
/// A printable shard card. `index == data_count` is the parity card.
pub(all) struct ShardCard {
deck : String
index : Int
data_count : Int
width : Int
original_len : Int
checksum : Int
cells : Array[Int]
} derive(Eq, @debug.Debug)
///|
/// Recovery result for a deck of shard cards.
pub(all) enum Recovery {
Restored(Array[Int])
NeedCards(Array[Int])
Damaged(String)
} derive(Eq, @debug.Debug)
///|
/// Build data cards plus one parity card. The parity card can recover one
/// missing data card, and every card carries a checksum for copy mistakes.
pub fn weave(
deck : String,
payload : Array[Int],
data_count : Int,
) -> Array[ShardCard] {
let clean_count = clamp(data_count, 2, 12)
let width = max_int(1, ceil_div(payload.length(), clean_count))
let cards : Array[ShardCard] = []
for i in 0.. Recovery {
guard cards.length() > 0 else { return NeedCards([0]) }
match deck_profile(cards) {
Some(profile) => {
let deck = profile.deck
let data_count = profile.data_count
let width = profile.width
let original_len = profile.original_len
let total = data_count + 1
let mut parity_present = false
let buckets : Array[Array[Int]?] = []
for _ in 0..= 0 && current.index < total else {
return Damaged("card index is outside the deck")
}
guard validate_card(current) else {
return Damaged(
"checksum mismatch at card " + current.index.to_string(),
)
}
guard current.cells.length() == width else {
return Damaged(
"card width mismatch at card " + current.index.to_string(),
)
}
buckets[current.index] = Some(current.cells)
if current.index == data_count {
parity_present = true
}
}
let missing : Array[Int] = []
for i in 0.. Damaged("card deck is empty or malformed")
}
}
///|
/// Check the checksum carried by one card.
pub fn validate_card(card : ShardCard) -> Bool {
card.checksum == checksum(card)
}
///|
/// Return a compact, copy-friendly line for one card.
pub fn render_card(card : ShardCard) -> String {
"SN1|" +
card.deck +
"|" +
role_name(card) +
"|" +
"i=" +
card.index.to_string() +
"/" +
card.data_count.to_string() +
"|" +
"w=" +
card.width.to_string() +
"|" +
"n=" +
card.original_len.to_string() +
"|" +
"c=" +
card.checksum.to_string() +
"|" +
"x=" +
hex_cells(card.cells)
}
///|
/// Render several cards as one printable note sheet.
pub fn render_sheet(cards : Array[ShardCard]) -> String {
let lines : Array[String] = ["# shard-note"]
for current in cards {
lines.push(render_card(current))
}
lines.join("\n")
}
///|
/// A small built-in demo payload. It spells "MOONBIT" as byte values.
pub fn demo_payload() -> Array[Int] {
[77, 79, 79, 78, 66, 73, 84]
}
///|
/// Return the expected validation commands for this package.
pub fn validation_commands() -> Array[String] {
["moon check", "moon build", "moon test", "moon run cmd/main"]
}
///|
fn card(
deck : String,
index : Int,
data_count : Int,
width : Int,
original_len : Int,
cells : Array[Int],
) -> ShardCard {
let normalized = normalize_cells(cells)
let check = checksum_values(
index, data_count, width, original_len, normalized,
)
{
deck,
index,
data_count,
width,
original_len,
checksum: check,
cells: normalized,
}
}
///|
fn deck_profile(cards : Array[ShardCard]) -> ShardCard? {
guard cards.length() > 0 else { return None }
Some(cards[0])
}
///|
fn parity_cells(
cards : Array[ShardCard],
data_count : Int,
width : Int,
) -> Array[Int] {
let out : Array[Int] = []
for offset in 0.. Array[Int] {
let out : Array[Int] = []
for offset in 0.. Array[Int] {
let out : Array[Int] = []
for i in 0.. Array[Int] {
let out : Array[Int] = []
for offset in 0.. Array[Int] {
let out : Array[Int] = []
let take = min_int(payload.length(), original_len)
for i in 0.. Array[Int] {
let out : Array[Int] = []
for cell in cells {
out.push(normalize_byte(cell))
}
out
}
///|
fn checksum(card : ShardCard) -> Int {
checksum_values(
card.index,
card.data_count,
card.width,
card.original_len,
card.cells,
)
}
///|
fn checksum_values(
index : Int,
data_count : Int,
width : Int,
original_len : Int,
cells : Array[Int],
) -> Int {
let mut value = 1729
value = checksum_step(value, index)
value = checksum_step(value, data_count)
value = checksum_step(value, width)
value = checksum_step(value, original_len)
for cell in cells {
value = checksum_step(value, cell)
}
value % 65521
}
///|
fn checksum_step(seed : Int, value : Int) -> Int {
(seed * 131 + normalize_byte(value) + 17) % 65521
}
///|
fn xor_byte(left : Int, right : Int) -> Int {
let mut a = normalize_byte(left)
let mut b = normalize_byte(right)
let mut bit = 1
let mut out = 0
for _step in 0..<8 {
let abit = a % 2
let bbit = b % 2
if abit != bbit {
out = out + bit
}
a = a / 2
b = b / 2
bit = bit * 2
}
out
}
///|
fn normalize_byte(value : Int) -> Int {
if value < 0 {
0
} else if value > 255 {
value % 256
} else {
value
}
}
///|
fn hex_cells(cells : Array[Int]) -> String {
let parts : Array[String] = []
for cell in cells {
parts.push(hex_byte(cell))
}
parts.join("")
}
///|
fn hex_byte(value : Int) -> String {
let byte = normalize_byte(value)
hex_digit(byte / 16) + hex_digit(byte % 16)
}
///|
fn hex_digit(value : Int) -> String {
match value {
0 => "0"
1 => "1"
2 => "2"
3 => "3"
4 => "4"
5 => "5"
6 => "6"
7 => "7"
8 => "8"
9 => "9"
10 => "A"
11 => "B"
12 => "C"
13 => "D"
14 => "E"
_ => "F"
}
}
///|
fn role_name(card : ShardCard) -> String {
if card.index == card.data_count {
"parity"
} else {
"data"
}
}
///|
fn ceil_div(left : Int, right : Int) -> Int {
if right <= 0 {
0
} else {
(left + right - 1) / right
}
}
///|
fn clamp(value : Int, low : Int, high : Int) -> Int {
if value < low {
low
} else if value > high {
high
} else {
value
}
}
///|
fn min_int(left : Int, right : Int) -> Int {
if left < right {
left
} else {
right
}
}
///|
fn max_int(left : Int, right : Int) -> Int {
if left > right {
left
} else {
right
}
}