Add crypto and documentation
This commit is contained in:
parent
2ae904cc8a
commit
4d4f9336d6
5 changed files with 229 additions and 71 deletions
|
@ -11,3 +11,10 @@ readme = "README.md"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nom = "7.1"
|
nom = "7.1"
|
||||||
|
base64 = "0.13"
|
||||||
|
|
||||||
|
blake2 = { version = "0.10", optional = true }
|
||||||
|
ed25519-dalek = { version = "1.0", optional = true }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = [ "blake2", "ed25519-dalek" ]
|
||||||
|
|
36
src/crypto.rs
Normal file
36
src/crypto.rs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
pub enum CryptoError {
|
||||||
|
InvalidHash,
|
||||||
|
InvalidSignature,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "blake2")]
|
||||||
|
mod b2 {
|
||||||
|
use super::CryptoError;
|
||||||
|
use blake2::{Blake2b512, Digest};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||||
|
pub struct Blake2Sum([u8; 64]);
|
||||||
|
|
||||||
|
impl Blake2Sum {
|
||||||
|
pub fn from_bytes(bytes: [u8; 64]) -> Self {
|
||||||
|
Self(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn compute(buf: &[u8]) -> Self {
|
||||||
|
let mut hasher = Blake2b512::new();
|
||||||
|
hasher.update(buf);
|
||||||
|
Self(hasher.finalize()[..].try_into().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check(&self, buf: &[u8]) -> Result<(), CryptoError> {
|
||||||
|
if Self::compute(buf) == *self {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(CryptoError::InvalidHash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "blake2")]
|
||||||
|
pub use b2::*;
|
|
@ -23,7 +23,7 @@ const STR_EXTRA_CHARS: &[u8] = b"._-*?";
|
||||||
pub enum DecodeError<'a> {
|
pub enum DecodeError<'a> {
|
||||||
/// Indicates that there is trailing garbage at the end of the decoded string
|
/// Indicates that there is trailing garbage at the end of the decoded string
|
||||||
Garbage(&'a [u8]),
|
Garbage(&'a [u8]),
|
||||||
/// Indicates that the entered string does not represent a complete NetText term
|
/// Indicates that the entered string does not represent a complete nettext term
|
||||||
IncompleteInput,
|
IncompleteInput,
|
||||||
/// Indicates a syntax error in the decoded term
|
/// Indicates a syntax error in the decoded term
|
||||||
NomError(&'a [u8], nom::error::ErrorKind),
|
NomError(&'a [u8], nom::error::ErrorKind),
|
||||||
|
@ -39,9 +39,6 @@ impl<'a> std::fmt::Debug for DecodeError<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The result type returned by the `decode` function
|
|
||||||
pub type DecodeResult<'a, T> = std::result::Result<T, DecodeError<'a>>;
|
|
||||||
|
|
||||||
impl<'a> From<nom::Err<nom::error::Error<&'a [u8]>>> for DecodeError<'a> {
|
impl<'a> From<nom::Err<nom::error::Error<&'a [u8]>>> for DecodeError<'a> {
|
||||||
fn from(e: nom::Err<nom::error::Error<&'a [u8]>>) -> DecodeError<'a> {
|
fn from(e: nom::Err<nom::error::Error<&'a [u8]>>) -> DecodeError<'a> {
|
||||||
match e {
|
match e {
|
||||||
|
@ -53,8 +50,8 @@ impl<'a> From<nom::Err<nom::error::Error<&'a [u8]>>> for DecodeError<'a> {
|
||||||
|
|
||||||
// ----
|
// ----
|
||||||
|
|
||||||
/// Decodes a NetText string into the term it represents.
|
/// Decodes a nettext string into the term it represents.
|
||||||
pub fn decode(input: &[u8]) -> DecodeResult<'_, Term<'_, 'static>> {
|
pub fn decode(input: &[u8]) -> std::result::Result<Term<'_, 'static>, DecodeError<'_>> {
|
||||||
let (rest, term) = decode_term(input)?;
|
let (rest, term) = decode_term(input)?;
|
||||||
let (end, _) = take_while(is_whitespace)(rest)?;
|
let (end, _) = take_while(is_whitespace)(rest)?;
|
||||||
if !end.is_empty() {
|
if !end.is_empty() {
|
||||||
|
@ -64,11 +61,8 @@ pub fn decode(input: &[u8]) -> DecodeResult<'_, Term<'_, 'static>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decode_term(input: &[u8]) -> IResult<&'_ [u8], AnyTerm<'_, 'static>> {
|
fn decode_term(input: &[u8]) -> IResult<&'_ [u8], AnyTerm<'_, 'static>> {
|
||||||
eprintln!("DT: `{}`", debug(input));
|
|
||||||
let (start, _) = take_while(is_whitespace)(input)?;
|
let (start, _) = take_while(is_whitespace)(input)?;
|
||||||
eprintln!("DT2: `{}`", debug(start));
|
|
||||||
let (rest, list) = separated_list1(take_while1(is_whitespace), decode_nonlist_term)(start)?;
|
let (rest, list) = separated_list1(take_while1(is_whitespace), decode_nonlist_term)(start)?;
|
||||||
eprintln!("DT3: `{}`", debug(rest));
|
|
||||||
|
|
||||||
if list.len() == 1 {
|
if list.len() == 1 {
|
||||||
Ok((rest, list.into_iter().next().unwrap().into()))
|
Ok((rest, list.into_iter().next().unwrap().into()))
|
||||||
|
@ -80,17 +74,14 @@ fn decode_term(input: &[u8]) -> IResult<&'_ [u8], AnyTerm<'_, 'static>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decode_nonlist_term(input: &[u8]) -> IResult<&'_ [u8], NonListTerm<'_, 'static>> {
|
fn decode_nonlist_term(input: &[u8]) -> IResult<&'_ [u8], NonListTerm<'_, 'static>> {
|
||||||
eprintln!("DNLT: `{}`", debug(input));
|
|
||||||
let (rest, term) = alt((
|
let (rest, term) = alt((
|
||||||
map(decode_str, NonListTerm::Str),
|
map(decode_str, NonListTerm::Str),
|
||||||
map(decode_dict, |(raw, d)| NonListTerm::Dict(raw, d)),
|
map(decode_dict, |(raw, d)| NonListTerm::Dict(raw, d)),
|
||||||
))(input)?;
|
))(input)?;
|
||||||
eprintln!("DNLTend: `{}` {:?}", debug(rest), term);
|
|
||||||
Ok((rest, term))
|
Ok((rest, term))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decode_str(input: &[u8]) -> IResult<&'_ [u8], &'_ [u8]> {
|
fn decode_str(input: &[u8]) -> IResult<&'_ [u8], &'_ [u8]> {
|
||||||
eprintln!("DS: `{}`", debug(input));
|
|
||||||
let (rest, data) = take_while1(is_string_char)(input)?;
|
let (rest, data) = take_while1(is_string_char)(input)?;
|
||||||
Ok((rest, data))
|
Ok((rest, data))
|
||||||
}
|
}
|
||||||
|
@ -98,16 +89,11 @@ fn decode_str(input: &[u8]) -> IResult<&'_ [u8], &'_ [u8]> {
|
||||||
type DictType<'a> = (&'a [u8], HashMap<&'a [u8], AnyTerm<'a, 'static>>);
|
type DictType<'a> = (&'a [u8], HashMap<&'a [u8], AnyTerm<'a, 'static>>);
|
||||||
|
|
||||||
fn decode_dict(dict_begin: &[u8]) -> IResult<&'_ [u8], DictType<'_>> {
|
fn decode_dict(dict_begin: &[u8]) -> IResult<&'_ [u8], DictType<'_>> {
|
||||||
eprintln!("DDbegin: `{}`", debug(dict_begin));
|
|
||||||
let (d, _) = tag(DICT_OPEN)(dict_begin)?;
|
let (d, _) = tag(DICT_OPEN)(dict_begin)?;
|
||||||
eprintln!("DD2: `{}`", debug(d));
|
|
||||||
let (d, items) = separated_list0(dict_separator, decode_dict_item)(d)?;
|
let (d, items) = separated_list0(dict_separator, decode_dict_item)(d)?;
|
||||||
eprintln!("DD3: `{}`", debug(d));
|
|
||||||
let (d, _) = opt(dict_separator)(d)?;
|
let (d, _) = opt(dict_separator)(d)?;
|
||||||
let (d, _) = take_while(is_whitespace)(d)?;
|
let (d, _) = take_while(is_whitespace)(d)?;
|
||||||
eprintln!("DD4: `{}`", debug(d));
|
|
||||||
let (dict_end, _) = tag(DICT_CLOSE)(d)?;
|
let (dict_end, _) = tag(DICT_CLOSE)(d)?;
|
||||||
eprintln!("DDend: `{}`", debug(dict_end));
|
|
||||||
|
|
||||||
let dict = items.into_iter().collect::<HashMap<_, _>>();
|
let dict = items.into_iter().collect::<HashMap<_, _>>();
|
||||||
|
|
||||||
|
@ -124,16 +110,11 @@ fn dict_separator(d: &[u8]) -> IResult<&'_ [u8], ()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decode_dict_item(d: &[u8]) -> IResult<&'_ [u8], (&'_ [u8], AnyTerm<'_, 'static>)> {
|
fn decode_dict_item(d: &[u8]) -> IResult<&'_ [u8], (&'_ [u8], AnyTerm<'_, 'static>)> {
|
||||||
eprintln!("DDI: `{}`", debug(d));
|
|
||||||
let (d, _) = take_while(is_whitespace)(d)?;
|
let (d, _) = take_while(is_whitespace)(d)?;
|
||||||
eprintln!("DDI1: `{}`", debug(d));
|
|
||||||
let (d, key) = decode_str(d)?;
|
let (d, key) = decode_str(d)?;
|
||||||
eprintln!("DDI2: `{}`", debug(d));
|
|
||||||
let (d, _) = take_while(is_whitespace)(d)?;
|
let (d, _) = take_while(is_whitespace)(d)?;
|
||||||
let (d, _) = tag(DICT_ASSIGN)(d)?;
|
let (d, _) = tag(DICT_ASSIGN)(d)?;
|
||||||
eprintln!("DDI3: `{}`", debug(d));
|
|
||||||
let (d, value) = decode_term(d)?;
|
let (d, value) = decode_term(d)?;
|
||||||
eprintln!("DDI4: `{}`", debug(d));
|
|
||||||
Ok((d, (key, value)))
|
Ok((d, (key, value)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
215
src/dec/mod.rs
215
src/dec/mod.rs
|
@ -2,9 +2,10 @@ mod decode;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::crypto::*;
|
||||||
pub use decode::*;
|
pub use decode::*;
|
||||||
|
|
||||||
/// A parsed NetText term.
|
/// A parsed nettext term, with many helpers for destructuring
|
||||||
///
|
///
|
||||||
/// Lifetime 'a is the lifetime of the buffer containing the encoded data.
|
/// Lifetime 'a is the lifetime of the buffer containing the encoded data.
|
||||||
///
|
///
|
||||||
|
@ -60,15 +61,30 @@ impl<'a> From<AnyTerm<'a, 'static>> for Term<'a, 'static> {
|
||||||
|
|
||||||
// ---- PUBLIC IMPLS ----
|
// ---- PUBLIC IMPLS ----
|
||||||
|
|
||||||
|
/// The type of errors returned by helper functions on `Term`
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum TypeError {
|
pub enum TypeError {
|
||||||
|
/// The term could not be decoded in the given type
|
||||||
WrongType(&'static str),
|
WrongType(&'static str),
|
||||||
|
/// The term is not an array of the requested length
|
||||||
WrongLength(usize, usize),
|
WrongLength(usize, usize),
|
||||||
|
/// The dictionnary is missing a key
|
||||||
MissingKey(String),
|
MissingKey(String),
|
||||||
|
/// The dictionnary contains an invalid key
|
||||||
UnexpectedKey(String),
|
UnexpectedKey(String),
|
||||||
|
/// The underlying raw string contains garbage (should not happen in theory)
|
||||||
|
Garbage,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::str::Utf8Error> for TypeError {
|
||||||
|
fn from(_x: std::str::Utf8Error) -> TypeError {
|
||||||
|
TypeError::Garbage
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> Term<'a, 'b> {
|
impl<'a, 'b> Term<'a, 'b> {
|
||||||
|
// ---- STRUCTURAL MAPPINGS ----
|
||||||
|
|
||||||
/// Get the term's raw representation
|
/// Get the term's raw representation
|
||||||
///
|
///
|
||||||
/// Example:
|
/// Example:
|
||||||
|
@ -91,14 +107,14 @@ impl<'a, 'b> Term<'a, 'b> {
|
||||||
/// use nettext::dec::decode;
|
/// use nettext::dec::decode;
|
||||||
///
|
///
|
||||||
/// let term1 = decode(b"hello").unwrap();
|
/// let term1 = decode(b"hello").unwrap();
|
||||||
/// assert_eq!(term1.str().unwrap(), b"hello");
|
/// assert_eq!(term1.str().unwrap(), "hello");
|
||||||
///
|
///
|
||||||
/// let term2 = decode(b"hello world").unwrap();
|
/// let term2 = decode(b"hello world").unwrap();
|
||||||
/// assert!(term2.str().is_err());
|
/// assert!(term2.str().is_err());
|
||||||
/// ```
|
/// ```
|
||||||
pub fn str(&self) -> Result<&'a [u8], TypeError> {
|
pub fn str(&self) -> Result<&'a str, TypeError> {
|
||||||
match &self.0 {
|
match &self.0 {
|
||||||
AnyTerm::Str(s) => Ok(s),
|
AnyTerm::Str(s) => Ok(std::str::from_utf8(s)?),
|
||||||
_ => Err(TypeError::WrongType("STR")),
|
_ => Err(TypeError::WrongType("STR")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -112,15 +128,17 @@ impl<'a, 'b> Term<'a, 'b> {
|
||||||
/// use nettext::dec::decode;
|
/// use nettext::dec::decode;
|
||||||
///
|
///
|
||||||
/// let term1 = decode(b"hello world").unwrap();
|
/// let term1 = decode(b"hello world").unwrap();
|
||||||
/// assert_eq!(term1.string().unwrap(), b"hello world");
|
/// assert_eq!(term1.string().unwrap(), "hello world");
|
||||||
///
|
///
|
||||||
/// let term2 = decode(b"hello { a= 5}").unwrap();
|
/// let term2 = decode(b"hello { a= 5}").unwrap();
|
||||||
/// assert!(term2.string().is_err());
|
/// assert!(term2.string().is_err());
|
||||||
/// ```
|
/// ```
|
||||||
pub fn string(&self) -> Result<&'a [u8], TypeError> {
|
pub fn string(&self) -> Result<&'a str, TypeError> {
|
||||||
match &self.0 {
|
match &self.0 {
|
||||||
AnyTerm::Str(s) => Ok(s),
|
AnyTerm::Str(s) => Ok(std::str::from_utf8(s)?),
|
||||||
AnyTerm::List(r, l) if l.iter().all(|x| matches!(x, NonListTerm::Str(_))) => Ok(r),
|
AnyTerm::List(r, l) if l.iter().all(|x| matches!(x, NonListTerm::Str(_))) => {
|
||||||
|
Ok(std::str::from_utf8(r)?)
|
||||||
|
}
|
||||||
_ => Err(TypeError::WrongType("STRING")),
|
_ => Err(TypeError::WrongType("STRING")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -137,13 +155,13 @@ impl<'a, 'b> Term<'a, 'b> {
|
||||||
/// let term1 = decode(b"hello").unwrap();
|
/// let term1 = decode(b"hello").unwrap();
|
||||||
/// let list1 = term1.list();
|
/// let list1 = term1.list();
|
||||||
/// assert_eq!(list1.len(), 1);
|
/// assert_eq!(list1.len(), 1);
|
||||||
/// assert_eq!(list1[0].str().unwrap(), b"hello");
|
/// assert_eq!(list1[0].str().unwrap(), "hello");
|
||||||
///
|
///
|
||||||
/// let term2 = decode(b"hello world").unwrap();
|
/// let term2 = decode(b"hello world").unwrap();
|
||||||
/// let list2 = term2.list();
|
/// let list2 = term2.list();
|
||||||
/// assert_eq!(list2.len(), 2);
|
/// assert_eq!(list2.len(), 2);
|
||||||
/// assert_eq!(list2[0].str().unwrap(), b"hello");
|
/// assert_eq!(list2[0].str().unwrap(), "hello");
|
||||||
/// assert_eq!(list2[1].str().unwrap(), b"world");
|
/// assert_eq!(list2[1].str().unwrap(), "world");
|
||||||
/// ```
|
/// ```
|
||||||
pub fn list(&self) -> Vec<Term<'a, '_>> {
|
pub fn list(&self) -> Vec<Term<'a, '_>> {
|
||||||
match self.0.mkref() {
|
match self.0.mkref() {
|
||||||
|
@ -152,7 +170,8 @@ impl<'a, 'b> Term<'a, 'b> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Same as `.list()`, but deconstructs it in a const length array.
|
/// Same as `.list()`, but deconstructs it in a const length array,
|
||||||
|
/// dynamically checking if there are the correct number of items.
|
||||||
/// This allows to directly bind the resulting list into discrete variables.
|
/// This allows to directly bind the resulting list into discrete variables.
|
||||||
///
|
///
|
||||||
/// Example:
|
/// Example:
|
||||||
|
@ -162,12 +181,12 @@ impl<'a, 'b> Term<'a, 'b> {
|
||||||
///
|
///
|
||||||
/// let term1 = decode(b"hello").unwrap();
|
/// let term1 = decode(b"hello").unwrap();
|
||||||
/// let [s1] = term1.list_of().unwrap();
|
/// let [s1] = term1.list_of().unwrap();
|
||||||
/// assert_eq!(s1.str().unwrap(), b"hello");
|
/// assert_eq!(s1.str().unwrap(), "hello");
|
||||||
///
|
///
|
||||||
/// let term2 = decode(b"hello world").unwrap();
|
/// let term2 = decode(b"hello world").unwrap();
|
||||||
/// let [s2a, s2b] = term2.list_of().unwrap();
|
/// let [s2a, s2b] = term2.list_of().unwrap();
|
||||||
/// assert_eq!(s2a.str().unwrap(), b"hello");
|
/// assert_eq!(s2a.str().unwrap(), "hello");
|
||||||
/// assert_eq!(s2b.str().unwrap(), b"world");
|
/// assert_eq!(s2b.str().unwrap(), "world");
|
||||||
/// ```
|
/// ```
|
||||||
pub fn list_of<const N: usize>(&self) -> Result<[Term<'a, '_>; N], TypeError> {
|
pub fn list_of<const N: usize>(&self) -> Result<[Term<'a, '_>; N], TypeError> {
|
||||||
let list = self.list();
|
let list = self.list();
|
||||||
|
@ -188,12 +207,12 @@ impl<'a, 'b> Term<'a, 'b> {
|
||||||
///
|
///
|
||||||
/// let term1 = decode(b"hello world").unwrap();
|
/// let term1 = decode(b"hello world").unwrap();
|
||||||
/// let [s1a, s1b] = term1.list_of_first().unwrap();
|
/// let [s1a, s1b] = term1.list_of_first().unwrap();
|
||||||
/// assert_eq!(s1a.str().unwrap(), b"hello");
|
/// assert_eq!(s1a.str().unwrap(), "hello");
|
||||||
/// assert_eq!(s1b.str().unwrap(), b"world");
|
/// assert_eq!(s1b.str().unwrap(), "world");
|
||||||
///
|
///
|
||||||
/// let term2 = decode(b"hello mighty world").unwrap();
|
/// let term2 = decode(b"hello mighty world").unwrap();
|
||||||
/// let [s2a, s2b] = term2.list_of_first().unwrap();
|
/// let [s2a, s2b] = term2.list_of_first().unwrap();
|
||||||
/// assert_eq!(s2a.str().unwrap(), b"hello");
|
/// assert_eq!(s2a.str().unwrap(), "hello");
|
||||||
/// assert_eq!(s2b.list().len(), 2);
|
/// assert_eq!(s2b.list().len(), 2);
|
||||||
/// assert_eq!(s2b.raw(), b"mighty world");
|
/// assert_eq!(s2b.raw(), b"mighty world");
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -231,7 +250,8 @@ impl<'a, 'b> Term<'a, 'b> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks term is a dictionnary and returns hashmap of inner terms
|
/// Checks term is a dictionnary and returns hashmap of inner terms.
|
||||||
|
/// For convenience, this transforms the keys of the dictionnary into `&str`'s.
|
||||||
///
|
///
|
||||||
/// Example:
|
/// Example:
|
||||||
///
|
///
|
||||||
|
@ -240,12 +260,18 @@ impl<'a, 'b> Term<'a, 'b> {
|
||||||
///
|
///
|
||||||
/// let term = decode(b"{ k1 = v1, k2 = v2 }").unwrap();
|
/// let term = decode(b"{ k1 = v1, k2 = v2 }").unwrap();
|
||||||
/// let dict = term.dict().unwrap();
|
/// let dict = term.dict().unwrap();
|
||||||
/// assert_eq!(dict.get(&b"k1"[..]).unwrap().str().unwrap(), b"v1");
|
/// assert_eq!(dict.get("k1").unwrap().str().unwrap(), "v1");
|
||||||
/// assert_eq!(dict.get(&b"k2"[..]).unwrap().str().unwrap(), b"v2");
|
/// assert_eq!(dict.get("k2").unwrap().str().unwrap(), "v2");
|
||||||
/// ```
|
/// ```
|
||||||
pub fn dict(&self) -> Result<HashMap<&'a [u8], Term<'a, '_>>, TypeError> {
|
pub fn dict(&self) -> Result<HashMap<&'a str, Term<'a, '_>>, TypeError> {
|
||||||
match self.0.mkref() {
|
match self.0.mkref() {
|
||||||
AnyTerm::DictRef(_, d) => Ok(d.iter().map(|(k, t)| (*k, Term(t.mkref()))).collect()),
|
AnyTerm::DictRef(_, d) => {
|
||||||
|
let mut res = HashMap::with_capacity(d.len());
|
||||||
|
for (k, t) in d.iter() {
|
||||||
|
res.insert(std::str::from_utf8(k)?, Term(t.mkref()));
|
||||||
|
}
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
_ => Err(TypeError::WrongType("DICT")),
|
_ => Err(TypeError::WrongType("DICT")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -259,34 +285,34 @@ impl<'a, 'b> Term<'a, 'b> {
|
||||||
/// use nettext::dec::decode;
|
/// use nettext::dec::decode;
|
||||||
///
|
///
|
||||||
/// let term = decode(b"{ k1 = v1, k2 = v2, k3 = v3 }").unwrap();
|
/// let term = decode(b"{ k1 = v1, k2 = v2, k3 = v3 }").unwrap();
|
||||||
/// let [s1, s2] = term.dict_of([b"k1", b"k2"], true).unwrap();
|
/// let [s1, s2] = term.dict_of(["k1", "k2"], true).unwrap();
|
||||||
/// assert_eq!(s1.str().unwrap(), b"v1");
|
/// assert_eq!(s1.str().unwrap(), "v1");
|
||||||
/// assert_eq!(s2.str().unwrap(), b"v2");
|
/// assert_eq!(s2.str().unwrap(), "v2");
|
||||||
///
|
///
|
||||||
/// assert!(term.dict_of([b"k1", b"k2"], false).is_err());
|
/// assert!(term.dict_of([b"k1", b"k2"], false).is_err());
|
||||||
/// ```
|
/// ```
|
||||||
pub fn dict_of<const N: usize>(
|
pub fn dict_of<const N: usize, T: AsRef<[u8]>>(
|
||||||
&self,
|
&self,
|
||||||
keys: [&'static [u8]; N],
|
keys: [T; N],
|
||||||
allow_extra_keys: bool,
|
allow_extra_keys: bool,
|
||||||
) -> Result<[Term<'a, '_>; N], TypeError> {
|
) -> Result<[Term<'a, '_>; N], TypeError> {
|
||||||
match self.0.mkref() {
|
match self.0.mkref() {
|
||||||
AnyTerm::DictRef(_, dict) => {
|
AnyTerm::DictRef(_, dict) => {
|
||||||
// Check all required keys exist in dictionnary
|
// Check all required keys exist in dictionnary
|
||||||
for k in keys.iter() {
|
for k in keys.iter() {
|
||||||
if !dict.contains_key(k) {
|
if !dict.contains_key(k.as_ref()) {
|
||||||
return Err(TypeError::MissingKey(debug(k).to_string()));
|
return Err(TypeError::MissingKey(debug(k.as_ref()).to_string()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !allow_extra_keys {
|
if !allow_extra_keys {
|
||||||
// Check that dictionnary contains no extraneous keys
|
// Check that dictionnary contains no extraneous keys
|
||||||
for k in dict.keys() {
|
for k in dict.keys() {
|
||||||
if !keys.contains(k) {
|
if !keys.iter().any(|k2| k2.as_ref() == *k) {
|
||||||
return Err(TypeError::UnexpectedKey(debug(k).to_string()));
|
return Err(TypeError::UnexpectedKey(debug(k).to_string()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(keys.map(|k| Term(dict.get(k).unwrap().mkref())))
|
Ok(keys.map(|k| Term(dict.get(k.as_ref()).unwrap().mkref())))
|
||||||
}
|
}
|
||||||
_ => Err(TypeError::WrongType("DICT")),
|
_ => Err(TypeError::WrongType("DICT")),
|
||||||
}
|
}
|
||||||
|
@ -302,15 +328,15 @@ impl<'a, 'b> Term<'a, 'b> {
|
||||||
///
|
///
|
||||||
/// let term = decode(b"{ k1 = v1, k2 = v2, k4 = v4 }").unwrap();
|
/// let term = decode(b"{ k1 = v1, k2 = v2, k4 = v4 }").unwrap();
|
||||||
/// let [s1, s2, s3] = term.dict_of_opt([b"k1", b"k2", b"k3"], true).unwrap();
|
/// let [s1, s2, s3] = term.dict_of_opt([b"k1", b"k2", b"k3"], true).unwrap();
|
||||||
/// assert_eq!(s1.unwrap().str().unwrap(), b"v1");
|
/// assert_eq!(s1.unwrap().str().unwrap(), "v1");
|
||||||
/// assert_eq!(s2.unwrap().str().unwrap(), b"v2");
|
/// assert_eq!(s2.unwrap().str().unwrap(), "v2");
|
||||||
/// assert!(s3.is_none());
|
/// assert!(s3.is_none());
|
||||||
///
|
///
|
||||||
/// assert!(term.dict_of_opt([b"k1", b"k2", b"k3"], false).is_err());
|
/// assert!(term.dict_of_opt(["k1", "k2", "k3"], false).is_err());
|
||||||
/// ```
|
/// ```
|
||||||
pub fn dict_of_opt<const N: usize>(
|
pub fn dict_of_opt<const N: usize, T: AsRef<[u8]>>(
|
||||||
&self,
|
&self,
|
||||||
keys: [&'static [u8]; N],
|
keys: [T; N],
|
||||||
allow_extra_keys: bool,
|
allow_extra_keys: bool,
|
||||||
) -> Result<[Option<Term<'a, '_>>; N], TypeError> {
|
) -> Result<[Option<Term<'a, '_>>; N], TypeError> {
|
||||||
match self.0.mkref() {
|
match self.0.mkref() {
|
||||||
|
@ -318,16 +344,123 @@ impl<'a, 'b> Term<'a, 'b> {
|
||||||
if !allow_extra_keys {
|
if !allow_extra_keys {
|
||||||
// Check that dictionnary contains no extraneous keys
|
// Check that dictionnary contains no extraneous keys
|
||||||
for k in dict.keys() {
|
for k in dict.keys() {
|
||||||
if !keys.contains(k) {
|
if !keys.iter().any(|x| x.as_ref() == *k) {
|
||||||
return Err(TypeError::UnexpectedKey(debug(k).to_string()));
|
return Err(TypeError::UnexpectedKey(debug(k).to_string()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(keys.map(|k| dict.get(k).map(|x| Term(x.mkref()))))
|
Ok(keys.map(|k| dict.get(k.as_ref()).map(|x| Term(x.mkref()))))
|
||||||
}
|
}
|
||||||
_ => Err(TypeError::WrongType("DICT")),
|
_ => Err(TypeError::WrongType("DICT")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- TYPE CASTS ----
|
||||||
|
|
||||||
|
/// Try to interpret this str as an i64
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use nettext::dec::decode;
|
||||||
|
///
|
||||||
|
/// let term = decode(b"42").unwrap();
|
||||||
|
/// assert_eq!(term.int().unwrap(), 42);
|
||||||
|
/// ```
|
||||||
|
pub fn int(&self) -> Result<i64, TypeError> {
|
||||||
|
self.str()?
|
||||||
|
.parse::<i64>()
|
||||||
|
.map_err(|_| TypeError::WrongType("INT"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to interpret this string as base64-encoded bytes (uses URL-safe, no-padding encoding)
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use nettext::dec::decode;
|
||||||
|
///
|
||||||
|
/// let term = decode(b"aGVsbG8sIHdvcmxkIQ").unwrap();
|
||||||
|
/// assert_eq!(term.bytes().unwrap(), b"hello, world!");
|
||||||
|
/// ```
|
||||||
|
pub fn bytes(&self) -> Result<Vec<u8>, TypeError> {
|
||||||
|
let encoded = match &self.0 {
|
||||||
|
AnyTerm::Str(s) => s,
|
||||||
|
AnyTerm::List(r, l) if l.iter().all(|x| matches!(x, NonListTerm::Str(_))) => r,
|
||||||
|
_ => return Err(TypeError::WrongType("BYTES")),
|
||||||
|
};
|
||||||
|
base64::decode_config(encoded, base64::URL_SAFE_NO_PAD)
|
||||||
|
.map_err(|_| TypeError::WrongType("BYTES"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to interpret this string as base64-encoded bytes,
|
||||||
|
/// with an exact length.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use nettext::dec::decode;
|
||||||
|
///
|
||||||
|
/// let term = decode(b"aGVsbG8sIHdvcmxkIQ").unwrap();
|
||||||
|
/// assert_eq!(&term.bytes_exact::<13>().unwrap(), b"hello, world!");
|
||||||
|
/// ```
|
||||||
|
pub fn bytes_exact<const N: usize>(&self) -> Result<[u8; N], TypeError> {
|
||||||
|
let bytes = self.bytes()?;
|
||||||
|
let bytes_len = bytes.len();
|
||||||
|
bytes
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| TypeError::WrongLength(bytes_len, N))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- CRYPTO HELPERS ----
|
||||||
|
|
||||||
|
#[cfg(feature = "blake2")]
|
||||||
|
impl<'a, 'b> Term<'a, 'b> {
|
||||||
|
/// Try to interpret this string as a Blake2b512 digest (32-bytes base64 encoded)
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use nettext::dec::decode;
|
||||||
|
///
|
||||||
|
/// let term = decode(b"{
|
||||||
|
/// message = hello,
|
||||||
|
/// hash = 5M-jmj03vjHFlgnoB5cHmcqmihm_qhUTXxZQheAdQaZboeGxRq62vQCStJ6sIUwQPM-jo2WVS7vlL3Sis2IMlA
|
||||||
|
/// }").unwrap();
|
||||||
|
/// let [msg, hash] = term.dict_of(["message", "hash"], false).unwrap();
|
||||||
|
/// assert!(hash.b2sum().unwrap().check(msg.raw()).is_ok());
|
||||||
|
/// ```
|
||||||
|
pub fn b2sum(&self) -> Result<Blake2Sum, TypeError> {
|
||||||
|
Ok(Blake2Sum::from_bytes(self.bytes_exact()?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ed25519-dalek")]
|
||||||
|
impl<'a, 'b> Term<'a, 'b> {
|
||||||
|
/// Try to interpret this string as an ed25519 keypair (64 bytes base64 encoded)
|
||||||
|
pub fn keypair(&self) -> Result<ed25519_dalek::Keypair, TypeError> {
|
||||||
|
let bytes = self.bytes_exact::<64>()?;
|
||||||
|
ed25519_dalek::Keypair::from_bytes(&bytes).map_err(|_| TypeError::WrongType("KEYPAIR"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to interpret this string as an ed25519 public key (32 bytes base64 encoded)
|
||||||
|
pub fn public_key(&self) -> Result<ed25519_dalek::PublicKey, TypeError> {
|
||||||
|
let bytes = self.bytes_exact::<32>()?;
|
||||||
|
ed25519_dalek::PublicKey::from_bytes(&bytes).map_err(|_| TypeError::WrongType("PUBLICKEY"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to interpret this string as an ed25519 secret key (32 bytes base64 encoded)
|
||||||
|
pub fn secret_key(&self) -> Result<ed25519_dalek::SecretKey, TypeError> {
|
||||||
|
let bytes = self.bytes_exact::<32>()?;
|
||||||
|
ed25519_dalek::SecretKey::from_bytes(&bytes).map_err(|_| TypeError::WrongType("SECRETKEY"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to interpret this string as an ed25519 signature (64 bytes base64 encoded)
|
||||||
|
pub fn signature(&self) -> Result<ed25519_dalek::Signature, TypeError> {
|
||||||
|
let bytes = self.bytes_exact::<64>()?;
|
||||||
|
ed25519_dalek::Signature::from_bytes(&bytes).map_err(|_| TypeError::WrongType("SIGNATURE"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- INTERNAL IMPLS ----
|
// ---- INTERNAL IMPLS ----
|
||||||
|
@ -371,7 +504,7 @@ impl<'a, 'b> NonListTerm<'a, 'b> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- DISPLAY REPR = Raw NetText representation ----
|
// ---- DISPLAY REPR = Raw nettext representation ----
|
||||||
|
|
||||||
impl<'a, 'b> std::fmt::Display for AnyTerm<'a, 'b> {
|
impl<'a, 'b> std::fmt::Display for AnyTerm<'a, 'b> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||||
|
@ -391,7 +524,7 @@ impl<'a, 'b> std::fmt::Display for Term<'a, 'b> {
|
||||||
|
|
||||||
// ---- DEBUG REPR ----
|
// ---- DEBUG REPR ----
|
||||||
|
|
||||||
pub fn debug(x: &[u8]) -> &str {
|
pub(crate) fn debug(x: &[u8]) -> &str {
|
||||||
std::str::from_utf8(x).unwrap_or("<invalid ascii>")
|
std::str::from_utf8(x).unwrap_or("<invalid ascii>")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
|
pub mod crypto;
|
||||||
pub mod dec;
|
pub mod dec;
|
||||||
|
|
Loading…
Reference in a new issue