Compare commits

..

No commits in common. "eae4a0443a1f927a988323f146bdd557745daafe" and "52f6bd177c6d692f6522e697cd34d786701d4c18" have entirely different histories.

8 changed files with 114 additions and 345 deletions

View file

@ -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 }

View file

@ -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")),
} }
} }

View file

@ -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()),

View file

@ -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()
} }

View file

@ -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()))
} }
} }

View file

@ -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);
} }

View file

@ -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(())
} }

View file

@ -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);
}
}
}