Решение на Network Packets от Йоан Стоянов

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

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

Резултати

  • 19 точки от тестове
  • 0 бонус точки
  • 19 точки общо
  • 14 успешни тест(а)
  • 1 неуспешни тест(а)

Код

use std::str;
use std::fmt;
#[derive(Debug, PartialEq)]
pub enum PacketError {
InvalidPacket,
InvalidChecksum,
UnknownProtocolVersion,
CorruptedMessage,
}
impl fmt::Display for PacketError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let msg = match &self {
PacketError::InvalidPacket => "InvalidPacket",
PacketError::InvalidChecksum => "InvalidChecksum",
PacketError::UnknownProtocolVersion => "UnknownProtocolVersion",
PacketError::CorruptedMessage => "CorruptedMessage",
};
write!(f, "{}", msg)
}
}
impl std::error::Error for PacketError {}
#[derive(PartialEq, Debug)]
pub struct Packet {
version: u8,
length: u8,
payload: Vec<u8>,
checksum: u32,
}
impl Clone for Packet {
fn clone(&self) -> Self {
let x = &self.payload();
Packet{
version: self.version,
length: self.length,
payload: x.to_vec(),
checksum: self.checksum,
}
}
}
impl Packet {
pub fn from_source(source: &[u8], size: u8) -> (Self, &[u8]) {
if size == 0 {
panic!("SOS");
}
let len:usize = source.len();
let mut end:usize = size as usize;
if end > len{ end = len; }
let pay = &source[0..end];
let rem = &source[end..len];
let pay = pay.to_vec();
let mut sum: u32 = 0;
for elem in &pay {
sum += *elem as u32
}
let pac = Packet{version: 1, length: end as u8, payload: pay, checksum: sum};
(pac,rem)
}
pub fn payload(&self) -> &[u8] {
&self.payload[..]
}
pub fn serialize(&self) -> Vec<u8> {
let mut ser: Vec<u8> = vec![self.version, self.length].iter().chain(&self.payload).map(|&x|x).collect();

Вместо да алокираш временен вектор с vec! и после още един чрез collect, можеше да използваш slice на стека:

let mut ser: Vec<u8> = [self.version, self.length].iter().chain(&self.payload).map(|&x|x).collect();

Няма голямо практическо значение -- просто го казвам, за да знаеш, че е възможно да избегнеш алокирането на динамична памет, за разлика от доста модерни езици.

let bytes = self.checksum.to_be_bytes();
for byte in &bytes{
ser.push(*byte)
}
ser
}
pub fn deserialize(bytes: &[u8]) -> Result<(Packet, &[u8]), PacketError> {
let byte_len = bytes.len();
if byte_len < 2 {
return Err(PacketError::InvalidPacket);
}
let version: u8 = bytes[0];
let length: u8 = bytes[1];
if version != 1 {
return Err(PacketError::UnknownProtocolVersion);
}
let pay_size:usize = (length as usize) + 2;
if byte_len < 4+pay_size {
return Err(PacketError::InvalidPacket);
}
let payload = (&bytes[2..pay_size]).to_vec();;
let mut sum: u32 = 0;
for elem in &payload {
sum += *elem as u32
}
let checksum = &bytes[pay_size..pay_size+4];
let bytes_of_sum = sum.to_be_bytes();
for (i, &item) in checksum.iter().enumerate() {
if item != bytes_of_sum[i] {
return Err(PacketError::InvalidChecksum);
}
}
let rem = &bytes[pay_size+4..(byte_len as usize)];
Ok((Packet{version,length,payload, checksum: sum}, rem))
}
}
#[derive(Debug)]
pub struct PacketSerializer {
packets: Vec<Packet>,
index: usize,
}
impl Iterator for PacketSerializer{
type Item = Packet;
fn next(& mut self) -> Option<Self::Item> {
let result = match self.packets.get(self.index) {
Some(x) => x,
None => return None,
};
self.index += 1;
Some(result.clone())
}
}
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 {
let mut packets: Vec<Packet> = Vec::new();
let bytes = self.as_bytes();
let mut start:usize = 0;
loop {
let (current,b) = Packet::from_source(&bytes[start..], packet_size);
packets.push(current);
start += packet_size as usize;
if b.len() == 0 {break}

Тук е единия ти бъг, другия е подобен. Ако низа е празен, ще се създаде пакет със съдържание празен низ, преди да се break-не. Може вместо тази проверка накрая, да сложиш проверка в началото:

if start >= bytes.len() { break; }
}
PacketSerializer{ packets, index: 0}
}
fn to_packet_data(&self, packet_size: u8) -> Vec<u8> {
let mut ser: Vec<u8> = Vec::new();
let iterator = Packetable::to_packets(self, packet_size);
for item in iterator {
let mut serialized_item = item.serialize();
ser.append(&mut serialized_item);
}
ser
}
fn from_packet_data(packet_data: &[u8]) -> Result<Self, PacketError> {
let mut packets: Vec<u8> = Vec::new();
let mut bytes = packet_data;
loop {
let res = Packet::deserialize(&bytes);
match res{
Ok((packet,rem)) => {
let payload = packet.payload();
packets.append(&mut payload.to_vec());
bytes = rem;
}
Err(x) => return Err(x),
}
if bytes.len() == 0 {break}

И тук имаш същия проблем като по-горе -- пакета ще се опита да бъде десериализиран от празен низ и ще върне грешка. Вместо това, ако проверката е по-горе, веднага ще се провери, че bytes.len() е 0 и ще се върне празен низ, което е доста по-смислено. Празен низ се сериализира в празен byte vector, който се десериализира обратно до празен низ.

И в двата случая можеш да заместиш loop + break с while цикъл, което обикновено е по-четимо. При един loop трябва да търсиш внимателно за break, докато в while или for цикъл, условията са ясни. Не че не можеш да имаш break и при тях, но ако можеш да го избегнеш и да енкапсулираш условията в главата на цикъла, обикновено е по-лесно да прецениш как работи въпросния цикъл.

}
let result = match str::from_utf8(&packets) {
Ok(value) => Ok(value.to_string()),
Err(_) => Err(PacketError::CorruptedMessage),
};
result

Типа String също има from_utf8 функция, която връща директно String, така че можеш да избегнеш експлицитния .to_string:

let result = match String::from_utf8(packets) {
    Ok(value) => Ok(value),
    Err(_) => Err(PacketError::CorruptedMessage),
};
result

Съответно, предвид че в Ok случая не правиш никаква промяна, единствено ти трябва да промениш грешката, и можеш да използваш оператор ?:

let result = String::from_utf8(packets).map_err(|_| PacketError::CorruptedMessage)?;
Ok(result)

Или направо:

String::from_utf8(packets).map_err(|_| PacketError::CorruptedMessage)
}
}
#[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");
}
}

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

Compiling solution v0.1.0 (/tmp/d20200111-2173579-rb4ke6/solution)
warning: unnecessary trailing semicolon
   --> src/lib.rs:102:54
    |
102 |         let payload = (&bytes[2..pay_size]).to_vec();;
    |                                                      ^ help: remove this semicolon
    |
    = note: `#[warn(redundant_semicolon)]` on by default

warning: unnecessary trailing semicolon
   --> src/lib.rs:102:54
    |
102 |         let payload = (&bytes[2..pay_size]).to_vec();;
    |                                                      ^ help: remove this semicolon
    |
    = note: `#[warn(redundant_semicolon)]` on by default

    Finished test [unoptimized + debuginfo] target(s) in 4.19s
     Running target/debug/deps/solution-a73e64ec87929bd0

running 2 tests
test test_basic_iteration ... ok
test test_basic_packets ... ok

test result: ok. 2 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 ... ok
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 ... 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 ... thread '<unnamed>' panicked at 'assertion failed: `(left == right)`
  left: `1`,
 right: `0`', tests/solution_test.rs:191:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
FAILED
test solution_test::test_serialize_packet ... ok
test solution_test::test_zero_size ... ok

failures:

---- solution_test::test_iterating_packets_for_zero_size_string stdout ----
thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `1`,
 right: `0`', tests/solution_test.rs:186:5


failures:
    solution_test::test_iterating_packets_for_zero_size_string

test result: FAILED. 14 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out

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

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

Йоан качи първо решение на 03.12.2019 16:26 (преди почти 6 години)

Решението е добро, макар че може имената на променливи да бъдат малко по-четими :). Няма много полза в b вместо bytes, pay вместо payload, rem вместо remainder (или remaining). Може да разгледаш останалите решения за още идеи как да си опростиш кода.