Как системный язык, Rust изначально имеет возможность взаимодействовать с необработанной памятью, не требуя специальной оболочки.
Следовательно, чтение u32
из памяти просто требует разбрызгивания unsafe
:
fn read_u32(bytes: &[u8]) -> u32 {
assert!(bytes.as_ptr() as usize % std::mem::align_of::<u32>() == 0);
assert!(bytes.len() >= 4);
unsafe { *(bytes.as_ptr() as *const u32) }
}
Эта необработанная способность может быть использована для создания лучших абстракций. Примечательно, что абстракции заботятся о выравнивании и порядке байтов.
Ящик byteorder
обеспечивает такую абстракцию: оба типа LitteEndian
и BigEndian
реализуют черту ByteOrder
.
Вышеупомянутая функция может быть улучшена до:
fn read_u32(bytes: &[u8]) -> u32 { LittleEndian::read_u32(bytes) }
, который позаботится о:
- Выполнение преобразования.
- Равномерно обрабатывать доступ без выравнивания вместо паники.
- с явным порядком байтов.
На самом деле он учитывает только примитивные типы, и именно здесь bytes
клеть входит.
Например, давайте расшифруем заголовок UDP :
use std::io::Cursor;
use bytes::buf::Buf;
struct UdpHeader {
src_port: u16,
dst_port: u16,
length: u16,
checksum: u16,
}
fn read_udp_header<T: AsRef<[u8]>>(bytes: &mut Cursor<T>) -> UdpHeader {
UdpHeader {
src_port: bytes.read_u16_be(),
dst_port: bytes.read_u16_be(),
length: bytes.read_u16_be(),
checksum: bytes.read_u16_be(),
}
}
Использует структуру Cursor
из стандартной библиотеки и реализацию черты Buf
из bytes
.
Вы можете создать курсор вокруг части байта (&[u8]
), начиная с любой точки памяти; чтение из него продвигает его, помещая его для следующего чтения, и оно будет обрабатывать выравнивание, порядковый номер и проверку границ.
Примечание: к сожалению, не существует версии, возвращающей Option<u16>
; Я бы, вероятно, продлил бы это, если бы это было проблемой.
Таким образом, я думаю, что у вас есть правильная идея с перечисленными ящиками, они охватывают все требования, которые вы изложили.