Решение на Network Packets от Ричи Хаджиев

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

Към профила на Ричи Хаджиев

Резултати

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

Код

use std::fmt;
use std::str::from_utf8;
#[derive(Debug)]
pub enum PacketError {
InvalidPacket,
InvalidChecksum,
UnknownProtocolVersion,
CorruptedMessage,
}
impl fmt::Display for PacketError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let printable = match *self {
PacketError::InvalidPacket => "InvalidPacket",
PacketError::InvalidChecksum => "InvalidChecksum",
PacketError::UnknownProtocolVersion => "UnknownProtocolVersion",
PacketError::CorruptedMessage => "CorruptedMessage"
};
write!(f, "{}", printable)
}
}
impl std::error::Error for PacketError {
fn description(&self) -> &str {
match *self {
PacketError::InvalidPacket => "Version, checksum or payload size is missing or payload size is too small to parse valid package.",
PacketError::InvalidChecksum => "Checksum is different than the sum of payload's bytes",
PacketError::UnknownProtocolVersion => "Version is different than 1",
PacketError::CorruptedMessage => "Probably there was a missing packet while building the message.",
}
}
}

Ако не имплементираш description, този метод просто използва Display форматирането -- това беше идеята зад имплементацията на Display. Можеше тези описания да ги сложиш по-горе.

#[derive(PartialEq, Debug)]
pub struct Packet<'a> {
version: u8,
payload_size: u8,
payload: &'a[u8],
checksum: u32
}
impl<'a> Packet<'a> {
pub fn from_source(source: &'a[u8], size: u8) -> (Self, &[u8]) {
let mut delimiter = size as usize;
if delimiter > source.len() {
delimiter = source.len()
}
let first_half = source.get(0..delimiter).unwrap();
let second_half = source.get(delimiter..source.len()).unwrap();
let mut sum: u32 = 0;
for element in first_half.iter() {
sum += *element as u32
}
let packet = Packet {
version: 1,
payload_size: delimiter as u8,
payload: first_half,
checksum: sum
};
(packet, second_half)
}
pub fn payload(&self) -> &[u8] {
return self.payload;
}
pub fn serialize(&self) -> Vec<u8> {
let mut vec: Vec<u8> = Vec::new();
vec.push(self.version);
vec.push(self.payload_size);
for element in self.payload() {
vec.push(*element)
}
let big_endian_checksum = self.checksum.to_be_bytes();
for element in 0..4 {
vec.push(big_endian_checksum[element]);
}
return vec;
}
pub fn deserialize(bytes: &[u8]) -> Result<(Packet, &[u8]), PacketError> {
let version_byte: &u8;
match bytes.get(0) {
None => return Err(PacketError::InvalidPacket),
Some(val) => version_byte = val
}
if *version_byte != 1 {
return Err(PacketError::UnknownProtocolVersion)
}
let packet_size: &u8;
match bytes.get(1) {
None => return Err(PacketError::InvalidPacket),
Some(val) => packet_size = val
}

Няма нужда да декларираш отделна променлива, която да запазиш тук. Можеш да използваш резултата от match клаузата:

let packet_size = match.bytes.get(1) {
    Some(val) => val,
    ...
}

Also, би могъл да конвертираш Option-а в Result и да използваш оператор ? за да избегнеш match клаузата изцяло:

let packet_size = bytes.get(1).ok_or_else(|| PacketError::InvalidPacket)?;
let delimiter = *packet_size as usize;
let checksum_bytes: &[u8];
match bytes.get((delimiter+2)..(delimiter+6)) {
None => return Err(PacketError::InvalidPacket),
Some(val) => checksum_bytes = val
}
let mut sum: u32 = 0;
for element in checksum_bytes.iter() {
sum += *element as u32
}
let payload_bytes = bytes.get(2..delimiter+2).unwrap();
let mut payload_sum: u32 = 0;
for element in payload_bytes.iter() {
payload_sum += *element as u32
}
let big_endian_payload_sum = payload_sum.to_be_bytes();
for element in 0..4 {
if big_endian_payload_sum[element] != bytes[delimiter+2+element] {
return Err(PacketError::InvalidPacket)
}
}
let remaining_bytes = bytes.get(delimiter+6..bytes.len()).unwrap();
let packet = Packet {
version: bytes[0],
payload_size: bytes[1],
payload: payload_bytes,
checksum: sum
};
Ok((packet, remaining_bytes))
}
}
pub struct PacketSerializer<'a> {
remaining_bytes: &'a [u8],
packet_size: u8
}
impl<'a> Iterator for PacketSerializer<'a> {
type Item = Packet<'a>;
fn next(&mut self) -> Option<Self::Item> {
let from_source_data = Packet::from_source(self.remaining_bytes, self.packet_size);
self.remaining_bytes = from_source_data.1;
Some(from_source_data.0)
}

В тази функция имаш безкраен цикъл. Никъде нямаш край на итерацията, никъде не проверяваш, че е време да върнеш None. Този код в началото на next щеше да ти даде още 3 минаващи теста:

if self.remaining_bytes.len() == 0 {
    return 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 {
fn to_packets(&self, packet_size: u8) -> PacketSerializer {
let remaining_bytes = self.as_bytes();
let mut new_packet_size: usize = packet_size as usize;
if new_packet_size > remaining_bytes.len() {
new_packet_size = remaining_bytes.len();
}
let packet_serializer = PacketSerializer {
remaining_bytes,
packet_size: new_packet_size as u8
};
packet_serializer
}
fn to_packet_data(&self, packet_size: u8) -> Vec<u8> {
let mut packets = self.to_packets(packet_size);
let mut vec: Vec<u8> = Vec::new();
while packets.remaining_bytes.len() > 0 {
vec.append(packets.next().unwrap().serialize().as_mut())
}
vec
}
fn from_packet_data(packet_data: &[u8]) -> Result<Self, PacketError> {
let mut final_string = String::from("");
let mut remaining_bytes = packet_data;
while remaining_bytes.len() > 0 {
let deserialized_bytes = Packet::deserialize(remaining_bytes);
match deserialized_bytes {
Err(e) => return Err(e),
Ok(val) => {
let packet_payload_as_string = from_utf8(val.0.payload()).unwrap();
final_string.push_str(packet_payload_as_string);
remaining_bytes = val.1;
}
}
}
Ok(final_string)
}
}

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

Compiling solution v0.1.0 (/tmp/d20200111-2173579-15020ym/solution)
    Finished test [unoptimized + debuginfo] target(s) in 3.58s
     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 ... 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 ... ok
test solution_test::test_full_roundtrip_for_zero_size_string ... ok
test solution_test::test_invalid_packet_combination ... FAILED
test solution_test::test_iterating_packets ... FAILED
test solution_test::test_iterating_packets_for_zero_size_string ... FAILED
test solution_test::test_serialize_packet ... ok
test solution_test::test_zero_size ... FAILED

failures:

---- solution_test::test_consuming_packets stdout ----
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Timeout', src/libcore/result.rs:1165:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

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

---- solution_test::test_deserialize_packet stdout ----
thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `(Packet { version: 1, payload_size: 3, payload: [120, 121, 122], checksum: 108 }, [])`,
 right: `(Packet { version: 1, payload_size: 3, payload: [120, 121, 122], checksum: 363 }, [])`', tests/solution_test.rs:114:5

---- solution_test::test_deserialize_unicode_packet stdout ----
thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `(Packet { version: 1, payload_size: 10, payload: [209, 133, 208, 184, 209, 130, 209, 128, 208, 190], checksum: 23 }, [])`,
 right: `(Packet { version: 1, payload_size: 10, payload: [209, 133, 208, 184, 209, 130, 209, 128, 208, 190], checksum: 1808 }, [])`', tests/solution_test.rs:131:5

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

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

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

---- solution_test::test_zero_size stdout ----
note: test did not panic as expected

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_invalid_packet_combination
    solution_test::test_iterating_packets
    solution_test::test_iterating_packets_for_zero_size_string
    solution_test::test_zero_size

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

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

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

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

С малко грижа, може да се докара до работещо решение, макар че вероятно може да се опрости. Виж моето решение за това как съм имплементирал deserialize със split-ване на slice-а вместо с жонглиране на индекси.

Подозирам, че е предимно проблем с късното пускане на решение :). Другия път почвай по-отрано.