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]
|
||||
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> {
|
||||
/// Indicates that there is trailing garbage at the end of the decoded string
|
||||
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,
|
||||
/// Indicates a syntax error in the decoded term
|
||||
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> {
|
||||
fn from(e: nom::Err<nom::error::Error<&'a [u8]>>) -> DecodeError<'a> {
|
||||
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.
|
||||
pub fn decode(input: &[u8]) -> DecodeResult<'_, Term<'_, 'static>> {
|
||||
/// Decodes a nettext string into the term it represents.
|
||||
pub fn decode(input: &[u8]) -> std::result::Result<Term<'_, 'static>, DecodeError<'_>> {
|
||||
let (rest, term) = decode_term(input)?;
|
||||
let (end, _) = take_while(is_whitespace)(rest)?;
|
||||
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>> {
|
||||
eprintln!("DT: `{}`", debug(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)?;
|
||||
eprintln!("DT3: `{}`", debug(rest));
|
||||
|
||||
if list.len() == 1 {
|
||||
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>> {
|
||||
eprintln!("DNLT: `{}`", debug(input));
|
||||
let (rest, term) = alt((
|
||||
map(decode_str, NonListTerm::Str),
|
||||
map(decode_dict, |(raw, d)| NonListTerm::Dict(raw, d)),
|
||||
))(input)?;
|
||||
eprintln!("DNLTend: `{}` {:?}", debug(rest), term);
|
||||
Ok((rest, term))
|
||||
}
|
||||
|
||||
fn decode_str(input: &[u8]) -> IResult<&'_ [u8], &'_ [u8]> {
|
||||
eprintln!("DS: `{}`", debug(input));
|
||||
let (rest, data) = take_while1(is_string_char)(input)?;
|
||||
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>>);
|
||||
|
||||
fn decode_dict(dict_begin: &[u8]) -> IResult<&'_ [u8], DictType<'_>> {
|
||||
eprintln!("DDbegin: `{}`", debug(dict_begin));
|
||||
let (d, _) = tag(DICT_OPEN)(dict_begin)?;
|
||||
eprintln!("DD2: `{}`", debug(d));
|
||||
let (d, items) = separated_list0(dict_separator, decode_dict_item)(d)?;
|
||||
eprintln!("DD3: `{}`", debug(d));
|
||||
let (d, _) = opt(dict_separator)(d)?;
|
||||
let (d, _) = take_while(is_whitespace)(d)?;
|
||||
eprintln!("DD4: `{}`", debug(d));
|
||||
let (dict_end, _) = tag(DICT_CLOSE)(d)?;
|
||||
eprintln!("DDend: `{}`", debug(dict_end));
|
||||
|
||||
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>)> {
|
||||
eprintln!("DDI: `{}`", debug(d));
|
||||
let (d, _) = take_while(is_whitespace)(d)?;
|
||||
eprintln!("DDI1: `{}`", debug(d));
|
||||
let (d, key) = decode_str(d)?;
|
||||
eprintln!("DDI2: `{}`", debug(d));
|
||||
let (d, _) = take_while(is_whitespace)(d)?;
|
||||
let (d, _) = tag(DICT_ASSIGN)(d)?;
|
||||
eprintln!("DDI3: `{}`", debug(d));
|
||||
let (d, value) = decode_term(d)?;
|
||||
eprintln!("DDI4: `{}`", debug(d));
|
||||
Ok((d, (key, value)))
|
||||
}
|
||||
|
||||
|
|
231
src/dec/mod.rs
231
src/dec/mod.rs
|
@ -2,9 +2,10 @@ mod decode;
|
|||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::crypto::*;
|
||||
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.
|
||||
///
|
||||
|
@ -60,15 +61,30 @@ impl<'a> From<AnyTerm<'a, 'static>> for Term<'a, 'static> {
|
|||
|
||||
// ---- PUBLIC IMPLS ----
|
||||
|
||||
/// The type of errors returned by helper functions on `Term`
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TypeError {
|
||||
/// The term could not be decoded in the given type
|
||||
WrongType(&'static str),
|
||||
/// The term is not an array of the requested length
|
||||
WrongLength(usize, usize),
|
||||
/// The dictionnary is missing a key
|
||||
MissingKey(String),
|
||||
/// The dictionnary contains an invalid key
|
||||
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> {
|
||||
// ---- STRUCTURAL MAPPINGS ----
|
||||
|
||||
/// Get the term's raw representation
|
||||
///
|
||||
/// Example:
|
||||
|
@ -90,15 +106,15 @@ impl<'a, 'b> Term<'a, 'b> {
|
|||
/// ```
|
||||
/// use nettext::dec::decode;
|
||||
///
|
||||
/// let term1 = decode(b" hello ").unwrap();
|
||||
/// assert_eq!(term1.str().unwrap(), b"hello");
|
||||
/// let term1 = decode(b"hello").unwrap();
|
||||
/// assert_eq!(term1.str().unwrap(), "hello");
|
||||
///
|
||||
/// let term2 = decode(b"hello world").unwrap();
|
||||
/// assert!(term2.str().is_err());
|
||||
/// ```
|
||||
pub fn str(&self) -> Result<&'a [u8], TypeError> {
|
||||
pub fn str(&self) -> Result<&'a str, TypeError> {
|
||||
match &self.0 {
|
||||
AnyTerm::Str(s) => Ok(s),
|
||||
AnyTerm::Str(s) => Ok(std::str::from_utf8(s)?),
|
||||
_ => Err(TypeError::WrongType("STR")),
|
||||
}
|
||||
}
|
||||
|
@ -111,16 +127,18 @@ impl<'a, 'b> Term<'a, 'b> {
|
|||
/// ```
|
||||
/// use nettext::dec::decode;
|
||||
///
|
||||
/// let term1 = decode(b" hello world ").unwrap();
|
||||
/// assert_eq!(term1.string().unwrap(), b"hello world");
|
||||
/// let term1 = decode(b"hello world").unwrap();
|
||||
/// assert_eq!(term1.string().unwrap(), "hello world");
|
||||
///
|
||||
/// let term2 = decode(b"hello { a= 5}").unwrap();
|
||||
/// assert!(term2.string().is_err());
|
||||
/// ```
|
||||
pub fn string(&self) -> Result<&'a [u8], TypeError> {
|
||||
pub fn string(&self) -> Result<&'a str, TypeError> {
|
||||
match &self.0 {
|
||||
AnyTerm::Str(s) => Ok(s),
|
||||
AnyTerm::List(r, l) if l.iter().all(|x| matches!(x, NonListTerm::Str(_))) => Ok(r),
|
||||
AnyTerm::Str(s) => Ok(std::str::from_utf8(s)?),
|
||||
AnyTerm::List(r, l) if l.iter().all(|x| matches!(x, NonListTerm::Str(_))) => {
|
||||
Ok(std::str::from_utf8(r)?)
|
||||
}
|
||||
_ => Err(TypeError::WrongType("STRING")),
|
||||
}
|
||||
}
|
||||
|
@ -134,16 +152,16 @@ impl<'a, 'b> Term<'a, 'b> {
|
|||
/// ```
|
||||
/// use nettext::dec::decode;
|
||||
///
|
||||
/// let term1 = decode(b" hello ").unwrap();
|
||||
/// let term1 = decode(b"hello").unwrap();
|
||||
/// let list1 = term1.list();
|
||||
/// 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();
|
||||
/// assert_eq!(list2.len(), 2);
|
||||
/// assert_eq!(list2[0].str().unwrap(), b"hello");
|
||||
/// assert_eq!(list2[1].str().unwrap(), b"world");
|
||||
/// assert_eq!(list2[0].str().unwrap(), "hello");
|
||||
/// assert_eq!(list2[1].str().unwrap(), "world");
|
||||
/// ```
|
||||
pub fn list(&self) -> Vec<Term<'a, '_>> {
|
||||
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.
|
||||
///
|
||||
/// Example:
|
||||
|
@ -160,14 +179,14 @@ impl<'a, 'b> Term<'a, 'b> {
|
|||
/// ```
|
||||
/// use nettext::dec::decode;
|
||||
///
|
||||
/// let term1 = decode(b" hello ").unwrap();
|
||||
/// let term1 = decode(b"hello").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();
|
||||
/// assert_eq!(s2a.str().unwrap(), b"hello");
|
||||
/// assert_eq!(s2b.str().unwrap(), b"world");
|
||||
/// assert_eq!(s2a.str().unwrap(), "hello");
|
||||
/// assert_eq!(s2b.str().unwrap(), "world");
|
||||
/// ```
|
||||
pub fn list_of<const N: usize>(&self) -> Result<[Term<'a, '_>; N], TypeError> {
|
||||
let list = self.list();
|
||||
|
@ -186,14 +205,14 @@ impl<'a, 'b> Term<'a, 'b> {
|
|||
/// ```
|
||||
/// use nettext::dec::decode;
|
||||
///
|
||||
/// let term1 = decode(b" hello world ").unwrap();
|
||||
/// let term1 = decode(b"hello world").unwrap();
|
||||
/// let [s1a, s1b] = term1.list_of_first().unwrap();
|
||||
/// assert_eq!(s1a.str().unwrap(), b"hello");
|
||||
/// assert_eq!(s1b.str().unwrap(), b"world");
|
||||
/// assert_eq!(s1a.str().unwrap(), "hello");
|
||||
/// 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();
|
||||
/// assert_eq!(s2a.str().unwrap(), b"hello");
|
||||
/// assert_eq!(s2a.str().unwrap(), "hello");
|
||||
/// assert_eq!(s2b.list().len(), 2);
|
||||
/// 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:
|
||||
///
|
||||
|
@ -240,12 +260,18 @@ impl<'a, 'b> Term<'a, 'b> {
|
|||
///
|
||||
/// let term = decode(b"{ k1 = v1, k2 = v2 }").unwrap();
|
||||
/// let dict = term.dict().unwrap();
|
||||
/// assert_eq!(dict.get(&b"k1"[..]).unwrap().str().unwrap(), b"v1");
|
||||
/// assert_eq!(dict.get(&b"k2"[..]).unwrap().str().unwrap(), b"v2");
|
||||
/// assert_eq!(dict.get("k1").unwrap().str().unwrap(), "v1");
|
||||
/// 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() {
|
||||
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")),
|
||||
}
|
||||
}
|
||||
|
@ -259,34 +285,34 @@ impl<'a, 'b> Term<'a, 'b> {
|
|||
/// use nettext::dec::decode;
|
||||
///
|
||||
/// let term = decode(b"{ k1 = v1, k2 = v2, k3 = v3 }").unwrap();
|
||||
/// let [s1, s2] = term.dict_of([b"k1", b"k2"], true).unwrap();
|
||||
/// assert_eq!(s1.str().unwrap(), b"v1");
|
||||
/// assert_eq!(s2.str().unwrap(), b"v2");
|
||||
/// let [s1, s2] = term.dict_of(["k1", "k2"], true).unwrap();
|
||||
/// assert_eq!(s1.str().unwrap(), "v1");
|
||||
/// assert_eq!(s2.str().unwrap(), "v2");
|
||||
///
|
||||
/// 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,
|
||||
keys: [&'static [u8]; N],
|
||||
keys: [T; N],
|
||||
allow_extra_keys: bool,
|
||||
) -> Result<[Term<'a, '_>; N], TypeError> {
|
||||
match self.0.mkref() {
|
||||
AnyTerm::DictRef(_, dict) => {
|
||||
// Check all required keys exist in dictionnary
|
||||
for k in keys.iter() {
|
||||
if !dict.contains_key(k) {
|
||||
return Err(TypeError::MissingKey(debug(k).to_string()));
|
||||
if !dict.contains_key(k.as_ref()) {
|
||||
return Err(TypeError::MissingKey(debug(k.as_ref()).to_string()));
|
||||
}
|
||||
}
|
||||
if !allow_extra_keys {
|
||||
// Check that dictionnary contains no extraneous 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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
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")),
|
||||
}
|
||||
|
@ -302,15 +328,15 @@ impl<'a, 'b> Term<'a, 'b> {
|
|||
///
|
||||
/// 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();
|
||||
/// assert_eq!(s1.unwrap().str().unwrap(), b"v1");
|
||||
/// assert_eq!(s2.unwrap().str().unwrap(), b"v2");
|
||||
/// assert_eq!(s1.unwrap().str().unwrap(), "v1");
|
||||
/// assert_eq!(s2.unwrap().str().unwrap(), "v2");
|
||||
/// 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,
|
||||
keys: [&'static [u8]; N],
|
||||
keys: [T; N],
|
||||
allow_extra_keys: bool,
|
||||
) -> Result<[Option<Term<'a, '_>>; N], TypeError> {
|
||||
match self.0.mkref() {
|
||||
|
@ -318,16 +344,123 @@ impl<'a, 'b> Term<'a, 'b> {
|
|||
if !allow_extra_keys {
|
||||
// Check that dictionnary contains no extraneous 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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
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")),
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 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 ----
|
||||
|
@ -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> {
|
||||
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 ----
|
||||
|
||||
pub fn debug(x: &[u8]) -> &str {
|
||||
pub(crate) fn debug(x: &[u8]) -> &str {
|
||||
std::str::from_utf8(x).unwrap_or("<invalid ascii>")
|
||||
}
|
||||
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
pub mod crypto;
|
||||
pub mod dec;
|
||||
|
|
Loading…
Reference in a new issue