use std::default::Default;
use std::fmt;
use toxcore::binary_io::*;
use toxcore::crypto_core::*;
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 new() -> Self {
let mut nospam = [0; NOSPAMBYTES];
randombytes_into(&mut nospam);
NoSpam(nospam)
}
}
impl Default for NoSpam {
fn default() -> Self {
NoSpam::new()
}
}
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::new();
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::new();
}
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 {
extern crate rand;
use quickcheck::quickcheck;
use ::toxcore::crypto_core::*;
use ::toxcore::toxid::*;
fn test_is_hexdump_uppercase(s: &str) -> bool {
fn test_is_hexdump_uppercase_b(b: u8) -> bool {
if let b'A' ... b'F' = b {
true
} else if let b'0' ... b'9' = b {
true
} else {
false
}
}
s.bytes().all(test_is_hexdump_uppercase_b)
}
#[cfg(test)]
fn no_spam_no_empty(ns: &NoSpam) {
assert!(ns.0 != [0; NOSPAMBYTES])
}
#[test]
fn no_spam_new_test() {
let ns = NoSpam::new();
no_spam_no_empty(&ns);
}
#[test]
fn no_spam_default_test() {
let ns = NoSpam::default();
no_spam_no_empty(&ns);
}
#[test]
fn no_spam_fmt_test() {
let nospam = NoSpam::new();
assert_eq!(false, test_is_hexdump_uppercase("Not HexDump"));
assert_eq!(true, test_is_hexdump_uppercase(&format!("{:X}", nospam)));
assert_eq!(true, test_is_hexdump_uppercase(&format!("{}", nospam)));
assert_eq!(true, test_is_hexdump_uppercase(&format!("{:X}", NoSpam([0, 0, 0, 0]))));
assert_eq!(true, test_is_hexdump_uppercase(&format!("{}", NoSpam([0, 0, 0, 0]))));
assert_eq!(true, test_is_hexdump_uppercase(&format!("{:X}", NoSpam([15, 15, 15, 15]))));
assert_eq!(true, test_is_hexdump_uppercase(&format!("{}", NoSpam([15, 15, 15, 15]))));
}
#[test]
fn no_spam_from_bytes_test() {
fn with_bytes(bytes: Vec<u8>) {
if bytes.len() < NOSPAMBYTES {
assert!(NoSpam::from_bytes(&bytes).is_incomplete());
} else {
let nospam = NoSpam::from_bytes(&bytes)
.to_result()
.expect("Failed to get NoSpam!");
assert_eq!(bytes[0], nospam.0[0]);
assert_eq!(bytes[1], nospam.0[1]);
assert_eq!(bytes[2], nospam.0[2]);
assert_eq!(bytes[3], nospam.0[3]);
}
}
quickcheck(with_bytes as fn(Vec<u8>));
}
encode_decode_test!(
no_spam_encode_decode,
NoSpam::new()
);
#[test]
fn tox_id_new_nospam_test() {
let (pk, _) = gen_keypair();
let toxid = ToxId::new(pk);
let mut toxid2 = toxid;
toxid2.new_nospam(None);
assert!(toxid != toxid2);
assert_eq!(toxid.pk, toxid2.pk);
let mut toxid3 = toxid;
let nospam = NoSpam::new();
toxid2.new_nospam(Some(nospam));
toxid3.new_nospam(Some(nospam));
assert_eq!(toxid2, toxid3);
}
#[test]
fn tox_id_fmt_test() {
let (pk, _) = gen_keypair();
let toxid = ToxId::new(pk);
assert_eq!(true, test_is_hexdump_uppercase(&format!("{:X}", toxid)));
assert_eq!(true, test_is_hexdump_uppercase(&format!("{}", toxid)));
}
encode_decode_test!(
toxid_encode_decode,
ToxId::new(gen_keypair().0)
);
}