use std::fmt;
use nom::{
named,
do_parse, map, call, take,
};
use cookie_factory::{do_gen, gen_slice};
use tox_binary_io::*;
use tox_crypto::*;
pub fn xor_checksum(lhs: [u8; 2], rhs: [u8; 2]) -> [u8; 2] {
[lhs[0] ^ rhs[0], lhs[1] ^ rhs[1]]
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct NoSpam(pub [u8; NOSPAMBYTES]);
pub const NOSPAMBYTES: usize = 4;
impl NoSpam {
pub fn random() -> Self {
let mut nospam = [0; NOSPAMBYTES];
randombytes_into(&mut nospam);
NoSpam(nospam)
}
}
impl fmt::UpperHex for NoSpam {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:02X}{:02X}{:02X}{:02X}",
self.0[0], self.0[1], self.0[2], self.0[3])
}
}
impl fmt::Display for NoSpam {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:X}", self)
}
}
impl FromBytes for NoSpam {
named!(from_bytes<NoSpam>, map!(take!(NOSPAMBYTES), |bytes| {
NoSpam([bytes[0], bytes[1], bytes[2], bytes[3]])
}));
}
impl ToBytes for NoSpam {
fn to_bytes<'a>(&self, buf: (&'a mut [u8], usize)) -> Result<(&'a mut [u8], usize), GenError> {
do_gen!(buf,
gen_slice!(&self.0)
)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ToxId {
pub pk: PublicKey,
nospam: NoSpam,
checksum: [u8; 2],
}
pub const CHECKSUMBYTES: usize = 2;
pub const TOXIDBYTES: usize = PUBLICKEYBYTES + NOSPAMBYTES + 2;
impl ToxId {
pub fn checksum(&PublicKey(ref pk): &PublicKey, nospam: NoSpam) -> [u8; 2] {
let mut bytes = Vec::with_capacity(TOXIDBYTES - 2);
bytes.extend_from_slice(pk);
bytes.extend_from_slice(nospam.0.as_ref());
let mut checksum = [0; 2];
for pair in bytes.chunks(2) {
checksum = xor_checksum(checksum, [pair[0], pair[1]]);
}
checksum
}
pub fn new(pk: PublicKey) -> Self {
let nospam = NoSpam::random();
let checksum = Self::checksum(&pk, nospam);
ToxId { pk, nospam, checksum }
}
pub fn new_nospam(&mut self, nospam: Option<NoSpam>) {
if let Some(nospam) = nospam {
self.nospam = nospam;
} else {
self.nospam = NoSpam::random();
}
self.checksum = Self::checksum(&self.pk, self.nospam);
}
}
impl FromBytes for ToxId {
named!(from_bytes<ToxId>, do_parse!(
pk: call!(PublicKey::from_bytes) >>
nospam: call!(NoSpam::from_bytes) >>
checksum: map!(take!(CHECKSUMBYTES), |bytes| { [bytes[0], bytes[1]] }) >>
(ToxId { pk, nospam, checksum })
));
}
impl ToBytes for ToxId {
fn to_bytes<'a>(&self, buf: (&'a mut [u8], usize)) -> Result<(&'a mut [u8], usize), GenError> {
do_gen!(buf,
gen_slice!(self.pk.as_ref()) >>
gen_slice!(&self.nospam.0) >>
gen_slice!(&self.checksum)
)
}
}
impl fmt::UpperHex for ToxId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut string = String::with_capacity(TOXIDBYTES * 2);
let PublicKey(ref pk_bytes) = self.pk;
for byte in pk_bytes {
string.push_str(&format!("{:02X}", byte));
}
for byte in &self.nospam.0 {
string.push_str(&format!("{:02X}", byte));
}
string.push_str(&format!("{:02X}{:02X}", self.checksum[0],
self.checksum[1]));
write!(f, "{}", string)
}
}
impl fmt::Display for ToxId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:X}", self)
}
}
#[cfg(test)]
mod tests {
use tox_crypto::*;
use crate::toxid::*;
fn test_is_hexdump_uppercase(s: &str) -> bool {
fn test_is_hexdump_uppercase_b(b: u8) -> bool {
matches!(b, b'A' ..= b'F') || matches!(b, b'0' ..= b'9')
}
s.bytes().all(test_is_hexdump_uppercase_b)
}
#[test]
fn no_spam_new_test() {
crypto_init().unwrap();
let ns = NoSpam::random();
assert_ne!(ns.0, [0; NOSPAMBYTES])
}
#[test]
fn no_spam_fmt() {
crypto_init().unwrap();
let nospam = NoSpam::random();
assert!(!test_is_hexdump_uppercase("Not HexDump"));
assert!(test_is_hexdump_uppercase(&format!("{:X}", nospam)));
assert!(test_is_hexdump_uppercase(&format!("{}", nospam)));
assert!(test_is_hexdump_uppercase(&format!("{:X}", NoSpam([0, 0, 0, 0]))));
assert!(test_is_hexdump_uppercase(&format!("{}", NoSpam([0, 0, 0, 0]))));
assert!(test_is_hexdump_uppercase(&format!("{:X}", NoSpam([15, 15, 15, 15]))));
assert!(test_is_hexdump_uppercase(&format!("{}", NoSpam([15, 15, 15, 15]))));
}
encode_decode_test!(
tox_crypto::crypto_init().unwrap(),
no_spam_encode_decode,
NoSpam::random()
);
#[test]
fn tox_id_new_nospam() {
crypto_init().unwrap();
let (pk, _) = gen_keypair();
let toxid = ToxId::new(pk);
let mut toxid2 = toxid;
toxid2.new_nospam(None);
assert_ne!(toxid, toxid2);
assert_eq!(toxid.pk, toxid2.pk);
let mut toxid3 = toxid;
let nospam = NoSpam::random();
toxid2.new_nospam(Some(nospam));
toxid3.new_nospam(Some(nospam));
assert_eq!(toxid2, toxid3);
}
#[test]
fn tox_id_fmt() {
crypto_init().unwrap();
let (pk, _) = gen_keypair();
let toxid = ToxId::new(pk);
assert!(test_is_hexdump_uppercase(&format!("{:X}", toxid)));
assert!(test_is_hexdump_uppercase(&format!("{}", toxid)));
}
encode_decode_test!(
tox_crypto::crypto_init().unwrap(),
toxid_encode_decode,
ToxId::new(gen_keypair().0)
);
}