add peer config happy path tests
This commit is contained in:
committed by
Jędrzej Stuczyński
parent
7042d155eb
commit
7dd0f6cfa1
@@ -1,69 +1,161 @@
|
||||
use std::char::MAX;
|
||||
|
||||
use crate::LpError;
|
||||
use rand09::{self, CryptoRng, Rng};
|
||||
|
||||
use crate::LpError;
|
||||
|
||||
pub const MAX_HOPS: u8 = 16;
|
||||
|
||||
pub const LP_PEER_CONFIG_SIZE: usize = 20;
|
||||
|
||||
const SEED_LEN: usize = 16;
|
||||
const CONFIG_LEN: usize = 1;
|
||||
const FILLER_LEN: usize = LP_PEER_CONFIG_SIZE - SEED_LEN - CONFIG_LEN;
|
||||
|
||||
// 20 bytes
|
||||
#[derive(PartialEq)]
|
||||
pub struct LpPeerConfig {
|
||||
// these 3 fields will be packed in one u8
|
||||
// these FILLER_LEN fields will be packed in one u8
|
||||
// with 2 bits left at the end
|
||||
|
||||
// determine the hop id.
|
||||
// should be 0 if node_initiator is true
|
||||
// should be > 1 if is_exit is true
|
||||
hop_id: u8,
|
||||
|
||||
// determine if the recipient should be an exit node
|
||||
is_exit: bool,
|
||||
|
||||
// determine if we are establishing a node<>node connection
|
||||
// should be false if is_exit is true
|
||||
node_initiator: bool,
|
||||
|
||||
// enable censorship resistance countermeasures
|
||||
censorship_resistance: bool,
|
||||
|
||||
|
||||
// if we add config params later, we can use this
|
||||
filler: [u8; 3],
|
||||
filler: [u8; FILLER_LEN],
|
||||
|
||||
seed: [u8; 16],
|
||||
seed: [u8; SEED_LEN],
|
||||
}
|
||||
|
||||
impl LpPeerConfig {
|
||||
pub fn new<R>(
|
||||
pub fn new_client_to_entry<R>(rng: &mut R, censorship_resistance: bool) -> Self
|
||||
where
|
||||
R: Rng + CryptoRng,
|
||||
{
|
||||
Self::build(
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
censorship_resistance,
|
||||
rng.random(),
|
||||
rng.random(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_client_to_exit<R>(
|
||||
rng: &mut R,
|
||||
hop_id: u8,
|
||||
is_exit: bool,
|
||||
censorship_resistance: bool,
|
||||
) -> Result<Self, LpError>
|
||||
where
|
||||
R: Rng + CryptoRng,
|
||||
{
|
||||
let seed: [u8; 16] = rng.random();
|
||||
let filler: [u8; 3] = rng.random();
|
||||
Self::build(hop_id, is_exit, censorship_resistance, seed, filler)
|
||||
Self::new(rng, hop_id, true, false, censorship_resistance)
|
||||
}
|
||||
|
||||
pub fn new_client_to_intermediate<R>(
|
||||
rng: &mut R,
|
||||
hop_id: u8,
|
||||
censorship_resistance: bool,
|
||||
) -> Result<Self, LpError>
|
||||
where
|
||||
R: Rng + CryptoRng,
|
||||
{
|
||||
Self::new(rng, hop_id, false, false, censorship_resistance)
|
||||
}
|
||||
|
||||
pub fn new_node_to_node<R>(rng: &mut R) -> Result<Self, LpError>
|
||||
where
|
||||
R: Rng + CryptoRng,
|
||||
{
|
||||
// no need for censorship resistance between nodes (for now)
|
||||
// hop_id between nodes is 0
|
||||
Self::new(rng, 0, false, true, false)
|
||||
}
|
||||
|
||||
pub fn new<R>(
|
||||
rng: &mut R,
|
||||
hop_id: u8,
|
||||
is_exit: bool,
|
||||
node_initiator: bool,
|
||||
censorship_resistance: bool,
|
||||
) -> Result<Self, LpError>
|
||||
where
|
||||
R: Rng + CryptoRng,
|
||||
{
|
||||
Self::build_checked(
|
||||
hop_id,
|
||||
is_exit,
|
||||
node_initiator,
|
||||
censorship_resistance,
|
||||
rng.random(),
|
||||
rng.random(),
|
||||
)
|
||||
}
|
||||
fn build(
|
||||
hop_id: u8,
|
||||
is_exit: bool,
|
||||
node_initiator: bool,
|
||||
censorship_resistance: bool,
|
||||
seed: [u8; 16],
|
||||
filler: [u8; 3],
|
||||
seed: [u8; SEED_LEN],
|
||||
filler: [u8; FILLER_LEN],
|
||||
) -> Self {
|
||||
Self {
|
||||
hop_id,
|
||||
is_exit,
|
||||
node_initiator,
|
||||
censorship_resistance,
|
||||
filler,
|
||||
seed,
|
||||
}
|
||||
}
|
||||
fn build_checked(
|
||||
hop_id: u8,
|
||||
is_exit: bool,
|
||||
node_initiator: bool,
|
||||
censorship_resistance: bool,
|
||||
seed: [u8; SEED_LEN],
|
||||
filler: [u8; FILLER_LEN],
|
||||
) -> Result<Self, LpError> {
|
||||
if hop_id >= MAX_HOPS {
|
||||
if node_initiator && is_exit {
|
||||
Err(LpError::Internal(
|
||||
"A node cannot establish an exit node for itself.".into(),
|
||||
))
|
||||
} else if node_initiator && hop_id != 0 {
|
||||
Err(LpError::Internal(
|
||||
"Hop id in node to node connections must be zero.".into(),
|
||||
))
|
||||
} else if !node_initiator && hop_id >= MAX_HOPS {
|
||||
Err(LpError::Internal(format!(
|
||||
"Requested hop index ({}) is greater than the allowed maximum {}.",
|
||||
hop_id,
|
||||
MAX_HOPS - 1
|
||||
)))
|
||||
} else if is_exit && hop_id == 0 {
|
||||
} else if !node_initiator && is_exit && hop_id == 0 {
|
||||
Err(LpError::Internal(
|
||||
"Hop index for exit node cannot be zero".into(),
|
||||
"Hop id for exit node cannot be zero.".into(),
|
||||
))
|
||||
} else if !node_initiator && !is_exit && hop_id == 15 {
|
||||
Err(LpError::Internal(
|
||||
"The hop with id 15 must be an exit node.".into(),
|
||||
))
|
||||
} else {
|
||||
Ok(Self {
|
||||
Ok(Self::build(
|
||||
hop_id,
|
||||
is_exit,
|
||||
node_initiator,
|
||||
censorship_resistance,
|
||||
|
||||
seed,
|
||||
filler,
|
||||
})
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +165,7 @@ impl LpPeerConfig {
|
||||
output_bytes[4..].copy_from_slice(&self.seed);
|
||||
output_bytes
|
||||
}
|
||||
pub fn deserialize(bytes: &[u8]) -> Result<Self, LpError> {
|
||||
pub fn deserialize(bytes: &[u8]) -> Result<Self, LpError> {
|
||||
if bytes.len() != LP_PEER_CONFIG_SIZE {
|
||||
Err(LpError::DeserializationError(format!(
|
||||
"Invalid Lp Config Length ({}), expected ({})",
|
||||
@@ -81,12 +173,23 @@ impl LpPeerConfig {
|
||||
LP_PEER_CONFIG_SIZE
|
||||
)))
|
||||
} else {
|
||||
let (hop_id, is_exit, censorship_resistance) = Self::unpack_first_byte(bytes[0]);
|
||||
let (hop_id, is_exit, node_initiator, censorship_resistance) =
|
||||
Self::unpack_first_byte(bytes[0]);
|
||||
|
||||
let filler: [u8; 3] = [bytes[1], bytes[2], bytes[3]];
|
||||
let mut seed: [u8; 16] = [0u8; 16];
|
||||
seed.copy_from_slice(&bytes[4..LP_PEER_CONFIG_SIZE]);
|
||||
Self::build(hop_id, is_exit, censorship_resistance, seed, filler)
|
||||
let mut filler: [u8; FILLER_LEN] = [0u8; FILLER_LEN];
|
||||
filler.copy_from_slice(&bytes[CONFIG_LEN..CONFIG_LEN + FILLER_LEN]);
|
||||
|
||||
let mut seed: [u8; SEED_LEN] = [0u8; SEED_LEN];
|
||||
seed.copy_from_slice(&bytes[CONFIG_LEN + FILLER_LEN..LP_PEER_CONFIG_SIZE]);
|
||||
|
||||
Self::build_checked(
|
||||
hop_id,
|
||||
is_exit,
|
||||
node_initiator,
|
||||
censorship_resistance,
|
||||
seed,
|
||||
filler,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,28 +205,181 @@ impl LpPeerConfig {
|
||||
fn pack_first_byte(&self) -> u8 {
|
||||
let mut byte = self.hop_id;
|
||||
|
||||
// Set the 5th bit to determine if the node is an exit node
|
||||
if self.is_exit {
|
||||
byte |= 0b0001_0000;
|
||||
}
|
||||
if self.censorship_resistance {
|
||||
// Set the 6th bit to determine if we're establishing a node to node connection
|
||||
if self.node_initiator {
|
||||
byte |= 0b0010_0000;
|
||||
}
|
||||
// Set the 7th bit to determine if we should use censorship resistance measures
|
||||
if self.censorship_resistance {
|
||||
byte |= 0b0100_0000;
|
||||
}
|
||||
|
||||
// there will be 2 free bits at the end
|
||||
// There will be 1 free bit at the end
|
||||
|
||||
byte
|
||||
}
|
||||
fn unpack_first_byte(byte: u8) -> (u8, bool, bool) {
|
||||
|
||||
fn unpack_first_byte(byte: u8) -> (u8, bool, bool, bool) {
|
||||
// extract 4 bits
|
||||
let hop_id = byte & 0b0000_1111;
|
||||
|
||||
// extract 5th bit
|
||||
let is_exit = (byte & 0b0001_0000) >> 4 == 1;
|
||||
// extract 6th bit
|
||||
let censorship_resistance = (byte & 0b0010_0000) >> 5 == 1;
|
||||
let node_initiator = (byte & 0b0010_0000) >> 5 == 1;
|
||||
// extract 7th bit
|
||||
let censorship_resistance = (byte & 0b0100_0000) >> 6 == 1;
|
||||
|
||||
// if we need to use the last 2 bits, we can add something here
|
||||
(hop_id, is_exit, censorship_resistance)
|
||||
// If we need to use the last bit, we can add something here
|
||||
(hop_id, is_exit, node_initiator, censorship_resistance)
|
||||
}
|
||||
|
||||
pub fn is_client_entry(&self) -> bool {
|
||||
self.hop_id == 0 && !self.is_exit && !self.node_initiator
|
||||
}
|
||||
|
||||
pub fn is_client_intermediate_node(&self) -> bool {
|
||||
self.hop_id > 0 && !self.is_exit && !self.node_initiator
|
||||
}
|
||||
|
||||
pub fn is_client_exit(&self) -> bool {
|
||||
self.hop_id > 0 && self.is_exit && !self.node_initiator
|
||||
}
|
||||
|
||||
pub fn is_node_to_node(&self) -> bool {
|
||||
self.hop_id == 0 && !self.is_exit && self.node_initiator
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::peer_config::LpPeerConfig;
|
||||
|
||||
#[test]
|
||||
fn test_pack_config() {
|
||||
let mut rng = rand09::rng();
|
||||
|
||||
// Node to node, no censorship resistance
|
||||
{
|
||||
let expected_conf = 0b0010_0000;
|
||||
let conf = LpPeerConfig::new(&mut rng, 0, false, true, false).unwrap();
|
||||
let conf_bytes = conf.serialize();
|
||||
let deserialized_conf_first_byte = LpPeerConfig::deserialize(&conf_bytes)
|
||||
.unwrap()
|
||||
.pack_config()[0];
|
||||
|
||||
assert!(expected_conf == conf_bytes[0]);
|
||||
assert!(expected_conf == deserialized_conf_first_byte);
|
||||
assert!(
|
||||
conf_bytes[0]
|
||||
== LpPeerConfig::new_node_to_node(&mut rng)
|
||||
.unwrap()
|
||||
.serialize()[0]
|
||||
);
|
||||
assert!(conf.is_node_to_node());
|
||||
}
|
||||
|
||||
// Node to node, with censorship resistance
|
||||
{
|
||||
let expected_conf = 0b0110_0000;
|
||||
let conf = LpPeerConfig::new(&mut rng, 0, false, true, true).unwrap();
|
||||
let conf_bytes = conf.serialize();
|
||||
let deserialized_conf_first_byte = LpPeerConfig::deserialize(&conf_bytes)
|
||||
.unwrap()
|
||||
.pack_config()[0];
|
||||
|
||||
assert!(expected_conf == conf_bytes[0]);
|
||||
assert!(expected_conf == deserialized_conf_first_byte);
|
||||
assert!(conf.is_node_to_node());
|
||||
}
|
||||
|
||||
// Client to Entry, no censorship resistance
|
||||
{
|
||||
let expected_conf = 0b0000_0000;
|
||||
let conf = LpPeerConfig::new(&mut rng, 0, false, false, false).unwrap();
|
||||
let conf_bytes = conf.serialize();
|
||||
let deserialized_conf_first_byte = LpPeerConfig::deserialize(&conf_bytes)
|
||||
.unwrap()
|
||||
.pack_config()[0];
|
||||
let conf_alt_first_byte =
|
||||
LpPeerConfig::new_client_to_entry(&mut rng, false).serialize()[0];
|
||||
|
||||
assert!(expected_conf == conf_bytes[0]);
|
||||
assert!(expected_conf == deserialized_conf_first_byte);
|
||||
assert!(conf_bytes[0] == conf_alt_first_byte);
|
||||
assert!(conf.is_client_entry())
|
||||
}
|
||||
|
||||
// Client to Entry, with censorship resistance
|
||||
{
|
||||
let expected_conf = 0b0100_0000;
|
||||
let conf = LpPeerConfig::new(&mut rng, 0, false, false, true).unwrap();
|
||||
let conf_bytes = conf.serialize();
|
||||
let deserialized_conf_first_byte = LpPeerConfig::deserialize(&conf_bytes)
|
||||
.unwrap()
|
||||
.pack_config()[0];
|
||||
let conf_alt_first_byte =
|
||||
LpPeerConfig::new_client_to_entry(&mut rng, true).serialize()[0];
|
||||
|
||||
assert!(expected_conf == conf_bytes[0]);
|
||||
assert!(expected_conf == deserialized_conf_first_byte);
|
||||
assert!(conf_bytes[0] == conf_alt_first_byte);
|
||||
assert!(conf.is_client_entry());
|
||||
}
|
||||
|
||||
// Client to Exit(exit hop = 1), with censorship resistance
|
||||
{
|
||||
let expected_conf = 0b0101_0001;
|
||||
let conf = LpPeerConfig::new(&mut rng, 1, true, false, true).unwrap();
|
||||
let conf_bytes = conf.serialize();
|
||||
let deserialized_conf_first_byte = LpPeerConfig::deserialize(&conf_bytes)
|
||||
.unwrap()
|
||||
.pack_config()[0];
|
||||
let conf_alt_first_byte =
|
||||
LpPeerConfig::new_client_to_exit(&mut rng, 1, true).unwrap().serialize()[0];
|
||||
|
||||
assert!(expected_conf == conf_bytes[0]);
|
||||
assert!(expected_conf == deserialized_conf_first_byte);
|
||||
assert!(conf_bytes[0] == conf_alt_first_byte);
|
||||
assert!(conf.is_client_exit());
|
||||
}
|
||||
|
||||
// Client to Exit(exit hop = 2), without censorship resistance
|
||||
{
|
||||
let expected_conf = 0b0001_0010;
|
||||
let conf = LpPeerConfig::new(&mut rng, 2, true, false, false).unwrap();
|
||||
let conf_bytes = conf.serialize();
|
||||
let deserialized_conf_first_byte = LpPeerConfig::deserialize(&conf_bytes)
|
||||
.unwrap()
|
||||
.pack_config()[0];
|
||||
let conf_alt_first_byte =
|
||||
LpPeerConfig::new_client_to_exit(&mut rng, 2, false).unwrap().serialize()[0];
|
||||
|
||||
assert!(expected_conf == conf_bytes[0]);
|
||||
assert!(expected_conf == deserialized_conf_first_byte);
|
||||
assert!(conf_bytes[0] == conf_alt_first_byte);
|
||||
assert!(conf.is_client_exit());
|
||||
}
|
||||
// Client to Intermediate (hop_id = 14), without censorship resistance
|
||||
{
|
||||
let expected_conf = 0b0000_1110;
|
||||
let conf = LpPeerConfig::new(&mut rng, 14, false, false, false).unwrap();
|
||||
let conf_bytes = conf.serialize();
|
||||
let deserialized_conf_first_byte = LpPeerConfig::deserialize(&conf_bytes)
|
||||
.unwrap()
|
||||
.pack_config()[0];
|
||||
let conf_alt_first_byte =
|
||||
LpPeerConfig::new_client_to_intermediate(&mut rng, 14, false).unwrap().serialize()[0];
|
||||
|
||||
|
||||
assert!(expected_conf == conf_bytes[0]);
|
||||
assert!(expected_conf == deserialized_conf_first_byte);
|
||||
assert!(conf_bytes[0] == conf_alt_first_byte);
|
||||
assert!(conf.is_client_intermediate_node());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user