Решение на Network Packets от Стоян Ефтимов

Обратно към всички решения

Към профила на Стоян Ефтимов

Резултати

  • 15 точки от тестове
  • 1 бонус точка
  • 16 точки общо
  • 11 успешни тест(а)
  • 4 неуспешни тест(а)

Код

use std::fmt;
const VERSION: u8 = 1;
/// Грешките, които ще очакваме да върнете. По-долу ще е описано кои от тези грешки очакваме да се
/// върнат в каква ситуация.
///
#[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 {
match self {
PacketError::InvalidPacket => write!(f, "{}", "Invalid packet"),
PacketError::InvalidChecksum => write!(f, "{}", "Invalid checksum"),
PacketError::UnknownProtocolVersion => write!(f, "{}", "Unknown protocol version"),
PacketError::CorruptedMessage => write!(f, "{}", "Packet corrupted"),
}
}
}
/// Тази имплементация би трябвало да сработи директно благодарение на горните. При желание, можете
/// да си имплементирате ръчно някои от методите, само внимавайте.
///
impl std::error::Error for PacketError {}
/// Един пакет, съдържащ част от съобщението. Изберете сами какви полета да използвате за
/// съхранение.
///
/// Може да е нужно да добавите lifetimes на дефиницията тук и/или на методите в impl блока.
///
#[derive(PartialEq, Debug)]
pub struct Packet<> {
version: u8,
size: u8,
data: Vec<u8>,
checksum: u32,
}
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]) {
if size == 0 {
panic!("could not create packet from empty source")
}
let payload_size = std::cmp::min(source.len(), size as usize);
let data = source[..payload_size].to_vec();
let checksum = Packet::calc_checksum(&data);
let packet = Packet {
version: VERSION,
size: payload_size as u8,
data,
checksum,
};
(packet, &source[payload_size..])
}
/// Връща само slice-а който пакета опакова. Тоест, ако сме конструирали пакета със
/// `Packet::from_source(b"abc", 3)`, очакваме `.payload()` да ни върне `b"abc"`.
///
/// Защо това просто не е публично property? За да не позволяваме мутация, а само конструиране
/// и четене.
///
pub fn payload(&self) -> &[u8] {
&self.data
}
/// Сериализира пакета, тоест превръща го в байтове, готови за трансфер. Версия, дължина,
/// съобщение (payload), checksum. Вижте по-горе за детайлно обяснение.
///
pub fn serialize(&self) -> Vec<u8> {
let mut v = vec![self.version, self.size];
v.extend_from_slice(&self.data);
v.extend(&self.checksum.to_be_bytes());
v
}
/// Имайки 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> {
let mut iter = bytes.iter();
let version = match iter.next() {
None => return Err(PacketError::InvalidPacket),
Some(version) => *version,
};
if version != VERSION {
return Err(PacketError::UnknownProtocolVersion);
}
let size = match iter.next() {
None => return Err(PacketError::InvalidPacket),
Some(size) => *size,
} as usize;
if size == 0 {
panic!("failed to deserialize packet with size 0")
}
let mut payload = vec![];
for _ in 0..size {
let byte = match iter.next() {
None => return Err(PacketError::InvalidPacket),
Some(byte) => *byte
};
payload.push(byte);
}
match std::str::from_utf8(&payload) {
Err(_) => return Err(PacketError::CorruptedMessage),
_ => (),
};

Не е задължително тук да бъде валиден utf8 низ съобщението. Чак по-долу, когато го съберем в низ, трябваше да се направи тази проверка. Интересното е, че това не fail-ва тестове, което ме учудва. Може би наша грешка, че не сме хванали този случай.

let mut checksum_bytes: [u8; 4] = [0; 4];
for i in 0..4 {
let byte = match iter.next() {
None => return Err(PacketError::InvalidPacket),
Some(byte) => *byte,
};
checksum_bytes[i] = byte;
}
let checksum = u32::from_be_bytes(checksum_bytes);
if checksum != Packet::calc_checksum(&payload) {
return Err(PacketError::InvalidChecksum)
}
let packet = Packet {
version,
size: size as u8,
data: payload,
checksum,
};
Ok((packet, &bytes[size + 6..]))
}
fn calc_checksum(payload: &[u8]) -> u32 {
let mut checksum = 0u32;
checksum += VERSION as u32 + payload.len() as u32;
for &byte in payload {
checksum += byte as u32;
}
checksum
}

Не сме се разбрали тук за логиката на чексумата. Когато в условието пишем за "сумата от байтовете на съобщението" имахме предвид payload частта. Може би трябваше да сложим "payload", но думата "съобщение" също се използва консистентно за тази част в инструкциите:

  • Байт 1: Размера на съобщението, което се съхранява в следващите байтове: N.
  • Следващите точно N байта: Самото съобщение, в байтове. Термина, който ще използваме по-надолу за тази част от пакета е "payload".

Не е лесно да се изразим напълно ясно. "По-надолу"-то трябваше да бъде в инструкциите в кода, но сигурно си помислил, че щом не използваме "payload" сигурно имаме предвид всичко до момента. Ще ми се да беше питал, но какво да се прави. Загубата на 4 точки не мисля, че е твърде голяма драма.

}
/// Структура, която ще служи за итериране по пакети. Ще я конструираме от някакво съобщение, и
/// итерацията ще връща всеки следващ пакет, докато съобщението не бъде напълно "изпратено".
/// Изберете каквито полета ви трябват.
///
/// Може да е нужно да добавите lifetimes на дефиницията тук и/или на методите в impl блока.
///
pub struct PacketSerializer {
message: Vec<u8>,
size: u8,
}
impl PacketSerializer {
fn new(message: &[u8], size: u8) -> PacketSerializer {
return PacketSerializer {
message: message.to_vec(),
size,
}
}
}
impl Iterator for PacketSerializer {
type Item = Packet;
fn next(&mut self) -> Option<Self::Item> {
if self.message.len() == 0 {
return None
}
let (packet, rest) = Packet::from_source(&self.message, self.size);
self.message = rest.to_vec(); // TODO: don't copy.
Some(packet)
}
}
/// Този 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 {
let s = self.clone();
PacketSerializer::new(&s.into_bytes(), packet_size)
}
/// Имайки итератор по пакети, лесно можем да сериализираме всеки индивидуален пакет в поредица
/// от байтове със `.serialize()` и да го натъпчем във вектора.
///
/// Както при `.from_source`, ако подадения `packet_size` е по-голям от дължината на оставащите
/// байтове, приемаме, че размера на съответния пакет ще е колкото остава.
///
fn to_packet_data(&self, packet_size: u8) -> Vec<u8> {
let mut data = vec![];
for packet in self.to_packets(packet_size) {
data.extend(packet.serialize());
}
data
}
/// Обратното на горния метод е тази асоциирана функция -- имайки slice от байтове които са
/// сериализирана репрезентация на пакети, искаме да десериализираме пакети от този slice, да
/// им извадим payload-ите, и да ги сглобим в оригиналното съобщение.
///
/// Грешките, които могат да се върнат, са същите, които идват от `.deserialize()`.
///
/// Една допълнителна грешка, която може да се случи е при сглобяване на съобщението -- ако е
/// имало липсващ пакет, може съчетанието на байтовете да не генерира правилно UTF8 съобщение.
/// Тогава връщаме `PacketError::CorruptedMessage`.
///
fn from_packet_data(packet_data: &[u8]) -> Result<Self, PacketError> {
let mut s = String::new();
let mut data = packet_data;
loop {
if data.len() == 0 {
return Ok(s)
}
match Packet::deserialize(data) {
Err(e) => return Err(e),
Ok((packet, rest)) => {
match String::from_utf8(packet.payload().to_vec()) {
Err(_) => return Err(PacketError::CorruptedMessage),
Ok(res) => s.push_str(&res),
};
data = rest

Пак, тук правиш проверка за utf8-валидност на всеки индивидуален пакет, което няма винаги да работи. Спокойно можеш да имаш пакети, които са невалиден utf8, payload-овете им са просто байтове. Важното беше събраното съобщение да е ок.

},
}
}
}
}
#[test]
fn test_basic_packets() {
let source = b"hello";
let (packet, remainder) = Packet::from_source(source, 100);
assert_eq!(packet.payload().len(), source.len());
assert_eq!(remainder, b"");
assert!(packet.serialize().len() > 0);
match Packet::deserialize(&packet.serialize()) {
Err(_) => assert!(false, "Couldn't deserialize serialized packet"),
Ok((pack, rest)) => {
assert_eq!(packet.payload(), pack.payload());
assert_eq!(rest.len(), 0)
}
}
}
#[test]
fn test_from_source() {
let source: Vec<u8> = vec![1, 2, 3, 4, 5];
let (packet, remainder) = Packet::from_source(&source, 3);
let mut payload = packet.payload().iter();
assert_eq!(payload.next(), Some(&1u8));
assert_eq!(payload.next(), Some(&2u8));
assert_eq!(payload.next(), Some(&3u8));
assert_eq!(payload.next(), None);
let mut rem = remainder.iter();
assert_eq!(rem.next(), Some(&4u8));
assert_eq!(rem.next(), Some(&5u8));
assert_eq!(rem.next(), None);
}
#[test]
fn test_serialize() {
let source: Vec<u8> = vec![1, 2, 3, 4, 5];
let (packet, _) = Packet::from_source(&source, 2);
let message = packet.serialize();
let mut iter = message.iter();
assert_eq!(iter.next(), Some(&1u8)); // version
assert_eq!(iter.next(), Some(&2u8)); // size
assert_eq!(iter.next(), Some(&1u8)); // byte1
assert_eq!(iter.next(), Some(&2u8)); // byte2
assert_eq!(iter.next(), Some(&0u8)); // checksum byte1
assert_eq!(iter.next(), Some(&0u8)); // checksum byte2
assert_eq!(iter.next(), Some(&0u8)); // checksum byte3
assert_eq!(iter.next(), Some(&6u8)); // checksum byte4
assert_eq!(iter.next(), None);
}
#[test]
fn test_deserialize() {
let source: Vec<u8> = vec![1, 2, 1, 2, 0, 0, 0, 6, /*next packet*/ 1, 2];
let (packet, rest) = Packet::deserialize(&source).unwrap(); // TODO: check err
let mut payload = packet.payload().iter();
assert_eq!(payload.next(), Some(&1));
assert_eq!(payload.next(), Some(&2));
assert_eq!(payload.next(), None);
let res = Packet::deserialize(&rest);
match res {
Err(e) => match e {
PacketError::InvalidPacket => (),
_ => assert!(false, "expected parsing invalid packet to return err InvalidPacket"),
}
_ => assert!(false, "Successfully deserialized corrupted packet"),
};

Един начин да си опростиш тези тестове е с макрос:

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));
        }
    }
}

Тогава този тест ще е нещо такова:

assert_match!(Packet::deserialize(&data), Err(PacketError::InvalidPacket));
}
#[test]
fn test_deserialize_unknown_version() {
let source: Vec<u8> = vec![0, 1, 1, 0, 0, 0, 2];
match Packet::deserialize(&source) {
Err(e) => match e {
PacketError::UnknownProtocolVersion => (),
_ => assert!(false, "expected parsing invalid packet to return err UnknownProtocolVersion"),
}
_ => assert!(false, "Successfully deserialized corrupted packet"),
};
}
#[test]
fn test_deserialize_invalid_checksum() {
let source: Vec<u8> = vec![1, 1, 1, 0, 0, 0, 1];
match Packet::deserialize(&source) {
Err(e) => match e {
PacketError::InvalidChecksum => (),
_ => assert!(false, "expected parsing invalid packet to return err InvalidChecksum"),
}
_ => assert!(false, "Successfully deserialized corrupted packet"),
};
}
#[test]
fn test_deserialize_corrupted_message() {
let source: Vec<u8> = vec![1, 1, 0xFC, 0, 0, 0, 1];
match Packet::deserialize(&source) {
Err(e) => match e {
PacketError::CorruptedMessage => (),
_ => assert!(false, "expected parsing invalid packet to return err CorruptedMessage"),
}
_ => assert!(false, "Successfully deserialized corrupted packet"),
};
}
#[test]
fn test_iteration() {
let source = String::from("hello, world");
let packets = source.to_packets(6).collect::<Vec<Packet>>();
assert_eq!(packets.len(), 2);
assert_eq!(packets[0].payload(), b"hello,");
assert_eq!(packets[1].payload(), b" world");
}
#[test]
fn test_to_packet_data() {
let source = String::from_utf8(vec![100, 101, 102]).unwrap();
let packet_data = source.to_packet_data(2);
assert_eq!(packet_data[0], 1);
assert_eq!(packet_data[1], 2);
assert_eq!(packet_data[2], 100);
assert_eq!(packet_data[3], 101);
assert_eq!(packet_data[4], 0);
assert_eq!(packet_data[5], 0);
assert_eq!(packet_data[6], 0);
assert_eq!(packet_data[7], 204);
assert_eq!(packet_data[8], 1);
assert_eq!(packet_data[9], 1);
assert_eq!(packet_data[10], 102);
assert_eq!(packet_data[11], 0);
assert_eq!(packet_data[12], 0);
assert_eq!(packet_data[13], 0);
assert_eq!(packet_data[14], 104);
}
#[test]
fn from_packet_data() {
let packet_data: Vec<u8> = vec![1, 2, 100, 101, 0, 0, 0, 204, 1, 1, 102, 0, 0, 0, 104];
assert_eq!(
String::from_utf8(vec![100, 101, 102]).unwrap(),
String::from_packet_data(&packet_data).unwrap()
);
}

Лог от изпълнението

Compiling solution v0.1.0 (/tmp/d20200111-2173579-15m5fc1/solution)
    Finished test [unoptimized + debuginfo] target(s) in 4.75s
     Running target/debug/deps/solution-a73e64ec87929bd0

running 10 tests
test from_packet_data ... ok
test test_basic_packets ... ok
test test_deserialize ... ok
test test_deserialize_corrupted_message ... ok
test test_deserialize_invalid_checksum ... ok
test test_deserialize_unknown_version ... ok
test test_from_source ... ok
test test_iteration ... ok
test test_serialize ... ok
test test_to_packet_data ... ok

test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

     Running target/debug/deps/solution_test-38971695424b36d5

running 15 tests
test solution_test::test_construct_packet_from_unicode ... ok
test solution_test::test_construct_packet_no_remainder ... ok
test solution_test::test_construct_packet_with_remainder ... ok
test solution_test::test_construct_packet_with_remainder_cyrillic ... ok
test solution_test::test_consuming_packets ... ok
test solution_test::test_deserialize_invalid_packet ... FAILED
test solution_test::test_deserialize_packet ... FAILED
test solution_test::test_deserialize_unicode_packet ... FAILED
test solution_test::test_full_roundtrip ... ok
test solution_test::test_full_roundtrip_for_zero_size_string ... ok
test solution_test::test_invalid_packet_combination ... ok
test solution_test::test_iterating_packets ... ok
test solution_test::test_iterating_packets_for_zero_size_string ... ok
test solution_test::test_serialize_packet ... FAILED
test solution_test::test_zero_size ... ok

failures:

---- solution_test::test_deserialize_invalid_packet stdout ----
thread 'main' panicked at 'assertion failed: Packet::deserialize(&data).is_ok()', tests/solution_test.rs:149:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

---- solution_test::test_deserialize_packet stdout ----
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: InvalidChecksum', src/libcore/result.rs:1165:5

---- solution_test::test_deserialize_unicode_packet stdout ----
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: InvalidChecksum', src/libcore/result.rs:1165:5

---- solution_test::test_serialize_packet stdout ----
thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `[0, 0, 1, 111]`,
 right: `[0, 0, 1, 107]`', tests/solution_test.rs:103:5


failures:
    solution_test::test_deserialize_invalid_packet
    solution_test::test_deserialize_packet
    solution_test::test_deserialize_unicode_packet
    solution_test::test_serialize_packet

test result: FAILED. 11 passed; 4 failed; 0 ignored; 0 measured; 0 filtered out

error: test failed, to rerun pass '--test solution_test'

История (2 версии и 5 коментара)

Стоян качи първо решение на 03.12.2019 00:50 (преди почти 6 години)

Стоян качи решение на 03.12.2019 01:23 (преди почти 6 години)

use std::fmt;
const VERSION: u8 = 1;
/// Грешките, които ще очакваме да върнете. По-долу ще е описано кои от тези грешки очакваме да се
/// върнат в каква ситуация.
///
#[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!()
+ match self {
+ PacketError::InvalidPacket => write!(f, "{}", "Invalid packet"),
+ PacketError::InvalidChecksum => write!(f, "{}", "Invalid checksum"),
+ PacketError::UnknownProtocolVersion => write!(f, "{}", "Unknown protocol version"),
+ PacketError::CorruptedMessage => write!(f, "{}", "Packet corrupted"),
+ }
}
}
/// Тази имплементация би трябвало да сработи директно благодарение на горните. При желание, можете
/// да си имплементирате ръчно някои от методите, само внимавайте.
///
impl std::error::Error for PacketError {}
/// Един пакет, съдържащ част от съобщението. Изберете сами какви полета да използвате за
/// съхранение.
///
/// Може да е нужно да добавите lifetimes на дефиницията тук и/или на методите в impl блока.
///
#[derive(PartialEq, Debug)]
pub struct Packet<> {
version: u8,
size: u8,
- _payload: Vec<u8>,
+ data: Vec<u8>,
checksum: u32,
}
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]) {
if size == 0 {
panic!("could not create packet from empty source")
}
let payload_size = std::cmp::min(source.len(), size as usize);
- let payload = source[..payload_size].to_vec();
- let checksum = Packet::calc_checksum(&payload);
+ let data = source[..payload_size].to_vec();
+ let checksum = Packet::calc_checksum(&data);
let packet = Packet {
version: VERSION,
size: payload_size as u8,
- _payload: payload,
+ data,
checksum,
};
(packet, &source[payload_size..])
}
/// Връща само slice-а който пакета опакова. Тоест, ако сме конструирали пакета със
/// `Packet::from_source(b"abc", 3)`, очакваме `.payload()` да ни върне `b"abc"`.
///
/// Защо това просто не е публично property? За да не позволяваме мутация, а само конструиране
/// и четене.
///
pub fn payload(&self) -> &[u8] {
- &self._payload
+ &self.data
}
/// Сериализира пакета, тоест превръща го в байтове, готови за трансфер. Версия, дължина,
/// съобщение (payload), checksum. Вижте по-горе за детайлно обяснение.
///
pub fn serialize(&self) -> Vec<u8> {
let mut v = vec![self.version, self.size];
- for &p in self._payload.iter() {
- v.push(p);
- }
+ v.extend_from_slice(&self.data);
v.extend(&self.checksum.to_be_bytes());
v
}
/// Имайки 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> {
let mut iter = bytes.iter();
let version = match iter.next() {
None => return Err(PacketError::InvalidPacket),
Some(version) => *version,
};
if version != VERSION {
return Err(PacketError::UnknownProtocolVersion);
}
let size = match iter.next() {
None => return Err(PacketError::InvalidPacket),
Some(size) => *size,
} as usize;
if size == 0 {
panic!("failed to deserialize packet with size 0")
}
let mut payload = vec![];
for _ in 0..size {
let byte = match iter.next() {
None => return Err(PacketError::InvalidPacket),
Some(byte) => *byte
};
payload.push(byte);
}
match std::str::from_utf8(&payload) {
Err(_) => return Err(PacketError::CorruptedMessage),
_ => (),
};

Не е задължително тук да бъде валиден utf8 низ съобщението. Чак по-долу, когато го съберем в низ, трябваше да се направи тази проверка. Интересното е, че това не fail-ва тестове, което ме учудва. Може би наша грешка, че не сме хванали този случай.

let mut checksum_bytes: [u8; 4] = [0; 4];
for i in 0..4 {
let byte = match iter.next() {
None => return Err(PacketError::InvalidPacket),
Some(byte) => *byte,
};
checksum_bytes[i] = byte;
}
let checksum = u32::from_be_bytes(checksum_bytes);
if checksum != Packet::calc_checksum(&payload) {
return Err(PacketError::InvalidChecksum)
}
let packet = Packet {
version,
size: size as u8,
- _payload: payload,
+ data: payload,
checksum,
};
Ok((packet, &bytes[size + 6..]))
}
fn calc_checksum(payload: &[u8]) -> u32 {
let mut checksum = 0u32;
checksum += VERSION as u32 + payload.len() as u32;
for &byte in payload {
checksum += byte as u32;
}
checksum
}

Не сме се разбрали тук за логиката на чексумата. Когато в условието пишем за "сумата от байтовете на съобщението" имахме предвид payload частта. Може би трябваше да сложим "payload", но думата "съобщение" също се използва консистентно за тази част в инструкциите:

  • Байт 1: Размера на съобщението, което се съхранява в следващите байтове: N.
  • Следващите точно N байта: Самото съобщение, в байтове. Термина, който ще използваме по-надолу за тази част от пакета е "payload".

Не е лесно да се изразим напълно ясно. "По-надолу"-то трябваше да бъде в инструкциите в кода, но сигурно си помислил, че щом не използваме "payload" сигурно имаме предвид всичко до момента. Ще ми се да беше питал, но какво да се прави. Загубата на 4 точки не мисля, че е твърде голяма драма.

}
/// Структура, която ще служи за итериране по пакети. Ще я конструираме от някакво съобщение, и
/// итерацията ще връща всеки следващ пакет, докато съобщението не бъде напълно "изпратено".
/// Изберете каквито полета ви трябват.
///
/// Може да е нужно да добавите lifetimes на дефиницията тук и/или на методите в impl блока.
///
pub struct PacketSerializer {
message: Vec<u8>,
size: u8,
}
impl PacketSerializer {
fn new(message: &[u8], size: u8) -> PacketSerializer {
return PacketSerializer {
message: message.to_vec(),
size,
}
}
}
impl Iterator for PacketSerializer {
type Item = Packet;
fn next(&mut self) -> Option<Self::Item> {
if self.message.len() == 0 {
return None
}
let (packet, rest) = Packet::from_source(&self.message, self.size);
self.message = rest.to_vec(); // TODO: don't copy.
Some(packet)
}
}
/// Този 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 {
let s = self.clone();
PacketSerializer::new(&s.into_bytes(), packet_size)
}
/// Имайки итератор по пакети, лесно можем да сериализираме всеки индивидуален пакет в поредица
/// от байтове със `.serialize()` и да го натъпчем във вектора.
///
/// Както при `.from_source`, ако подадения `packet_size` е по-голям от дължината на оставащите
/// байтове, приемаме, че размера на съответния пакет ще е колкото остава.
///
fn to_packet_data(&self, packet_size: u8) -> Vec<u8> {
let mut data = vec![];
for packet in self.to_packets(packet_size) {
data.extend(packet.serialize());
}
data
}
/// Обратното на горния метод е тази асоциирана функция -- имайки slice от байтове които са
/// сериализирана репрезентация на пакети, искаме да десериализираме пакети от този slice, да
/// им извадим payload-ите, и да ги сглобим в оригиналното съобщение.
///
/// Грешките, които могат да се върнат, са същите, които идват от `.deserialize()`.
///
/// Една допълнителна грешка, която може да се случи е при сглобяване на съобщението -- ако е
/// имало липсващ пакет, може съчетанието на байтовете да не генерира правилно UTF8 съобщение.
/// Тогава връщаме `PacketError::CorruptedMessage`.
///
fn from_packet_data(packet_data: &[u8]) -> Result<Self, PacketError> {
let mut s = String::new();
let mut data = packet_data;
loop {
if data.len() == 0 {
return Ok(s)
}
match Packet::deserialize(data) {
Err(e) => return Err(e),
Ok((packet, rest)) => {
match String::from_utf8(packet.payload().to_vec()) {
Err(_) => return Err(PacketError::CorruptedMessage),
Ok(res) => s.push_str(&res),
};
data = rest

Пак, тук правиш проверка за utf8-валидност на всеки индивидуален пакет, което няма винаги да работи. Спокойно можеш да имаш пакети, които са невалиден utf8, payload-овете им са просто байтове. Важното беше събраното съобщение да е ок.

},
}
}
}
}
#[test]
fn test_basic_packets() {
let source = b"hello";
let (packet, remainder) = Packet::from_source(source, 100);
assert_eq!(packet.payload().len(), source.len());
assert_eq!(remainder, b"");
assert!(packet.serialize().len() > 0);
match Packet::deserialize(&packet.serialize()) {
Err(_) => assert!(false, "Couldn't deserialize serialized packet"),
Ok((pack, rest)) => {
assert_eq!(packet.payload(), pack.payload());
assert_eq!(rest.len(), 0)
}
}
}
#[test]
fn test_from_source() {
let source: Vec<u8> = vec![1, 2, 3, 4, 5];
let (packet, remainder) = Packet::from_source(&source, 3);
let mut payload = packet.payload().iter();
assert_eq!(payload.next(), Some(&1u8));
assert_eq!(payload.next(), Some(&2u8));
assert_eq!(payload.next(), Some(&3u8));
assert_eq!(payload.next(), None);
let mut rem = remainder.iter();
assert_eq!(rem.next(), Some(&4u8));
assert_eq!(rem.next(), Some(&5u8));
assert_eq!(rem.next(), None);
}
#[test]
fn test_serialize() {
let source: Vec<u8> = vec![1, 2, 3, 4, 5];
let (packet, _) = Packet::from_source(&source, 2);
let message = packet.serialize();
let mut iter = message.iter();
assert_eq!(iter.next(), Some(&1u8)); // version
assert_eq!(iter.next(), Some(&2u8)); // size
assert_eq!(iter.next(), Some(&1u8)); // byte1
assert_eq!(iter.next(), Some(&2u8)); // byte2
assert_eq!(iter.next(), Some(&0u8)); // checksum byte1
assert_eq!(iter.next(), Some(&0u8)); // checksum byte2
assert_eq!(iter.next(), Some(&0u8)); // checksum byte3
assert_eq!(iter.next(), Some(&6u8)); // checksum byte4
assert_eq!(iter.next(), None);
}
#[test]
fn test_deserialize() {
let source: Vec<u8> = vec![1, 2, 1, 2, 0, 0, 0, 6, /*next packet*/ 1, 2];
let (packet, rest) = Packet::deserialize(&source).unwrap(); // TODO: check err
let mut payload = packet.payload().iter();
assert_eq!(payload.next(), Some(&1));
assert_eq!(payload.next(), Some(&2));
assert_eq!(payload.next(), None);
let res = Packet::deserialize(&rest);
match res {
Err(e) => match e {
PacketError::InvalidPacket => (),
_ => assert!(false, "expected parsing invalid packet to return err InvalidPacket"),
+ }
+ _ => assert!(false, "Successfully deserialized corrupted packet"),
+ };

Един начин да си опростиш тези тестове е с макрос:

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));
        }
    }
}

Тогава този тест ще е нещо такова:

assert_match!(Packet::deserialize(&data), Err(PacketError::InvalidPacket));
+}
+
+#[test]
+fn test_deserialize_unknown_version() {
+ let source: Vec<u8> = vec![0, 1, 1, 0, 0, 0, 2];
+ match Packet::deserialize(&source) {
+ Err(e) => match e {
+ PacketError::UnknownProtocolVersion => (),
+ _ => assert!(false, "expected parsing invalid packet to return err UnknownProtocolVersion"),
+ }
+ _ => assert!(false, "Successfully deserialized corrupted packet"),
+ };
+}
+
+#[test]
+fn test_deserialize_invalid_checksum() {
+ let source: Vec<u8> = vec![1, 1, 1, 0, 0, 0, 1];
+ match Packet::deserialize(&source) {
+ Err(e) => match e {
+ PacketError::InvalidChecksum => (),
+ _ => assert!(false, "expected parsing invalid packet to return err InvalidChecksum"),
+ }
+ _ => assert!(false, "Successfully deserialized corrupted packet"),
+ };
+}
+
+#[test]
+fn test_deserialize_corrupted_message() {
+ let source: Vec<u8> = vec![1, 1, 0xFC, 0, 0, 0, 1];
+ match Packet::deserialize(&source) {
+ Err(e) => match e {
+ PacketError::CorruptedMessage => (),
+ _ => assert!(false, "expected parsing invalid packet to return err CorruptedMessage"),
}
_ => assert!(false, "Successfully deserialized corrupted packet"),
};
}
#[test]
fn test_iteration() {
let source = String::from("hello, world");
let packets = source.to_packets(6).collect::<Vec<Packet>>();
assert_eq!(packets.len(), 2);
assert_eq!(packets[0].payload(), b"hello,");
assert_eq!(packets[1].payload(), b" world");
}
#[test]
fn test_to_packet_data() {
let source = String::from_utf8(vec![100, 101, 102]).unwrap();
let packet_data = source.to_packet_data(2);
assert_eq!(packet_data[0], 1);
assert_eq!(packet_data[1], 2);
assert_eq!(packet_data[2], 100);
assert_eq!(packet_data[3], 101);
assert_eq!(packet_data[4], 0);
assert_eq!(packet_data[5], 0);
assert_eq!(packet_data[6], 0);
assert_eq!(packet_data[7], 204);
assert_eq!(packet_data[8], 1);
assert_eq!(packet_data[9], 1);
assert_eq!(packet_data[10], 102);
assert_eq!(packet_data[11], 0);
assert_eq!(packet_data[12], 0);
assert_eq!(packet_data[13], 0);
assert_eq!(packet_data[14], 104);
}
#[test]
fn from_packet_data() {
let packet_data: Vec<u8> = vec![1, 2, 100, 101, 0, 0, 0, 204, 1, 1, 102, 0, 0, 0, 104];
assert_eq!(
String::from_utf8(vec![100, 101, 102]).unwrap(),
String::from_packet_data(&packet_data).unwrap()
);
}

Добро решение. Не сме се разбрали с условието, но отвъд това е сравнително добре имплементирано. Все пак може да разгледаш останалите домашни за идеи.

Ще ти дам и една бонус точка за писането на тестове.