diff --git a/codecov.yml b/codecov.yml index c563749..d704478 100644 --- a/codecov.yml +++ b/codecov.yml @@ -2,7 +2,7 @@ coverage: status: project: default: - target: 95 + target: 90 paths: ["src"] patch: default: diff --git a/src/core/indexer/mod.rs b/src/core/indexer/mod.rs new file mode 100644 index 0000000..5c12a5e --- /dev/null +++ b/src/core/indexer/mod.rs @@ -0,0 +1,594 @@ +pub(crate) mod tables; + +use base64::{engine::general_purpose as b64_engine, Engine}; + +use crate::{ + core::indexer::tables::{BothSigCodex, CurrentSigCodex}, + error::{err, Error, Result}, +}; + +use super::util; + +/// Indexer is fully qualified cryptographic material primitive base class for +/// indexed primitives. Indexed codes are a mix of indexed and variable length +/// because code table has two char codes for compact variable length. +pub(crate) struct Indexer { + /// stable (hard) part of derivation code + code: String, + /// unqualified crypto material usable for crypto operations + raw: Vec, + /// main index offset into list or length of material + index: u32, + /// other index offset into list or length of material + ondex: Option, +} + +impl Default for Indexer { + fn default() -> Self { + Indexer { + raw: vec![], + code: tables::Codex::Ed25519.code().to_string(), + index: 0, + ondex: Some(u32::MAX), + } + } +} + +impl Indexer { + pub fn new_with_code_and_raw( + code: &str, + raw: &[u8], + index: u32, + mut ondex: Option, + ) -> Result { + if code.is_empty() { + return err!(Error::EmptyMaterial("empty code".to_string())); + } + + let szg = tables::sizage(code)?; + + // both hard + soft code size + let cs = szg.hs + szg.ss; + let ms = szg.ss - szg.os; + + if index > (64_u32.pow(ms - 1)) { + return err!(Error::InvalidVarIndex(format!( + "Invalid index '{index}' for code '{code}'" + ))); + } + + if let Some(o) = ondex { + if szg.os > 0 && o > 64_u32.pow(szg.os - 1) { + return err!(Error::InvalidVarIndex(format!( + "Invalid ondex '{o}' for code '{code}'" + ))); + } + } + + if CurrentSigCodex::has_code(code) && ondex.is_some() { + return err!(Error::InvalidVarIndex(format!( + "Non None ondex '{o}' for code '{code}'", + o = ondex.unwrap() + ))); + } + + if BothSigCodex::has_code(code) { + if ondex.is_none() { + // when not provided make ondex match index + ondex = Some(index); + } + } else if let Some(o) = ondex { + if o != index && szg.os == 0 { + return err!(Error::InvalidVarIndex(format!( + "Non matching ondex '{o}' and index '{index}' for code = '{code}'." + ))); + } + } + + // compute fs from index + let mut fs = szg.fs; + if fs == 0 { + if cs % 4 != 0 { + return err!(Error::InvalidCodeSize(format!( + "Whole code size not multiple of 4 for variable length material. cs = '{cs}'." + ))); + } + + if szg.os != 0 { + return err!(Error::InvalidCodeSize(format!( + "Non-zero other index size for variable length material. os = '{o}'.", + o = szg.os + ))); + } + + fs = (index * 4) + cs + } + + let rize = (fs - cs) * 3 / 4; + if raw.len() < rize as usize { + return err!(Error::Shortage(format!( + "insufficient raw material: raw size = '{}', rize = '{rize}'", + raw.len() + ))); + } + + Ok(Indexer { code: code.to_string(), raw: raw[..rize as usize].to_vec(), index, ondex }) + } + + pub fn new_with_qb64(qb64: &str) -> Result { + let mut i: Indexer = Default::default(); + i.exfil(qb64)?; + Ok(i) + } + + pub fn new_with_qb64b(qb64b: &[u8]) -> Result { + let qb64 = String::from_utf8(qb64b.to_vec())?; + + let mut i: Indexer = Default::default(); + i.exfil(&qb64)?; + Ok(i) + } + + pub fn new_with_qb2(qb2: &[u8]) -> Result { + let i: Indexer = Default::default(); + i.bexfil(qb2)?; + Ok(i) + } + + /// Extracts self.code, self.index, and self.raw from qualified base64 bytes qb64b + /// cs = hs + ss + /// ms = ss - os (main index size) + /// when fs None then size computed & fs = size * 4 + cs + fn exfil(&mut self, qb64: &str) -> Result<()> { + if qb64.is_empty() { + return err!(Error::EmptyMaterial("empty qb64".to_string())); + } + + let first = qb64.chars().next().unwrap(); + let hs = tables::hardage(first)? as usize; + if qb64.len() < hs { + return err!(Error::Shortage(format!( + "Need '{s}' more characters.", + s = (hs - qb64.len()) + ))); + } + + let hard = &qb64[..hs]; + let szg = tables::sizage(hard)?; + + // both hard + soft code size + let cs = szg.hs + szg.ss; + let ms = szg.ss - szg.os; + + if qb64.len() < cs as usize { + return err!(Error::Shortage(format!( + "Need '{l}' more characters", + l = ((cs as usize) - qb64.len()), + ))); + } + + let index = util::b64_to_u32(&qb64[hs..(hs + ms as usize)])?; + let odx = &qb64[(hs + ms as usize)..(hs + (ms + szg.os) as usize)]; + + let mut ondex: Option = None; + if CurrentSigCodex::has_code(hard) { + if szg.os != 0 { + ondex = Some(util::b64_to_u32(odx)?); + } + // not zero or None + if ondex.is_some() && ondex.unwrap() != 0 { + return err!(Error::Value(format!( + "Invalid ondex = '{o}' for code = '{hard}'.", + o = ondex.unwrap() + ))); + } + } else if szg.os != 0 { + ondex = Some(util::b64_to_u32(odx)?); + } else { + ondex = Some(index); + } + + // index is index for some codes and variable length for others + let mut fs = szg.fs; + if fs == 0 { + if (cs % 4) != 0 { + return err!(Error::Validation(format!( + "Whole code size not multiple of 4 for variable length material. cs = '{cs}'" + ))); + } + + if szg.os != 0 { + return err!(Error::Validation(format!( + "Non-zero other index size for variable length material. os = '{o}'", + o = szg.os + ))); + } + + fs = (index * 4) + cs; + } + + if qb64.len() < (fs as usize) { + return err!(Error::Shortage(format!( + "Need '{m}' more chars.", + m = { fs as usize - qb64.len() } + ))); + } + + let qb64_ = &qb64[..fs as usize]; + let ps = cs % 4; + let pbs = 2 * if ps != 0 { ps } else { szg.ls }; + + let raw: Vec; + if ps != 0 { + let mut buf = "A".repeat(ps as usize); + buf.push_str(&qb64_[(cs as usize)..]); + + let mut paw = Vec::::new(); + base64::engine::general_purpose::URL_SAFE.decode_vec(buf, &mut paw)?; + + let mut pi: i32 = 0; + for b in &paw[..ps as usize] { + pi = (pi * 256) + (*b as i32) + } + + if (pi & (2_i32.pow(pbs) - 1)) != 0 { + return err!(Error::Prepad()); + } + + raw = paw[ps as usize..].to_owned(); + paw.clear(); + } else { + let buf = &qb64_[cs as usize..]; + let mut paw = Vec::::new(); + base64::engine::general_purpose::URL_SAFE.decode_vec(buf, &mut paw)?; + + let mut li: u32 = 0; + for b in &paw[..szg.ls as usize] { + li = (li * 256) + (*b as u32); + } + + if li != 0 { + return if szg.ls == 1 { + err!(Error::NonZeroedLeadByte()) + } else { + err!(Error::NonZeroedLeadBytes()) + }; + } + + raw = paw[ps as usize..].to_owned(); + paw.clear(); + } + + self.code = hard.to_string(); + self.index = index; + self.ondex = ondex; + self.raw = raw; + + Ok(()) + } + + /// Extracts self.code, self.index, and self.raw from qualified base2 bytes qb2 + /// cs = hs + ss + /// ms = ss - os (main index size) + /// when fs None then size computed & fs = size * 4 + cs + fn bexfil(&self, _qb2: &[u8]) -> Result<()> { + // if qb2.is_empty() { + // return err!(Error::EmptyMaterial(format!("empty qualified base2"))); + // } + + // let first_byte = util::nab_sextets(qb2, 1)?[0]; + // if first_byte > 0x3d { + // if first_byte == 0x3e { + // return err!(Error::UnexpectedCountCode( + // "unexpected start during extraction".to_string(), + // )); + // } else if first_byte == 0x3f { + // return err!(Error::UnexpectedOpCode( + // "unexpected start during extraction".to_string(), + // )); + // } else { + // // unreachable + // // we just shifted a u8 right by 2, making it max 0x3f. + // // we then validated it was > 0x3d and not 0x3f or 0x3e + // return err!(Error::UnexpectedCode(format!( + // "unexpected code start: sextet = {first_byte}", + // ))); + // } + // } + todo!() + } + + /// Returns fully qualified attached sig base64 bytes computed from + /// self.raw, self.code and self.index. + /// cs = hs + ss + /// os = ss - ms (main index size) + /// when fs None then size computed & fs = size * 4 + cs + fn infil(&self) -> Result { + let code = &self.code; + let index = self.index; + let ondex = self.ondex.unwrap_or_default(); + let mut raw = self.raw.clone(); + + let ps = (3 - (raw.len() % 3)) % 3; + let szg = tables::sizage(code)?; + let cs = szg.hs + szg.ss; + let ms = szg.ss + szg.os; + + let mut fs = szg.fs; + if szg.fs == 0 { + if (cs % 4) != 0 { + return err!(Error::InvalidCodeSize(format!( + "Whole code size not multiple of 4 for variable length material. cs = '{cs}'." + ))); + } + if szg.os != 0 { + return err!(Error::InvalidCodeSize(format!( + "Non-zero other index size for variable length material. os = '{}'.", + szg.os + ))); + } + + fs = (index * 4) + cs + } + + if index > 64_u32.pow(ms - 1) { + return err!(Error::InvalidVarIndex(format!( + "Invalid index = '{index}' for code = '{code}'." + ))); + } + + if szg.os == 1 && ondex > 64_u32.pow(szg.os - 1) { + return err!(Error::InvalidVarIndex(format!( + "Invalid ondex = '{ondex}' for os = '{os}' and code = '{code}'.", + os = szg.os + ))); + } + + // both is hard code + converted index + converted ondex + let both = format!( + "{code}{}{}", + util::u32_to_b64(index, ms as usize)?, + util::u32_to_b64(ondex, szg.os as usize)? + ); + if both.len() != cs as usize { + return err!(Error::InvalidCodeSize(format!( + "Mismatch code size = {} with table = {}.", + cs, + both.len() + ))); + } + + if (cs % 4) != (ps as u32 - szg.ls) { + return err!(Error::InvalidCodeSize(format!( + "Invalid code={both} for converted raw pad size={ps}." + ))); + } + + for _ in 0..ps { + raw.insert(0, 0); + } + + let b64 = b64_engine::URL_SAFE.encode(raw); + let full = format!("{both}{}", &b64[(ps - szg.ls as usize)..]); + + if full.len() != fs as usize { + return err!(Error::InvalidCodeSize(format!( + "Invalid code={both} for raw size={} {}.", + full.len(), + fs + ))); + } + + Ok(full) + } + + /// Returns bytes of fully qualified base2 bytes, that is .qb2 + /// self.code and self.index converted to Base2 + self.raw left shifted + /// with pad bits equivalent of Base64 decode of .qb64 into .qb2 + fn binfil(&self) -> Result> { + let code = &self.code; + let index = self.index; + let ondex = self.ondex.unwrap_or_default(); + let mut raw = self.raw.clone(); + + let ps = (3 - (raw.len() % 3)) % 3; + let szg = tables::sizage(code)?; + let cs = szg.hs + szg.ss; + let ms = szg.ss - szg.os; + + if index > 64_u32.pow(szg.ss - 1) { + return err!(Error::InvalidVarIndex(format!( + "Invalid index = '{index}' for code = '{code}'." + ))); + } + + if szg.os == 1 && ondex > 64_u32.pow(szg.os - 1) { + return err!(Error::InvalidVarIndex(format!( + "Invalid ondex = '{ondex}' for os = '{}' and code = '{code}'.", + szg.os + ))); + } + + let mut fs = szg.fs; + if fs == 0 { + if (cs % 4) == 1 { + return err!(Error::InvalidCodeSize(format!( + "Whole code size not multiple of 4 for variable length material. cs = '{cs}'." + ))); + } + + if szg.os != 0 { + return err!(Error::InvalidCodeSize(format!( + "Non-zero other index size for variable length material. os = '{}'.", + szg.os + ))); + } + + fs = (index * 4) + cs; + } + + // both is hard code + converted index + let both = format!( + "{code}{}{}", + util::u32_to_b64(index, ms as usize)?, + util::u32_to_b64(ondex, szg.os as usize)? + ); + + if both.len() != cs as usize { + return err!(Error::InvalidCodeSize(format!( + "Mismatch code size = '{cs}' with table = '{}'.", + both.len() + ))); + } + + if (cs % 4) != (ps as u32 - szg.ls) { + return err!(Error::InvalidCodeSize(format!( + "Invalid code = '{both}' for converted raw pad size = '{ps}'.", + ))); + } + + // 3870 + let n = ((cs + 1) * 3) / 4; + let mut full: Vec; + if n <= tables::SMALL_VRZ_BYTES { + full = (util::b64_to_u32(&both)? << (2 * (cs % 4))).to_be_bytes().to_vec(); + } else if n <= tables::LARGE_VRZ_BYTES { + full = (util::b64_to_u64(&both)? << (2 * (cs % 4))).to_be_bytes().to_vec(); + } else { + // unreachable + // programmer error - sizages will not permit cs > 8, thus: + // (8 + 1) * 3 / 4 == 6, which is <= 6, always. + return err!(Error::InvalidCodeSize(format!("Unsupported code size: cs = '{cs}'",))); + } + // unpad code + full.drain(0..full.len() - n as usize); + // pad lead + full.resize(full.len() + szg.ls as usize, 0); + full.append(&mut raw); + + let bfs = full.len(); + if bfs % 3 != 0 || (bfs * 4 / 3) != fs as usize { + return err!(Error::InvalidCodeSize(format!( + "Invalid code for raw size: code = '{both}', raw size = '{}'", + raw.len() + ))); + } + + Ok(full) + } + + pub fn code(&self) -> String { + self.code.clone() + } + + pub fn raw(&self) -> Vec { + self.raw.clone() + } + + /// Fully Qualified Base64 Version + /// Assumes self.raw and self.code are correctly populated + pub fn qb64(&self) -> Result { + self.infil() + } + + /// Fully Qualified Base64 Version encoded as bytes + /// Assumes self.raw and self.code are correctly populated + pub fn qb64b(&self) -> Result> { + Ok(self.qb64()?.as_bytes().to_vec()) + } + + /// Fully Qualified Binary Version Bytes + pub fn qb2(&self) -> Result> { + self.binfil() + } +} + +#[cfg(test)] +mod indexer_tests { + use base64::{engine::general_purpose as b64_engine, Engine}; + + use crate::core::{ + indexer::{tables::Codex, Indexer}, + util, + }; + + #[test] + fn test_indexer() { + let sig = b"\x99\xd2<9$$0\x9fk\xfb\x18\xa0\x8c@r\x122.k\xb2\xc7\x1fp\x0e'm\x8f@\xaa\xa5\x8c\xc8n\x85\xc8!\xf6q\x91p\xa9\xec\xcf\x92\xaf)\xde\xca\xfc\x7f~\xd7o|\x17\x82\x1d\xd4 &'static str { + match self { + Codex::Ed25519 => "A", // Ed25519 sig appears same in both lists if any. + Codex::Ed25519_Crt => "B", // Ed25519 sig appears in current list only. + Codex::ECDSA_256k1 => "C", // ECDSA secp256k1 sig appears same in both lists if any. + Codex::ECDSA_256k1_Crt => "D", // ECDSA secp256k1 sig appears in current list. + Codex::Ed448 => "0A", // Ed448 signature appears in both lists. + Codex::Ed448_Crt => "0B", // Ed448 signature appears in current list only. + Codex::Ed25519_Big => "2A", // Ed25519 sig appears in both lists. + Codex::Ed25519_Big_Crt => "2B", // Ed25519 sig appears in current list only. + Codex::ECDSA_256k1_Big => "2C", // ECDSA secp256k1 sig appears in both lists. + Codex::ECDSA_256k1_Big_Crt => "2D", // ECDSA secp256k1 sig appears in current list only. + Codex::Ed448_Big => "3A", // Ed448 signature appears in both lists. + Codex::Ed448_Big_Crt => "3B", // Ed448 signature appears in current list only. + Codex::TBD0 => "0z", // Test of Var len label L=N*4 <= 4095 char quadlets includes code + Codex::TBD1 => "1z", // Test of index sig lead 1 + Codex::TBD4 => "4z", // Test of index sig lead 1 big + } + } +} + +/// SigCodex is all indexed signature derivation codes +#[allow(non_camel_case_types, clippy::enum_variant_names)] +#[derive(Debug, Eq, PartialEq, Clone)] +pub(crate) enum SigCodex { + Ed25519, + Ed25519_Crt, + ECDSA_256k1, + ECDSA_256k1_Crt, + Ed448, + Ed448_Crt, + Ed25519_Big, + Ed25519_Big_Crt, + ECDSA_256k1_Big, + ECDSA_256k1_Big_Crt, + Ed448_Big, + Ed448_Big_Crt, +} + +impl SigCodex { + pub(crate) fn code(&self) -> &'static str { + match self { + SigCodex::Ed25519 => "A", // Ed25519 sig appears same in both lists if any. + SigCodex::Ed25519_Crt => "B", // Ed25519 sig appears in current list only. + SigCodex::ECDSA_256k1 => "C", // ECDSA secp256k1 sig appears same in both lists if any. + SigCodex::ECDSA_256k1_Crt => "D", // ECDSA secp256k1 sig appears in current list. + SigCodex::Ed448 => "0A", // Ed448 signature appears in both lists. + SigCodex::Ed448_Crt => "0B", // Ed448 signature appears in current list only. + SigCodex::Ed25519_Big => "2A", // Ed25519 sig appears in both lists. + SigCodex::Ed25519_Big_Crt => "2B", // Ed25519 sig appears in current list only. + SigCodex::ECDSA_256k1_Big => "2C", // ECDSA secp256k1 sig appears in both lists. + SigCodex::ECDSA_256k1_Big_Crt => "2D", // ECDSA secp256k1 sig appears in current list only. + SigCodex::Ed448_Big => "3A", // Ed448 signature appears in both lists. + SigCodex::Ed448_Big_Crt => "3B", // Ed448 signature appears in current list only. + } + } +} + +/// CurrentSigCodex is codex indexed signature codes for current list. +#[allow(non_camel_case_types, clippy::enum_variant_names)] +#[derive(Debug, Eq, PartialEq, Clone)] +pub(crate) enum CurrentSigCodex { + Ed25519_Crt, + ECDSA_256k1_Crt, + Ed448_Crt, + Ed25519_Big_Crt, + ECDSA_256k1_Big_Crt, + Ed448_Big_Crt, +} + +impl CurrentSigCodex { + pub(crate) fn code(&self) -> &'static str { + match self { + CurrentSigCodex::Ed25519_Crt => "B", // Ed25519 sig appears in current list only. + CurrentSigCodex::ECDSA_256k1_Crt => "D", // ECDSA secp256k1 sig appears in current list only. + CurrentSigCodex::Ed448_Crt => "0B", // Ed448 signature appears in current list only. + CurrentSigCodex::Ed25519_Big_Crt => "2B", // Ed25519 sig appears in current list only. + CurrentSigCodex::ECDSA_256k1_Big_Crt => "2D", // ECDSA secp256k1 sig appears in current list only. + CurrentSigCodex::Ed448_Big_Crt => "3B", // Ed448 signature appears in current list only. + } + } + + pub(crate) fn from_code(code: &str) -> Result { + Ok(match code { + "B" => CurrentSigCodex::Ed25519_Crt, + "D" => CurrentSigCodex::ECDSA_256k1_Crt, + "0B" => CurrentSigCodex::Ed448_Crt, + "2B" => CurrentSigCodex::Ed25519_Big_Crt, + "2D" => CurrentSigCodex::ECDSA_256k1_Big_Crt, + "3B" => CurrentSigCodex::Ed448_Big_Crt, + _ => return Err(Box::new(Error::UnexpectedCode(code.to_string()))), + }) + } + + pub(crate) fn has_code(code: &str) -> bool { + lazy_static! { + static ref CODES: Vec<&'static str> = vec![ + CurrentSigCodex::Ed25519_Crt.code(), + CurrentSigCodex::ECDSA_256k1_Crt.code(), + CurrentSigCodex::Ed448_Crt.code(), + CurrentSigCodex::Ed25519_Big_Crt.code(), + CurrentSigCodex::ECDSA_256k1_Big_Crt.code(), + CurrentSigCodex::Ed448_Big_Crt.code(), + ]; + } + + CODES.contains(&code) + } +} + +/// BothSigCodex is codex indexed signature codes for both lists. +#[allow(non_camel_case_types, clippy::enum_variant_names)] +#[derive(Debug, Eq, PartialEq, Clone)] +pub(crate) enum BothSigCodex { + Ed25519, + ECDSA_256k1, + Ed448, + Ed25519_Big, + ECDSA_256k1_Big, + Ed448_Big, +} + +impl BothSigCodex { + pub(crate) fn code(&self) -> &'static str { + match self { + BothSigCodex::Ed25519 => "A", // Ed25519 sig appears same in both lists if any. + BothSigCodex::ECDSA_256k1 => "C", // ECDSA secp256k1 sig appears same in both lists if any. + BothSigCodex::Ed448 => "0A", // Ed448 signature appears in both lists. + BothSigCodex::Ed25519_Big => "2A", // Ed25519 sig appears in both listsy. + BothSigCodex::ECDSA_256k1_Big => "2C", // ECDSA secp256k1 sig appears in both lists. + BothSigCodex::Ed448_Big => "3A", // Ed448 signature appears in both lists. + } + } + + pub(crate) fn from_code(code: &str) -> Result { + Ok(match code { + "A" => BothSigCodex::Ed25519, + "C" => BothSigCodex::ECDSA_256k1, + "0A" => BothSigCodex::Ed448, + "2A" => BothSigCodex::Ed25519_Big, + "2C" => BothSigCodex::ECDSA_256k1_Big, + "3A" => BothSigCodex::Ed448_Big, + _ => return Err(Box::new(Error::UnexpectedCode(code.to_string()))), + }) + } + + pub(crate) fn has_code(code: &str) -> bool { + lazy_static! { + static ref CODES: Vec<&'static str> = vec![ + BothSigCodex::Ed25519.code(), + BothSigCodex::ECDSA_256k1.code(), + BothSigCodex::Ed448.code(), + BothSigCodex::Ed25519_Big.code(), + BothSigCodex::ECDSA_256k1_Big.code(), + BothSigCodex::Ed448_Big.code(), + ]; + } + + CODES.contains(&code) + } +} + +/// Sizes table maps hs chars of code to Sizage (hs), ss, os, fs, ls) +/// where hs is hard size, ss is soft size, os is other index size, +/// and fs is full size, ls is lead size. +/// where ss includes os, so main index size ms = ss - os +/// soft size, ss, should always be > 0 for Indexer +#[derive(Debug, PartialEq)] +pub(crate) struct Sizage { + pub hs: u32, + pub ss: u32, + pub os: u32, + pub ls: u32, + pub fs: u32, +} + +pub(crate) fn sizage(s: &str) -> Result { + Ok(match s { + "A" => Sizage { hs: 1, ss: 1, os: 0, fs: 88, ls: 0 }, + "B" => Sizage { hs: 1, ss: 1, os: 0, fs: 88, ls: 0 }, + "C" => Sizage { hs: 1, ss: 1, os: 0, fs: 88, ls: 0 }, + "D" => Sizage { hs: 1, ss: 1, os: 0, fs: 88, ls: 0 }, + "0A" => Sizage { hs: 2, ss: 2, os: 1, fs: 156, ls: 0 }, + "0B" => Sizage { hs: 2, ss: 2, os: 1, fs: 156, ls: 0 }, + "2A" => Sizage { hs: 2, ss: 4, os: 2, fs: 92, ls: 0 }, + "2B" => Sizage { hs: 2, ss: 4, os: 2, fs: 92, ls: 0 }, + "2C" => Sizage { hs: 2, ss: 4, os: 2, fs: 92, ls: 0 }, + "2D" => Sizage { hs: 2, ss: 4, os: 2, fs: 92, ls: 0 }, + "3A" => Sizage { hs: 2, ss: 6, os: 3, fs: 160, ls: 0 }, + "3B" => Sizage { hs: 2, ss: 6, os: 3, fs: 160, ls: 0 }, + "0z" => Sizage { hs: 2, ss: 2, os: 0, fs: 0, ls: 0 }, + "1z" => Sizage { hs: 2, ss: 2, os: 1, fs: 76, ls: 1 }, + "4z" => Sizage { hs: 2, ss: 6, os: 3, fs: 80, ls: 1 }, + _ => return Err(Box::new(Error::UnknownSizage(s.to_string()))), + }) +} + +pub(crate) fn hardage(c: char) -> Result { + match c { + 'A'..='Z' | 'a'..='z' => Ok(1), + '0'..='4' => Ok(2), + '-' => Err(Box::new(Error::UnexpectedCode("count code start".to_owned()))), + '_' => Err(Box::new(Error::UnexpectedCode("op code start".to_owned()))), + _ => Err(Box::new(Error::UnknownHardage(c.to_string()))), + } +} + +#[cfg(test)] +mod index_tables_tests { + use crate::core::indexer::tables::{ + hardage, sizage, BothSigCodex, Codex, CurrentSigCodex, SigCodex, Sizage, + }; + + #[test] + fn test_codex() { + assert_eq!(Codex::Ed25519.code(), "A"); + assert_eq!(Codex::Ed25519_Crt.code(), "B"); + assert_eq!(Codex::ECDSA_256k1.code(), "C"); + assert_eq!(Codex::ECDSA_256k1_Crt.code(), "D"); + assert_eq!(Codex::Ed448.code(), "0A"); + assert_eq!(Codex::Ed448_Crt.code(), "0B"); + assert_eq!(Codex::Ed25519_Big.code(), "2A"); + assert_eq!(Codex::Ed25519_Big_Crt.code(), "2B"); + assert_eq!(Codex::ECDSA_256k1_Big.code(), "2C"); + assert_eq!(Codex::ECDSA_256k1_Big_Crt.code(), "2D"); + assert_eq!(Codex::Ed448_Big.code(), "3A"); + assert_eq!(Codex::Ed448_Big_Crt.code(), "3B"); + assert_eq!(Codex::TBD0.code(), "0z"); + assert_eq!(Codex::TBD1.code(), "1z"); + assert_eq!(Codex::TBD4.code(), "4z"); + } + + #[test] + fn test_code() { + assert_eq!(SigCodex::Ed25519.code(), "A"); + assert_eq!(SigCodex::Ed25519_Crt.code(), "B"); + assert_eq!(SigCodex::ECDSA_256k1.code(), "C"); + assert_eq!(SigCodex::ECDSA_256k1_Crt.code(), "D"); + assert_eq!(SigCodex::Ed448.code(), "0A"); + assert_eq!(SigCodex::Ed448_Crt.code(), "0B"); + assert_eq!(SigCodex::Ed25519_Big.code(), "2A"); + assert_eq!(SigCodex::Ed25519_Big_Crt.code(), "2B"); + assert_eq!(SigCodex::ECDSA_256k1_Big.code(), "2C"); + assert_eq!(SigCodex::ECDSA_256k1_Big_Crt.code(), "2D"); + assert_eq!(SigCodex::Ed448_Big.code(), "3A"); + assert_eq!(SigCodex::Ed448_Big_Crt.code(), "3B"); + } + + #[test] + fn test_current_code() { + assert_eq!(CurrentSigCodex::Ed25519_Crt.code(), "B"); + assert_eq!(CurrentSigCodex::ECDSA_256k1_Crt.code(), "D"); + assert_eq!(CurrentSigCodex::Ed448_Crt.code(), "0B"); + assert_eq!(CurrentSigCodex::Ed25519_Big_Crt.code(), "2B"); + assert_eq!(CurrentSigCodex::ECDSA_256k1_Big_Crt.code(), "2D"); + assert_eq!(CurrentSigCodex::Ed448_Big_Crt.code(), "3B"); + } + + #[test] + fn test_both_code() { + assert_eq!(BothSigCodex::Ed25519.code(), "A"); + assert_eq!(BothSigCodex::ECDSA_256k1.code(), "C"); + assert_eq!(BothSigCodex::Ed448.code(), "0A"); + assert_eq!(BothSigCodex::Ed25519_Big.code(), "2A"); + assert_eq!(BothSigCodex::ECDSA_256k1_Big.code(), "2C"); + assert_eq!(BothSigCodex::Ed448_Big.code(), "3A"); + } + + #[test] + fn test_sizage() { + let mut s: Sizage; + + s = sizage(Codex::Ed25519.code()).unwrap(); + assert_eq!(s.hs, 1); + assert_eq!(s.ss, 1); + assert_eq!(s.os, 0); + assert_eq!(s.fs, 88); + assert_eq!(s.ls, 0); + + s = sizage(Codex::Ed25519_Crt.code()).unwrap(); + assert_eq!(s.hs, 1); + assert_eq!(s.ss, 1); + assert_eq!(s.os, 0); + assert_eq!(s.fs, 88); + assert_eq!(s.ls, 0); + + s = sizage(Codex::ECDSA_256k1.code()).unwrap(); + assert_eq!(s.hs, 1); + assert_eq!(s.ss, 1); + assert_eq!(s.os, 0); + assert_eq!(s.fs, 88); + assert_eq!(s.ls, 0); + + s = sizage(Codex::ECDSA_256k1_Crt.code()).unwrap(); + assert_eq!(s.hs, 1); + assert_eq!(s.ss, 1); + assert_eq!(s.os, 0); + assert_eq!(s.fs, 88); + assert_eq!(s.ls, 0); + + s = sizage(Codex::Ed448.code()).unwrap(); + assert_eq!(s.hs, 2); + assert_eq!(s.ss, 2); + assert_eq!(s.os, 1); + assert_eq!(s.fs, 156); + assert_eq!(s.ls, 0); + + s = sizage(Codex::Ed448_Crt.code()).unwrap(); + assert_eq!(s.hs, 2); + assert_eq!(s.ss, 2); + assert_eq!(s.os, 1); + assert_eq!(s.fs, 156); + assert_eq!(s.ls, 0); + + s = sizage(Codex::Ed25519_Big.code()).unwrap(); + assert_eq!(s.hs, 2); + assert_eq!(s.ss, 4); + assert_eq!(s.os, 2); + assert_eq!(s.fs, 92); + assert_eq!(s.ls, 0); + + s = sizage(Codex::Ed25519_Big_Crt.code()).unwrap(); + assert_eq!(s.hs, 2); + assert_eq!(s.ss, 4); + assert_eq!(s.os, 2); + assert_eq!(s.fs, 92); + assert_eq!(s.ls, 0); + + s = sizage(Codex::ECDSA_256k1_Big.code()).unwrap(); + assert_eq!(s.hs, 2); + assert_eq!(s.ss, 4); + assert_eq!(s.os, 2); + assert_eq!(s.fs, 92); + assert_eq!(s.ls, 0); + + s = sizage(Codex::ECDSA_256k1_Big_Crt.code()).unwrap(); + assert_eq!(s.hs, 2); + assert_eq!(s.ss, 4); + assert_eq!(s.os, 2); + assert_eq!(s.fs, 92); + assert_eq!(s.ls, 0); + + s = sizage(Codex::Ed448_Big.code()).unwrap(); + assert_eq!(s.hs, 2); + assert_eq!(s.ss, 6); + assert_eq!(s.os, 3); + assert_eq!(s.fs, 160); + assert_eq!(s.ls, 0); + + s = sizage(Codex::Ed448_Big_Crt.code()).unwrap(); + assert_eq!(s.hs, 2); + assert_eq!(s.ss, 6); + assert_eq!(s.os, 3); + assert_eq!(s.fs, 160); + assert_eq!(s.ls, 0); + + s = sizage(Codex::TBD0.code()).unwrap(); + assert_eq!(s.hs, 2); + assert_eq!(s.ss, 2); + assert_eq!(s.os, 0); + assert_eq!(s.fs, 0); + assert_eq!(s.ls, 0); + + s = sizage(Codex::TBD1.code()).unwrap(); + assert_eq!(s.hs, 2); + assert_eq!(s.ss, 2); + assert_eq!(s.os, 1); + assert_eq!(s.fs, 76); + assert_eq!(s.ls, 1); + + s = sizage(Codex::TBD4.code()).unwrap(); + assert_eq!(s.hs, 2); + assert_eq!(s.ss, 6); + assert_eq!(s.os, 3); + assert_eq!(s.fs, 80); + assert_eq!(s.ls, 1); + } + + #[test] + fn test_unkown_size() { + assert!(sizage("z").is_err()); + } + + #[test] + fn test_hardage() { + assert_eq!(hardage('A').unwrap(), 1); + assert_eq!(hardage('G').unwrap(), 1); + assert_eq!(hardage('b').unwrap(), 1); + assert_eq!(hardage('z').unwrap(), 1); + assert_eq!(hardage('0').unwrap(), 2); + assert_eq!(hardage('1').unwrap(), 2); + assert_eq!(hardage('2').unwrap(), 2); + assert_eq!(hardage('3').unwrap(), 2); + assert_eq!(hardage('4').unwrap(), 2); + } + + #[test] + fn test_unexpected_count_code() { + assert!(hardage('-').is_err()); + } + + #[test] + fn test_unexpected_op_code() { + assert!(hardage('_').is_err()); + } + + #[test] + fn test_unknown_hardage() { + assert!(hardage('8').is_err()); + } + + #[test] + fn test_current_sig_from_code() { + assert_eq!(CurrentSigCodex::from_code("B").unwrap(), CurrentSigCodex::Ed25519_Crt); + assert_eq!(CurrentSigCodex::from_code("D").unwrap(), CurrentSigCodex::ECDSA_256k1_Crt); + assert_eq!(CurrentSigCodex::from_code("0B").unwrap(), CurrentSigCodex::Ed448_Crt); + assert_eq!(CurrentSigCodex::from_code("2B").unwrap(), CurrentSigCodex::Ed25519_Big_Crt); + assert_eq!(CurrentSigCodex::from_code("2D").unwrap(), CurrentSigCodex::ECDSA_256k1_Big_Crt); + assert_eq!(CurrentSigCodex::from_code("3B").unwrap(), CurrentSigCodex::Ed448_Big_Crt); + + assert!(CurrentSigCodex::from_code("ZZ").is_err()); + } + + #[test] + fn test_both_sig_from_code() { + assert_eq!(BothSigCodex::from_code("A").unwrap(), BothSigCodex::Ed25519); + assert_eq!(BothSigCodex::from_code("C").unwrap(), BothSigCodex::ECDSA_256k1); + assert_eq!(BothSigCodex::from_code("0A").unwrap(), BothSigCodex::Ed448); + assert_eq!(BothSigCodex::from_code("2A").unwrap(), BothSigCodex::Ed25519_Big); + assert_eq!(BothSigCodex::from_code("2C").unwrap(), BothSigCodex::ECDSA_256k1_Big); + assert_eq!(BothSigCodex::from_code("3A").unwrap(), BothSigCodex::Ed448_Big); + + assert!(BothSigCodex::from_code("ZZ").is_err()); + } +} diff --git a/src/core/mod.rs b/src/core/mod.rs index be33f99..d64b318 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,6 +1,7 @@ pub mod cigar; pub mod counter; pub mod diger; +pub mod indexer; pub mod matter; pub mod util; pub mod verfer; diff --git a/src/error.rs b/src/error.rs index 628156e..73d1f49 100644 --- a/src/error.rs +++ b/src/error.rs @@ -59,6 +59,10 @@ pub enum Error { ParseQb2(String), #[error("conversion error: {0}")] Conversion(String), + #[error("{0}")] + Value(String), + #[error("{0}")] + Validation(String), } macro_rules! err {