Compare commits
No commits in common. "eae4a0443a1f927a988323f146bdd557745daafe" and "52f6bd177c6d692f6522e697cd34d786701d4c18" have entirely different histories.
eae4a0443a
...
52f6bd177c
|
@ -2,7 +2,7 @@
|
||||||
name = "nettext"
|
name = "nettext"
|
||||||
description = "A text-based data format for cryptographic network protocols"
|
description = "A text-based data format for cryptographic network protocols"
|
||||||
authors = ["Alex Auvolat <alex@adnab.me>"]
|
authors = ["Alex Auvolat <alex@adnab.me>"]
|
||||||
version = "0.3.2"
|
version = "0.3.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "AGPL-3.0"
|
license = "AGPL-3.0"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@ -12,7 +12,6 @@ readme = "README.md"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nom = "7.1"
|
nom = "7.1"
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
hex = "0.4"
|
|
||||||
err-derive = "0.3"
|
err-derive = "0.3"
|
||||||
|
|
||||||
dryoc = { version = "0.4", optional = true }
|
dryoc = { version = "0.4", optional = true }
|
||||||
|
|
|
@ -378,10 +378,8 @@ impl<'a, 'b> Term<'a, 'b> {
|
||||||
/// ```
|
/// ```
|
||||||
pub fn list(&self) -> Result<Vec<Term<'a, '_>>, TypeError> {
|
pub fn list(&self) -> Result<Vec<Term<'a, '_>>, TypeError> {
|
||||||
match self.0.mkref() {
|
match self.0.mkref() {
|
||||||
AnyTerm::ListRef(_r, l) => {
|
AnyTerm::ListRef(_r, l) => Ok(l.iter().map(|x| Term(x.mkref().into())).collect::<Vec<_>>()),
|
||||||
Ok(l.iter().map(|x| Term(x.mkref().into())).collect::<Vec<_>>())
|
_ => Err(TypeError::WrongType("LIST")),
|
||||||
}
|
|
||||||
_ => Err(TypeError::WrongType("LIST")),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
210
src/enc/mod.rs
210
src/enc/mod.rs
|
@ -23,7 +23,7 @@ use std::borrow::{Borrow, Cow};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::dec::{self, decode};
|
use crate::dec::{self, decode};
|
||||||
use crate::{is_string_char, is_whitespace, switch64, BytesEncoding};
|
use crate::{is_string_char, is_whitespace};
|
||||||
|
|
||||||
pub use error::Error;
|
pub use error::Error;
|
||||||
|
|
||||||
|
@ -96,71 +96,6 @@ pub fn raw(bytes: &[u8]) -> Result<'_> {
|
||||||
Ok(Term(T::Str(bytes)))
|
Ok(Term(T::Str(bytes)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Term corresponding to a byte slice,
|
|
||||||
/// encoding using base64 url-safe encoding without padding.
|
|
||||||
/// Since empty strings are not possible in nettext,
|
|
||||||
/// an empty byte string is encoded as an empty list (`[]`).
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use nettext::enc::*;
|
|
||||||
///
|
|
||||||
/// assert_eq!(bytes(b"hello, world!").encode(), b"aGVsbG8sIHdvcmxkIQ");
|
|
||||||
/// ```
|
|
||||||
pub fn bytes(bytes: &[u8]) -> Term<'static> {
|
|
||||||
bytes_format(bytes, BytesEncoding::Base64 { split: false })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Same as `bytes()`, but splits the byte slice in 48-byte chunks
|
|
||||||
/// and encodes each chunk separately, putting them in a sequence of terms.
|
|
||||||
/// Usefull for long byte slices to have cleaner representations,
|
|
||||||
/// mainly usefull for dictionnary keys.
|
|
||||||
pub fn bytes_split(bytes: &[u8]) -> Term<'static> {
|
|
||||||
bytes_format(bytes, BytesEncoding::Base64 { split: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn bytes_format(bytes: &[u8], encoding: BytesEncoding) -> Term<'static> {
|
|
||||||
match encoding {
|
|
||||||
BytesEncoding::Base64 { .. } | BytesEncoding::Hex { .. } if bytes.is_empty() => {
|
|
||||||
Term(T::List(vec![]))
|
|
||||||
}
|
|
||||||
BytesEncoding::Base64 { split: false } => Term(T::OwnedStr(
|
|
||||||
base64::encode_config(bytes, base64::URL_SAFE_NO_PAD).into_bytes(),
|
|
||||||
)),
|
|
||||||
BytesEncoding::Base64 { split: true } => {
|
|
||||||
let chunks = bytes
|
|
||||||
.chunks(48)
|
|
||||||
.map(|b| {
|
|
||||||
T::OwnedStr(base64::encode_config(b, base64::URL_SAFE_NO_PAD).into_bytes())
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
if chunks.len() > 1 {
|
|
||||||
Term(T::Seq(chunks))
|
|
||||||
} else {
|
|
||||||
Term(chunks.into_iter().next().unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BytesEncoding::Hex { split: false } => Term(T::OwnedStr(hex::encode(bytes).into_bytes())),
|
|
||||||
BytesEncoding::Hex { split: true } => {
|
|
||||||
let chunks = bytes
|
|
||||||
.chunks(32)
|
|
||||||
.map(|b| T::OwnedStr(hex::encode(b).into_bytes()))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
if chunks.len() > 1 {
|
|
||||||
Term(T::Seq(chunks))
|
|
||||||
} else {
|
|
||||||
Term(chunks.into_iter().next().unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BytesEncoding::Switch64 { allow_whitespace } => {
|
|
||||||
Term(T::OwnedStr(switch64::encode(bytes, allow_whitespace)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- composed terms -----
|
|
||||||
|
|
||||||
/// Term corresponding to a sequence of terms. Subsequences are banned and will raise an error.
|
/// Term corresponding to a sequence of terms. Subsequences are banned and will raise an error.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -229,6 +164,38 @@ pub fn dict<'a, I: IntoIterator<Item = (&'a str, Term<'a>)>>(pairs: I) -> Result
|
||||||
Ok(Term(T::Dict(tmp)))
|
Ok(Term(T::Dict(tmp)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Term corresponding to a byte slice,
|
||||||
|
/// encoding using base64 url-safe encoding without padding
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use nettext::enc::*;
|
||||||
|
///
|
||||||
|
/// assert_eq!(bytes(b"hello, world!").encode(), b"aGVsbG8sIHdvcmxkIQ");
|
||||||
|
/// ```
|
||||||
|
pub fn bytes(bytes: &[u8]) -> Term<'static> {
|
||||||
|
Term(T::OwnedStr(
|
||||||
|
base64::encode_config(bytes, base64::URL_SAFE_NO_PAD).into_bytes(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Same as `bytes()`, but splits the byte slice in 48-byte chunks
|
||||||
|
/// and encodes each chunk separately, putting them in a sequence of terms.
|
||||||
|
/// Usefull for long byte slices to have cleaner representations,
|
||||||
|
/// mainly usefull for dictionnary keys.
|
||||||
|
pub fn bytes_split(bytes: &[u8]) -> Term<'static> {
|
||||||
|
let chunks = bytes
|
||||||
|
.chunks(48)
|
||||||
|
.map(|b| T::OwnedStr(base64::encode_config(b, base64::URL_SAFE_NO_PAD).into_bytes()))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if chunks.len() > 1 {
|
||||||
|
Term(T::Seq(chunks))
|
||||||
|
} else {
|
||||||
|
Term(chunks.into_iter().next().unwrap_or(T::Str(b".")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> Term<'a> {
|
impl<'a> Term<'a> {
|
||||||
/// Append a term to an existing term.
|
/// Append a term to an existing term.
|
||||||
/// Transforms the initial term into a seq if necessary.
|
/// Transforms the initial term into a seq if necessary.
|
||||||
|
@ -314,9 +281,9 @@ impl<'a> T<'a> {
|
||||||
buf.extend_from_slice(b"{}");
|
buf.extend_from_slice(b"{}");
|
||||||
} else if d.len() == 1 {
|
} else if d.len() == 1 {
|
||||||
let (k, v) = d.into_iter().next().unwrap();
|
let (k, v) = d.into_iter().next().unwrap();
|
||||||
buf.extend_from_slice(b"{ ");
|
buf.extend_from_slice(b"{ ");
|
||||||
buf.extend_from_slice(k.borrow());
|
buf.extend_from_slice(k.borrow());
|
||||||
buf.extend_from_slice(b" = ");
|
buf.extend_from_slice(b" = ");
|
||||||
v.encode_aux(buf, indent + 2, false);
|
v.encode_aux(buf, indent + 2, false);
|
||||||
buf.extend_from_slice(b" }");
|
buf.extend_from_slice(b" }");
|
||||||
} else {
|
} else {
|
||||||
|
@ -340,32 +307,29 @@ impl<'a> T<'a> {
|
||||||
buf.push(b'}');
|
buf.push(b'}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
T::List(l) => {
|
T::List(l) => {
|
||||||
if l.len() == 0 {
|
if l.len() == 0 {
|
||||||
buf.extend_from_slice(b"[]");
|
buf.extend_from_slice(b"[]");
|
||||||
} else if l.len() == 1 {
|
} else if l.len() == 1 {
|
||||||
buf.extend_from_slice(b"[ ");
|
buf.extend_from_slice(b"[ ");
|
||||||
l.into_iter()
|
l.into_iter().next().unwrap().encode_aux(buf, indent + 2, false);
|
||||||
.next()
|
buf.extend_from_slice(b" ]");
|
||||||
.unwrap()
|
} else {
|
||||||
.encode_aux(buf, indent + 2, false);
|
let indent2 = indent + 2;
|
||||||
buf.extend_from_slice(b" ]");
|
buf.extend_from_slice(b"[\n");
|
||||||
} else {
|
for item in l {
|
||||||
let indent2 = indent + 2;
|
for _ in 0..indent2 {
|
||||||
buf.extend_from_slice(b"[\n");
|
buf.push(b' ');
|
||||||
for item in l {
|
}
|
||||||
for _ in 0..indent2 {
|
item.encode_aux(buf, indent2, false);
|
||||||
buf.push(b' ');
|
buf.extend_from_slice(b",\n");
|
||||||
}
|
}
|
||||||
item.encode_aux(buf, indent2, false);
|
for _ in 0..indent {
|
||||||
buf.extend_from_slice(b",\n");
|
buf.push(b' ');
|
||||||
}
|
}
|
||||||
for _ in 0..indent {
|
buf.push(b']');
|
||||||
buf.push(b' ');
|
}
|
||||||
}
|
}
|
||||||
buf.push(b']');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
T::Seq(l) => {
|
T::Seq(l) => {
|
||||||
let indent2 = indent + 2;
|
let indent2 = indent + 2;
|
||||||
for (i, v) in l.into_iter().enumerate() {
|
for (i, v) in l.into_iter().enumerate() {
|
||||||
|
@ -388,30 +352,30 @@ impl<'a> T<'a> {
|
||||||
T::Str(s) => buf.extend_from_slice(s),
|
T::Str(s) => buf.extend_from_slice(s),
|
||||||
T::OwnedStr(s) => buf.extend_from_slice(&s),
|
T::OwnedStr(s) => buf.extend_from_slice(&s),
|
||||||
T::Dict(mut d) => {
|
T::Dict(mut d) => {
|
||||||
buf.push(b'{');
|
buf.push(b'{');
|
||||||
let mut keys = d.keys().cloned().collect::<Vec<_>>();
|
let mut keys = d.keys().cloned().collect::<Vec<_>>();
|
||||||
keys.sort();
|
keys.sort();
|
||||||
for (i, k) in keys.into_iter().enumerate() {
|
for (i, k) in keys.into_iter().enumerate() {
|
||||||
|
if i > 0 {
|
||||||
|
buf.push(b',');
|
||||||
|
}
|
||||||
|
let v = d.remove(&k).unwrap();
|
||||||
|
buf.extend_from_slice(k.borrow());
|
||||||
|
buf.push(b'=');
|
||||||
|
v.encode_concise_aux(buf);
|
||||||
|
}
|
||||||
|
buf.push(b'}');
|
||||||
|
}
|
||||||
|
T::List(l) => {
|
||||||
|
buf.push(b'[');
|
||||||
|
for (i,item) in l.into_iter().enumerate() {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
buf.push(b',');
|
buf.push(b',');
|
||||||
}
|
}
|
||||||
let v = d.remove(&k).unwrap();
|
item.encode_concise_aux(buf);
|
||||||
buf.extend_from_slice(k.borrow());
|
}
|
||||||
buf.push(b'=');
|
buf.push(b']');
|
||||||
v.encode_concise_aux(buf);
|
}
|
||||||
}
|
|
||||||
buf.push(b'}');
|
|
||||||
}
|
|
||||||
T::List(l) => {
|
|
||||||
buf.push(b'[');
|
|
||||||
for (i, item) in l.into_iter().enumerate() {
|
|
||||||
if i > 0 {
|
|
||||||
buf.push(b',');
|
|
||||||
}
|
|
||||||
item.encode_concise_aux(buf);
|
|
||||||
}
|
|
||||||
buf.push(b']');
|
|
||||||
}
|
|
||||||
T::Seq(l) => {
|
T::Seq(l) => {
|
||||||
for (i, v) in l.into_iter().enumerate() {
|
for (i, v) in l.into_iter().enumerate() {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
|
@ -426,15 +390,18 @@ impl<'a> T<'a> {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use crate::debug;
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::debug;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn complex1() {
|
fn complex1() {
|
||||||
let input = seq([
|
let input = seq([
|
||||||
string("HELLO").unwrap(),
|
string("HELLO").unwrap(),
|
||||||
string("alexhelloworld").unwrap(),
|
string("alexhelloworld").unwrap(),
|
||||||
list([string("dude").unwrap(), string("why").unwrap()]),
|
list([
|
||||||
|
string("dude").unwrap(),
|
||||||
|
string("why").unwrap(),
|
||||||
|
]),
|
||||||
dict([
|
dict([
|
||||||
("from", string("jxx").unwrap()),
|
("from", string("jxx").unwrap()),
|
||||||
("subject", string("hello").unwrap()),
|
("subject", string("hello").unwrap()),
|
||||||
|
@ -453,14 +420,17 @@ mod tests {
|
||||||
subject = hello,
|
subject = hello,
|
||||||
}";
|
}";
|
||||||
assert_eq!(debug(&input.encode()), expected);
|
assert_eq!(debug(&input.encode()), expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn complex1_concise() {
|
fn complex1_concise() {
|
||||||
let input = seq([
|
let input = seq([
|
||||||
string("HELLO").unwrap(),
|
string("HELLO").unwrap(),
|
||||||
string("alexhelloworld").unwrap(),
|
string("alexhelloworld").unwrap(),
|
||||||
list([string("dude").unwrap(), string("why").unwrap()]),
|
list([
|
||||||
|
string("dude").unwrap(),
|
||||||
|
string("why").unwrap(),
|
||||||
|
]),
|
||||||
dict([
|
dict([
|
||||||
("from", string("jxx").unwrap()),
|
("from", string("jxx").unwrap()),
|
||||||
("subject", string("hello").unwrap()),
|
("subject", string("hello").unwrap()),
|
||||||
|
|
37
src/lib.rs
37
src/lib.rs
|
@ -87,7 +87,6 @@
|
||||||
|
|
||||||
pub mod dec;
|
pub mod dec;
|
||||||
pub mod enc;
|
pub mod enc;
|
||||||
pub mod switch64;
|
|
||||||
|
|
||||||
#[cfg(feature = "dryoc")]
|
#[cfg(feature = "dryoc")]
|
||||||
pub mod crypto;
|
pub mod crypto;
|
||||||
|
@ -95,35 +94,6 @@ pub mod crypto;
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
pub mod serde;
|
pub mod serde;
|
||||||
|
|
||||||
/// Possible encodings for byte strings in NetText
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub enum BytesEncoding {
|
|
||||||
/// Base64 encoding (default)
|
|
||||||
Base64 { split: bool },
|
|
||||||
/// Hexadecimal encoding
|
|
||||||
Hex { split: bool },
|
|
||||||
/// Switch64 encoding, a mix of plain text and base64
|
|
||||||
Switch64 { allow_whitespace: bool },
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for BytesEncoding {
|
|
||||||
fn default() -> Self {
|
|
||||||
BytesEncoding::Base64 { split: true }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BytesEncoding {
|
|
||||||
pub fn without_whitespace(&self) -> Self {
|
|
||||||
match self {
|
|
||||||
BytesEncoding::Base64 { .. } => BytesEncoding::Base64 { split: false },
|
|
||||||
BytesEncoding::Hex { .. } => BytesEncoding::Hex { split: false },
|
|
||||||
BytesEncoding::Switch64 { .. } => BytesEncoding::Switch64 {
|
|
||||||
allow_whitespace: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- syntactic elements of the data format ----
|
// ---- syntactic elements of the data format ----
|
||||||
|
|
||||||
pub(crate) const DICT_OPEN: u8 = b'{';
|
pub(crate) const DICT_OPEN: u8 = b'{';
|
||||||
|
@ -133,17 +103,12 @@ pub(crate) const DICT_DELIM: u8 = b',';
|
||||||
pub(crate) const LIST_OPEN: u8 = b'[';
|
pub(crate) const LIST_OPEN: u8 = b'[';
|
||||||
pub(crate) const LIST_CLOSE: u8 = b']';
|
pub(crate) const LIST_CLOSE: u8 = b']';
|
||||||
pub(crate) const LIST_DELIM: u8 = b',';
|
pub(crate) const LIST_DELIM: u8 = b',';
|
||||||
pub(crate) const STR_EXTRA_CHARS: &[u8] = b"._-+*?@:/\\";
|
pub(crate) const STR_EXTRA_CHARS: &[u8] = b"._-+*?@:";
|
||||||
|
|
||||||
pub(crate) const SWITCH64_SEPARATOR: u8 = b'\\';
|
|
||||||
pub(crate) const SWITCH64_EXTRA_CHARS: &[u8] = b"._-+*?@:/";
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn is_string_char(c: u8) -> bool {
|
pub(crate) fn is_string_char(c: u8) -> bool {
|
||||||
c.is_ascii_alphanumeric() || STR_EXTRA_CHARS.contains(&c)
|
c.is_ascii_alphanumeric() || STR_EXTRA_CHARS.contains(&c)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn is_whitespace(c: u8) -> bool {
|
pub(crate) fn is_whitespace(c: u8) -> bool {
|
||||||
c.is_ascii_whitespace()
|
c.is_ascii_whitespace()
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ use crate::serde::error::{Error, Result};
|
||||||
pub struct Deserializer<'de, 'a>(Term<'de, 'a>);
|
pub struct Deserializer<'de, 'a>(Term<'de, 'a>);
|
||||||
|
|
||||||
impl<'de, 'a> Deserializer<'de, 'a> {
|
impl<'de, 'a> Deserializer<'de, 'a> {
|
||||||
pub fn from_term(input: &'a Term<'de, 'a>) -> Deserializer<'de, 'a> {
|
fn from_term(input: &'a Term<'de, 'a>) -> Deserializer<'de, 'a> {
|
||||||
Deserializer(Term(input.0.mkref()))
|
Deserializer(Term(input.0.mkref()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ mod de;
|
||||||
mod error;
|
mod error;
|
||||||
mod ser;
|
mod ser;
|
||||||
|
|
||||||
pub use crate::BytesEncoding;
|
|
||||||
pub use de::{from_bytes, from_term, Deserializer};
|
pub use de::{from_bytes, from_term, Deserializer};
|
||||||
pub use error::{Error, Result};
|
pub use error::{Error, Result};
|
||||||
pub use ser::{to_bytes, to_term, Serializer};
|
pub use ser::{to_bytes, to_term, Serializer};
|
||||||
|
@ -12,9 +11,9 @@ pub use ser::{to_bytes, to_term, Serializer};
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::debug;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use crate::debug;
|
||||||
|
|
||||||
fn test_bidir<T: Serialize + for<'de> Deserialize<'de> + PartialEq + std::fmt::Debug>(
|
fn test_bidir<T: Serialize + for<'de> Deserialize<'de> + PartialEq + std::fmt::Debug>(
|
||||||
input: T,
|
input: T,
|
||||||
|
@ -32,17 +31,6 @@ mod tests {
|
||||||
eprintln!("Serialized (concise): {}", ser_concise);
|
eprintln!("Serialized (concise): {}", ser_concise);
|
||||||
assert_eq!(ser_concise, expected_concise);
|
assert_eq!(ser_concise, expected_concise);
|
||||||
assert_eq!(from_bytes::<T>(ser_concise.as_bytes()).unwrap(), input);
|
assert_eq!(from_bytes::<T>(ser_concise.as_bytes()).unwrap(), input);
|
||||||
|
|
||||||
let ser_str_hex = input
|
|
||||||
.serialize(&mut Serializer {
|
|
||||||
string_format: BytesEncoding::Switch64 {
|
|
||||||
allow_whitespace: true,
|
|
||||||
},
|
|
||||||
bytes_format: BytesEncoding::Hex { split: true },
|
|
||||||
})
|
|
||||||
.unwrap()
|
|
||||||
.encode();
|
|
||||||
panic!("{}", debug(&ser_str_hex));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -108,7 +96,8 @@ mod tests {
|
||||||
E::Struct { a: 1 },
|
E::Struct { a: 1 },
|
||||||
E::Tuple(3, 2),
|
E::Tuple(3, 2),
|
||||||
];
|
];
|
||||||
let expected = r#"[E.Unit,E.Unit,E.Newtype 1,E.Tuple 1 2,E.Struct {a=1},E.Tuple 3 2]"#;
|
let expected =
|
||||||
|
r#"[E.Unit,E.Unit,E.Newtype 1,E.Tuple 1 2,E.Struct {a=1},E.Tuple 3 2]"#;
|
||||||
test_bidir(input, expected);
|
test_bidir(input, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,22 +2,17 @@ use serde::{ser, Serialize};
|
||||||
|
|
||||||
use crate::enc::*;
|
use crate::enc::*;
|
||||||
use crate::serde::error::{Error, Result};
|
use crate::serde::error::{Error, Result};
|
||||||
use crate::BytesEncoding;
|
|
||||||
use serde::ser::Error as SerError;
|
use serde::ser::Error as SerError;
|
||||||
|
|
||||||
/// Serde serializer for nettext
|
/// Serde serializer for nettext
|
||||||
#[derive(Clone, Copy, Default)]
|
pub struct Serializer;
|
||||||
pub struct Serializer {
|
|
||||||
pub string_format: BytesEncoding,
|
|
||||||
pub bytes_format: BytesEncoding,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Serialize value to nettext encoder term
|
/// Serialize value to nettext encoder term
|
||||||
pub fn to_term<T>(value: &T) -> Result<Term<'static>>
|
pub fn to_term<T>(value: &T) -> Result<Term<'static>>
|
||||||
where
|
where
|
||||||
T: Serialize,
|
T: Serialize,
|
||||||
{
|
{
|
||||||
value.serialize(&mut Serializer::default())
|
value.serialize(&mut Serializer)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Serialize value to nettext
|
/// Serialize value to nettext
|
||||||
|
@ -25,7 +20,7 @@ pub fn to_bytes<T>(value: &T) -> Result<Vec<u8>>
|
||||||
where
|
where
|
||||||
T: Serialize,
|
T: Serialize,
|
||||||
{
|
{
|
||||||
Ok(value.serialize(&mut Serializer::default())?.encode())
|
Ok(value.serialize(&mut Serializer)?.encode())
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ser::Serializer for &'a mut Serializer {
|
impl<'a> ser::Serializer for &'a mut Serializer {
|
||||||
|
@ -94,11 +89,11 @@ impl<'a> ser::Serializer for &'a mut Serializer {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize_str(self, v: &str) -> Result<Self::Ok> {
|
fn serialize_str(self, v: &str) -> Result<Self::Ok> {
|
||||||
Ok(bytes_format(v.as_bytes(), self.string_format))
|
Ok(bytes(v.as_bytes()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize_bytes(self, v: &[u8]) -> Result<Self::Ok> {
|
fn serialize_bytes(self, v: &[u8]) -> Result<Self::Ok> {
|
||||||
Ok(bytes_format(v, self.bytes_format))
|
Ok(bytes(v))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize_none(self) -> Result<Self::Ok> {
|
fn serialize_none(self) -> Result<Self::Ok> {
|
||||||
|
@ -153,16 +148,12 @@ impl<'a> ser::Serializer for &'a mut Serializer {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq> {
|
fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq> {
|
||||||
Ok(ListSerializer {
|
Ok(ListSerializer { items: vec![] })
|
||||||
items: vec![],
|
|
||||||
ser: *self,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple> {
|
fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple> {
|
||||||
Ok(SeqSerializer {
|
Ok(SeqSerializer {
|
||||||
items: Vec::with_capacity(len),
|
items: Vec::with_capacity(len),
|
||||||
ser: *self,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,7 +164,7 @@ impl<'a> ser::Serializer for &'a mut Serializer {
|
||||||
) -> Result<Self::SerializeTupleStruct> {
|
) -> Result<Self::SerializeTupleStruct> {
|
||||||
let mut items = Vec::with_capacity(len + 1);
|
let mut items = Vec::with_capacity(len + 1);
|
||||||
items.push(string(name)?);
|
items.push(string(name)?);
|
||||||
Ok(SeqSerializer { items, ser: *self })
|
Ok(SeqSerializer { items })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize_tuple_variant(
|
fn serialize_tuple_variant(
|
||||||
|
@ -185,14 +176,13 @@ impl<'a> ser::Serializer for &'a mut Serializer {
|
||||||
) -> Result<Self::SerializeTupleVariant> {
|
) -> Result<Self::SerializeTupleVariant> {
|
||||||
let mut items = Vec::with_capacity(len + 1);
|
let mut items = Vec::with_capacity(len + 1);
|
||||||
items.push(string_owned(format!("{}.{}", name, variant))?);
|
items.push(string_owned(format!("{}.{}", name, variant))?);
|
||||||
Ok(SeqSerializer { items, ser: *self })
|
Ok(SeqSerializer { items })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap> {
|
fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap> {
|
||||||
Ok(MapSerializer {
|
Ok(MapSerializer {
|
||||||
next: None,
|
next: None,
|
||||||
fields: vec![],
|
fields: vec![],
|
||||||
ser: *self,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,7 +190,6 @@ impl<'a> ser::Serializer for &'a mut Serializer {
|
||||||
Ok(StructSerializer {
|
Ok(StructSerializer {
|
||||||
name,
|
name,
|
||||||
fields: Vec::with_capacity(len),
|
fields: Vec::with_capacity(len),
|
||||||
ser: *self,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,7 +204,6 @@ impl<'a> ser::Serializer for &'a mut Serializer {
|
||||||
name,
|
name,
|
||||||
variant,
|
variant,
|
||||||
fields: Vec::with_capacity(len),
|
fields: Vec::with_capacity(len),
|
||||||
ser: *self,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -224,7 +212,6 @@ impl<'a> ser::Serializer for &'a mut Serializer {
|
||||||
|
|
||||||
pub struct SeqSerializer {
|
pub struct SeqSerializer {
|
||||||
items: Vec<Term<'static>>,
|
items: Vec<Term<'static>>,
|
||||||
ser: Serializer,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ser::SerializeTuple for SeqSerializer {
|
impl ser::SerializeTuple for SeqSerializer {
|
||||||
|
@ -235,7 +222,7 @@ impl ser::SerializeTuple for SeqSerializer {
|
||||||
where
|
where
|
||||||
T: ?Sized + Serialize,
|
T: ?Sized + Serialize,
|
||||||
{
|
{
|
||||||
self.items.push(value.serialize(&mut self.ser)?);
|
self.items.push(value.serialize(&mut Serializer)?);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,7 +239,7 @@ impl ser::SerializeTupleStruct for SeqSerializer {
|
||||||
where
|
where
|
||||||
T: ?Sized + Serialize,
|
T: ?Sized + Serialize,
|
||||||
{
|
{
|
||||||
self.items.push(value.serialize(&mut self.ser)?);
|
self.items.push(value.serialize(&mut Serializer)?);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -269,7 +256,7 @@ impl ser::SerializeTupleVariant for SeqSerializer {
|
||||||
where
|
where
|
||||||
T: ?Sized + Serialize,
|
T: ?Sized + Serialize,
|
||||||
{
|
{
|
||||||
self.items.push(value.serialize(&mut self.ser)?);
|
self.items.push(value.serialize(&mut Serializer)?);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,7 +267,6 @@ impl ser::SerializeTupleVariant for SeqSerializer {
|
||||||
|
|
||||||
pub struct ListSerializer {
|
pub struct ListSerializer {
|
||||||
items: Vec<Term<'static>>,
|
items: Vec<Term<'static>>,
|
||||||
ser: Serializer,
|
|
||||||
}
|
}
|
||||||
impl ser::SerializeSeq for ListSerializer {
|
impl ser::SerializeSeq for ListSerializer {
|
||||||
type Ok = Term<'static>;
|
type Ok = Term<'static>;
|
||||||
|
@ -290,7 +276,7 @@ impl ser::SerializeSeq for ListSerializer {
|
||||||
where
|
where
|
||||||
T: ?Sized + Serialize,
|
T: ?Sized + Serialize,
|
||||||
{
|
{
|
||||||
self.items.push(value.serialize(&mut self.ser)?);
|
self.items.push(value.serialize(&mut Serializer)?);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -302,7 +288,6 @@ impl ser::SerializeSeq for ListSerializer {
|
||||||
pub struct MapSerializer {
|
pub struct MapSerializer {
|
||||||
next: Option<Vec<u8>>,
|
next: Option<Vec<u8>>,
|
||||||
fields: Vec<(Vec<u8>, Term<'static>)>,
|
fields: Vec<(Vec<u8>, Term<'static>)>,
|
||||||
ser: Serializer,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ser::SerializeMap for MapSerializer {
|
impl ser::SerializeMap for MapSerializer {
|
||||||
|
@ -313,11 +298,7 @@ impl ser::SerializeMap for MapSerializer {
|
||||||
where
|
where
|
||||||
T: ?Sized + Serialize,
|
T: ?Sized + Serialize,
|
||||||
{
|
{
|
||||||
let mut ser = Serializer {
|
self.next = Some(key.serialize(&mut Serializer)?.encode());
|
||||||
string_format: self.ser.string_format.without_whitespace(),
|
|
||||||
bytes_format: self.ser.bytes_format.without_whitespace(),
|
|
||||||
};
|
|
||||||
self.next = Some(key.serialize(&mut ser)?.encode());
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -329,7 +310,7 @@ impl ser::SerializeMap for MapSerializer {
|
||||||
self.next
|
self.next
|
||||||
.take()
|
.take()
|
||||||
.ok_or_else(|| Self::Error::custom("no key"))?,
|
.ok_or_else(|| Self::Error::custom("no key"))?,
|
||||||
value.serialize(&mut self.ser)?,
|
value.serialize(&mut Serializer)?,
|
||||||
));
|
));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -342,7 +323,6 @@ impl ser::SerializeMap for MapSerializer {
|
||||||
pub struct StructSerializer {
|
pub struct StructSerializer {
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
fields: Vec<(&'static str, Term<'static>)>,
|
fields: Vec<(&'static str, Term<'static>)>,
|
||||||
ser: Serializer,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ser::SerializeStruct for StructSerializer {
|
impl ser::SerializeStruct for StructSerializer {
|
||||||
|
@ -353,7 +333,7 @@ impl ser::SerializeStruct for StructSerializer {
|
||||||
where
|
where
|
||||||
T: ?Sized + Serialize,
|
T: ?Sized + Serialize,
|
||||||
{
|
{
|
||||||
self.fields.push((key, value.serialize(&mut self.ser)?));
|
self.fields.push((key, value.serialize(&mut Serializer)?));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -366,7 +346,6 @@ pub struct StructVariantSerializer {
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
variant: &'static str,
|
variant: &'static str,
|
||||||
fields: Vec<(&'static str, Term<'static>)>,
|
fields: Vec<(&'static str, Term<'static>)>,
|
||||||
ser: Serializer,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ser::SerializeStructVariant for StructVariantSerializer {
|
impl ser::SerializeStructVariant for StructVariantSerializer {
|
||||||
|
@ -377,7 +356,7 @@ impl ser::SerializeStructVariant for StructVariantSerializer {
|
||||||
where
|
where
|
||||||
T: ?Sized + Serialize,
|
T: ?Sized + Serialize,
|
||||||
{
|
{
|
||||||
self.fields.push((key, value.serialize(&mut self.ser)?));
|
self.fields.push((key, value.serialize(&mut Serializer)?));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
131
src/switch64.rs
131
src/switch64.rs
|
@ -1,131 +0,0 @@
|
||||||
//! The Switch64 encoding for text strings
|
|
||||||
//!
|
|
||||||
//! Allowed characters are encoded as-is.
|
|
||||||
//! Others are encoded using base64.
|
|
||||||
//! Plain parts and base64-encoded parts are separated by a backslasah `\`
|
|
||||||
|
|
||||||
use crate::{SWITCH64_EXTRA_CHARS, SWITCH64_SEPARATOR};
|
|
||||||
|
|
||||||
pub fn encode(bytes: &[u8], allow_whitespace: bool) -> Vec<u8> {
|
|
||||||
let mut output = Vec::with_capacity(bytes.len());
|
|
||||||
|
|
||||||
let mut pos = 0;
|
|
||||||
while pos < bytes.len() {
|
|
||||||
// Determine how many bytes to copy as-is
|
|
||||||
let cnt = bytes[pos..]
|
|
||||||
.iter()
|
|
||||||
.take_while(|c| is_valid_plaintext_char(**c, allow_whitespace))
|
|
||||||
.count();
|
|
||||||
|
|
||||||
// Copy those bytes as-is
|
|
||||||
output.extend_from_slice(&bytes[pos..pos + cnt]);
|
|
||||||
pos += cnt;
|
|
||||||
|
|
||||||
// If some bytes remain, switch to base64 encoding
|
|
||||||
if pos < bytes.len() {
|
|
||||||
output.push(SWITCH64_SEPARATOR);
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Count how many bytes to write as base64
|
|
||||||
// We stop at the first position where we find three consecutive
|
|
||||||
// characters to encode as-is
|
|
||||||
let mut b64end = bytes.len();
|
|
||||||
for i in pos..bytes.len() - 3 {
|
|
||||||
if bytes[i..i + 3]
|
|
||||||
.iter()
|
|
||||||
.all(|c| is_valid_plaintext_char(*c, allow_whitespace))
|
|
||||||
{
|
|
||||||
b64end = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
output.extend_from_slice(
|
|
||||||
base64::encode_config(&bytes[pos..b64end], base64::URL_SAFE_NO_PAD).as_bytes(),
|
|
||||||
);
|
|
||||||
pos = b64end;
|
|
||||||
|
|
||||||
if pos < bytes.len() {
|
|
||||||
output.push(SWITCH64_SEPARATOR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
output
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn decode(bytes: &[u8]) -> Result<Vec<u8>, base64::DecodeError> {
|
|
||||||
let mut output = Vec::with_capacity(bytes.len());
|
|
||||||
|
|
||||||
let mut pos = 0;
|
|
||||||
while pos < bytes.len() {
|
|
||||||
let cnt = bytes[pos..]
|
|
||||||
.iter()
|
|
||||||
.take_while(|c| **c != SWITCH64_SEPARATOR)
|
|
||||||
.count();
|
|
||||||
output.extend_from_slice(&bytes[pos..pos + cnt]);
|
|
||||||
pos += cnt + 1;
|
|
||||||
|
|
||||||
if pos >= bytes.len() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
let cnt = bytes[pos..]
|
|
||||||
.iter()
|
|
||||||
.take_while(|c| **c != SWITCH64_SEPARATOR)
|
|
||||||
.count();
|
|
||||||
output.extend_from_slice(&base64::decode_config(
|
|
||||||
&bytes[pos..pos + cnt],
|
|
||||||
base64::URL_SAFE_NO_PAD,
|
|
||||||
)?);
|
|
||||||
pos += cnt + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(output)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn is_valid_plaintext_char(c: u8, allow_whitespace: bool) -> bool {
|
|
||||||
c.is_ascii_alphanumeric()
|
|
||||||
|| (allow_whitespace && c.is_ascii_whitespace())
|
|
||||||
|| SWITCH64_EXTRA_CHARS.contains(&c)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use crate::debug;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_encode() {
|
|
||||||
assert_eq!(debug(&encode(&b"hello world"[..], true)), "hello world");
|
|
||||||
assert_eq!(
|
|
||||||
debug(&encode(&b"hello, world!"[..], true)),
|
|
||||||
"hello\\LA\\ world\\IQ"
|
|
||||||
);
|
|
||||||
assert_eq!(debug(&encode(&b",;,@$;8"[..], true)), "\\LDssQCQ7OA");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_decode() {
|
|
||||||
assert_eq!(debug(&decode(&b"hello world"[..]).unwrap()), "hello world");
|
|
||||||
assert_eq!(
|
|
||||||
debug(&decode(&b"hello\\LA\\ world\\IQ"[..]).unwrap()),
|
|
||||||
"hello, world!"
|
|
||||||
);
|
|
||||||
assert_eq!(debug(&decode(&b"\\LDssQCQ7OA"[..]).unwrap()), ",;,@$;8");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_encdec() {
|
|
||||||
for s in [
|
|
||||||
br#"assert_eq!(debug(&decode(&b"hello\\LA\\ world\\IQ"[..]).unwrap()), "hello, world!");"#.to_vec(),
|
|
||||||
br#"- a list, which may contain any number of any kind of terms (can be mixed)"#.to_vec(),
|
|
||||||
base64::decode("dVcG5EzJqGP/2ZGkVu4ewzfAug1W96tb2KiBOVyPUXfw8uD34DEepW/PPqRzi0HL").unwrap()
|
|
||||||
] {
|
|
||||||
assert_eq!(decode(&encode(&s, true)).unwrap(), s);
|
|
||||||
assert_eq!(decode(&encode(&s, false)).unwrap(), s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue