diff --git a/src/toxcore/dht_new/codec.rs b/src/toxcore/dht_new/codec.rs new file mode 100644 index 000000000..5fa496ba3 --- /dev/null +++ b/src/toxcore/dht_new/codec.rs @@ -0,0 +1,152 @@ +/* + Copyright (C) 2013 Tox project All Rights Reserved. + Copyright © 2016-2017 Zetok Zalbavar + Copyright © 2018 Namsoo CHO + + This file is part of Tox. + + Tox is libre software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Tox is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Tox. If not, see . +*/ + +/*! Codec for encoding/decoding DHT Packets & DHT Request packets using tokio-io +*/ + +use toxcore::dht_new::packet::*; +use toxcore::binary_io_new::*; + +use std::io; +use std::io::{Error, ErrorKind}; +use tokio_core::net::UdpCodec; +use std::net::SocketAddr; + +/// Type representing Dht UDP packets. +pub type DhtUdpPacket = (SocketAddr, DhtBase); + +/// Type representing received Dht UDP packets. +pub type DhtRecvUdpPacket = (SocketAddr, Option); + +/** +SendNodes +size | description +1 | packet type +32 | public key +24 | nonce +1 | number of response nodes +[39,204]| packed nodes +8 | Request Id (Ping Id) +--------------------------------- +270 bytes maximun. +Because size of SendNodes is largest in DHT related packets +512 is enough for DhtPacket +*/ +pub const MAX_DHT_PACKET_SIZE: usize = 512; + +/// Struct to use for {de-,}serializing DHT UDP packets. +pub struct DhtCodec; + +impl UdpCodec for DhtCodec { + type In = DhtRecvUdpPacket; + type Out = DhtUdpPacket; + + fn decode(&mut self, src: &SocketAddr, buf: &[u8]) -> io::Result + { + match DhtBase::from_bytes(buf) { + IResult::Incomplete(_) => { + Err(Error::new(ErrorKind::Other, + "DhtBase packet should not be incomplete")) + }, + IResult::Error(e) => { + Err(Error::new(ErrorKind::Other, + format!("deserialize DhtBase packet error: {:?}", e))) + }, + IResult::Done(_, encrypted_packet) => { + Ok((*src, Some(encrypted_packet))) + } + } + } + + fn encode(&mut self, (addr, dp): Self::Out, into: &mut Vec) -> SocketAddr { + let mut buf = [0; MAX_DHT_PACKET_SIZE]; + if let Ok((_, size)) = dp.to_bytes((&mut buf, 0)) { + into.extend(&buf[..size]); + } else { + // TODO: move from tokio-core to tokio and return error instead of panic + panic!("DhtBase to_bytes error {:?}", dp); + } + addr + } +} + +#[cfg(test)] +mod test { + use tokio_core::net::UdpCodec; + use std::net::SocketAddr; + + use super::*; + use toxcore::dht_new::packet_kind::*; + + use quickcheck::{quickcheck, TestResult}; + + #[test] + fn dht_codec_decode_test() { + fn with_dp(dp: DhtPacket) -> TestResult { + // need an invalid PacketKind for DhtPacket + if dp.packet_kind as u8 <= PacketKind::SendNodes as u8 { + return TestResult::discard() + } + + let kind = dp.packet_kind.clone() as u8; + // TODO: random SocketAddr + let addr = SocketAddr::V4("0.1.2.3:4".parse().unwrap()); + let mut tc = DhtCodec; + + let mut buf = [0; 1024]; + let bytes = dp.to_bytes((&mut buf, 0)).unwrap().0; + + let (decoded_a, decoded_dp) = tc.decode(&addr, &bytes) + .unwrap(); + // it did have correct packet + let decoded_dp = decoded_dp.unwrap(); + + assert_eq!(addr, decoded_a); + assert_eq!(DhtBase::DhtPacket(dp), decoded_dp); + + // make it error + bytes[0] = kind; + let (r_addr, none) = tc.decode(&addr, &bytes).unwrap(); + assert_eq!(addr, r_addr); + assert!(none.is_none()); + + TestResult::passed() + } + quickcheck(with_dp as fn(DhtPacket) -> TestResult); + } + + #[test] + fn dht_codec_encode_test() { + fn with_dp(dp: DhtPacket) { + // TODO: random SocketAddr + let addr = SocketAddr::V4("5.6.7.8:9".parse().unwrap()); + let mut buf = Vec::new(); + let mut tc = DhtCodec; + + let socket = tc.encode((addr, DhtBase::DhtPacket(dp.clone())), &mut buf); + assert_eq!(addr, socket); + let mut enc_buf = [0; MAX_DHT_PACKET_SIZE]; + let (_, size) = dp.to_bytes((&mut enc_buf, 0)).unwrap(); + assert_eq!(buf, enc_buf[..size].to_vec()); + } + quickcheck(with_dp as fn(DhtPacket)); + } +} diff --git a/src/toxcore/dht_new/kbucket.rs b/src/toxcore/dht_new/kbucket.rs index bab4c8053..55c49e135 100644 --- a/src/toxcore/dht_new/kbucket.rs +++ b/src/toxcore/dht_new/kbucket.rs @@ -148,7 +148,7 @@ impl Bucket { #[cfg(test)] fn find(&self, pk: &PublicKey) -> Option { for (n, node) in self.nodes.iter().enumerate() { - if node.pk() == pk { + if &node.pk == pk { return Some(n) } } @@ -181,7 +181,7 @@ impl Bucket { trace!(target: "Bucket", "With bucket: {:?}; PK: {:?} and new node: {:?}", self, base_pk, new_node); - match self.nodes.binary_search_by(|n| base_pk.distance(n.pk(), new_node.pk())) { + match self.nodes.binary_search_by(|n| base_pk.distance(&n.pk, &new_node.pk)) { Ok(index) => { debug!(target: "Bucket", "Updated: the node was already in the bucket."); @@ -229,7 +229,7 @@ impl Bucket { */ pub fn remove(&mut self, base_pk: &PublicKey, node_pk: &PublicKey) { trace!(target: "Bucket", "Removing PackedNode with PK: {:?}", node_pk); - match self.nodes.binary_search_by(|n| base_pk.distance(n.pk(), node_pk) ) { + match self.nodes.binary_search_by(|n| base_pk.distance(&n.pk, node_pk) ) { Ok(index) => { self.nodes.remove(index); }, @@ -241,7 +241,7 @@ impl Bucket { /// Check if node with given PK is in the `Bucket`. pub fn contains(&self, pk: &PublicKey) -> bool { - self.nodes.iter().any(|n| n.pk() == pk) + self.nodes.iter().any(|n| &n.pk == pk) } /// Get the capacity of the Bucket. @@ -335,13 +335,7 @@ impl Kbucket { self.buckets.len() as u8 } - /// Get the PK of the Kbucket. Used in tests only #[cfg(test)] - pub fn pk(&self) -> PublicKey { - self.pk - } - - #[cfg(test)] fn find(&self, pk: &PublicKey) -> Option<(usize, usize)> { for (bucket_index, bucket) in self.buckets.iter().enumerate() { match bucket.find(pk) { @@ -382,7 +376,7 @@ impl Kbucket { debug!(target: "Kbucket", "Trying to add PackedNode."); trace!(target: "Kbucket", "With PN: {:?}; and self: {:?}", node, self); - match self.bucket_index(node.pk()) { + match self.bucket_index(&node.pk) { Some(index) => self.buckets[index].try_add(&self.pk, node), None => { trace!("Failed to add node: {:?}", node); @@ -623,7 +617,7 @@ mod test { fn dht_bucket_1_capacity_try_add_test() { fn with_nodes(n1: PackedNode, n2: PackedNode) -> TestResult { let pk = PublicKey([0; PUBLICKEYBYTES]); - if pk.distance(n2.pk(), n1.pk()) != Ordering::Greater { + if pk.distance(&n2.pk, &n1.pk) != Ordering::Greater { // n2 should be greater to check we can't add it return TestResult::discard() } @@ -651,7 +645,7 @@ mod test { let mut bucket = Bucket::new(Some(bucket_size)); let non_existent_node: PackedNode = Arbitrary::arbitrary(&mut rng); - bucket.remove(&base_pk, non_existent_node.pk()); // "removing" non-existent node + bucket.remove(&base_pk, &non_existent_node.pk); // "removing" non-existent node assert_eq!(true, bucket.is_empty()); let nodes = vec![Arbitrary::arbitrary(&mut rng); num as usize]; @@ -667,7 +661,7 @@ mod test { } for node in &nodes { - bucket.remove(&base_pk, node.pk()); + bucket.remove(&base_pk, &node.pk); } assert_eq!(true, bucket.is_empty()); } @@ -729,7 +723,7 @@ mod test { let pk = nums_to_pk(a, b, c, d); let kbucket = Kbucket::new(buckets, &pk); assert_eq!(buckets, kbucket.size()); - assert_eq!(pk, kbucket.pk()); + assert_eq!(pk, kbucket.pk); } quickcheck(with_pk as fn(u64, u64, u64, u64, u8)); } @@ -792,7 +786,7 @@ mod test { // Check for actual removing for node in &nodes { - kbucket.remove(node.pk()); + kbucket.remove(&node.pk); } assert!(kbucket.is_empty()); TestResult::passed() @@ -820,7 +814,7 @@ mod test { // check whether number of correct nodes that are returned is right let correctness = |should, kbc: &Kbucket| { - assert_eq!(kbc.get_closest(&pk), kbc.get_closest(&kbc.pk())); + assert_eq!(kbc.get_closest(&pk), kbc.get_closest(&kbc.pk)); let got_nodes = kbc.get_closest(&pk); let mut got_correct = 0; @@ -901,9 +895,9 @@ mod test { kbucket.try_add(n1); kbucket.try_add(n2); kbucket.try_add(n3); - assert_eq!(Some((46, 0)), kbucket.find(n1.pk())); - assert_eq!(Some((46, 1)), kbucket.find(n2.pk())); - assert_eq!(Some((46, 2)), kbucket.find(n3.pk())); + assert_eq!(Some((46, 0)), kbucket.find(&n1.pk)); + assert_eq!(Some((46, 1)), kbucket.find(&n2.pk)); + assert_eq!(Some((46, 2)), kbucket.find(&n3.pk)); }); with_data(|kbucket, n1, n2, n3| { // insert order: n3 n2 n1 maps to position @@ -911,9 +905,9 @@ mod test { kbucket.try_add(n3); kbucket.try_add(n2); kbucket.try_add(n1); - assert_eq!(Some((46, 0)), kbucket.find(n1.pk())); - assert_eq!(Some((46, 1)), kbucket.find(n2.pk())); - assert_eq!(Some((46, 2)), kbucket.find(n3.pk())); + assert_eq!(Some((46, 0)), kbucket.find(&n1.pk)); + assert_eq!(Some((46, 1)), kbucket.find(&n2.pk)); + assert_eq!(Some((46, 2)), kbucket.find(&n3.pk)); }); // Check that removing order does not affect // the order of nodes inside @@ -923,10 +917,10 @@ mod test { kbucket.try_add(n2); // => 1 kbucket.try_add(n3); // => 2 // test removing from the beginning (n1 => 0) - kbucket.remove(n1.pk()); - assert_eq!(None, kbucket.find(n1.pk())); - assert_eq!(Some((46, 0)), kbucket.find(n2.pk())); - assert_eq!(Some((46, 1)), kbucket.find(n3.pk())); + kbucket.remove(&n1.pk); + assert_eq!(None, kbucket.find(&n1.pk)); + assert_eq!(Some((46, 0)), kbucket.find(&n2.pk)); + assert_eq!(Some((46, 1)), kbucket.find(&n3.pk)); }); with_data(|kbucket, n1, n2, n3| { // prepare kbucket @@ -934,10 +928,10 @@ mod test { kbucket.try_add(n2); // => 1 kbucket.try_add(n3); // => 2 // test removing from the middle (n2 => 1) - kbucket.remove(n2.pk()); - assert_eq!(Some((46, 0)), kbucket.find(n1.pk())); - assert_eq!(None, kbucket.find(n2.pk())); - assert_eq!(Some((46, 1)), kbucket.find(n3.pk())); + kbucket.remove(&n2.pk); + assert_eq!(Some((46, 0)), kbucket.find(&n1.pk)); + assert_eq!(None, kbucket.find(&n2.pk)); + assert_eq!(Some((46, 1)), kbucket.find(&n3.pk)); }); with_data(|kbucket, n1, n2, n3| { // prepare kbucket @@ -945,10 +939,10 @@ mod test { kbucket.try_add(n2); // => 1 kbucket.try_add(n3); // => 2 // test removing from the end (n3 => 2) - kbucket.remove(n3.pk()); - assert_eq!(Some((46, 0)), kbucket.find(n1.pk())); - assert_eq!(Some((46, 1)), kbucket.find(n2.pk())); - assert_eq!(None, kbucket.find(n3.pk())); + kbucket.remove(&n3.pk); + assert_eq!(Some((46, 0)), kbucket.find(&n1.pk)); + assert_eq!(Some((46, 1)), kbucket.find(&n2.pk)); + assert_eq!(None, kbucket.find(&n3.pk)); }); } @@ -961,13 +955,13 @@ mod test { let (pk, _) = gen_keypair(); let mut kbucket = Kbucket::new(n, &pk); assert!(!kbucket.contains(&pk)); - assert!(pns.iter().all(|pn| !kbucket.contains(pn.pk()))); + assert!(pns.iter().all(|pn| !kbucket.contains(&pn.pk))); for pn in &pns { kbucket.try_add(pn); } - assert!(kbucket.iter().all(|pn| kbucket.contains(pn.pk()))); + assert!(kbucket.iter().all(|pn| kbucket.contains(&pn.pk))); TestResult::passed() } @@ -984,7 +978,7 @@ mod test { { let fitting_nodes = pns.iter().any(|p1| pns.iter() .filter(|p2| p1 != *p2) - .any(|p2| kbucket_index(&pk, p1.pk()) == kbucket_index(&pk, p2.pk()))); + .any(|p2| kbucket_index(&pk, &p1.pk) == kbucket_index(&pk, &p2.pk))); if !fitting_nodes { return TestResult::discard() } @@ -997,12 +991,12 @@ mod test { for node in &pns { if kbucket.try_add(node) { - let index = kbucket_index(&pk, node.pk()); + let index = kbucket_index(&pk, &node.pk); // none of nodes with the same index can be added // to the kbucket assert!(pns.iter() - .filter(|pn| kbucket_index(&pk, pn.pk()) == index) - .all(|pn| !kbucket.can_add(pn.pk()))); + .filter(|pn| kbucket_index(&pk, &pn.pk) == index) + .all(|pn| !kbucket.can_add(&pn.pk))); } } diff --git a/src/toxcore/dht_new/mod.rs b/src/toxcore/dht_new/mod.rs index e93a7e2fc..9d71e7943 100644 --- a/src/toxcore/dht_new/mod.rs +++ b/src/toxcore/dht_new/mod.rs @@ -26,3 +26,5 @@ pub mod packet; pub mod kbucket; pub mod packed_node; +pub mod codec; +pub mod packet_kind; diff --git a/src/toxcore/dht_new/packed_node.rs b/src/toxcore/dht_new/packed_node.rs index e3cf54fdc..39967c9ee 100644 --- a/src/toxcore/dht_new/packed_node.rs +++ b/src/toxcore/dht_new/packed_node.rs @@ -149,13 +149,6 @@ impl PackedNode { trace!("With address: {:?}", self); self.saddr } - - /// Get an PK from the `PackedNode`. - pub fn pk(&self) -> &PublicKey { - trace!(target: "PackedNode", "Getting PK from PackedNode."); - trace!("With PK: {:?}", self); - &self.pk - } } diff --git a/src/toxcore/dht_new/packet.rs b/src/toxcore/dht_new/packet.rs index 65a543a81..eea3970ba 100644 --- a/src/toxcore/dht_new/packet.rs +++ b/src/toxcore/dht_new/packet.rs @@ -37,7 +37,7 @@ * takes care of the serializing and de-serializing DHT packets */ -use nom::{le_u8, le_u16, be_u64}; +use nom::{le_u8, le_u16, be_u64, rest}; use std::net::{ IpAddr, @@ -47,19 +47,67 @@ use std::net::{ use toxcore::binary_io_new::*; use toxcore::crypto_core::*; +use toxcore::dht_new::packet_kind::*; use toxcore::dht_new::packed_node::PackedNode; /// Length in bytes of [`PingRequest`](./struct.PingRequest.html) and /// [`PingResponse`](./struct.PingResponse.html) when serialized into bytes. pub const PING_SIZE: usize = 9; -/** Standard DHT packet that encapsulates in the payload -[`DhtPacketT`](./trait.DhtPacketT.html). +/** DHT packet base enum that encapsulates +[`DhtPacket`](./struct.DhtPacket.html) or [`DhtRequest`](./struct.DhtRequest.html). + +https://zetok.github.io/tox-spec/#dht-packet +https://zetok.github.io/tox-spec/#dht-request-packets +*/ +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum DhtBase { + /// DhtBase are wrapper for DhtPacket and DhtRequest + DhtPacket(DhtPacket), + /// DhtBase are wrapper for DhtPacket and DhtRequest + DhtRequest(DhtRequest), +} + +/** DHT packet struct that encapsulates in the payload +[`DhtPacketPayload`](./enum.DhtPacketPayload.html). https://zetok.github.io/tox-spec/#dht-packet */ #[derive(Clone, Debug, Eq, PartialEq)] -pub enum DhtPacket { +pub struct DhtPacket { + /// first class packet kind + pub packet_kind: PacketKind, + /// Public Key of Request Packet + pub pk: PublicKey, + /// one time serial number + pub nonce : Nonce, + /// payload of DhtPacket + pub payload: Vec, +} + +/** DHT Request packet struct. + +https://zetok.github.io/tox-spec/#dht-request-packets +*/ +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct DhtRequest { + /// receiver publik key + pub rpk: PublicKey, + /// sender publick key + pub spk: PublicKey, + /// one time serial number + pub nonce: Nonce, + /// payload of DhtRequest packet + pub payload: Vec, +} + +/** Standard DHT packet that embedded in the payload of +[`DhtPacket`](./struct.DhtPacket.html). + +https://zetok.github.io/tox-spec/#dht-packet +*/ +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum DhtPacketPayload { /// [`PingRequest`](./struct.PingRequest.html) structure. PingRequest(PingRequest), /// [`PingResponse`](./struct.PingResponse.html) structure. @@ -70,23 +118,123 @@ pub enum DhtPacket { SendNodes(SendNodes), } -impl ToBytes for DhtPacket { +/** Standart DHT Request packet that embedded in the payload of +[`DhtRequest`](./struct.DhtRequest.html).. + +https://zetok.github.io/tox-spec/#dht-request-packets +*/ +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum DhtRequestPayload { + /// [`NatPingRequest`](./struct.NatPingRequest.html) structure. + NatPingRequest(NatPingRequest), + /// [`NatPingResponse`](./struct.NatPingResponse.html) structure. + NatPingResponse(NatPingResponse), +} + + +impl ToBytes for DhtBase { fn to_bytes<'a>(&self, buf: (&'a mut [u8], usize)) -> Result<(&'a mut [u8], usize), GenError> { match *self { - DhtPacket::PingRequest(ref p) => p.to_bytes(buf), - DhtPacket::PingResponse(ref p) => p.to_bytes(buf), - DhtPacket::GetNodes(ref p) => p.to_bytes(buf), - DhtPacket::SendNodes(ref p) => p.to_bytes(buf), + DhtBase::DhtPacket(ref p) => p.to_bytes(buf), + DhtBase::DhtRequest(ref p) => p.to_bytes(buf), } } } +impl FromBytes for DhtBase { + named!(from_bytes, alt!( + map!(DhtPacket::from_bytes, DhtBase::DhtPacket) | + map!(DhtRequest::from_bytes, DhtBase::DhtRequest) + )); +} + +impl ToBytes for DhtPacket { + fn to_bytes<'a>(&self, buf: (&'a mut [u8], usize)) -> Result<(&'a mut [u8], usize), GenError> { + do_gen!(buf, + gen_be_u8!(self.packet_kind as u8) >> + gen_slice!(self.pk.as_ref()) >> + gen_slice!(self.nonce.as_ref()) >> + gen_slice!(self.payload.as_slice()) + ) + } +} + impl FromBytes for DhtPacket { - named!(from_bytes, alt!( - map!(PingRequest::from_bytes, DhtPacket::PingRequest) | - map!(PingResponse::from_bytes, DhtPacket::PingResponse) | - map!(GetNodes::from_bytes, DhtPacket::GetNodes) | - map!(SendNodes::from_bytes, DhtPacket::SendNodes) + named!(from_bytes, do_parse!( + packet_kind: verify!(call!(PacketKind::from_bytes), |packet_type| match packet_type { + PacketKind::PingRequest | PacketKind::PingResponse | + PacketKind::GetNodes | PacketKind::SendNodes => true, + _ => false + }) >> + pk: call!(PublicKey::from_bytes) >> + nonce: call!(Nonce::from_bytes) >> + payload: map!(rest, |bytes| bytes.to_vec() ) >> + (DhtPacket { + packet_kind: packet_kind, + pk: pk, + nonce: nonce, + payload: payload + }) + )); +} + +impl ToBytes for DhtPacketPayload { + fn to_bytes<'a>(&self, buf: (&'a mut [u8], usize)) -> Result<(&'a mut [u8], usize), GenError> { + match *self { + DhtPacketPayload::PingRequest(ref p) => p.to_bytes(buf), + DhtPacketPayload::PingResponse(ref p) => p.to_bytes(buf), + DhtPacketPayload::GetNodes(ref p) => p.to_bytes(buf), + DhtPacketPayload::SendNodes(ref p) => p.to_bytes(buf), + } + } +} + +impl ToBytes for DhtRequestPayload { + fn to_bytes<'a>(&self, buf: (&'a mut [u8], usize)) -> Result<(&'a mut [u8], usize), GenError> { + match *self { + DhtRequestPayload::NatPingRequest(ref p) => p.to_bytes(buf), + DhtRequestPayload::NatPingResponse(ref p) => p.to_bytes(buf), + } + } +} + +impl FromBytes for DhtPacketPayload { + named!(from_bytes, alt!( + map!(PingRequest::from_bytes, DhtPacketPayload::PingRequest) | + map!(PingResponse::from_bytes, DhtPacketPayload::PingResponse) | + map!(GetNodes::from_bytes, DhtPacketPayload::GetNodes) | + map!(SendNodes::from_bytes, DhtPacketPayload::SendNodes) + )); +} + +impl ToBytes for DhtRequest { + fn to_bytes<'a>(&self, buf: (&'a mut [u8], usize)) -> Result<(&'a mut [u8], usize), GenError> { + do_gen!(buf, + gen_be_u8!(0x20) >> + gen_slice!(self.rpk.as_ref()) >> + gen_slice!(self.spk.as_ref()) >> + gen_slice!(self.nonce.as_ref()) >> + gen_slice!(self.payload.as_slice()) + ) + } +} + +impl FromBytes for DhtRequest { + named!(from_bytes, do_parse!( + packet_type: verify!(call!(PacketKind::from_bytes), |packet_type| match packet_type { + PacketKind::DhtRequest => true, + _ => false + }) >> + rpk: call!(PublicKey::from_bytes) >> + spk: call!(PublicKey::from_bytes) >> + nonce: call!(Nonce::from_bytes) >> + payload: map!(rest, |bytes| bytes.to_vec() ) >> + (DhtRequest { + rpk: rpk, + spk: spk, + nonce: nonce, + payload: payload + }) )); } @@ -340,18 +488,6 @@ impl FromBytes for SendNodes { )); } -/** DHT Request packet. - -https://zetok.github.io/tox-spec/#dht-request-packets -*/ -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum DhtRequest { - /// [`NatPingRequest`](./struct.NatPingRequest.html) structure. - NatPingRequest(NatPingRequest), - /// [`NatPingResponse`](./struct.NatPingResponse.html) structure. - NatPingResponse(NatPingResponse), -} - /** NatPing request of DHT Request packet. */ #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -368,22 +504,6 @@ pub struct NatPingResponse { pub id: u64, } -impl ToBytes for DhtRequest { - fn to_bytes<'a>(&self, buf: (&'a mut [u8], usize)) -> Result<(&'a mut [u8], usize), GenError> { - match *self { - DhtRequest::NatPingRequest(ref p) => p.to_bytes(buf), - DhtRequest::NatPingResponse(ref p) => p.to_bytes(buf), - } - } -} - -impl FromBytes for DhtRequest { - named!(from_bytes, alt!( - map!(NatPingRequest::from_bytes, DhtRequest::NatPingRequest) | - map!(NatPingResponse::from_bytes, DhtRequest::NatPingResponse) - )); -} - impl FromBytes for NatPingRequest { named!(from_bytes, do_parse!( tag!(&[0xfe][..]) >> @@ -425,18 +545,134 @@ impl ToBytes for NatPingResponse { #[cfg(test)] mod test { use super::*; - use std::fmt::Debug; use byteorder::{ByteOrder, BigEndian, WriteBytesExt}; + use toxcore::dht_new::codec::*; +// use toxcore::dht_new::packet_kind::*; use quickcheck::{Arbitrary, Gen, quickcheck}; - #[derive(Clone, Copy, Debug, Eq, PartialEq)] - pub enum PacketKind { - PingRequest = 0, - PingResponse = 1, + const NAT_PING_REQUEST: PacketKind = PacketKind::PingRequest; + const NAT_PING_RESPONSE: PacketKind = PacketKind::PingResponse; + + impl DhtPacket { + pub fn new(shared_secret: &PrecomputedKey, pk: &PublicKey, dp: DhtPacketPayload) -> DhtPacket { + let nonce = &gen_nonce(); + let mut buf = [0; MAX_DHT_PACKET_SIZE]; + let (_, size) = dp.to_bytes((&mut buf, 0)).unwrap(); + let payload = seal_precomputed(&buf[..size] , nonce, shared_secret); + + DhtPacket { + packet_kind: dp.kind(), + pk: *pk, + nonce: *nonce, + payload: payload, + } + } + } + + impl DhtRequest { + /// create new DhtRequest object + pub fn new(shared_secret: &PrecomputedKey, rpk: &PublicKey, spk: &PublicKey, dp: DhtRequestPayload) -> DhtRequest { + let nonce = &gen_nonce(); + + let mut buf = [0; MAX_DHT_PACKET_SIZE]; + let (_, size) = dp.to_bytes((&mut buf, 0)).unwrap(); + let payload = seal_precomputed(&buf[..size], nonce, shared_secret); + + DhtRequest { + rpk: *rpk, + spk: *spk, + nonce: *nonce, + payload: payload, + } + } + } + + impl DhtPacketPayload { + /// Packet kind for enum DhtPacketPayload + pub fn kind(&self) -> PacketKind { + match *self { + DhtPacketPayload::PingRequest(_) => PacketKind::PingRequest, + DhtPacketPayload::PingResponse(_) => PacketKind::PingResponse, + DhtPacketPayload::GetNodes(_) => PacketKind::GetNodes, + DhtPacketPayload::SendNodes(_) => PacketKind::SendNodes, + } + } + } + + impl SendNodes { + /** + Create new `SendNodes`. Returns `None` if 0 or more than 4 nodes are + supplied. + + Created as a response to `GetNodes` request. + */ + pub fn with_nodes(request: &GetNodes, nodes: Vec) -> Option { + debug!(target: "SendNodes", "Creating SendNodes from GetNodes."); + trace!(target: "SendNodes", "With GetNodes: {:?}", request); + trace!("With nodes: {:?}", &nodes); + + if nodes.is_empty() || nodes.len() > 4 { + warn!(target: "SendNodes", "Wrong number of nodes supplied!"); + return None + } + + Some(SendNodes { nodes: nodes, id: request.id }) + } + } + + impl From for PingResponse { + fn from(p: PingRequest) -> Self { + PingResponse { id: p.id } + } + } + + impl Arbitrary for DhtBase { + fn arbitrary(g: &mut G) -> Self { + let choice = g.gen_range(0, 2); + if choice == 0 { + DhtBase::DhtPacket(DhtPacket::arbitrary(g)) + } else { + DhtBase::DhtRequest(DhtRequest::arbitrary(g)) + } + } + } + + impl Arbitrary for DhtPacket { + fn arbitrary(g: &mut G) -> Self { + let (pk, sk) = gen_keypair(); // "sender" keypair + let (r_pk, _) = gen_keypair(); // receiver PK + let precomputed = encrypt_precompute(&r_pk, &sk); + + let choice = g.gen_range(0, 4); + match choice { + 0 => + DhtPacket::new(&precomputed, &pk, DhtPacketPayload::PingRequest(PingRequest::arbitrary(g))), + 1 => + DhtPacket::new(&precomputed, &pk, DhtPacketPayload::PingResponse(PingResponse::arbitrary(g))), + 2 => + DhtPacket::new(&precomputed, &pk, DhtPacketPayload::GetNodes(GetNodes::arbitrary(g))), + 3 => + DhtPacket::new(&precomputed, &pk, DhtPacketPayload::SendNodes(SendNodes::arbitrary(g))), + _ => unreachable!("Arbitrary for DhtPacket - should not have happened!") + } + } + } + + impl Arbitrary for DhtRequest { + fn arbitrary(g: &mut G) -> Self { + let (pk, sk) = gen_keypair(); // "sender" keypair + let (r_pk, _) = gen_keypair(); // receiver PK + let precomputed = encrypt_precompute(&r_pk, &sk); + + let choice = g.gen_range(0, 2); + if choice == 0 { + DhtRequest::new(&precomputed, &r_pk, &pk,DhtRequestPayload::NatPingRequest(NatPingRequest::arbitrary(g))) + } else { + DhtRequest::new(&precomputed, &r_pk, &pk, DhtRequestPayload::NatPingResponse(NatPingResponse::arbitrary(g))) + } + } } - const NatPingRequest: PacketKind = PacketKind::PingRequest; - const NatPingResponse: PacketKind = PacketKind::PingResponse; // PingRequest:: impl Arbitrary for PingRequest { @@ -472,12 +708,6 @@ mod test { } } - impl From for PingResponse { - fn from(p: PingRequest) -> Self { - PingResponse { id: p.id } - } - } - // PingRequest:: impl Arbitrary for NatPingRequest { fn arbitrary(_g: &mut G) -> Self { @@ -518,16 +748,6 @@ mod test { } } - /// Trait for types of DHT packets that can be put in [`DhtPacket`] - /// (./struct.DhtPacket.html). - pub trait DhtPacketT: ToBytes + FromBytes + Eq + PartialEq + Debug { - /// Provide packet type number. - /// - /// To use for serialization: `.kind() as u8`. - fn kind(&self) -> PacketKind; - - } - macro_rules! tests_for_pings { ($($p:ident $b_t:ident $f_t:ident)+) => ($( @@ -582,11 +802,11 @@ mod test { } } - impl Arbitrary for DhtPacket { + impl Arbitrary for DhtPacketPayload { fn arbitrary(g: &mut G) -> Self { let mut a: [u8; PUBLICKEYBYTES] = [0; PUBLICKEYBYTES]; g.fill_bytes(&mut a); - DhtPacket::GetNodes(GetNodes { pk: PublicKey(a), id: g.gen() }) + DhtPacketPayload::GetNodes(GetNodes { pk: PublicKey(a), id: g.gen() }) } } @@ -624,30 +844,30 @@ mod test { quickcheck(with_bytes as fn(Vec)); } - // DhtPacket::GetNodes::to_bytes() + // DhtPacketPayload::GetNodes::to_bytes() #[test] fn dht_packet_get_nodes_to_bytes_test() { - fn with_gn(gn: DhtPacket) { + fn with_gn(gn: DhtPacketPayload) { let mut _buf = [0;1024]; let g_bytes = gn.to_bytes((&mut _buf, 0)).ok().unwrap().0; - if let DhtPacket::GetNodes(gp) = gn { + if let DhtPacketPayload::GetNodes(gp) = gn { let PublicKey(pk_bytes) = gp.pk; assert_eq!(&pk_bytes, &g_bytes[..PUBLICKEYBYTES]); assert_eq!(gp.id, BigEndian::read_u64(&g_bytes[PUBLICKEYBYTES..])); } } - quickcheck(with_gn as fn(DhtPacket)); + quickcheck(with_gn as fn(DhtPacketPayload)); } - // DhtPacket::GetNodes::from_bytes() + // DhtPacketPayload::GetNodes::from_bytes() #[test] fn dht_packet_get_nodes_from_bytes_test() { fn with_bytes(bytes: Vec) { if bytes.len() < GET_NODES_SIZE { assert!(!GetNodes::from_bytes(&bytes).is_done()); } else { - let gn = DhtPacket::from_bytes(&bytes).unwrap().1; - if let DhtPacket::GetNodes(gp) = gn { + let gn = DhtPacketPayload::from_bytes(&bytes).unwrap().1; + if let DhtPacketPayload::GetNodes(gp) = gn { // ping_id as bytes should match "original" bytes assert_eq!(BigEndian::read_u64(&bytes[PUBLICKEYBYTES..GET_NODES_SIZE]), gp.id); @@ -668,27 +888,6 @@ mod test { } } - impl SendNodes { - /** - Create new `SendNodes`. Returns `None` if 0 or more than 4 nodes are - supplied. - - Created as a response to `GetNodes` request. - */ - pub fn with_nodes(request: &GetNodes, nodes: Vec) -> Option { - debug!(target: "SendNodes", "Creating SendNodes from GetNodes."); - trace!(target: "SendNodes", "With GetNodes: {:?}", request); - trace!("With nodes: {:?}", &nodes); - - if nodes.is_empty() || nodes.len() > 4 { - warn!(target: "SendNodes", "Wrong number of nodes supplied!"); - return None - } - - Some(SendNodes { nodes: nodes, id: request.id }) - } - } - // SendNodes::to_bytes() #[test] fn packet_send_nodes_to_bytes_test() { @@ -773,7 +972,11 @@ mod test { let pb = p.to_bytes((&mut _buf, 0)).ok().unwrap(); assert_eq!(NAT_PING_SIZE, pb.1); assert_eq!(NAT_PING_TYPE as u8, pb.0[0]); - assert_eq!($np as u8, pb.0[1]); + if stringify!($np) == "NatPingRequest" { + assert_eq!(NAT_PING_REQUEST as u8, pb.0[1]); + } else { + assert_eq!(NAT_PING_RESPONSE as u8, pb.0[1]); + } } quickcheck(with_np as fn($np)); } @@ -794,7 +997,12 @@ mod test { quickcheck(with_bytes as fn(Vec)); // just in case - let mut ping = vec![NAT_PING_TYPE, $np as u8]; + let ping_kind = match stringify!($np) { + "NatPingRequest" => NAT_PING_REQUEST as u8, + "NatPingResponse" => NAT_PING_RESPONSE as u8, + e => unreachable!("can not occur {:?}", e) + }; + let mut ping = vec![NAT_PING_TYPE, ping_kind]; ping.write_u64::(random_u64()) .unwrap(); with_bytes(ping); diff --git a/src/toxcore/dht_new/packet_kind.rs b/src/toxcore/dht_new/packet_kind.rs new file mode 100644 index 000000000..466f0845d --- /dev/null +++ b/src/toxcore/dht_new/packet_kind.rs @@ -0,0 +1,64 @@ +/* + Copyright © 2016 Zetok Zalbavar + Copyright © 2018 Namsoo CHO + + This file is part of Tox. + + Tox is libre software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Tox is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Tox. If not, see . +*/ + +/*! Data associated with the `PacketKind`. Used by most of other `dht` + modules. + + Used by: + + * [`dht`](../dht/index.html) +*/ + +use nom::le_u8; + +use toxcore::binary_io_new::*; + + +/** Top-level packet kind names and their associated numbers. + + According to https://zetok.github.io/tox-spec.html#packet-kind. +*/ +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum PacketKind { + /// [`Ping`](./struct.Ping.html) request number. + PingRequest = 0, + /// [`Ping`](./struct.Ping.html) response number. + PingResponse = 1, + /// [`GetNodes`](./struct.GetNodes.html) packet number. + GetNodes = 2, + /// [`SendNodes`](./struct.SendNodes.html) packet number. + SendNodes = 4, + /// DHT Request. + DhtRequest = 32, +} + +/** Parse first byte from provided `bytes` as `PacketKind`. + + Returns `None` if no bytes provided, or first byte doesn't match. +*/ +impl FromBytes for PacketKind { + named!(from_bytes, switch!(le_u8, + 0 => value!(PacketKind::PingRequest) | + 1 => value!(PacketKind::PingResponse) | + 2 => value!(PacketKind::GetNodes) | + 4 => value!(PacketKind::SendNodes) | + 32 => value!(PacketKind::DhtRequest) + )); +}