use crate::typed::Decimal;
const fn b64_value(c: u8) -> u8 {
match c {
b'A'..=b'Z' => c - b'A',
b'a'..=b'z' => c - b'a' + 26,
b'0'..=b'9' => c - b'0' + 52,
b'+' | b'-' => 62,
b'/' | b'_' => 63,
_ => 64,
}
}
const fn hex_value(c: u8) -> Option<u8> {
match c {
b'0'..=b'9' => Some(c - b'0'),
b'a'..=b'f' => Some(c - b'a' + 10),
b'A'..=b'F' => Some(c - b'A' + 10),
_ => None,
}
}
#[must_use]
pub fn base64(input: &[u8]) -> Option<Vec<u8>> {
let body: &[u8] = match input.iter().position(|&c| c == b'=') {
Some(at) => &input[..at],
None => input,
};
if body.len() % 4 == 1 {
return None;
}
let mut out = Vec::with_capacity(body.len() / 4 * 3 + 2);
let mut acc: u32 = 0;
let mut bits = 0u32;
for &c in body {
let v = b64_value(c);
if v == 64 {
return None;
}
acc = (acc << 6) | u32::from(v);
bits += 6;
if bits >= 8 {
bits -= 8;
out.push(((acc >> bits) & 0xFF) as u8);
}
}
Some(out)
}
#[must_use]
pub fn jwt_half(text: &[u8], payload: bool) -> Option<Vec<u8>> {
let mut parts = text.split(|&c| c == b'.');
let header = parts.next()?;
let claims = parts.next()?;
parts.next()?;
base64(if payload { claims } else { header })
}
#[must_use]
pub fn bits_per_byte(bytes: &[u8]) -> Decimal {
if bytes.is_empty() {
return Decimal::from_u128(0);
}
let mut counts = [0u32; 256];
for &b in bytes {
counts[b as usize] += 1;
}
let total = bytes.len() as f64;
let mut h = 0.0f64;
for &c in &counts {
if c > 0 {
let p = f64::from(c) / total;
h -= p * p.log2();
}
}
Decimal::parse(&format!("{h:.2}")).expect("an entropy renders as a decimal")
}
#[must_use]
pub fn texture(bytes: &[u8]) -> crate::spectral::Texture {
let field = crate::spectral::analyze(bytes);
crate::spectral::texture_of(&field.signature(0, bytes.len()))
}
#[must_use]
pub fn period(bytes: &[u8]) -> u16 {
crate::spectral::dominant_period(bytes, bytes.len() / 2).0
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Json {
Str(String),
Num(String),
Bool(bool),
Null,
Nested(String),
}
impl Json {
#[must_use]
pub fn text(&self) -> String {
match self {
Json::Str(s) | Json::Num(s) | Json::Nested(s) => s.clone(),
Json::Bool(true) => "true".to_string(),
Json::Bool(false) => "false".to_string(),
Json::Null => "null".to_string(),
}
}
}
fn spaces(b: &[u8], mut i: usize) -> usize {
while i < b.len() && matches!(b[i], b' ' | b'\t' | b'\n' | b'\r') {
i += 1;
}
i
}
fn string_at(b: &[u8], i: usize) -> Option<(String, usize)> {
if b.get(i) != Some(&b'"') {
return None;
}
let mut out = String::new();
let mut j = i + 1;
while j < b.len() {
match b[j] {
b'"' => return Some((out, j + 1)),
b'\\' => {
let e = *b.get(j + 1)?;
j += 2;
match e {
b'n' => out.push('\n'),
b't' => out.push('\t'),
b'r' => out.push('\r'),
b'b' => out.push('\u{8}'),
b'f' => out.push('\u{c}'),
b'u' => {
let mut code = 0u32;
for k in 0..4 {
code = code * 16 + u32::from(hex_value(*b.get(j + k)?)?);
}
j += 4;
out.push(char::from_u32(code).unwrap_or('\u{fffd}'));
}
other => out.push(char::from(other)),
}
}
_ => {
let (c, len) = crate::lexer::utf8_char_at(b, j);
out.push(c);
j += len;
}
}
}
None
}
fn value_end(b: &[u8], i: usize) -> Option<usize> {
match *b.get(i)? {
b'"' => Some(string_at(b, i)?.1),
b'{' | b'[' => {
let (open, close) = if b[i] == b'{' { (b'{', b'}') } else { (b'[', b']') };
let mut depth = 0usize;
let mut j = i;
while j < b.len() {
match b[j] {
b'"' => j = string_at(b, j)?.1,
c if c == open => {
depth += 1;
j += 1;
}
c if c == close => {
depth -= 1;
j += 1;
if depth == 0 {
return Some(j);
}
}
_ => j += 1,
}
}
None
}
_ => {
let mut j = i;
while j < b.len() && !matches!(b[j], b',' | b'}' | b']' | b' ' | b'\t' | b'\n' | b'\r') {
j += 1;
}
(j > i).then_some(j)
}
}
}
#[must_use]
pub fn member(json: &[u8], key: &str) -> Option<Json> {
let b = json;
let mut i = spaces(b, 0);
if b.get(i) != Some(&b'{') {
return None;
}
i = spaces(b, i + 1);
while i < b.len() && b[i] != b'}' {
let (name, next) = string_at(b, i)?;
i = spaces(b, next);
if b.get(i) != Some(&b':') {
return None;
}
i = spaces(b, i + 1);
let end = value_end(b, i)?;
if name == key {
let raw = &b[i..end];
return Some(match raw[0] {
b'"' => Json::Str(string_at(b, i)?.0),
b'{' | b'[' => Json::Nested(String::from_utf8_lossy(raw).into_owned()),
b't' => Json::Bool(true),
b'f' => Json::Bool(false),
b'n' => Json::Null,
_ => Json::Num(String::from_utf8_lossy(raw).into_owned()),
});
}
i = spaces(b, end);
if b.get(i) == Some(&b',') {
i = spaces(b, i + 1);
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn both_alphabets_decode_padded_and_bare() {
assert_eq!(base64(b"SGVsbG8gV29ybGQ=").as_deref(), Some(&b"Hello World"[..]));
assert_eq!(base64(b"SGVsbG8gV29ybGQ").as_deref(), Some(&b"Hello World"[..]));
assert_eq!(base64(b"eyJhbGciOiJub25lIn0").as_deref(), Some(&br#"{"alg":"none"}"#[..]));
assert_eq!(base64(b"-_8=").as_deref(), Some(&[0xfb, 0xff][..]));
assert_eq!(base64(b"+/8=").as_deref(), Some(&[0xfb, 0xff][..]));
assert_eq!(base64(b"A"), None, "one character encodes no whole byte");
assert_eq!(base64(b"SGVs bG8="), None, "a byte outside both alphabets");
assert_eq!(base64(b"").as_deref(), Some(&[][..]));
}
#[test]
fn a_token_splits_into_its_decoded_halves() {
let jwt = b"eyJhbGciOiJub25lIn0.eyJzdWIiOiIxMjMiLCJleHAiOjE3ODk0MzA0MDB9.";
let header = jwt_half(jwt, false).expect("three segments");
let payload = jwt_half(jwt, true).expect("three segments");
assert_eq!(String::from_utf8_lossy(&header), r#"{"alg":"none"}"#);
assert_eq!(String::from_utf8_lossy(&payload), r#"{"sub":"123","exp":1789430400}"#);
assert!(jwt_half(b"eyJhbGciOiJub25lIn0.eyJzdWIiOiIxMjMifQ", true).is_none(), "two segments");
}
#[test]
fn a_members_value_reads_in_the_text_it_was_written_in() {
let json = br#"{"alg":"none","n":42,"f":1.5,"ok":true,"no":false,"z":null,"a":[1,2],"o":{"k":"v"},"esc":"a\"b\nA"}"#;
assert_eq!(member(json, "alg"), Some(Json::Str("none".into())));
assert_eq!(member(json, "n"), Some(Json::Num("42".into())));
assert_eq!(member(json, "f"), Some(Json::Num("1.5".into())));
assert_eq!(member(json, "ok"), Some(Json::Bool(true)));
assert_eq!(member(json, "no"), Some(Json::Bool(false)));
assert_eq!(member(json, "z"), Some(Json::Null));
assert_eq!(member(json, "a"), Some(Json::Nested("[1,2]".into())));
assert_eq!(member(json, "o"), Some(Json::Nested(r#"{"k":"v"}"#.into())));
assert_eq!(member(json, "esc"), Some(Json::Str("a\"b\nA".into())));
assert_eq!(member(json, "missing"), None);
assert_eq!(member(b"not an object", "x"), None);
assert_eq!(member(json, "k"), None);
assert_eq!(member("{\"k\":\"A\u{e9}\"}".as_bytes(), "k"), Some(Json::Str("A\u{e9}".into())));
assert_eq!(member(br#"{"k":"\ud800"}"#, "k"), Some(Json::Str("\u{fffd}".into())));
}
#[test]
fn the_byte_readings_read_the_decoded_bytes() {
assert_eq!(bits_per_byte(b"").to_text(), "0");
assert_eq!(bits_per_byte(b"aaaaaaaa").to_text(), "0");
assert_eq!(bits_per_byte(b"abababab").to_text(), "1");
assert_eq!(bits_per_byte(b"abcdabcd").to_text(), "2");
let table: Vec<u8> = "abc,12,x\n".repeat(16).into_bytes();
assert_eq!(period(&table), 9);
}
}