e5afd54ce0
* Basic storage stub * New models for new node status api * Route handling * Mounting new routes * Missing selective commit * Moved network monitor related files to separate package * Starting to see some sqlx action * Schema updates * Log statement upon finished migration * Removed old diesel related imports * Converted mixnode cache initialisation into a fairing * Moved cache related functionalities to separate package Also defined staging there * Created run method for validator cache + removed unwrap * Removed old node-status-api types and left bunch of todo placeholders in their place * Fixed managing validatorcache * Status reports are starting to get constructed * Submitting some dummy results to the database * Removing duplicate code for generating reports * Removed statuses older than 48h * Initial attempt at trying to obtain reports for all active nodes * Removed duplicates from the full report * Grabbing uptime history * Updating historical uptimes of active nodes * Updated sqlx-data.json * Removed all placeholder foomp owner values * Changed Layer serde behaviour for easier usage * Extended validator api config * Initial (seems working !) integration with network monitor * Added database path configuration to config * Using ValidatorCache in NetworkMonitor * Flag indicating whether validator cache has been initialised * Introduced a locla-only route for reward script to perform daily chores * Flag to save config to a file * Moved spawning of receiving future to run method rather than new * Removed arguments that dont make sense to be configured via CLI * Removed dead code from config file * More dead code removal * Added validator API to CI * Corrected manifest-path arguments * Constructing network monitor by passing config * Combined validator API CI with the main CI file * Using query_as for NodeStatus * Checking if historical uptimes were already calculated on particular day * Making id field NOT NULL * More query_as! action * Updated sqlx-data.json * Removed unused chrono feature * Renamed the migration file * Changed default validator endpoint to point to local validator * Removing unnecessary clone * More appropriate naming * Removed dead code * Lock file updates * Updated network monitor address in contract code * Don't stage node status api if network monitor is disabled * cargo fmt * Updated all license notices to SPDX
329 lines
12 KiB
Rust
329 lines
12 KiB
Rust
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
use crate::packet::{FramedSphinxPacket, Header};
|
|
use bytes::{Buf, BufMut, BytesMut};
|
|
use nymsphinx_params::packet_modes::InvalidPacketMode;
|
|
use nymsphinx_params::packet_sizes::{InvalidPacketSize, PacketSize};
|
|
use nymsphinx_types::SphinxPacket;
|
|
use std::convert::TryFrom;
|
|
use std::io;
|
|
use tokio_util::codec::{Decoder, Encoder};
|
|
|
|
#[derive(Debug)]
|
|
pub enum SphinxCodecError {
|
|
InvalidPacketSize,
|
|
InvalidPacketMode,
|
|
MalformedSphinxPacket,
|
|
IoError(io::Error),
|
|
}
|
|
|
|
impl From<io::Error> for SphinxCodecError {
|
|
fn from(err: io::Error) -> Self {
|
|
SphinxCodecError::IoError(err)
|
|
}
|
|
}
|
|
|
|
impl From<SphinxCodecError> for io::Error {
|
|
fn from(err: SphinxCodecError) -> Self {
|
|
match err {
|
|
SphinxCodecError::InvalidPacketSize => {
|
|
io::Error::new(io::ErrorKind::InvalidInput, "invalid packet size")
|
|
}
|
|
SphinxCodecError::InvalidPacketMode => {
|
|
io::Error::new(io::ErrorKind::InvalidInput, "invalid packet mode")
|
|
}
|
|
SphinxCodecError::MalformedSphinxPacket => {
|
|
io::Error::new(io::ErrorKind::InvalidData, "malformed packet")
|
|
}
|
|
SphinxCodecError::IoError(err) => err,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<InvalidPacketSize> for SphinxCodecError {
|
|
fn from(_: InvalidPacketSize) -> Self {
|
|
SphinxCodecError::InvalidPacketSize
|
|
}
|
|
}
|
|
|
|
impl From<InvalidPacketMode> for SphinxCodecError {
|
|
fn from(_: InvalidPacketMode) -> Self {
|
|
SphinxCodecError::InvalidPacketMode
|
|
}
|
|
}
|
|
|
|
// TODO: in the future it could be extended to have state containing symmetric encryption key
|
|
// so that all data could be encrypted easily (alternatively we could just slap TLS)
|
|
pub struct SphinxCodec;
|
|
|
|
impl Encoder<FramedSphinxPacket> for SphinxCodec {
|
|
type Error = SphinxCodecError;
|
|
|
|
fn encode(&mut self, item: FramedSphinxPacket, dst: &mut BytesMut) -> Result<(), Self::Error> {
|
|
item.header.encode(dst);
|
|
dst.put(item.packet.to_bytes().as_ref());
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl Decoder for SphinxCodec {
|
|
type Item = FramedSphinxPacket;
|
|
type Error = SphinxCodecError;
|
|
|
|
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
|
if src.is_empty() {
|
|
// can't do anything if we have no bytes, but let's reserve enough for the most
|
|
// conservative case, i.e. receiving an ack packet
|
|
src.reserve(Header::SIZE + PacketSize::AckPacket.size());
|
|
return Ok(None);
|
|
}
|
|
|
|
// because header is so small and simple it makes no point in trying to cache
|
|
// this result. It will be just simpler to re-decode it
|
|
let header = match Header::decode(src)? {
|
|
Some(header) => header,
|
|
None => return Ok(None), // we have some data but not enough to get header back
|
|
};
|
|
|
|
let sphinx_packet_size = header.packet_size.size();
|
|
let frame_len = Header::SIZE + sphinx_packet_size;
|
|
|
|
if src.len() < frame_len {
|
|
// we don't have enough bytes to read the rest of frame
|
|
src.reserve(sphinx_packet_size);
|
|
return Ok(None);
|
|
}
|
|
|
|
// advance buffer past the header - at this point we have enough bytes
|
|
src.advance(Header::SIZE);
|
|
let sphinx_packet_bytes = src.split_to(sphinx_packet_size);
|
|
let sphinx_packet = match SphinxPacket::from_bytes(&sphinx_packet_bytes) {
|
|
Ok(sphinx_packet) => sphinx_packet,
|
|
// here it could be debatable whether stream is corrupt or not,
|
|
// but let's go with the safer approach and assume it is.
|
|
Err(_) => return Err(SphinxCodecError::MalformedSphinxPacket),
|
|
};
|
|
|
|
let nymsphinx_packet = FramedSphinxPacket {
|
|
header,
|
|
packet: sphinx_packet,
|
|
};
|
|
|
|
// As per docs:
|
|
// Before returning from the function, implementations should ensure that the buffer
|
|
// has appropriate capacity in anticipation of future calls to decode.
|
|
// Failing to do so leads to inefficiency.
|
|
|
|
// if we have at least one more byte available, we can reserve enough bytes for
|
|
// the entire next frame, if not, we assume the next frame is an ack packet and
|
|
// reserve for that.
|
|
if !src.is_empty() {
|
|
let next_packet_len = match PacketSize::try_from(src[0]) {
|
|
Ok(next_packet_len) => next_packet_len,
|
|
// the next frame will be malformed but let's leave handling the error to the next
|
|
// call to 'decode', as presumably, the current sphinx packet is still valid
|
|
Err(_) => return Ok(Some(nymsphinx_packet)),
|
|
};
|
|
let next_frame_len = next_packet_len.size() + Header::SIZE;
|
|
src.reserve(next_frame_len - 1);
|
|
} else {
|
|
src.reserve(Header::SIZE + PacketSize::AckPacket.size());
|
|
}
|
|
|
|
Ok(Some(nymsphinx_packet))
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod packet_encoding {
|
|
use super::*;
|
|
use nymsphinx_types::builder::SphinxPacketBuilder;
|
|
use nymsphinx_types::{
|
|
crypto, Delay as SphinxDelay, Destination, DestinationAddressBytes, Node, NodeAddressBytes,
|
|
DESTINATION_ADDRESS_LENGTH, IDENTIFIER_LENGTH, NODE_ADDRESS_LENGTH,
|
|
};
|
|
|
|
fn make_valid_sphinx_packet(size: PacketSize) -> SphinxPacket {
|
|
let (_, node1_pk) = crypto::keygen();
|
|
let node1 = Node::new(
|
|
NodeAddressBytes::from_bytes([5u8; NODE_ADDRESS_LENGTH]),
|
|
node1_pk,
|
|
);
|
|
let (_, node2_pk) = crypto::keygen();
|
|
let node2 = Node::new(
|
|
NodeAddressBytes::from_bytes([4u8; NODE_ADDRESS_LENGTH]),
|
|
node2_pk,
|
|
);
|
|
let (_, node3_pk) = crypto::keygen();
|
|
let node3 = Node::new(
|
|
NodeAddressBytes::from_bytes([2u8; NODE_ADDRESS_LENGTH]),
|
|
node3_pk,
|
|
);
|
|
|
|
let route = [node1, node2, node3];
|
|
let destination = Destination::new(
|
|
DestinationAddressBytes::from_bytes([3u8; DESTINATION_ADDRESS_LENGTH]),
|
|
[4u8; IDENTIFIER_LENGTH],
|
|
);
|
|
let delays = vec![
|
|
SphinxDelay::new_from_nanos(42),
|
|
SphinxDelay::new_from_nanos(42),
|
|
SphinxDelay::new_from_nanos(42),
|
|
];
|
|
SphinxPacketBuilder::new()
|
|
.with_payload_size(size.payload_size())
|
|
.build_packet(b"foomp".to_vec(), &route, &destination, &delays)
|
|
.unwrap()
|
|
}
|
|
|
|
#[test]
|
|
fn whole_packet_can_be_decoded_from_a_valid_encoded_instance() {
|
|
let header = Default::default();
|
|
let sphinx_packet = make_valid_sphinx_packet(Default::default());
|
|
let sphinx_bytes = sphinx_packet.to_bytes();
|
|
|
|
let packet = FramedSphinxPacket {
|
|
header,
|
|
packet: sphinx_packet,
|
|
};
|
|
|
|
let mut bytes = BytesMut::new();
|
|
SphinxCodec.encode(packet, &mut bytes).unwrap();
|
|
let decoded = SphinxCodec.decode(&mut bytes).unwrap().unwrap();
|
|
|
|
assert_eq!(decoded.header, header);
|
|
assert_eq!(decoded.packet.to_bytes(), sphinx_bytes)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod decode_will_allocate_enough_bytes_for_next_call {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn for_empty_bytes() {
|
|
// empty bytes should allocate for header + ack packet
|
|
let mut empty_bytes = BytesMut::new();
|
|
assert!(SphinxCodec.decode(&mut empty_bytes).unwrap().is_none());
|
|
assert_eq!(
|
|
empty_bytes.capacity(),
|
|
Header::SIZE + PacketSize::AckPacket.size()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn for_bytes_with_header() {
|
|
// if header gets decoded there should be enough bytes for the entire frame
|
|
let packet_sizes = vec![
|
|
PacketSize::AckPacket,
|
|
PacketSize::RegularPacket,
|
|
PacketSize::ExtendedPacket,
|
|
];
|
|
for packet_size in packet_sizes {
|
|
let header = Header {
|
|
packet_size,
|
|
packet_mode: Default::default(),
|
|
};
|
|
let mut bytes = BytesMut::new();
|
|
header.encode(&mut bytes);
|
|
assert!(SphinxCodec.decode(&mut bytes).unwrap().is_none());
|
|
|
|
assert_eq!(bytes.capacity(), Header::SIZE + packet_size.size())
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn for_full_frame() {
|
|
// if full frame is used exactly, there should be enough space for header + ack packet
|
|
let packet = FramedSphinxPacket {
|
|
header: Header::default(),
|
|
packet: make_valid_sphinx_packet(Default::default()),
|
|
};
|
|
|
|
let mut bytes = BytesMut::new();
|
|
SphinxCodec.encode(packet, &mut bytes).unwrap();
|
|
assert!(SphinxCodec.decode(&mut bytes).unwrap().is_some());
|
|
assert_eq!(
|
|
bytes.capacity(),
|
|
Header::SIZE + PacketSize::AckPacket.size()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn for_full_frame_with_extra_byte() {
|
|
// if there was at least 1 byte left, there should be enough space for entire next frame
|
|
let packet_sizes = vec![
|
|
PacketSize::AckPacket,
|
|
PacketSize::RegularPacket,
|
|
PacketSize::ExtendedPacket,
|
|
];
|
|
|
|
for packet_size in packet_sizes {
|
|
let first_packet = FramedSphinxPacket {
|
|
header: Header::default(),
|
|
packet: make_valid_sphinx_packet(Default::default()),
|
|
};
|
|
|
|
let mut bytes = BytesMut::new();
|
|
SphinxCodec.encode(first_packet, &mut bytes).unwrap();
|
|
bytes.put_u8(packet_size as u8);
|
|
assert!(SphinxCodec.decode(&mut bytes).unwrap().is_some());
|
|
|
|
assert!(bytes.capacity() >= Header::SIZE + packet_size.size())
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn can_decode_two_packets_immediately() {
|
|
let packet1 = FramedSphinxPacket {
|
|
header: Header::default(),
|
|
packet: make_valid_sphinx_packet(Default::default()),
|
|
};
|
|
|
|
let packet2 = FramedSphinxPacket {
|
|
header: Header::default(),
|
|
packet: make_valid_sphinx_packet(Default::default()),
|
|
};
|
|
|
|
let mut bytes = BytesMut::new();
|
|
|
|
SphinxCodec.encode(packet1, &mut bytes).unwrap();
|
|
SphinxCodec.encode(packet2, &mut bytes).unwrap();
|
|
|
|
assert!(SphinxCodec.decode(&mut bytes).unwrap().is_some());
|
|
assert!(SphinxCodec.decode(&mut bytes).unwrap().is_some());
|
|
assert!(SphinxCodec.decode(&mut bytes).unwrap().is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn can_decode_two_packets_in_separate_calls() {
|
|
let packet1 = FramedSphinxPacket {
|
|
header: Header::default(),
|
|
packet: make_valid_sphinx_packet(Default::default()),
|
|
};
|
|
|
|
let packet2 = FramedSphinxPacket {
|
|
header: Header::default(),
|
|
packet: make_valid_sphinx_packet(Default::default()),
|
|
};
|
|
|
|
let mut bytes = BytesMut::new();
|
|
let mut bytes_tmp = BytesMut::new();
|
|
|
|
SphinxCodec.encode(packet1, &mut bytes).unwrap();
|
|
SphinxCodec.encode(packet2, &mut bytes_tmp).unwrap();
|
|
|
|
let tmp = bytes_tmp.split_off(100);
|
|
bytes.put(bytes_tmp);
|
|
|
|
assert!(SphinxCodec.decode(&mut bytes).unwrap().is_some());
|
|
assert!(SphinxCodec.decode(&mut bytes).unwrap().is_none());
|
|
|
|
bytes.put(tmp);
|
|
|
|
assert!(SphinxCodec.decode(&mut bytes).unwrap().is_some());
|
|
assert!(SphinxCodec.decode(&mut bytes).unwrap().is_none());
|
|
}
|
|
}
|