Решение на Network Packets от Любослав Карев

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

Към профила на Любослав Карев

Резултати

  • 11 точки от тестове
  • 1 бонус точка
  • 12 точки общо
  • 8 успешни тест(а)
  • 7 неуспешни тест(а)

Код

use std::fmt;
#[derive(Debug)]
pub enum PacketError {
InvalidPacket,
InvalidChecksum,
UnknownProtocolVersion,
CorruptedMessage,
}
/// Warning - bad jokes below
impl fmt::Display for PacketError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self{
PacketError::InvalidPacket => write!(f,"Roses are red.\n\
Violets are blue.\n\
InvalidPacket !\n\
What will you do ?"),
PacketError::InvalidChecksum => write!(f,"When life gives you lemons, you make an InvalidChecksum lemonade, of course."),
PacketError::UnknownProtocolVersion => write!(f,"The answer is always one (UnknownProtocolVersion)"),
PacketError::CorruptedMessage => write!(f,"Coŕrup̧t̢edM҉e͢ssage"),
}
}
impl std::error::Error for PacketError {}
fn generate_checksum(obj: &Packet) -> u32{
let mut checksum:u32 = 0;
checksum += obj.version as u32;
checksum += obj.size_of as u32;
for item in &obj.data{
checksum += *item as u32;
}
checksum
}

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

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

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

По принцип си прав, да, може би имаше логика да checksum-нем payload-а + header-ите. Цялото нещо е малко arbitrary, така че eh, така го измислихме. Догодина може би различно :).

#[derive(PartialEq, Debug, Clone)]
pub struct Packet {
version: u8,
size_of: usize,
data: Vec<u8>,
checksum: u32,
}
impl Packet {
pub fn from_source(source: &[u8], size: u8) -> (Self, &[u8]) {
let mut new_object = Packet{
version: 1,
size_of: size as usize,
data: vec![],
checksum: 0
};
if size == 0{
panic!("Invalid argument");
}
if (size as usize) < source.len()
{
for index in 0..(size as usize){
new_object.data.push(source[index]);
}
new_object.checksum = generate_checksum(&new_object);
return (new_object, &source[(size as usize)..source.len()]);
}
else
{
new_object.size_of = source.len();
for index in 0..source.len(){
new_object.data.push(source[index]);
}
new_object.checksum = generate_checksum(&new_object);
return (new_object, &[]);
}
}
pub fn payload(&self) -> &[u8] {
self.data.as_slice()
}
pub fn serialize(&self) -> Vec<u8> {
let mut result = vec![];
result.push(self.version);
result.push(self.size_of as u8);
for item in &self.data{
result.push(*item)
}
for byte in &self.checksum.to_be_bytes(){
result.push(*byte);
}
result
}
pub fn deserialize(bytes: &[u8]) -> Result<(Packet, &[u8]), PacketError> {
if bytes.len() < 4{
return Err(PacketError::InvalidPacket);
}
if bytes[0] != 1{
return Err(PacketError::UnknownProtocolVersion);
}
if (bytes[1] as usize) < bytes.len() - 6{
return Err(PacketError::InvalidPacket);
}
let packet_to_return = Packet{
version: bytes[0],
size_of: bytes[1] as usize,
data: {
let mut data_vec = vec![];
for index in 0..bytes[1] as usize{
data_vec.push(bytes[index + 2])
}
data_vec
},
checksum: {
let length = bytes[1] as usize;
let checksum_bytes = [bytes[length + 2], bytes[length + 3],
bytes[length + 4], bytes[length + 5]];
u32::from_be_bytes(checksum_bytes)
}
};
if generate_checksum(&packet_to_return) != packet_to_return.checksum
{
return Err(PacketError::InvalidChecksum);
}
let return_bytes = &bytes[packet_to_return.size_of + 5..bytes.len()];
Ok((packet_to_return, return_bytes))
}
}
pub struct PacketSerializer{
packets: Vec<Packet>,
current_packet: usize
}
impl Iterator for PacketSerializer{
type Item = Packet;
fn next(&mut self) -> Option<Self::Item> {
if self.current_packet < self.packets.len()
{
self.current_packet += 1;
Some(self.packets[self.current_packet - 1].clone())
}
else{
None
}
}
}
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-а на всеки пакет. Връща
/// итератор върху въпросните пакети. Низа трябва да се използва под формата на байтове.
///
fn to_packets(&self, packet_size: u8) -> PacketSerializer {
let input_bytes: &[u8] = self.as_bytes();
PacketSerializer{
packets: {
let mut temp_packet_holder = vec![];
for item in input_bytes.chunks(packet_size as usize){
temp_packet_holder.push(Packet::from_source(item, packet_size).0);
}

По принцип идеята на това Packet::from_source да връща остатък беше да използвате този остатък за следващата итерация. И това работи, разбира се, и в някои отношения, даже е по-удобно. Ако бих имплементирал логиката по този начин, бих накарал from_source да връща само един елемент, и даже да не приема packet_size, но разбира се ти нямаше как да правиш промени по интерфейса.

temp_packet_holder
},
current_packet: 0
}
}
/// Имайки итератор по пакети, лесно можем да сериализираме всеки индивидуален пакет в поредица
/// от байтове със `.serialize()` и да го натъпчем във вектора.
///
fn to_packet_data(&self, packet_size: u8) -> Vec<u8> {
let packets = self.to_packets(packet_size);
let mut packet_data = vec![];
for item in packets{
packet_data.extend(item.serialize());
}
packet_data
}
/// Обратното на горния метод е тази асоциирана функция -- имайки slice от байтове които са
/// сериализирана репрезентация на пакети, искаме да десериализираме пакети от този slice, да
/// им извадим payload-ите, и да ги сглобим в оригиналното съобщение.
///
/// Грешките, които могат да се върнат, са същите, които идват от `.deserialize()`.
///
/// Една допълнителна грешка, която може да се случи е при сглобяване на съобщението -- ако е
/// имало липсващ пакет, може съчетанието на байтовете да не генерира правилно UTF8 съобщение.
/// Тогава връщаме `PacketError::CorruptedMessage`.
///
fn from_packet_data(packet_data: &[u8]) -> Result<Self, PacketError> {
let mut current_packet : Packet;
let mut result: String = String::from("");
let mut loop_data = packet_data;
while let Ok(item) = Packet::deserialize(loop_data){
current_packet = item.0;
match String::from_utf8(current_packet.data){
Ok(text) => result.push_str(text.as_str()),
Err(_) => return Err(PacketError::CorruptedMessage),
}
loop_data = item.1;

Не е необходимо всеки индивидуален пакет да бъде utf8-валиден низ. Пакетите съдържат просто поредици от байтове, отсечени в произволна точка, идеята беше съчетанието от байтове да бъде низ, понеже се конструира от низ. (И то конкретно в имплементацията за String. Ако искахме имплементация примерно за Vec<u8>, нямаше да има нужда от тази проверка.)

}
Ok(result)
}
}
#[cfg(test)]
mod tests {
use crate::*;
#[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);
if let Err(_) = Packet::deserialize(&packet.serialize()) {
assert!(false, "Couldn't deserialize serialized packet");
}
}
#[test]
fn test_basic_iteration() {
let source = String::from("hello");
let packets = source.to_packets(100).collect::<Vec<Packet>>();
assert!(packets.len() > 0);
let data = source.to_packet_data(100);
assert!(data.len() > 0);
if let Err(_) = String::from_packet_data(&data) {
assert!(false, "Couldn't deserialize serialized packet data");
}
}
#[test]
fn test_advanced_packets()
{
let source = b"Please send this text.";
let (packet, mut remainder) = Packet::from_source(source, 5);
let mut packets = vec![];
packets.push(packet);
while remainder != []{
let (temp_packet, temp_remainder) = Packet::from_source(remainder, 5);
packets.push(temp_packet);
remainder = temp_remainder;
}
assert_eq!("Pleas", String::from_utf8(packets[0].data.clone()).unwrap());
assert_eq!("e sen", String::from_utf8(packets[1].data.clone()).unwrap());
assert_eq!("d thi", String::from_utf8(packets[2].data.clone()).unwrap());
assert_eq!("s tex", String::from_utf8(packets[3].data.clone()).unwrap());
assert_eq!("t.", String::from_utf8(packets[4].data.clone()).unwrap());
assert_eq!(b"abc", Packet::from_source(b"abc", 3).0.payload());
assert_eq!(b"abcq", Packet::from_source(b"abcqwe", 4).0.payload());
//Откраднах този тест от Discord
let s = String::new();
let x = s.to_packets(10).next();
assert_eq!(x, None);
assert_eq!(String::new().to_packets(10).next(), None);
//Проверка за кирилица + сериализация
let serialize_source = String::from("Айрян");
let serialize_packet = Packet::from_source(serialize_source.as_bytes(), 100).0;
let serialized_bytes = serialize_packet.serialize();
assert_eq!("Айрян", String::from_utf8(serialize_packet.data.clone()).unwrap());
assert_eq!([1, 10, 208, 144, 208, 185, 209, 128, 209, 143, 208, 189, 0, 0, 7, 50], serialized_bytes.as_slice());
let deserialized_result = Packet::deserialize(&serialized_bytes);
let deserialized_packet_leftover = deserialized_result.unwrap();
let deserialized_packet = deserialized_packet_leftover.0;
assert_eq!("Айрян", String::from_utf8(deserialized_packet.data.clone()).unwrap());
}
#[test]
fn test_advanced_iteration() {
let source = String::from("Please send this text.");
let packets = source.to_packets(5);
let results: [&str; 5] = ["Pleas", "e sen", "d thi", "s tex", "t."];
assert_eq!("Pleas", String::from_utf8(packets.packets[0].data.clone()).unwrap());
assert_eq!("e sen", String::from_utf8(packets.packets[1].data.clone()).unwrap());
assert_eq!("d thi", String::from_utf8(packets.packets[2].data.clone()).unwrap());
assert_eq!("s tex", String::from_utf8(packets.packets[3].data.clone()).unwrap());
assert_eq!("t.", String::from_utf8(packets.packets[4].data.clone()).unwrap());
//Мързеливо програмиране - можеше да го напиша по-добре
let mut index = 0;
for item in packets{
assert_eq!(String::from(results[index]), String::from_utf8(item.data.clone()).unwrap());
index+=1;
}
//Отново - грозен код
let source_serialize = String::from("Айрян");
assert_eq!(vec![1, 10, 208, 144, 208, 185, 209, 128, 209, 143, 208, 189, 0, 0, 7, 50], source_serialize.to_packet_data(100));
assert_eq!("Айрян", String::from_packet_data(&[1, 10, 208, 144, 208, 185, 209, 128, 209, 143, 208, 189, 0, 0, 7, 50]).unwrap());
}
}

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

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

running 4 tests
test tests::test_advanced_iteration ... ok
test tests::test_advanced_packets ... ok
test tests::test_basic_iteration ... ok
test tests::test_basic_packets ... ok

test result: ok. 4 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 ... thread '<unnamed>' panicked at 'assertion failed: `(left == right)`
  left: `""`,
 right: `"foo bar baz"`', tests/solution_test.rs:204:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
FAILED
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 ... thread '<unnamed>' panicked at 'assertion failed: `(left == right)`
  left: `"foo bar baz"`,
 right: `""`', tests/solution_test.rs:215:9
FAILED
test solution_test::test_full_roundtrip_for_zero_size_string ... ok
test solution_test::test_invalid_packet_combination ... thread '<unnamed>' panicked at 'Expression Ok("") does not match the pattern "Err(PacketError::CorruptedMessage)"', tests/solution_test.rs:239:9
FAILED
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_consuming_packets stdout ----
thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `""`,
 right: `"foo bar baz"`', tests/solution_test.rs:197:5

---- solution_test::test_deserialize_invalid_packet stdout ----
thread 'main' panicked at 'assertion failed: Packet::deserialize(&data).is_ok()', tests/solution_test.rs:149:5

---- 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_full_roundtrip stdout ----
thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `"foo bar baz"`,
 right: `""`', tests/solution_test.rs:210:5

---- solution_test::test_invalid_packet_combination stdout ----
thread 'main' panicked at 'Expression Ok("") does not match the pattern "Err(PacketError::CorruptedMessage)"', tests/solution_test.rs:232: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_consuming_packets
    solution_test::test_deserialize_invalid_packet
    solution_test::test_deserialize_packet
    solution_test::test_deserialize_unicode_packet
    solution_test::test_full_roundtrip
    solution_test::test_invalid_packet_combination
    solution_test::test_serialize_packet

test result: FAILED. 8 passed; 7 failed; 0 ignored; 0 measured; 0 filtered out

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

История (3 версии и 8 коментара)

Любослав качи първо решение на 03.12.2019 11:17 (преди почти 6 години)

Любослав качи решение на 03.12.2019 11:55 (преди почти 6 години)

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 {}
fn generate_checksum(obj: &Packet) -> u32{
let mut checksum:u32 = 0;
checksum += obj.version as u32;
checksum += obj.size_of as u32;
for item in &obj.data{
checksum += *item as u32;
}
checksum
}
/// Един пакет, съдържащ част от съобщението. Изберете сами какви полета да използвате за
/// съхранение.
///
/// Може да е нужно да добавите lifetimes на дефиницията тук и/или на методите в impl блока.
///
#[derive(PartialEq, Debug, Clone)]
pub struct Packet {
version: u8,
size_of: usize,
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]) {
let mut new_object = Packet{
version: 1,
size_of: size as usize,
data: vec![],
checksum: 0
};
if size == 0{
panic!("Invalid argument");
}
if (size as usize) < source.len()
{
for index in 0..(size as usize){
new_object.data.push(source[index]);
}
new_object.checksum = generate_checksum(&new_object);
return (new_object, &source[source.len() - (size as usize)..source.len()]);
}
else
{
new_object.size_of = source.len();
for index in 0..source.len(){
new_object.data.push(source[index]);
}
new_object.checksum = generate_checksum(&new_object);
return (new_object, &[]);
}
}
/// Връща само slice-а който пакета опакова. Тоест, ако сме конструирали пакета със
/// `Packet::from_source(b"abc", 3)`, очакваме `.payload()` да ни върне `b"abc"`.
///
/// Защо това просто не е публично property? За да не позволяваме мутация, а само конструиране
/// и четене.
///
pub fn payload(&self) -> &[u8] {
self.data.as_slice()
}
/// Сериализира пакета, тоест превръща го в байтове, готови за трансфер. Версия, дължина,
/// съобщение (payload), checksum. Вижте по-горе за детайлно обяснение.
///
pub fn serialize(&self) -> Vec<u8> {
let mut result = vec![];
result.push(self.version);
result.push(self.size_of as u8);
for item in &self.data{
result.push(*item)
}
for byte in &self.checksum.to_be_bytes(){
result.push(*byte);
}
result
}
/// Имайки 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> {
if bytes.len() < 4{
return Err(PacketError::InvalidPacket);
}
if bytes[0] != 1{
return Err(PacketError::UnknownProtocolVersion);
}
if (bytes[1] as usize) < bytes.len() - 6{
return Err(PacketError::InvalidPacket);
}
let packet_to_return = Packet{
version: bytes[0],
size_of: bytes[1] as usize,
data: {
let mut data_vec = vec![];
for index in 0..bytes[1] as usize{
- print!("{}", index+2);
data_vec.push(bytes[index + 2])
}
data_vec
},
checksum: {
let length = bytes[1] as usize;
let checksum_bytes = [bytes[length + 2], bytes[length + 3],
bytes[length + 4], bytes[length + 5]];
u32::from_be_bytes(checksum_bytes)
}
};
if generate_checksum(&packet_to_return) != packet_to_return.checksum
{
return Err(PacketError::InvalidChecksum);
}
-
- Ok((packet_to_return, bytes))
+ let return_bytes = &bytes[packet_to_return.size_of + 5..bytes.len()];
+ Ok((packet_to_return, return_bytes))
}
}
/// Структура, която ще служи за итериране по пакети. Ще я конструираме от някакво съобщение, и
/// итерацията ще връща всеки следващ пакет, докато съобщението не бъде напълно "изпратено".
/// Изберете каквито полета ви трябват.
///
/// Може да е нужно да добавите lifetimes на дефиницията тук и/или на методите в impl блока.
///
pub struct PacketSerializer{
packets: Vec<Packet>,
current_packet: usize
}
-impl PacketSerializer{
- fn new() -> Self{
- PacketSerializer{
- packets: vec![],
- current_packet: 0
- }
- }
-}
-
impl Iterator for PacketSerializer{
type Item = Packet;
fn next(&mut self) -> Option<Self::Item> {
- println!("current_packet: {}, packtes.len() {}", self.current_packet, self.packets.len());
- if self.current_packet <= self.packets.len()
+ if self.current_packet < self.packets.len()
{
self.current_packet += 1;
Some(self.packets[self.current_packet - 1].clone())
}
else{
None
}
}
}
/// Този trait ще ни позволи да конвертираме един `String` (а ако искаме, и др1уги неща) от и до
/// комплект от байтове за прехвърляне по мрежата.
///
/// Детайли за методите вижте по-долу в имплементацията на този 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-а на всеки пакет. Връща
/// итератор върху въпросните пакети. Низа трябва да се използва под формата на байтове.
///
fn to_packets(&self, packet_size: u8) -> PacketSerializer {
let input_bytes: &[u8] = self.as_bytes();
PacketSerializer{
packets: {
let mut temp_packet_holder = vec![];
for item in input_bytes.chunks(packet_size as usize){
temp_packet_holder.push(Packet::from_source(item, packet_size).0);
}
temp_packet_holder
},
- current_packet: input_bytes.chunks(packet_size as usize).len()
+ current_packet: 0
}
}
/// Имайки итератор по пакети, лесно можем да сериализираме всеки индивидуален пакет в поредица
/// от байтове със `.serialize()` и да го натъпчем във вектора.
///
fn to_packet_data(&self, packet_size: u8) -> Vec<u8> {
let packets = self.to_packets(packet_size);
let mut packet_data = vec![];
for item in packets{
packet_data.extend(item.serialize());
}
packet_data
}
/// Обратното на горния метод е тази асоциирана функция -- имайки slice от байтове които са
/// сериализирана репрезентация на пакети, искаме да десериализираме пакети от този slice, да
/// им извадим payload-ите, и да ги сглобим в оригиналното съобщение.
///
/// Грешките, които могат да се върнат, са същите, които идват от `.deserialize()`.
///
/// Една допълнителна грешка, която може да се случи е при сглобяване на съобщението -- ако е
/// имало липсващ пакет, може съчетанието на байтовете да не генерира правилно UTF8 съобщение.
/// Тогава връщаме `PacketError::CorruptedMessage`.
///
fn from_packet_data(packet_data: &[u8]) -> Result<Self, PacketError> {
let mut current_packet : Packet;
let mut result: String = String::from("");
-
- while let Ok(item) = Packet::deserialize(packet_data){
+ let mut loop_data = packet_data;
+ while let Ok(item) = Packet::deserialize(loop_data){
current_packet = item.0;
match String::from_utf8(current_packet.data){
Ok(text) => result.push_str(text.as_str()),
Err(_) => return Err(PacketError::CorruptedMessage),
}
+ loop_data = item.1;
}
Ok(result)
}
}
#[cfg(test)]
mod tests {
use crate::*;
#[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);
if let Err(_) = Packet::deserialize(&packet.serialize()) {
assert!(false, "Couldn't deserialize serialized packet");
}
}
#[test]
fn test_basic_iteration() {
let source = String::from("hello");
let packets = source.to_packets(100).collect::<Vec<Packet>>();
- println!("{:?}", source.to_packets(100).packets);
- assert!(packets.len() > 0);
+ assert!(packets.len() > 0);
let data = source.to_packet_data(100);
assert!(data.len() > 0);
-
if let Err(_) = String::from_packet_data(&data) {
assert!(false, "Couldn't deserialize serialized packet data");
}
}
}

Любослав качи решение на 03.12.2019 14:15 (преди почти 6 години)

use std::fmt;
-/// Грешките, които ще очакваме да върнете. По-долу ще е описано кои от тези грешки очакваме да се
-/// върнат в каква ситуация.
-///
#[derive(Debug)]
pub enum PacketError {
InvalidPacket,
InvalidChecksum,
UnknownProtocolVersion,
CorruptedMessage,
}
-/// Нужна е имплементация на Display за грешките, за да може да имплементират `std::error::Error`.
-/// Свободни сте да напишете каквито искате съобщения, ще тестваме само типовете, не низовия им
-/// вид.
-///
-/// Ако са във формат на хайку, няма да получите бонус точки, но може да получите чувство на
-/// вътрешно удовлетворение.
-///
+/// Warning - bad jokes below
impl fmt::Display for PacketError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- unimplemented!()
+ match self{
+ PacketError::InvalidPacket => write!(f,"Roses are red.\n\
+ Violets are blue.\n\
+ InvalidPacket !\n\
+ What will you do ?"),
+ PacketError::InvalidChecksum => write!(f,"When life gives you lemons, you make an InvalidChecksum lemonade, of course."),
+
+ PacketError::UnknownProtocolVersion => write!(f,"The answer is always one (UnknownProtocolVersion)"),
+
+ PacketError::CorruptedMessage => write!(f,"Coŕrup̧t̢edM҉e͢ssage"),
+ }
}
-/// Тази имплементация би трябвало да сработи директно благодарение на горните. При желание, можете
-/// да си имплементирате ръчно някои от методите, само внимавайте.
-///
impl std::error::Error for PacketError {}
-
fn generate_checksum(obj: &Packet) -> u32{
let mut checksum:u32 = 0;
checksum += obj.version as u32;
checksum += obj.size_of as u32;
for item in &obj.data{
checksum += *item as u32;
}
checksum
}

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

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

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

По принцип си прав, да, може би имаше логика да checksum-нем payload-а + header-ите. Цялото нещо е малко arbitrary, така че eh, така го измислихме. Догодина може би различно :).

-/// Един пакет, съдържащ част от съобщението. Изберете сами какви полета да използвате за
-/// съхранение.
-///
-/// Може да е нужно да добавите lifetimes на дефиницията тук и/или на методите в impl блока.
-///
#[derive(PartialEq, Debug, Clone)]
pub struct Packet {
version: u8,
size_of: usize,
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]) {
let mut new_object = Packet{
version: 1,
size_of: size as usize,
data: vec![],
checksum: 0
};
if size == 0{
panic!("Invalid argument");
}
if (size as usize) < source.len()
{
for index in 0..(size as usize){
new_object.data.push(source[index]);
}
new_object.checksum = generate_checksum(&new_object);
- return (new_object, &source[source.len() - (size as usize)..source.len()]);
+ return (new_object, &source[(size as usize)..source.len()]);
}
else
{
new_object.size_of = source.len();
for index in 0..source.len(){
new_object.data.push(source[index]);
}
new_object.checksum = generate_checksum(&new_object);
return (new_object, &[]);
}
}
-
- /// Връща само slice-а който пакета опакова. Тоест, ако сме конструирали пакета със
- /// `Packet::from_source(b"abc", 3)`, очакваме `.payload()` да ни върне `b"abc"`.
- ///
- /// Защо това просто не е публично property? За да не позволяваме мутация, а само конструиране
- /// и четене.
- ///
pub fn payload(&self) -> &[u8] {
self.data.as_slice()
}
-
-
- /// Сериализира пакета, тоест превръща го в байтове, готови за трансфер. Версия, дължина,
- /// съобщение (payload), checksum. Вижте по-горе за детайлно обяснение.
- ///
pub fn serialize(&self) -> Vec<u8> {
let mut result = vec![];
result.push(self.version);
result.push(self.size_of as u8);
for item in &self.data{
result.push(*item)
}
for byte in &self.checksum.to_be_bytes(){
result.push(*byte);
}
result
}
-
- /// Имайки 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> {
if bytes.len() < 4{
return Err(PacketError::InvalidPacket);
}
if bytes[0] != 1{
return Err(PacketError::UnknownProtocolVersion);
}
if (bytes[1] as usize) < bytes.len() - 6{
return Err(PacketError::InvalidPacket);
}
let packet_to_return = Packet{
version: bytes[0],
size_of: bytes[1] as usize,
data: {
let mut data_vec = vec![];
for index in 0..bytes[1] as usize{
data_vec.push(bytes[index + 2])
}
data_vec
},
checksum: {
let length = bytes[1] as usize;
let checksum_bytes = [bytes[length + 2], bytes[length + 3],
bytes[length + 4], bytes[length + 5]];
u32::from_be_bytes(checksum_bytes)
}
};
if generate_checksum(&packet_to_return) != packet_to_return.checksum
{
return Err(PacketError::InvalidChecksum);
}
let return_bytes = &bytes[packet_to_return.size_of + 5..bytes.len()];
Ok((packet_to_return, return_bytes))
}
}
-/// Структура, която ще служи за итериране по пакети. Ще я конструираме от някакво съобщение, и
-/// итерацията ще връща всеки следващ пакет, докато съобщението не бъде напълно "изпратено".
-/// Изберете каквито полета ви трябват.
-///
-/// Може да е нужно да добавите lifetimes на дефиницията тук и/или на методите в impl блока.
-///
pub struct PacketSerializer{
packets: Vec<Packet>,
current_packet: usize
}
impl Iterator for PacketSerializer{
type Item = Packet;
fn next(&mut self) -> Option<Self::Item> {
if self.current_packet < self.packets.len()
{
self.current_packet += 1;
Some(self.packets[self.current_packet - 1].clone())
}
else{
None
}
}
}
-/// Този trait ще ни позволи да конвертираме един `String` (а ако искаме, и др1уги неща) от и до
-/// комплект от байтове за прехвърляне по мрежата.
-///
-/// Детайли за методите вижте по-долу в имплементацията на този 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-а на всеки пакет. Връща
/// итератор върху въпросните пакети. Низа трябва да се използва под формата на байтове.
///
fn to_packets(&self, packet_size: u8) -> PacketSerializer {
let input_bytes: &[u8] = self.as_bytes();
PacketSerializer{
packets: {
let mut temp_packet_holder = vec![];
for item in input_bytes.chunks(packet_size as usize){
temp_packet_holder.push(Packet::from_source(item, packet_size).0);
}

По принцип идеята на това Packet::from_source да връща остатък беше да използвате този остатък за следващата итерация. И това работи, разбира се, и в някои отношения, даже е по-удобно. Ако бих имплементирал логиката по този начин, бих накарал from_source да връща само един елемент, и даже да не приема packet_size, но разбира се ти нямаше как да правиш промени по интерфейса.

temp_packet_holder
},
current_packet: 0
}
}
/// Имайки итератор по пакети, лесно можем да сериализираме всеки индивидуален пакет в поредица
/// от байтове със `.serialize()` и да го натъпчем във вектора.
///
fn to_packet_data(&self, packet_size: u8) -> Vec<u8> {
let packets = self.to_packets(packet_size);
let mut packet_data = vec![];
for item in packets{
packet_data.extend(item.serialize());
}
packet_data
}
/// Обратното на горния метод е тази асоциирана функция -- имайки slice от байтове които са
/// сериализирана репрезентация на пакети, искаме да десериализираме пакети от този slice, да
/// им извадим payload-ите, и да ги сглобим в оригиналното съобщение.
///
/// Грешките, които могат да се върнат, са същите, които идват от `.deserialize()`.
///
/// Една допълнителна грешка, която може да се случи е при сглобяване на съобщението -- ако е
/// имало липсващ пакет, може съчетанието на байтовете да не генерира правилно UTF8 съобщение.
/// Тогава връщаме `PacketError::CorruptedMessage`.
///
fn from_packet_data(packet_data: &[u8]) -> Result<Self, PacketError> {
let mut current_packet : Packet;
let mut result: String = String::from("");
let mut loop_data = packet_data;
while let Ok(item) = Packet::deserialize(loop_data){
current_packet = item.0;
match String::from_utf8(current_packet.data){
Ok(text) => result.push_str(text.as_str()),
Err(_) => return Err(PacketError::CorruptedMessage),
}
loop_data = item.1;

Не е необходимо всеки индивидуален пакет да бъде utf8-валиден низ. Пакетите съдържат просто поредици от байтове, отсечени в произволна точка, идеята беше съчетанието от байтове да бъде низ, понеже се конструира от низ. (И то конкретно в имплементацията за String. Ако искахме имплементация примерно за Vec<u8>, нямаше да има нужда от тази проверка.)

}
Ok(result)
}
}
#[cfg(test)]
mod tests {
use crate::*;
#[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);
if let Err(_) = Packet::deserialize(&packet.serialize()) {
assert!(false, "Couldn't deserialize serialized packet");
}
}
#[test]
fn test_basic_iteration() {
let source = String::from("hello");
let packets = source.to_packets(100).collect::<Vec<Packet>>();
assert!(packets.len() > 0);
let data = source.to_packet_data(100);
assert!(data.len() > 0);
if let Err(_) = String::from_packet_data(&data) {
assert!(false, "Couldn't deserialize serialized packet data");
}
+ }
+
+ #[test]
+ fn test_advanced_packets()
+ {
+ let source = b"Please send this text.";
+ let (packet, mut remainder) = Packet::from_source(source, 5);
+
+ let mut packets = vec![];
+ packets.push(packet);
+
+ while remainder != []{
+ let (temp_packet, temp_remainder) = Packet::from_source(remainder, 5);
+ packets.push(temp_packet);
+ remainder = temp_remainder;
+ }
+
+ assert_eq!("Pleas", String::from_utf8(packets[0].data.clone()).unwrap());
+ assert_eq!("e sen", String::from_utf8(packets[1].data.clone()).unwrap());
+ assert_eq!("d thi", String::from_utf8(packets[2].data.clone()).unwrap());
+ assert_eq!("s tex", String::from_utf8(packets[3].data.clone()).unwrap());
+ assert_eq!("t.", String::from_utf8(packets[4].data.clone()).unwrap());
+
+ assert_eq!(b"abc", Packet::from_source(b"abc", 3).0.payload());
+ assert_eq!(b"abcq", Packet::from_source(b"abcqwe", 4).0.payload());
+
+ //Откраднах този тест от Discord
+ let s = String::new();
+ let x = s.to_packets(10).next();
+ assert_eq!(x, None);
+ assert_eq!(String::new().to_packets(10).next(), None);
+
+
+ //Проверка за кирилица + сериализация
+ let serialize_source = String::from("Айрян");
+ let serialize_packet = Packet::from_source(serialize_source.as_bytes(), 100).0;
+ let serialized_bytes = serialize_packet.serialize();
+ assert_eq!("Айрян", String::from_utf8(serialize_packet.data.clone()).unwrap());
+ assert_eq!([1, 10, 208, 144, 208, 185, 209, 128, 209, 143, 208, 189, 0, 0, 7, 50], serialized_bytes.as_slice());
+
+ let deserialized_result = Packet::deserialize(&serialized_bytes);
+ let deserialized_packet_leftover = deserialized_result.unwrap();
+ let deserialized_packet = deserialized_packet_leftover.0;
+
+ assert_eq!("Айрян", String::from_utf8(deserialized_packet.data.clone()).unwrap());
+
+ }
+
+ #[test]
+ fn test_advanced_iteration() {
+ let source = String::from("Please send this text.");
+ let packets = source.to_packets(5);
+ let results: [&str; 5] = ["Pleas", "e sen", "d thi", "s tex", "t."];
+
+ assert_eq!("Pleas", String::from_utf8(packets.packets[0].data.clone()).unwrap());
+ assert_eq!("e sen", String::from_utf8(packets.packets[1].data.clone()).unwrap());
+ assert_eq!("d thi", String::from_utf8(packets.packets[2].data.clone()).unwrap());
+ assert_eq!("s tex", String::from_utf8(packets.packets[3].data.clone()).unwrap());
+ assert_eq!("t.", String::from_utf8(packets.packets[4].data.clone()).unwrap());
+
+ //Мързеливо програмиране - можеше да го напиша по-добре
+ let mut index = 0;
+ for item in packets{
+ assert_eq!(String::from(results[index]), String::from_utf8(item.data.clone()).unwrap());
+ index+=1;
+ }
+
+ //Отново - грозен код
+ let source_serialize = String::from("Айрян");
+ assert_eq!(vec![1, 10, 208, 144, 208, 185, 209, 128, 209, 143, 208, 189, 0, 0, 7, 50], source_serialize.to_packet_data(100));
+
+ assert_eq!("Айрян", String::from_packet_data(&[1, 10, 208, 144, 208, 185, 209, 128, 209, 143, 208, 189, 0, 0, 7, 50]).unwrap());
}
}