extern crate base64;
use buffered_reader::BufferedReader;
use std::io::{Cursor, Read, Write};
use std::io::{Result, Error, ErrorKind};
use std::path::Path;
use std::cmp::min;
use std::str;
use quickcheck::{Arbitrary, Gen};
use packet::Header;
const LINE_LENGTH: usize = 64;
const LINE_ENDING: &str = "\n";
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Kind {
Message,
PublicKey,
SecretKey,
Signature,
File,
}
impl Arbitrary for Kind {
fn arbitrary<G: Gen>(g: &mut G) -> Self {
use self::Kind::*;
match u8::arbitrary(g) % 5 {
0 => Message,
1 => PublicKey,
2 => SecretKey,
3 => Signature,
4 => File,
_ => unreachable!(),
}
}
}
impl Kind {
fn detect(blurb: &[u8]) -> Option<Self> {
if blurb.len() < "-----BEGIN PGP MESSAGE-----".len()
|| ! blurb.starts_with(b"-----BEGIN PGP ")
{
return None;
}
let kind = &blurb[15..];
if kind.starts_with(b"MESSAGE-----") {
Some(Kind::Message)
} else if kind.starts_with(b"PUBLIC KEY BLOCK-----") {
Some(Kind::PublicKey)
} else if kind.starts_with(b"PRIVATE KEY BLOCK-----") {
Some(Kind::SecretKey)
} else if kind.starts_with(b"SIGNATURE-----") {
Some(Kind::Signature)
} else if kind.starts_with(b"ARMORED FILE-----") {
Some(Kind::File)
} else {
None
}
}
fn blurb(&self) -> &str {
match self {
&Kind::Message => "MESSAGE",
&Kind::PublicKey => "PUBLIC KEY BLOCK",
&Kind::SecretKey => "PRIVATE KEY BLOCK",
&Kind::Signature => "SIGNATURE",
&Kind::File => "ARMORED FILE",
}
}
fn begin(&self) -> String {
format!("-----BEGIN PGP {}-----", self.blurb())
}
fn end(&self) -> String {
format!("-----END PGP {}-----", self.blurb())
}
fn footer_max_len(&self) -> usize {
(5 + 4 + 18 + self.blurb().len()
+ 2 )
}
}
pub struct Writer<W: Write> {
sink: W,
kind: Kind,
stash: Vec<u8>,
column: usize,
crc: CRC,
epilogue: Vec<u8>,
dirty: bool,
finalized: bool,
}
impl<W: Write> Writer<W> {
pub fn new(inner: W, kind: Kind, headers: &[(&str, &str)]) -> Result<Self> {
let mut w = Writer {
sink: inner,
kind: kind,
stash: Vec::<u8>::with_capacity(2),
column: 0,
crc: CRC::new(),
epilogue: Vec::with_capacity(128),
dirty: false,
finalized: false,
};
{
let mut cur = Cursor::new(&mut w.epilogue);
write!(&mut cur, "{}{}", kind.begin(), LINE_ENDING)?;
for h in headers {
write!(&mut cur, "{}: {}{}", h.0, h.1, LINE_ENDING)?;
}
write!(&mut cur, "{}", LINE_ENDING)?;
}
Ok(w)
}
fn write_epilogue(&mut self) -> Result<()> {
if ! self.dirty {
self.dirty = true;
self.sink.write_all(&self.epilogue)?;
self.epilogue.clear();
self.epilogue.shrink_to_fit();
}
Ok(())
}
pub fn finalize(&mut self) -> Result<()> {
if self.finalized {
return Err(Error::new(ErrorKind::BrokenPipe, "Writer is finalized."));
}
if ! self.dirty {
return Ok(());
}
self.write_epilogue()?;
if self.stash.len() > 0 {
self.sink.write_all(base64::encode_config(&self.stash,
base64::STANDARD).as_bytes())?;
self.column += 4;
}
self.linebreak()?;
if self.column > 0 {
write!(self.sink, "{}", LINE_ENDING)?;
}
let crc = self.crc.finalize();
let bytes: [u8; 3] = [
(crc >> 16) as u8,
(crc >> 8) as u8,
(crc >> 0) as u8,
];
write!(self.sink, "={}{}{}{}",
base64::encode_config(&bytes, base64::STANDARD_NO_PAD),
LINE_ENDING, self.kind.end(), LINE_ENDING)?;
self.finalized = true;
Ok(())
}
fn linebreak(&mut self) -> Result<()> {
assert!(self.column <= LINE_LENGTH);
if self.column == LINE_LENGTH {
write!(self.sink, "{}", LINE_ENDING)?;
self.column = 0;
}
Ok(())
}
}
impl<W: Write> Write for Writer<W> {
fn write(&mut self, buf: &[u8]) -> Result<usize> {
if self.finalized {
return Err(Error::new(ErrorKind::BrokenPipe, "Writer is finalized."));
}
self.write_epilogue()?;
self.crc.update(buf);
let mut input = buf;
let mut written = 0;
assert!(self.stash.len() <= 3);
if self.stash.len() > 0 {
while self.stash.len() < 3 {
if input.len() == 0 {
return Ok(written);
}
self.stash.push(input[0]);
input = &input[1..];
written += 1;
}
assert_eq!(self.stash.len(), 3);
self.sink.write_all(base64::encode_config(&self.stash,
base64::STANDARD_NO_PAD).as_bytes())?;
self.column += 4;
self.linebreak()?;
self.stash.clear();
}
while input.len() % 3 > 0 {
self.stash.push(input[input.len()-1]);
input = &input[..input.len()-1];
written += 1;
}
self.stash.reverse();
assert!(self.stash.len() < 3);
assert!(input.len() % 3 == 0);
let encoded = base64::encode_config(input, base64::STANDARD_NO_PAD);
written += input.len();
let mut enc = encoded.as_bytes();
while enc.len() > 0 {
let n = min(LINE_LENGTH - self.column, enc.len());
self.sink.write_all(&enc[..n])?;
enc = &enc[n..];
self.column += n;
self.linebreak()?;
}
assert_eq!(written, buf.len());
Ok(written)
}
fn flush(&mut self) -> Result<()> {
self.sink.flush()
}
}
impl<W: Write> Drop for Writer<W> {
fn drop(&mut self) {
let _ = self.finalize();
}
}
pub struct Reader<'a> {
source: Box<'a + BufferedReader<()>>,
kind: Option<Kind>,
strict: bool,
buffer: Vec<u8>,
crc: CRC,
expect_crc: Option<u32>,
initialized: bool,
headers: Vec<(String, String)>,
finalized: bool,
}
impl<'a> Reader<'a> {
pub fn new<R>(inner: R, kind: Option<Kind>) -> Self
where R: 'a + Read
{
Self::from_buffered_reader(
Box::new(buffered_reader::Generic::new(inner, None)),
kind)
}
pub fn from_reader<R>(reader: R, kind: Option<Kind>) -> Self
where R: 'a + Read
{
Self::from_buffered_reader(
Box::new(buffered_reader::Generic::new(reader, None)),
kind)
}
pub fn from_file<P>(path: P, kind: Option<Kind>) -> Result<Self>
where P: AsRef<Path>
{
Ok(Self::from_buffered_reader(
Box::new(buffered_reader::File::open(path)?),
kind))
}
pub fn from_bytes(bytes: &'a [u8], kind: Option<Kind>) -> Self {
Self::from_buffered_reader(
Box::new(buffered_reader::Memory::new(bytes)),
kind)
}
pub(crate) fn from_buffered_reader<C: 'a>(
inner: Box<'a + BufferedReader<C>>, kind: Option<Kind>) -> Self
{
Reader {
source: Box::new(buffered_reader::Generic::new(inner, None)),
kind: kind,
strict: kind.is_some(),
buffer: Vec::<u8>::with_capacity(1024),
crc: CRC::new(),
expect_crc: None,
headers: Vec::new(),
initialized: false,
finalized: false,
}
}
pub fn kind(&self) -> Option<Kind> {
self.kind
}
pub fn headers(&mut self) -> Result<&[(String, String)]> {
self.initialize()?;
Ok(&self.headers[..])
}
fn initialize(&mut self) -> Result<()> {
if self.initialized { return Ok(()) }
let mut n = 0;
let mut found_blob = false;
'search: loop {
self.source.consume(n);
let line = self.source.read_to('\n' as u8)?;
n = line.len();
if n == 0 {
return Err(
Error::new(ErrorKind::InvalidInput,
"Reached EOF looking for Armor Header Line"));
}
if ! self.strict {
let mut offset = 0;
loop {
if is_armored_pgp_blob(&line[offset..]) {
n = offset;
found_blob = true;
break 'search;
}
if let Some(o) = &line[offset..].iter()
.position(|c| c.is_ascii_whitespace())
{
offset += *o;
while offset < line.len()
&& line[offset].is_ascii_whitespace()
{
offset += 1;
}
} else {
break;
}
}
}
if line.len() < 27 {
continue;
}
for i in 0..(line.len() - 27 + 1) {
if let Some(kind) = Kind::detect(&line[i..]) {
if self.kind == None {
self.kind = Some(kind);
break 'search;
}
if self.kind == Some(kind) {
break 'search;
}
}
if ! line[i].is_ascii_whitespace() {
continue 'search;
}
}
}
self.source.consume(n);
if found_blob {
self.initialized = true;
return Ok(());
}
let mut n = 0;
loop {
self.source.consume(n);
let line = self.source.read_to('\n' as u8)?;
n = line.len();
let line = str::from_utf8(line);
if line.is_err() {
continue;
}
let line = line.unwrap();
let line = if line.ends_with(&"\r\n"[..]) {
&line[..line.len() - 2]
} else if line.len() > 0 {
&line[..line.len() - 1]
} else {
line
};
let key_value = line.splitn(2, ": ").collect::<Vec<&str>>();
if key_value.len() == 1 {
if line.trim_start().len() == 0 {
break;
}
} else {
let key = key_value[0];
let value = key_value[1];
self.headers.push((key.into(), value.into()));
}
}
self.source.consume(n);
self.initialized = true;
Ok(())
}
fn finalize(footer: &[u8], kind: Option<Kind>) -> Result<Option<u32>> {
let mut off = 0;
let crc = if footer.len() >= 6 && footer[0] == '=' as u8
&& footer[1..5].iter().all(is_base64_char)
{
let crc = match base64::decode_config(&footer[1..5], base64::MIME) {
Ok(d) => d,
Err(e) => return Err(Error::new(ErrorKind::InvalidInput, e)),
};
assert_eq!(crc.len(), 3);
let crc =
(crc[0] as u32) << 16
| (crc[1] as u32) << 8
| crc[2] as u32;
off += 5;
while off < footer.len() && footer[off].is_ascii_whitespace() {
off += 1;
}
Some(crc)
} else {
None
};
if let Some(kind) = kind {
if ! footer[off..].starts_with(&kind.end().into_bytes()) {
return Err(Error::new(ErrorKind::InvalidInput, "Invalid ASCII Armor footer."));
}
}
Ok(crc)
}
}
fn is_armored_pgp_blob(bytes: &[u8]) -> bool {
let bytes = if let Some(msg) = get_base64_prefix(bytes) {
msg
} else {
return false;
};
let mut end = bytes.len();
loop {
match base64::decode_config(&bytes[..end], base64::MIME) {
Ok(d) => {
if d.len() == 0 {
break false;
}
let mut br = buffered_reader::Memory::new(&d);
break if let Ok(header) = Header::parse(&mut br) {
header.ctb.tag.valid_start_of_message()
&& header.valid(false).is_ok()
} else {
false
};
},
Err(_) =>
if end == 0 {
break false;
} else {
end -= 1;
},
}
}
}
fn get_base64_prefix(bytes: &[u8]) -> Option<&[u8]> {
let mut seen_padding = false;
for (i, c) in bytes.iter().enumerate() {
if c.is_ascii_whitespace() {
continue;
}
if seen_padding && *c != '=' as u8 {
return Some(&bytes[..i]);
}
if *c == '=' as u8 {
seen_padding = true;
} else if ! is_base64_char(c) {
if i == 0 {
return None;
} else {
return Some(&bytes[..i]);
}
}
}
return Some(bytes);
}
fn is_base64_char(b: &u8) -> bool {
b.is_ascii_alphanumeric() || *b == '+' as u8 || *b == '/' as u8
}
fn is_footer(buf: &[u8], reference: &[u8]) -> Option<usize> {
if buf.len() < reference.len() {
return None;
}
let mut off = 0;
if buf.len() >= 6 && buf[0] == '=' as u8
&& buf[1..5].iter().all(is_base64_char)
{
off += 5;
while off < buf.len() && buf[off].is_ascii_whitespace() {
off += 1;
}
}
if buf[off..].starts_with(reference) {
Some(off + reference.len())
} else {
None
}
}
fn find_footer(buf: &[u8], kind: Kind) -> Option<(usize, usize)> {
let reference = kind.end().into_bytes();
if buf.len() < reference.len() {
return None;
}
for i in 0..buf.len() - reference.len() {
if let Some(length) = is_footer(&buf[i..], &reference) {
return Some((i, i + length));
}
}
None
}
impl<'a> Read for Reader<'a> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
self.initialize()?;
let mut read = 0;
let amount = min(buf.len(), self.buffer.len());
&mut buf[..amount].copy_from_slice(&self.buffer[..amount]);
self.buffer.drain(..amount);
read += amount;
if read == buf.len() {
return Ok(read);
}
assert_eq!(self.buffer.len(), 0);
if self.finalized {
return Ok(read);
}
let (consumed, decoded) = {
let mut want = (buf.len() - read + 2) / 3 * 4
+ self.kind.map(|k| k.footer_max_len()).unwrap_or(46);
let mut got = 0;
'readloop: loop {
let raw = self.source.data(want)?;
if raw.len() == got {
if self.strict {
return Err(Error::new(ErrorKind::UnexpectedEof,
"Armor footer is missing"));
} else {
let mut end = min(raw.len(), want);
loop {
match base64::decode_config(&raw[..end],
base64::MIME) {
Ok(d) => break 'readloop (end, d),
Err(_) =>
if end == 0 {
break 'readloop (raw.len(), vec![]);
} else {
end -= 1;
},
}
}
}
} else {
got = raw.len();
}
if let Some(kind) = self.kind {
if let Some((n, end)) = find_footer(&raw, kind) {
self.expect_crc = Reader::finalize(&raw[n..], self.kind)?;
self.finalized = true;
match base64::decode_config(&raw[..n], base64::MIME) {
Ok(d) => break (end, d),
Err(e) =>
return Err(Error::new(ErrorKind::InvalidInput, e)),
}
}
}
let n = &raw.iter().filter(
|c| ! (**c).is_ascii_whitespace()).count();
if n % 4 == 0 {
let mut end = raw.len();
loop {
match base64::decode_config(&raw[..end],
base64::MIME) {
Ok(d) => break 'readloop (end, d),
Err(_) =>
if end == 0 {
break 'readloop (raw.len(), vec![]);
} else {
end -= 1;
},
}
}
}
want = got + 4 - n % 4;
}
};
self.source.consume(consumed);
self.crc.update(&decoded);
if decoded.len() <= (buf.len() - read) {
&mut buf[read..read + decoded.len()].copy_from_slice(&decoded);
read += decoded.len();
} else {
let spill = decoded.len() - (buf.len() - read);
&mut buf[read..read + decoded.len() - spill].copy_from_slice(
&decoded[..decoded.len() - spill]);
read += decoded.len() - spill;
self.buffer.extend_from_slice(&decoded[decoded.len() - spill..]);
}
if let Some(crc) = self.expect_crc {
if self.crc.finalize() != crc {
return Err(Error::new(ErrorKind::InvalidInput, "Bad CRC sum."));
}
}
Ok(read)
}
}
const CRC24_INIT: u32 = 0xB704CE;
const CRC24_POLY: u32 = 0x1864CFB;
struct CRC {
n: u32,
}
impl CRC {
fn new() -> Self {
CRC { n: CRC24_INIT }
}
fn update(&mut self, buf: &[u8]) -> &Self {
for octet in buf {
self.n ^= (*octet as u32) << 16;
for _ in 0..8 {
self.n <<= 1;
if self.n & 0x1000000 > 0 {
self.n ^= CRC24_POLY;
}
}
}
self
}
fn finalize(&self) -> u32 {
self.n & 0xFFFFFF
}
}
#[cfg(test)]
mod test {
use std::io::Write;
use super::CRC;
use super::Kind;
use super::Writer;
#[test]
fn crc() {
let b = b"foobarbaz";
let crcs = [
0xb704ce,
0x6d2804,
0xa2d10d,
0x4fc255,
0x7aafca,
0xc79c46,
0x7334de,
0x77dc72,
0x000f65,
0xf40d86,
];
for len in 0..b.len() + 1 {
assert_eq!(CRC::new().update(&b[..len]).finalize(), crcs[len]);
}
}
use std::fs::File;
use std::io::prelude::*;
const TEST_VECTORS: [u8; 9] = [0, 1, 2, 3, 47, 48, 49, 50, 51];
#[test]
fn enarmor() {
for len in TEST_VECTORS.iter() {
let mut file = File::open(format!("tests/data/armor/test-{}.bin", len)).unwrap();
let mut bin = Vec::<u8>::new();
file.read_to_end(&mut bin).unwrap();
let mut file = File::open(format!("tests/data/armor/test-{}.asc", len)).unwrap();
let mut asc = Vec::<u8>::new();
file.read_to_end(&mut asc).unwrap();
let mut buf = Vec::new();
{
let mut w = Writer::new(&mut buf, Kind::File, &[]).unwrap();
w.write(&[]).unwrap(); w.write_all(&bin).unwrap();
}
assert_eq!(String::from_utf8_lossy(&buf),
String::from_utf8_lossy(&asc));
}
}
#[test]
fn enarmor_bytewise() {
for len in TEST_VECTORS.iter() {
let mut file = File::open(format!("tests/data/armor/test-{}.bin", len)).unwrap();
let mut bin = Vec::<u8>::new();
file.read_to_end(&mut bin).unwrap();
let mut file = File::open(format!("tests/data/armor/test-{}.asc", len)).unwrap();
let mut asc = Vec::<u8>::new();
file.read_to_end(&mut asc).unwrap();
let mut buf = Vec::new();
{
let mut w = Writer::new(&mut buf, Kind::File, &[]).unwrap();
w.write(&[]).unwrap(); for (i, _) in bin.iter().enumerate() {
w.write(&bin[i..i+1]).unwrap();
}
}
assert_eq!(String::from_utf8_lossy(&buf),
String::from_utf8_lossy(&asc));
}
}
#[test]
fn drop_writer() {
let mut buf = Vec::new();
{
drop(Writer::new(&mut buf, Kind::File, &[]).unwrap());
}
assert!(buf.is_empty());
let mut buf = Vec::new();
{
let mut w = Writer::new(&mut buf, Kind::File, &[]).unwrap();
w.write(&[]).unwrap();
}
assert_eq!(
&buf[..],
&b"-----BEGIN PGP ARMORED FILE-----\n\
\n\
=twTO\n\
-----END PGP ARMORED FILE-----\n"[..]);
}
use super::Reader;
#[test]
fn dearmor_robust() {
for len in TEST_VECTORS.iter() {
let mut file = File::open(format!("tests/data/armor/literal-{}.bin",
len)).unwrap();
let mut reference = Vec::<u8>::new();
file.read_to_end(&mut reference).unwrap();
for test in &["", "-no-header-with-chksum", "-no-header",
"-no-newlines"] {
let filename = format!("tests/data/armor/literal-{}{}.asc",
len, test);
let mut file = File::open(filename).unwrap();
let mut r = Reader::new(&mut file, None);
let mut dearmored = Vec::<u8>::new();
r.read_to_end(&mut dearmored).unwrap();
assert_eq!(&reference, &dearmored);
}
}
}
#[test]
fn dearmor_binary() {
for len in TEST_VECTORS.iter() {
let mut file = File::open(format!("tests/data/armor/test-{}.bin", len)).unwrap();
let mut r = Reader::new(&mut file, Some(Kind::Message));
let mut buf = [0; 5];
let e = r.read(&mut buf);
assert!(e.is_err());
}
}
#[test]
fn dearmor_wrong_kind() {
let mut file = File::open("tests/data/armor/test-0.asc").unwrap();
let mut r = Reader::new(&mut file, Some(Kind::Message));
let mut buf = [0; 5];
let e = r.read(&mut buf);
assert!(e.is_err());
}
#[test]
fn dearmor_wrong_crc() {
let mut file = File::open("tests/data/armor/test-0.bad-crc.asc").unwrap();
let mut r = Reader::new(&mut file, Some(Kind::File));
let mut buf = [0; 5];
let e = r.read(&mut buf);
assert!(e.is_err());
}
#[test]
fn dearmor_wrong_footer() {
let mut file = File::open("tests/data/armor/test-2.bad-footer.asc").unwrap();
let mut r = Reader::new(&mut file, Some(Kind::File));
let mut buf = [0; 5];
let e = r.read(&mut buf);
assert!(e.is_err());
}
#[test]
fn dearmor_no_crc() {
let mut file = File::open("tests/data/armor/test-1.no-crc.asc").unwrap();
let mut r = Reader::new(&mut file, Some(Kind::File));
let mut buf = [0; 5];
let e = r.read(&mut buf);
assert!(e.unwrap() == 1 && buf[0] == 0xde);
}
#[test]
fn dearmor_with_header() {
let mut file = File::open("tests/data/armor/test-3.with-headers.asc").unwrap();
let mut r = Reader::new(&mut file, Some(Kind::File));
assert_eq!(r.headers().unwrap(),
&[("Comment".into(), "Some Header".into()),
("Comment".into(), "Another one".into())]);
let mut buf = [0; 5];
let e = r.read(&mut buf);
assert!(e.is_ok());
}
#[test]
fn dearmor_any() {
let mut file = File::open("tests/data/armor/test-3.with-headers.asc").unwrap();
let mut r = Reader::new(&mut file, None);
let mut buf = [0; 5];
let e = r.read(&mut buf);
assert!(r.kind() == Some(Kind::File));
assert!(e.is_ok());
}
#[test]
fn dearmor_with_garbage() {
use std::io::Cursor;
let mut armored = Vec::new();
File::open("tests/data/armor/test-3.with-headers.asc")
.unwrap()
.read_to_end(&mut armored)
.unwrap();
let mut garbage = Vec::new();
write!(&mut garbage, "Some\ngarbage\nlines\n\t\r ").unwrap();
garbage.extend_from_slice(&armored);
let mut r = Reader::new(Cursor::new(&garbage), None);
let mut buf = [0; 5];
let e = r.read(&mut buf);
assert!(r.kind() == Some(Kind::File));
assert!(e.is_ok());
let mut garbage = Vec::new();
write!(&mut garbage, "Some\ngarbage\nlines\n\t.\r ").unwrap();
garbage.extend_from_slice(&armored);
let mut r = Reader::new(Cursor::new(&garbage), None);
let mut buf = [0; 5];
let e = r.read(&mut buf);
assert!(e.is_err());
}
#[test]
fn dearmor() {
for len in TEST_VECTORS.iter() {
let mut file = File::open(format!("tests/data/armor/test-{}.bin", len)).unwrap();
let mut bin = Vec::<u8>::new();
file.read_to_end(&mut bin).unwrap();
let mut file = File::open(format!("tests/data/armor/test-{}.asc", len)).unwrap();
let mut r = Reader::new(&mut file, Some(Kind::File));
let mut dearmored = Vec::<u8>::new();
r.read_to_end(&mut dearmored).unwrap();
assert_eq!(&bin, &dearmored);
}
}
#[test]
fn dearmor_bytewise() {
for len in TEST_VECTORS.iter() {
let mut file = File::open(format!("tests/data/armor/test-{}.bin", len)).unwrap();
let mut bin = Vec::<u8>::new();
file.read_to_end(&mut bin).unwrap();
let mut file = File::open(format!("tests/data/armor/test-{}.asc", len)).unwrap();
let r = Reader::new(&mut file, Some(Kind::File));
let mut dearmored = Vec::<u8>::new();
for c in r.bytes() {
dearmored.push(c.unwrap());
}
assert_eq!(&bin, &dearmored);
}
}
#[test]
fn dearmor_yuge() {
let mut file =
File::open("tests/data/keys/yuge-key-so-yuge-the-yugest.asc")
.unwrap();
let mut r = Reader::new(&mut file, None);
let mut dearmored = Vec::<u8>::new();
r.read_to_end(&mut dearmored).unwrap();
let mut file =
File::open("tests/data/keys/yuge-key-so-yuge-the-yugest.asc")
.unwrap();
let r = Reader::new(&mut file, None);
let mut dearmored = Vec::<u8>::new();
for c in r.bytes() {
dearmored.push(c.unwrap());
}
}
quickcheck! {
fn roundtrip(kind: Kind, payload: Vec<u8>) -> bool {
use std::io::Cursor;
if payload.is_empty() {
return true;
}
let mut encoded = Vec::new();
Writer::new(&mut encoded, kind, &[]).unwrap()
.write_all(&payload)
.unwrap();
let mut recovered = Vec::new();
Reader::new(Cursor::new(&encoded), Some(kind))
.read_to_end(&mut recovered)
.unwrap();
let mut recovered_any = Vec::new();
Reader::new(Cursor::new(&encoded), None)
.read_to_end(&mut recovered_any)
.unwrap();
payload == recovered && payload == recovered_any
}
}
}