Решение на Network Packets от Иво Стефанов

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

Към профила на Иво Стефанов

Резултати

  • 16 точки от тестове
  • 0 бонус точки
  • 16 точки общо
  • 12 успешни тест(а)
  • 3 неуспешни тест(а)

Код

use std::fmt;
use std::convert::TryFrom;
#[derive(Debug)]
pub enum PacketError {
InvalidPacket,
InvalidChecksum,
UnknownProtocolVersion,
CorruptedMessage,
}
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, "Corrupted Message"),
}
}
}
impl std::error::Error for PacketError {}
#[derive(PartialEq, Debug)]
pub struct Packet<'a> {
version: u8,
payload_size: u8,
payload: &'a[u8],
check_sum: u32,
}
impl<'a> Packet<'a> {
pub fn from_source(source: &'a[u8], size: u8) -> (Self, &[u8]) {
if size==0 {
panic!("Size is 0!");
}
if size as usize >= source.len() {
let check_sum = source.iter().map(|&b| b as u32).sum();
let new_size = u8::try_from(source.len()).unwrap();
return (Packet{version: 1, payload_size: new_size, payload: source, check_sum: check_sum}, &[]);
}
else{
let(payload, rest_of_msg) = source.split_at(size as usize);
let check_sum: u32 = payload.iter().map(|&b| b as u32).sum();
(Packet{version: 1, payload_size: size, payload: payload, check_sum: check_sum}, rest_of_msg)
}
}
pub fn payload(&self) -> &[u8] {
self.payload
}
pub fn serialize(&self) -> Vec<u8> {
let mut pay = self.payload.to_vec();
let mut vec = vec![];
let check_sum_arr = self.check_sum.to_be_bytes();
vec.push(self.version);
vec.push(self.payload_size);
vec.append(&mut pay);
vec.push(check_sum_arr[0]);
vec.push(check_sum_arr[1]);
vec.push(check_sum_arr[2]);
vec.push(check_sum_arr[3]);
vec
}
pub fn deserialize(bytes: &[u8]) -> Result<(Packet, &[u8]), PacketError> {
if bytes.len() < 7 {
return Err(PacketError::InvalidPacket);
}
let protocol_version: u8 = *(bytes.get(0).unwrap());
if protocol_version != 1 {
return Err(PacketError::UnknownProtocolVersion);
}
let bytes_vector = bytes.to_vec();
let payload_size = *(bytes.get(1).unwrap());

Тук не си проверил дали payload_size е число, което има смисъл да индексираш с него. Теста, който фейлва, просто ти слага числото 100 в размера, и ти директно индексираш 102 малко по-долу, без да провериш дали е възможно :).

Иначе, тук този unwrap работи, понеже си сложил проверка по-горе за размера, но предвид, че функцията връща Result, спокойно можеш да избегнеш началната проверка за минимален размер и да кажеш тук bytes.get(1).ok_or_else(|| PacketError::InvalidPacket)?. Малко по-дълго е, но пък си сигурен за себе си, че този код няма да panic-не ако не си домислил нещо.

let end_of_payload_index = (payload_size + 2) as usize;
if std::str::from_utf8(&(bytes_vector[2..end_of_payload_index])).is_err() {
return Err(PacketError::InvalidPacket);
}

Няма нужда payload-а да бъде валиден низ. Комбинацията от payload-ове се очаква да бъде, и то само в случая, когато сериализираме и десериализираме низ, но когато говорим за индивидуални пакети, работим само с байтове. Така че тази проверка вероятно ти е fail-нала няколко теста.

let calculated_check_sum: u32 = bytes_vector[2..end_of_payload_index].iter().map(|&b| b as u32).sum();
let written_check_sum = ((bytes_vector[end_of_payload_index] as u32) << 24) +
((bytes_vector[end_of_payload_index + 1] as u32) << 16) +
((bytes_vector[end_of_payload_index + 2] as u32) << 8) +
((bytes_vector[end_of_payload_index + 3] as u32) << 0);
if calculated_check_sum != written_check_sum {
return Err(PacketError::InvalidChecksum);
}
if bytes.len() <= end_of_payload_index + 4 {
Ok((Packet{version: protocol_version, payload_size: payload_size, payload: &bytes[2..end_of_payload_index], check_sum: written_check_sum}, &[]))
}
else {
Ok((Packet{version: protocol_version, payload_size: payload_size, payload: &bytes[2..end_of_payload_index], check_sum: written_check_sum}, &bytes[(end_of_payload_index + 4)..]))
}

Вероятно можеше да съкратиш този код, като извлечеш последния аргумент като примерно remainder и да имаш само едно място, на което връщаш последния Ok. Можеше и малко да съкратиш самата структура, payload_size: payload_size примерно може синтактично да се напише просто payload_size, понеже името на променливата и името на ключа са едни и същи.

}
}
pub struct PacketSerializer<'a> {
to_serialize: &'a[u8],
size: u8,
}
impl<'a> PacketSerializer<'a>{
fn new(message: &[u8], size: u8) -> PacketSerializer{
PacketSerializer{
to_serialize: &message,
size: size,
}
}
}
impl<'a> Iterator for PacketSerializer<'a> {
type Item = Packet<'a>;
fn next(&mut self) -> Option<Self::Item> {
if self.to_serialize.len() == 0 {
return None;
}
let (package, left_to_serialize) = Packet::from_source(self.to_serialize, self.size);
self.to_serialize = left_to_serialize;
Some(package)
}
}
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 {
fn to_packets(&self, packet_size: u8) -> PacketSerializer {
PacketSerializer::new(self.as_bytes(), packet_size)
}
fn to_packet_data(&self, packet_size: u8) -> Vec<u8> {
let packages = PacketSerializer::new(self.as_bytes(), packet_size);
let mut packet_data: Vec<u8> = vec![];
for pack in packages {
let package = &(pack).serialize();

Не би трябвало да ти е нужно тук да ползваш &(pack), вероятно щеше да сработи и със pack.serialize() -- rust автоматично взема референции в зависимост от сигнатурата на функцията.

packet_data.extend_from_slice(package);
}
packet_data
}
fn from_packet_data(packet_data: &[u8]) -> Result<Self, PacketError> {
let mut message: Vec<u8> = vec![];
let mut to_convert = packet_data;
loop{
let result = Packet::deserialize(to_convert);
match result{
Ok(result) => {
let (pack, rest_of_message) = result;
to_convert = rest_of_message;
message.extend_from_slice(pack.payload());
if to_convert.len() == 0 {
break;
}
},
Err(e) => return Err(e),
}
}
let result = std::str::from_utf8(&message);
match result{
Ok(result) => {
let message_as_string = String::from(result);
Ok(message_as_string)
},
Err(_) => Err(PacketError::CorruptedMessage)
}
}
}

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

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

running 0 tests

test result: ok. 0 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 ... ok
test solution_test::test_deserialize_unicode_packet ... ok
test solution_test::test_full_roundtrip ... ok
test solution_test::test_full_roundtrip_for_zero_size_string ... thread '<unnamed>' panicked at 'called `Result::unwrap()` on an `Err` value: InvalidPacket', src/libcore/result.rs:1165:5
FAILED
test solution_test::test_invalid_packet_combination ... thread '<unnamed>' panicked at 'Expression Err(InvalidPacket) 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 ... ok
test solution_test::test_zero_size ... ok

failures:

---- solution_test::test_deserialize_invalid_packet stdout ----
thread 'main' panicked at 'index 102 out of range for slice of length 9', src/libcore/slice/mod.rs:2664:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

---- solution_test::test_full_roundtrip_for_zero_size_string stdout ----
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: InvalidPacket', tests/solution_test.rs:221:5

---- solution_test::test_invalid_packet_combination stdout ----
thread 'main' panicked at 'Expression Err(InvalidPacket) does not match the pattern "Err(PacketError::CorruptedMessage)"', tests/solution_test.rs:232:5


failures:
    solution_test::test_deserialize_invalid_packet
    solution_test::test_full_roundtrip_for_zero_size_string
    solution_test::test_invalid_packet_combination

test result: FAILED. 12 passed; 3 failed; 0 ignored; 0 measured; 0 filtered out

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

История (1 версия и 6 коментара)

Иво качи първо решение на 02.12.2019 18:34 (преди почти 6 години)