Решение на Network Packets от Явор Иванов

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

Към профила на Явор Иванов

Резултати

  • 20 точки от тестове
  • 0 бонус точки
  • 20 точки общо
  • 15 успешни тест(а)
  • 0 неуспешни тест(а)

Код

use std::fmt;
use std::convert::TryInto;
use std::cmp;
use std::str;
#[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, "Unkown protocol version!"),
PacketError::CorruptedMessage => write!(f, "Corrupted message!"),
}
}
}
impl std::error::Error for PacketError {}
#[derive(PartialEq, Debug)]
pub struct Packet<'a> {
payload: &'a [u8],
}
impl<'a> Packet<'a> {
const VERSION: u8 = 1;
pub fn from_source(source: &'a[u8], size: u8) -> (Self, &[u8]) {
if size == 0 {
panic!("Unable to construct a packet with zero size");
}
let min_size = cmp::min(size as usize, source.len());
(Packet {payload: &source[..min_size]}, &source[min_size..])
}
pub fn payload(&self) -> &[u8] {
self.payload
}
pub fn serialize(&self) -> Vec<u8> {
let mut ser_packet = vec![Packet::VERSION, self.payload().len() as u8];
ser_packet.extend_from_slice(self.payload());
let checksum : u32 = self.payload().iter().map(|&b| b as u32).sum();
ser_packet.extend_from_slice(&checksum.to_be_bytes());
ser_packet
}
pub fn deserialize(bytes: &[u8]) -> Result<(Packet, &[u8]), PacketError> {
if bytes.len() < 7 || bytes.len() < (bytes[1] as usize + 6usize) {
return Err(PacketError::InvalidPacket);
}
if bytes[0] != Packet::VERSION {
return Err(PacketError::UnknownProtocolVersion);
}
let payload_end = 2 + bytes[1] as usize;
let checksum_bytes : [u8; 4] = bytes[payload_end..payload_end + 4].try_into().unwrap();
let checksum = u32::from_be_bytes(checksum_bytes);
let payload = &bytes[2usize..payload_end];
let sum : u32 = payload.iter().map(|&b| b as u32).sum();
if sum != checksum {
return Err(PacketError::InvalidChecksum);
}
Ok((Packet{ payload: payload }, &bytes[payload_end+4..]))
}
}
pub struct PacketSerializer<'a> {
byte_msg: &'a [u8],
packet_size: u8,
}
impl<'a> Iterator for PacketSerializer<'a> {
type Item = Packet<'a>;
fn next(&mut self) -> Option<Self::Item> {
if self.byte_msg.is_empty() {
return None
}
let packet_tuple = Packet::from_source(self.byte_msg, self.packet_size);
self.byte_msg = packet_tuple.1;
Some(packet_tuple.0)

Тук можеш директно да pattern-match-неш двете части на резултата:

let (packet, remainder) = Packet::from_source(self.byte_msg, self.packet_size);
self.byte_msg = remainder;
Some(packet)

По този начин избягваш сравнително тромавото packet_tuple.0/1.

}
}
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 {
return PacketSerializer{ byte_msg : self.as_bytes(), packet_size : packet_size };
}
fn to_packet_data(&self, packet_size: u8) -> Vec<u8> {
let mut packet_data: Vec<u8> = Vec::new();
for packet in self.to_packets(packet_size) {
packet_data.append(&mut packet.serialize());
}
packet_data
}
fn from_packet_data(packet_data: &[u8]) -> Result<Self, PacketError> {
let mut original_msg: Vec<u8> = Vec::new();
let mut packet_dat_ptr = packet_data;
while !packet_dat_ptr.is_empty() {
match Packet::deserialize(packet_dat_ptr) {
Ok(packet_tuple) => {
packet_dat_ptr = packet_tuple.1;
original_msg.extend_from_slice(packet_tuple.0.payload());
},
Err(e) => return Err(e),
}

Тук също можеш да pattern-match-неш двете части с Ok((packet, remainder)). И предвид, че тук просто връщаш грешката ако има такава, можеш да пренапишеш логиката с оператор ?:

let (packet, remainder) = Packet::deserialize(packet_dat_ptr)?;
packet_dat_ptr = remainder;
original_msg.extend_from_slice(packet.payload());
}
return match str::from_utf8(&original_msg) {
Ok(v) => Ok(v.to_string()),
Err(_) => Err(PacketError::CorruptedMessage),
};

Вместо str::from_utf8, можеш да използваш аналогичното String::from_utf8. Така Ok случая ти е идентитет и единствено имаш нужда да override-неш случая, в който имаш грешка:

String::from_utf8(&original_msg).map_err(|_| PacketError::CorruptedMessage);
}
}

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

Compiling solution v0.1.0 (/tmp/d20200111-2173579-iq9ora/solution)
    Finished test [unoptimized + debuginfo] target(s) in 3.73s
     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 ... 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 ... ok
test solution_test::test_serialize_packet ... ok
test solution_test::test_zero_size ... ok

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

   Doc-tests solution

running 0 tests

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

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