Network Packets

Предадени решения

Краен срок:
03.12.2019 17:00
Точки:
20

Срокът за предаване на решения е отминал

use solution::*;
macro_rules! timeout {
($time:expr, $body:block) => {
use std::panic::catch_unwind;
let (sender, receiver) = std::sync::mpsc::channel();
std::thread::spawn(move || {
if let Err(e) = catch_unwind(|| $body) {
sender.send(Err(e)).unwrap();
return;
}
match sender.send(Ok(())) {
Ok(()) => {}, // everything good
Err(_) => {}, // we have been released, don't panic
}
});
if let Err(any) = receiver.recv_timeout(std::time::Duration::from_millis($time)).unwrap() {
panic!("{}", any.downcast_ref::<String>().unwrap());
}
}
}
macro_rules! assert_match {
($expr:expr, $pat:pat) => {
if let $pat = $expr {
// all good
} else {
assert!(false, "Expression {:?} does not match the pattern {:?}", $expr, stringify!($pat));
}
}
}
#[test]
fn test_construct_packet_no_remainder() {
let (packet, remainder) = Packet::from_source(b"test packet", 20);
assert_eq!(packet.payload(), b"test packet");
assert_eq!(remainder, b"");
let (packet, remainder) = Packet::from_source("баба".as_bytes(), "баба".len() as u8);
assert_eq!(packet.payload(), "баба".as_bytes());
assert_eq!(remainder, b"");
}
#[test]
fn test_construct_packet_with_remainder() {
let (packet, remainder) = Packet::from_source(b"test packet", 5);
assert_eq!(packet.payload(), b"test ");
assert_eq!(remainder, b"packet");
let (packet, remainder) = Packet::from_source(&remainder, 5);
assert_eq!(packet.payload(), b"packe");
assert_eq!(remainder, b"t");
}
#[test]
fn test_construct_packet_with_remainder_cyrillic() {
let (packet, remainder) = Packet::from_source("дядо".as_bytes(), "дя".len() as u8);
assert_eq!(packet.payload(), "дя".as_bytes());
assert_eq!(remainder, "до".as_bytes());
let (packet, remainder) = Packet::from_source(&remainder, 100);
assert_eq!(packet.payload(), "до".as_bytes());
assert_eq!(remainder, b"");
}
#[test]
#[should_panic]
fn test_zero_size() {
let (_, _) = Packet::from_source(b"xyz", 0);
}
#[test]
fn test_construct_packet_from_unicode() {
let source = String::from("раз-два");
let (packet, remainder) = Packet::from_source(source.as_bytes(), 6);
assert_eq!(packet.payload(), "раз".as_bytes());
assert_eq!(remainder, "-два".as_bytes());
let source = String::from("раз-два");
let (packet, remainder) = Packet::from_source(source.as_bytes(), 1);
assert_eq!(packet.payload().get(0).unwrap(), "р".as_bytes().get(0).unwrap());
assert_eq!(remainder.get(0).unwrap(), "р".as_bytes().get(1).unwrap());
}
#[test]
fn test_serialize_packet() {
let (packet, _) = Packet::from_source(b"xyz", 100);
let data = packet.serialize();
assert_eq!(data[0], 1); // version
assert_eq!(data[1], 3); // size
assert_eq!(&data[2..=4], b"xyz"); // payload
let checksum = b'x' as u32 + b'y' as u32 + b'z' as u32;
assert_eq!(&data[5..=8], checksum.to_be_bytes()); // checksum
}
#[test]
fn test_deserialize_packet() {
let checksum = b'x' as u32 + b'y' as u32 + b'z' as u32;
let mut data = vec![1, 3];
data.extend_from_slice(b"xyz");
data.extend_from_slice(&checksum.to_be_bytes());
let (packet, _) = Packet::from_source(b"xyz", 100);
assert_eq!(Packet::deserialize(&data).unwrap(), (packet, b"" as &[u8]));
let (packet, _) = Packet::from_source(b"xyz", 100);
data.extend_from_slice(b"_extra_stuff");
assert_eq!(Packet::deserialize(&data).unwrap(), (packet, b"_extra_stuff" as &[u8]));
}
#[test]
fn test_deserialize_unicode_packet() {
let payload = "хитро";
let checksum = payload.as_bytes().into_iter().map(|b| *b as u32).sum::<u32>();
let mut data = vec![1, payload.len() as u8]; // version, size
data.extend_from_slice(payload.as_bytes());
data.extend_from_slice(&checksum.to_be_bytes());
let (packet, _) = Packet::from_source(payload.as_bytes(), 100);
assert_eq!(Packet::deserialize(&data).unwrap(), (packet, b"" as &[u8]));
let (packet, _) = Packet::from_source(payload.as_bytes(), 100);
data.extend_from_slice("и още нещо".as_bytes());
assert_eq!(Packet::deserialize(&data).unwrap(), (packet, "и още нещо".as_bytes()));
}
#[test]
fn test_deserialize_invalid_packet() {
let checksum = b'x' as u32 + b'y' as u32 + b'z' as u32;
let mut data = vec![1, 3];
data.extend_from_slice(b"xyz");
data.extend_from_slice(&checksum.to_be_bytes());
// Wrong version:
data[0] = 2;
assert_match!(Packet::deserialize(&data), Err(PacketError::UnknownProtocolVersion));
data[0] = 1;
assert!(Packet::deserialize(&data).is_ok());
// Wrong size:
data[1] = 100;
assert_match!(Packet::deserialize(&data), Err(PacketError::InvalidPacket));
data[1] = 4;
assert_match!(Packet::deserialize(&data), Err(PacketError::InvalidPacket));
data[1] = 2; // Note: shorter, so invalid checksum
assert_match!(Packet::deserialize(&data), Err(PacketError::InvalidChecksum));
data[1] = 3;
assert!(Packet::deserialize(&data).is_ok());
// Wrong checksum:
for checksum_byte in 5..=8 {
data[checksum_byte] += 1;
assert_match!(Packet::deserialize(&data), Err(PacketError::InvalidChecksum));
data[checksum_byte] -= 1;
assert!(Packet::deserialize(&data).is_ok());
}
}
#[test]
fn test_iterating_packets() {
timeout!(1000, {
let source = String::from("foo bar baz");
let serializer = source.to_packets(4);
let packets: Vec<Packet> = serializer.collect();
assert_eq!(packets.len(), 3);
assert_eq!(packets[0].payload(), b"foo ");
assert_eq!(packets[1].payload(), b"bar ");
assert_eq!(packets[2].payload(), b"baz");
});
}
#[test]
fn test_iterating_packets_for_zero_size_string() {
timeout!(1000, {
let source = String::new();
let serializer = source.to_packets(4);
let packets: Vec<Packet> = serializer.collect();
assert_eq!(packets.len(), 0);
});
}
#[test]
fn test_consuming_packets() {
timeout!(1000, {
let source = String::from("foo bar baz");
let serializer = source.to_packets(4);
let packets: Vec<u8> = serializer.flat_map(|p| p.serialize()).collect();
let result = String::from_packet_data(&packets).unwrap();
assert_eq!(result.as_str(), "foo bar baz");
});
}
#[test]
fn test_full_roundtrip() {
timeout!(1000, {
let source = String::from("foo bar baz");
let packet_data = source.to_packet_data(10);
let destination = String::from_packet_data(&packet_data).unwrap();
assert_eq!(source, destination);
});
}
#[test]
fn test_full_roundtrip_for_zero_size_string() {
timeout!(1000, {
let source = String::new();
let packet_data = source.to_packet_data(10);
let destination = String::from_packet_data(&packet_data).unwrap();
assert_eq!(source, destination);
});
}
#[test]
fn test_invalid_packet_combination() {
timeout!(1000, {
let source = String::from("сюрприз");
let mut packets = source.to_packets(3).collect::<Vec<Packet>>();
// Delete a packet in the middle, end up with bad utf8
packets.swap_remove(1);
let packet_data = packets.into_iter().flat_map(|packet| packet.serialize()).collect::<Vec<u8>>();
assert_match!(String::from_packet_data(&packet_data), Err(PacketError::CorruptedMessage));
});
}

Network Packets

Когато искаме да изпратим съобщение по мрежата, не става просто да метнем един низ и го прочетем от другата страна. Протоколи като TCP и UDP се грижат да го нацепят на парчета с фиксиран размер, и да добавят неща като пореден номер на пакета (защото може да пристигнат в грешния ред) и checksum -- някаква стойност генерирана от съобщението, с която може да се валидира, че е пристигнало в оригиналния си вид.

Ние ще имплементираме някаква проста форма на подобно пакетиране. Очакваме да вземем един низ, да го разбием на пакети, които да се сериализират до байтове, и после да изпарсим оригиналния низ от тези байтове.

Всеки един от индивидуалните пакети ще се сериализира в байтове (u8) по следния начин:

  • Байт 0: Версията на протокола. Винаги ще бъде 1, но трябва да има forward compatibility!
  • Байт 1: Размера на съобщението, което се съхранява в следващите байтове: N.
  • Следващите точно N байта: Самото съобщение, в байтове. Термина, който ще използваме по-надолу за тази част от пакета е "payload".
  • Следващите 4 байта: Едно u32 число, което е сумата от всички байтове на съобщението (checksum), записана в big endian формат (вижте документацията на u32).

Откъм интерфейс, очакваме кода да изглежда по този начин:

use std::fmt;

/// Грешките, които ще очакваме да върнете. По-долу ще е описано кои от тези грешки очакваме да се
/// върнат в каква ситуация.
///
#[derive(Debug)]
pub enum PacketError {
    InvalidPacket,
    InvalidChecksum,
    UnknownProtocolVersion,
    CorruptedMessage,
}

/// Нужна е имплементация на Display за грешките, за да може да имплементират `std::error::Error`.
/// Свободни сте да напишете каквито искате съобщения, ще тестваме само типовете, не низовия им
/// вид.
///
/// Ако са във формат на хайку, няма да получите бонус точки, но може да получите чувство на
/// вътрешно удовлетворение.
///
impl fmt::Display for PacketError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        unimplemented!()
    }
}

/// Тази имплементация би трябвало да сработи директно благодарение на горните. При желание, можете
/// да си имплементирате ръчно някои от методите, само внимавайте.
///
impl std::error::Error for PacketError {}

/// Един пакет, съдържащ част от съобщението. Изберете сами какви полета да използвате за
/// съхранение.
///
/// Може да е нужно да добавите lifetimes на дефиницията тук и/или на методите в impl блока.
///
#[derive(PartialEq, Debug)]
pub struct Packet {
    // ...
}

impl Packet {
    /// Конструира пакет от дадения slice от байтове. Приема параметър `size`, който е размера на
    /// payload-а на новия пакет. Връща двойка от пакет + оставащите байтове. Тоест, ако имате низа
    /// "abcd" и викнете метода върху байтовата му репрезентация с параметър `size` равен на 3, ще
    /// върнете двойката `(<пакет с payload "abc">, <байтовия низ "d">)`.
    ///
    /// Байтове от низ можете да извадите чрез `.as_bytes()`, можете и да си конструирате байтов
    /// литерал като b"abcd".
    ///
    /// Ако подадения `size` е по-голям от дължината на `source`, приемаме, че размера ще е точно
    /// дължината на `source` (и остатъка ще е празен slice).
    ///
    /// Ако параметъра `size` е 0, очакваме тази функция да panic-не (приемаме, че това извикване
    /// просто е невалидно, програмистка грешка).
    ///
    pub fn from_source(source: &[u8], size: u8) -> (Self, &[u8]) {
        unimplemented!()
    }

    /// Връща само slice-а който пакета опакова. Тоест, ако сме конструирали пакета със
    /// `Packet::from_source(b"abc", 3)`, очакваме `.payload()` да ни върне `b"abc"`.
    ///
    /// Защо това просто не е публично property? За да не позволяваме мутация, а само конструиране
    /// и четене.
    ///
    pub fn payload(&self) -> &[u8] {
        unimplemented!()
    }

    /// Сериализира пакета, тоест превръща го в байтове, готови за трансфер. Версия, дължина,
    /// съобщение (payload), checksum. Вижте по-горе за детайлно обяснение.
    ///
    pub fn serialize(&self) -> Vec<u8> {
        unimplemented!()
    }

    /// Имайки slice от байтове, искаме да извадим един пакет от началото и да върнем остатъка,
    /// пакетиран в `Result`.
    ///
    /// Байтовете са репрезентация на пакет -- версия, размер, и т.н. както е описано по-горе.
    ///
    /// Ако липсват версия, размер, чексума, или размера е твърде малък, за да може да се изпарси
    /// валиден пакет от байтовете, връщаме грешка `PacketError::InvalidPacket`.
    ///
    /// Ако версията е различна от 1, връщаме `PacketError::UnknownProtocolVersion`.
    ///
    /// Ако checksum-а, който прочитаме от последните 4 байта на пакета е различен от изчисления
    /// checksum на payload-а (сумата от байтовете му), връщаме `PacketError::InvalidChecksum`.
    ///
    /// Забележете, че ако размера е по-голям от истинския размер на payload-а, се очаква
    /// `PacketError::InvalidPacket`. Ако размера е по-малък от истинския размер на payload-а,
    /// въпросния ще се изпарси, но чексумата ще е грешна, така че ще очакваме
    /// `PacketError::InvalidChecksum`. Малко тъпо! Но уви, протоколите имат подобни тъпи ръбове,
    /// особено като са написани за един уикенд. Авторите обещават по-добър протокол за версия 2.
    ///
    pub fn deserialize(bytes: &[u8]) -> Result<(Packet, &[u8]), PacketError> {
        unimplemented!()
    }
}

/// Структура, която ще служи за итериране по пакети. Ще я конструираме от някакво съобщение, и
/// итерацията ще връща всеки следващ пакет, докато съобщението не бъде напълно "изпратено".
/// Изберете каквито полета ви трябват.
///
/// Може да е нужно да добавите lifetimes на дефиницията тук и/или на методите в impl блока.
///
pub struct PacketSerializer {
    // ...
}

impl Iterator for PacketSerializer {
    type Item = Packet;

    fn next(&mut self) -> Option<Self::Item> {
        unimplemented!()
    }
}

/// Този trait ще ни позволи да конвертираме един `String` (а ако искаме, и други неща) от и до
/// комплект от байтове за прехвърляне по мрежата.
///
/// Детайли за методите вижте по-долу в имплементацията на този trait за `String`.
///
pub trait Packetable: Sized {
    fn to_packets(&self, packet_size: u8) -> PacketSerializer;
    fn to_packet_data(&self, packet_size: u8) -> Vec<u8>;
    fn from_packet_data(packet_data: &[u8]) -> Result<Self, PacketError>;
}

impl Packetable for String {
    /// Този метод приема размер, който да използваме за размера на payload-а на всеки пакет. Връща
    /// итератор върху въпросните пакети. Низа трябва да се използва под формата на байтове.
    ///
    /// Както при `.from_source`, ако подадения `packet_size` е по-голям от дължината на оставащите
    /// байтове, приемаме, че размера на съответния пакет ще е колкото остава.
    ///
    fn to_packets(&self, packet_size: u8) -> PacketSerializer {
        unimplemented!()
    }

    /// Имайки итератор по пакети, лесно можем да сериализираме всеки индивидуален пакет в поредица
    /// от байтове със `.serialize()` и да го натъпчем във вектора.
    ///
    /// Както при `.from_source`, ако подадения `packet_size` е по-голям от дължината на оставащите
    /// байтове, приемаме, че размера на съответния пакет ще е колкото остава.
    ///
    fn to_packet_data(&self, packet_size: u8) -> Vec<u8> {
        unimplemented!()
    }

    /// Обратното на горния метод е тази асоциирана функция -- имайки slice от байтове които са
    /// сериализирана репрезентация на пакети, искаме да десериализираме пакети от този slice, да
    /// им извадим payload-ите, и да ги сглобим в оригиналното съобщение.
    ///
    /// Грешките, които могат да се върнат, са същите, които идват от `.deserialize()`.
    ///
    /// Една допълнителна грешка, която може да се случи е при сглобяване на съобщението -- ако е
    /// имало липсващ пакет, може съчетанието на байтовете да не генерира правилно UTF8 съобщение.
    /// Тогава връщаме `PacketError::CorruptedMessage`.
    ///
    fn from_packet_data(packet_data: &[u8]) -> Result<Self, PacketError> {
        unimplemented!()
    }
}

Тоест, един пълен round-trip на един низ би могъл да изглежда така:

let source      = String::from("hello");
let packet_data = source.to_packet_data(100);
let destination = String::from_packet_data(&packet_data).unwrap();

assert_eq!(source, destination);

Бележки за имплементацията

  • Внимавайте при жонглирането на типове. Вероятно ще имате ситуации, в които трябва да конвертирате между usize и u8 и u32. Мислете внимателно кога можете да си позволите просто да използвате as и къде е най-удобно да го използвате. Понякога, може да е по-удачно да ползвате .try_into() от std::convert::TryInto -- компилатора вероятно ще ви го предложи. Сами решете.

  • Ако имате разнообразни типове грешки от разни вградени функции, можете да си имплементирате From за конвертиране от тях до вашия тип. Не е необходимо обаче -- може да има и по-лесни ad-hoc начини от една грешка да получите друга грешка в тази ситуация, разгледайте документацията на Result.

  • Работим с байтове, не с char-ове. Това ще ви улесни някои неща, ще ви усложни други. Тествайте си кода внимателно с разнообразни типове низове.

  • Документацията на примитивния тип slice може да ви даде идеи как най-лесно да манипулирате slice-ове.

Както винаги, не забравяйте, че имате базов тест, с който кода задължително трябва да може да се компилира: test_basic.rs

Задължително прочетете (или си припомнете): Указания за предаване на домашни