#[derive(Clone, Copy, Debug)]
pub struct Key(u64);
impl Key {
#[must_use] #[inline(always)]
pub const fn unchecked(key: u64) -> Self { Self(key) }
#[must_use] #[inline(always)]
pub const fn checked(key: u64) -> Result<Self, Inadmissible> {
match check_admissibility(key) {
Ok(_) => Ok(Self(key)),
Err(e) => Err(e)
}
}
#[must_use] #[inline(always)]
pub const fn with_index(index: u64) -> Self { key(index) }
#[must_use] #[inline(always)]
pub const fn inner(self) -> u64 { self.0 }
}
const MASTER_KEY_A: Key = Key::unchecked(0xec13a6976ecf14ad);
const MASTER_KEY_B: Key = Key::unchecked(0x72f9c8a323a5e4f1);
#[must_use]
pub const fn key(index: u64) -> Key {
let mut nibbles: [u8; 15] = [
0x1, 0x2, 0x3, 0x4,
0x5, 0x6, 0x7, 0x8,
0x9, 0xA, 0xB, 0xC,
0xD, 0xE, 0xF
];
let destinations = super::u64(MASTER_KEY_A, index);
let mut i = 14;
while i > 0 {
let dst = ((destinations >> (i * 4)) % 15) as usize;
let dv = nibbles[dst];
nibbles[dst] = nibbles[i as usize];
nibbles[i as usize] = dv;
i -= 1;
}
if nibbles[0] % 2 == 0 {
let mut i = 1;
while i < 15 {
if nibbles[i] % 2 == 1 {
let v = nibbles[0];
nibbles[0] = nibbles[i];
nibbles[i] = v;
break;
}
i += 1;
}
}
let mut output = 0;
let mut i = 0;
while i < 8 {
let n = nibbles[i];
output |= (n as u64) << (i * 4);
i += 1;
}
let nib_8 = nibbles[7];
let mut nib_9 = nibbles[8];
if nib_8 == nib_9 {
let mut i = 9;
while i < 15 {
if nibbles[i] != nib_8 {
let t = nibbles[8];
nibbles[8] = nibbles[i];
nibbles[i] = t;
nib_9 = t;
break;
}
i += 1;
}
}
output |= (nib_9 as u64) << 32;
let mut rng_idx = index;
let mut upper = super::u32(MASTER_KEY_B, rng_idx);
let mut i = 9;
let mut j = 0;
while i < 16 {
let mut nib = 1 + ((upper >> (j * 4)) % 15);
while nib == nib_9 as u32 {
j += 1;
if j == 8 {
j = 0;
rng_idx += 1;
upper = super::u32(MASTER_KEY_B, rng_idx)
}
nib = 1 + ((upper >> (j * 4)) % 15);
}
j += 1;
if j == 8 {
j = 0;
rng_idx += 1;
upper = super::u32(MASTER_KEY_B, rng_idx)
}
output |= (nib as u64) << (i * 4);
i += 1;
}
Key(output)
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Inadmissible {
ContainsZeroNibble { position: usize, nibble: u8 },
DuplicateNibbleInLower8 { position: usize, nibble: u8 },
FirstNibbleEven { nibble: u8 },
NinthNibbleEqualsEighth { nibble: u8 },
NinthNibbleRepeated { position: usize, nibble: u8 },
}
impl Inadmissible {
pub const fn message(&self) -> &'static str {
match self {
Inadmissible::ContainsZeroNibble { .. } => "Nibble is out of valid range (1 to 15)",
Inadmissible::DuplicateNibbleInLower8 { .. } => "Duplicate nibble found in lower 8 nibbles",
Inadmissible::FirstNibbleEven { .. } => "First nibble must be odd",
Inadmissible::NinthNibbleEqualsEighth { .. } => "9th nibble is equal to the 8th nibble",
Inadmissible::NinthNibbleRepeated { .. } => "9th nibble is repeated in upper nibbles",
}
}
}
#[must_use]
const fn check_admissibility(key: u64) -> Result<(), Inadmissible> {
let mut nibbles: [u8; 16] = [0u8; 16];
let mut i = 0;
while i < 16 {
nibbles[i] = ((key >> (i * 4)) & 0xF) as u8;
i += 1;
}
i = 0;
while i < 16 {
let nibble = nibbles[i];
if nibble < 1 || nibble > 15 {
return Err(Inadmissible::ContainsZeroNibble { position: i, nibble });
}
i += 1;
}
let mut used_digits = [false; 16]; i = 0;
while i < 8 {
let nibble = nibbles[i];
if used_digits[nibble as usize] {
return Err(Inadmissible::DuplicateNibbleInLower8 { position: i, nibble });
}
used_digits[nibble as usize] = true;
i += 1;
}
if nibbles[0] % 2 == 0 {
return Err(Inadmissible::FirstNibbleEven { nibble: nibbles[0] });
}
if nibbles[8] == nibbles[7] {
return Err(Inadmissible::NinthNibbleEqualsEighth { nibble: nibbles[8] });
}
i = 9;
while i < 16 {
let nibble = nibbles[i];
if nibble == nibbles[8] {
return Err(Inadmissible::NinthNibbleRepeated { position: i, nibble });
}
i += 1;
}
Ok(())
}
#[cfg(test)]
mod tests {
use crate::u64;
use super::{check_admissibility, key, Key};
#[test]
fn key_properties_100m() {
let idx_key = Key::checked(0x16d7358fe8d9a17b).unwrap();
for i in 0..100_000_000 {
let index = u64(idx_key, i);
let k = key(index);
if let Err(e) = check_admissibility(k.inner()) {
panic!("Key at index {} is invalid: {:?}", index, e);
}
}
}
}