Files
nocloud-core/src/protocol.rs
T
Stanislav N Mikhailov 46f2721570 wip
2026-08-10 17:29:15 +03:00

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(())
}
}