// Парсим заголовок на базе 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 for Command { type Error = HeaderError; fn try_from(value: u8) -> Result { 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 { // Парсим заголовок: 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(()) } }