177 lines
5.5 KiB
Rust
177 lines
5.5 KiB
Rust
// Парсим заголовок на базе Nom
|
|
pub const HEADER_SIZE: usize = 20;
|
|
|
|
use nom::{
|
|
bytes::complete::tag,
|
|
// verify — комбинатор nom: проверяет результат парсера по условию.
|
|
// Пока не использую, т.к. собственная валидация через thiserror
|
|
// позволяет возвращать более конкретные ошибки.
|
|
// use nom::combinator::verify;
|
|
number::complete::{
|
|
be_u8,
|
|
be_u16,
|
|
be_u32,
|
|
be_u64,
|
|
},
|
|
IResult,
|
|
Parser,
|
|
};
|
|
|
|
use thiserror::Error;
|
|
|
|
#[derive(Debug, Error)]
|
|
pub enum HeaderError {
|
|
#[error("Некорректный формат заголовка")]
|
|
InvalidFormat,
|
|
|
|
#[error("Неподдерживаемая версия протокола: {0}")]
|
|
UnsupportedVersion(u8),
|
|
|
|
#[error("Неизвестная команда протокола: {0}")]
|
|
UnsupportedCommand(u8),
|
|
|
|
#[error("Неизвестный набор флагов: {0}")]
|
|
UnsupportedFlagSet(u16),
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct Header {
|
|
version: u8,
|
|
command: u8,
|
|
flags: u16,
|
|
request_id: u32,
|
|
body_size: u64,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum Command {
|
|
/// Клиент загружает файл на сервер.
|
|
Upload,
|
|
|
|
/// Клиент запрашивает скачивание файла с сервера.
|
|
Download,
|
|
|
|
/// Клиент запрашивает удаление файла на сервере.
|
|
Delete,
|
|
|
|
/// Клиент запрашивает информацию о файле.
|
|
Stat,
|
|
|
|
/// Ответ на ранее отправленный запрос.
|
|
Response,
|
|
|
|
/// Сервер передаёт клиенту запрошенный файл.
|
|
FileData,
|
|
}
|
|
|
|
impl TryFrom<u8> for Command {
|
|
type Error = HeaderError;
|
|
|
|
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
|
match value {
|
|
0x01 => Ok(Command::Upload), //клиент → сервер
|
|
0x02 => Ok(Command::Download),
|
|
0x03 => Ok(Command::Delete),
|
|
0x04 => Ok(Command::Stat),
|
|
0x80 => Ok(Command::Response),
|
|
0x81 => Ok(Command::FileData),
|
|
other => Err(HeaderError::UnsupportedCommand(other)),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn decode_header(
|
|
input: &[u8; HEADER_SIZE],
|
|
) -> Result<Header, HeaderError> {
|
|
// Парсим заголовок:
|
|
let (remaining, header) = parse_header(input)
|
|
.map_err(|_| HeaderError::InvalidFormat)?;
|
|
|
|
// Выходим с ошибкой, если заголовок был недочитан парсером:
|
|
if !remaining.is_empty() {
|
|
return Err(HeaderError::InvalidFormat);
|
|
}
|
|
|
|
//Валидация заголовка
|
|
header.validate()?;
|
|
|
|
Ok(header)
|
|
}
|
|
|
|
fn parse_header(
|
|
input: &[u8],
|
|
) -> IResult<&[u8],Header> {
|
|
//1. Парсим магические байты "NCLD"
|
|
let (input, _) = tag(&b"NCLD"[..]).parse(input)?;
|
|
//2. Парсим и валидируем версию
|
|
let (input, version) = be_u8(input)?;
|
|
//3. Парсим и валидируем Header
|
|
let (input, command) = be_u8(input)?;
|
|
//4. Парсим и валидируем flags
|
|
let (input, flags) = be_u16(input)?;
|
|
//5. Парсим Request без валидации
|
|
let (input, request_id) = be_u32(input)?;
|
|
//6. Парсим и валидируем величину заголовка
|
|
let (input, body_size) = be_u64(input)?;
|
|
|
|
let local_header = Header{
|
|
version,
|
|
command,
|
|
flags,
|
|
request_id,
|
|
body_size,
|
|
};
|
|
Ok((input,local_header))
|
|
}
|
|
|
|
// Реализация публичных методов-геттеров для безопасного доступа к приватным полям
|
|
impl Header {
|
|
/// Возвращает версию протокола пакета
|
|
pub fn version(&self) -> u8 {
|
|
// Так как тип u8 реализует трейт Copy, значение просто копируется наружу
|
|
self.version
|
|
}
|
|
|
|
/// Возвращает код команды (например, 1, 2, 80 или 81)
|
|
pub fn command(&self) -> Command {
|
|
Command::try_from(self.command)
|
|
.expect("Header уже прошёл валидацию команды")
|
|
}
|
|
|
|
/// Возвращает битовые флаги пакета (уже прошедшие валидацию)
|
|
pub fn flags(&self) -> u16 {
|
|
self.flags
|
|
}
|
|
|
|
/// Возвращает уникальный идентификатор запроса клиента
|
|
pub fn request_id(&self) -> u32 {
|
|
self.request_id
|
|
}
|
|
|
|
/// Возвращает размер тела сообщения в байтах
|
|
pub fn body_size(&self) -> u64 {
|
|
self.body_size
|
|
}
|
|
|
|
///Валидаторы для данных из заголовка:
|
|
///
|
|
// Валидация Header
|
|
pub fn validate(&self) -> Result<(), HeaderError> {
|
|
//Валидация version
|
|
if self.version != 1 {
|
|
return Err(
|
|
HeaderError::UnsupportedVersion(self.version)
|
|
);
|
|
}
|
|
|
|
// Валидация command
|
|
Command::try_from(self.command)?;
|
|
|
|
// Валидация flags
|
|
if !(0..=7).contains(&self.flags){
|
|
return Err(HeaderError::UnsupportedFlagSet (self.flags));
|
|
};
|
|
|
|
Ok(())
|
|
}
|
|
} |