feat(protocol): add typed command decoding and dispatch
- add Command enum for protocol command codes - convert raw u8 command values via TryFrom - validate commands through Command conversion - dispatch decoded commands in Connection - reject unknown command codes
This commit is contained in:
+30
-2
@@ -13,6 +13,7 @@ use crate::protocol::{
|
|||||||
Header,
|
Header,
|
||||||
HEADER_SIZE,
|
HEADER_SIZE,
|
||||||
MAX_BODY_SIZE,
|
MAX_BODY_SIZE,
|
||||||
|
Command,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct IncomingMessage {
|
struct IncomingMessage {
|
||||||
@@ -100,10 +101,37 @@ impl Connection {
|
|||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
//Заголовок скачен и валиден. Получаем команду из заголовка
|
||||||
|
match header.command() {
|
||||||
|
Command::Upload => {
|
||||||
|
println!("Команда UPLOAD");
|
||||||
|
}
|
||||||
|
|
||||||
|
Command::Download => {
|
||||||
|
println!("Команда DOWNLOAD");
|
||||||
|
}
|
||||||
|
|
||||||
|
Command::Delete => {
|
||||||
|
println!("Команда DELETE");
|
||||||
|
}
|
||||||
|
|
||||||
|
Command::Stat => {
|
||||||
|
println!("Команда STAT");
|
||||||
|
}
|
||||||
|
|
||||||
|
Command::FileData => {
|
||||||
|
println!("Команда FILEDATA");
|
||||||
|
}
|
||||||
|
|
||||||
|
Command::Response => {
|
||||||
|
println!("Команда RESPONSE");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Some(header))
|
Ok(Some(header))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn read_body(
|
async fn read_body( //todo: анахронизм, убрать после реализации storage
|
||||||
&mut self,
|
&mut self,
|
||||||
body_size: u64,
|
body_size: u64,
|
||||||
) -> io::Result<Vec<u8>> {
|
) -> io::Result<Vec<u8>> {
|
||||||
@@ -146,7 +174,7 @@ impl Connection {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let response = format!(
|
let response = format!(
|
||||||
"Соединение {}: запрос {} принят, cmd={}\n",
|
"Соединение {}: запрос {} принят, cmd={:?}\n",
|
||||||
self.id,
|
self.id,
|
||||||
message.header.request_id(),
|
message.header.request_id(),
|
||||||
message.header.command(),
|
message.header.command(),
|
||||||
|
|||||||
+118
-118
@@ -1,22 +1,25 @@
|
|||||||
# Статус разработки проекта
|
# Статус разработки проекта
|
||||||
|
|
||||||
## 📌 Текущее состояние
|
## 📌 Текущее состояние
|
||||||
* **Где остановился:** Окультурил контракт между protocol connection, работает точно также, но
|
|
||||||
* архитектурно красивей.
|
- **Где остановился:** Окультурил контракт между protocol connection, работает точно также, но
|
||||||
* Далее, нужно реализовать UPLOAD с проверкой валидностей имени файла, размеров. И последующей передачей в storage/
|
- архитектурно красивей.
|
||||||
* Ниже - чеклист для реализации.
|
- Далее, нужно реализовать UPLOAD с проверкой валидностей имени файла, размеров. И последующей передачей в storage/
|
||||||
* **Проблема:**
|
- Ниже - чеклист для реализации.
|
||||||
* - нет проблем 😇
|
- **Проблема:**
|
||||||
|
- - нет проблем 😇
|
||||||
|
|
||||||
## Чек-лист реализации `UPLOAD`
|
## Чек-лист реализации `UPLOAD`
|
||||||
|
|
||||||
### 1. Подготовить диспетчеризацию команд
|
### 1. Подготовить диспетчеризацию команд
|
||||||
|
|
||||||
* [ ] Оставить `decode_header()` единственной публичной функцией получения проверенного `Header`.
|
- [✔] Оставить `decode_header()` единственной публичной функцией получения проверенного `Header`.
|
||||||
* [ ] Убедиться, что `connection.rs` больше не вызывает `header.validate()`.
|
- [✔] Убедиться, что `connection.rs` больше не вызывает `header.validate()`.
|
||||||
* [ ] После получения `Header` выполнить `match` по `header.command()`.
|
- [✔] После получения `Header` выполнить `match` по `header.command()`.
|
||||||
* [ ] Для `0x01` вызвать обработчик `UPLOAD`.
|
- [✔] Для `0x01` вызвать обработчик `UPLOAD`.
|
||||||
* [ ] Для остальных команд пока возвращать ошибку `UnsupportedCommand`.
|
~~[✔] Для остальных команд пока возвращать ошибку `UnsupportedCommand`.~~
|
||||||
* [ ] Проверить: тестовый заголовок `command = 0x01` попадает в обработчик `UPLOAD`.
|
- [✔] Для остальных вызвать свои обработчики
|
||||||
|
- [✔] Проверить: тестовый заголовок `command = 0x01` попадает в обработчик `UPLOAD`.
|
||||||
|
|
||||||
Ожидаемый промежуточный результат:
|
Ожидаемый промежуточный результат:
|
||||||
|
|
||||||
@@ -26,20 +29,21 @@
|
|||||||
|
|
||||||
### 2. Создать постоянный модуль загрузки
|
### 2. Создать постоянный модуль загрузки
|
||||||
|
|
||||||
* [ ] Создать `upload.rs`.
|
- [ ] Создать `upload.rs`.
|
||||||
* [ ] Подключить его в `main.rs`:
|
- [ ] Подключить его в `main.rs`:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
mod upload;
|
mod upload;
|
||||||
```
|
```
|
||||||
|
|
||||||
* [ ] Создать в нём асинхронную функцию `handle()`.
|
- [ ] Создать в нём асинхронную функцию `handle()`.
|
||||||
* [ ] Передать в неё:
|
- [ ] Передать в неё:
|
||||||
|
|
||||||
* `&mut` сетевой поток;
|
- `&mut` сетевой поток;
|
||||||
* проверенный `Header`.
|
- проверенный `Header`.
|
||||||
* [ ] Пока только вывести заголовок и вернуть `Ok(())`.
|
|
||||||
* [ ] Убедиться, что после вызова управление возвращается в цикл `Connection`.
|
- [ ] Пока только вывести заголовок и вернуть `Ok(())`.
|
||||||
|
- [ ] Убедиться, что после вызова управление возвращается в цикл `Connection`.
|
||||||
|
|
||||||
Первая форма может иметь такой смысл:
|
Первая форма может иметь такой смысл:
|
||||||
|
|
||||||
@@ -55,19 +59,19 @@ pub async fn handle(
|
|||||||
|
|
||||||
### 3. Убрать чтение всего тела в `Vec`
|
### 3. Убрать чтение всего тела в `Vec`
|
||||||
|
|
||||||
* [ ] Не вызывать текущий `read_body()` для команды `UPLOAD`.
|
- [ ] Не вызывать текущий `read_body()` для команды `UPLOAD`.
|
||||||
* [ ] Не создавать:
|
- [ ] Не создавать:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
vec![0_u8; header.body_size() as usize]
|
vec![0_u8; header.body_size() as usize]
|
||||||
```
|
```
|
||||||
|
|
||||||
* [ ] Удалить или временно оставить `read_body()` только для будущих небольших команд.
|
- [ ] Удалить или временно оставить `read_body()` только для будущих небольших команд.
|
||||||
* [ ] Убедиться, что `UPLOAD` получает поток, стоящий точно перед первым байтом тела.
|
- [ ] Убедиться, что `UPLOAD` получает поток, стоящий точно перед первым байтом тела.
|
||||||
|
|
||||||
### 4. Описать метаданные загрузки
|
### 4. Описать метаданные загрузки
|
||||||
|
|
||||||
* [ ] В `protocol.rs` создать структуру:
|
- [ ] В `protocol.rs` создать структуру:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
pub struct UploadMetadata {
|
pub struct UploadMetadata {
|
||||||
@@ -76,14 +80,14 @@ pub struct UploadMetadata {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
* [ ] Добавить геттеры для `file_size` и `file_name`.
|
- [ ] Добавить геттеры для `file_size` и `file_name`.
|
||||||
* [ ] Добавить константу максимальной длины имени:
|
- [ ] Добавить константу максимальной длины имени:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
MAX_FILENAME_SIZE
|
MAX_FILENAME_SIZE
|
||||||
```
|
```
|
||||||
|
|
||||||
* [ ] Пока не добавлять в структуру содержимое файла.
|
- [ ] Пока не добавлять в структуру содержимое файла.
|
||||||
|
|
||||||
### 5. Прочитать фиксированную часть метаданных
|
### 5. Прочитать фиксированную часть метаданных
|
||||||
|
|
||||||
@@ -94,10 +98,10 @@ file_size: 8 байт
|
|||||||
name_size: 2 байта
|
name_size: 2 байта
|
||||||
```
|
```
|
||||||
|
|
||||||
* [ ] В `upload::handle()` создать буфер размером 10 байт.
|
- [ ] В `upload::handle()` создать буфер размером 10 байт.
|
||||||
* [ ] Прочитать в него ровно 10 байт через `read_exact()`.
|
- [ ] Прочитать в него ровно 10 байт через `read_exact()`.
|
||||||
* [ ] Пока вывести полученные байты.
|
- [ ] Пока вывести полученные байты.
|
||||||
* [ ] Проверить обрыв соединения внутри этих десяти байт.
|
- [ ] Проверить обрыв соединения внутри этих десяти байт.
|
||||||
|
|
||||||
Ожидаемый результат:
|
Ожидаемый результат:
|
||||||
|
|
||||||
@@ -107,12 +111,13 @@ name_size: 2 байта
|
|||||||
|
|
||||||
### 6. Разобрать фиксированную часть в `protocol.rs`
|
### 6. Разобрать фиксированную часть в `protocol.rs`
|
||||||
|
|
||||||
* [ ] Создать приватный парсер `parse_upload_prefix()`.
|
- [ ] Создать приватный парсер `parse_upload_prefix()`.
|
||||||
* [ ] Разобрать:
|
- [ ] Разобрать:
|
||||||
|
|
||||||
* `file_size`;
|
- `file_size`;
|
||||||
* `name_size`.
|
- `name_size`.
|
||||||
* [ ] Создать промежуточную структуру:
|
|
||||||
|
- [ ] Создать промежуточную структуру:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
UploadPrefix {
|
UploadPrefix {
|
||||||
@@ -121,29 +126,29 @@ UploadPrefix {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
* [ ] Создать публичную функцию `decode_upload_prefix()`.
|
- [ ] Создать публичную функцию `decode_upload_prefix()`.
|
||||||
* [ ] Скрыть ошибки `nom` внутри `protocol.rs`.
|
- [ ] Скрыть ошибки `nom` внутри `protocol.rs`.
|
||||||
* [ ] Проверить, что парсер использовал все 10 байт.
|
- [ ] Проверить, что парсер использовал все 10 байт.
|
||||||
* [ ] Вернуть проверенный `UploadPrefix`.
|
- [ ] Вернуть проверенный `UploadPrefix`.
|
||||||
|
|
||||||
### 7. Проверить длину имени
|
### 7. Проверить длину имени
|
||||||
|
|
||||||
* [ ] Отклонить `name_size = 0`.
|
- [ ] Отклонить `name_size = 0`.
|
||||||
* [ ] Отклонить `name_size > MAX_FILENAME_SIZE`.
|
- [ ] Отклонить `name_size > MAX_FILENAME_SIZE`.
|
||||||
* [ ] Не выделять память для имени до проверки размера.
|
- [ ] Не выделять память для имени до проверки размера.
|
||||||
* [ ] Добавить отдельные ошибки:
|
- [ ] Добавить отдельные ошибки:
|
||||||
|
|
||||||
* пустое имя;
|
- пустое имя;
|
||||||
* слишком длинное имя.
|
- слишком длинное имя.
|
||||||
|
|
||||||
### 8. Прочитать имя файла
|
### 8. Прочитать имя файла
|
||||||
|
|
||||||
* [ ] После проверки `name_size` выделить `Vec<u8>` только под имя.
|
- [ ] После проверки `name_size` выделить `Vec<u8>` только под имя.
|
||||||
* [ ] Прочитать ровно `name_size` байт.
|
- [ ] Прочитать ровно `name_size` байт.
|
||||||
* [ ] Передать эти байты в `protocol.rs`.
|
- [ ] Передать эти байты в `protocol.rs`.
|
||||||
* [ ] Проверить UTF-8.
|
- [ ] Проверить UTF-8.
|
||||||
* [ ] Получить `String`.
|
- [ ] Получить `String`.
|
||||||
* [ ] Собрать `UploadMetadata`.
|
- [ ] Собрать `UploadMetadata`.
|
||||||
|
|
||||||
Ожидаемый результат:
|
Ожидаемый результат:
|
||||||
|
|
||||||
@@ -156,110 +161,105 @@ UploadMetadata {
|
|||||||
|
|
||||||
### 9. Проверить согласованность размеров
|
### 9. Проверить согласованность размеров
|
||||||
|
|
||||||
* [ ] Вычислить ожидаемый размер тела:
|
- [ ] Вычислить ожидаемый размер тела:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
10 + name_size + file_size
|
10 + name_size + file_size
|
||||||
```
|
```
|
||||||
|
|
||||||
* [ ] Использовать `checked_add()`.
|
- [ ] Использовать `checked_add()`.
|
||||||
* [ ] Сравнить результат с `header.body_size()`.
|
- [ ] Сравнить результат с `header.body_size()`.
|
||||||
* [ ] Отклонить пакет, если размеры не совпадают.
|
- [ ] Отклонить пакет, если размеры не совпадают.
|
||||||
* [ ] Добавить ошибку переполнения размера.
|
- [ ] Добавить ошибку переполнения размера.
|
||||||
* [ ] Добавить ошибку несовпадения `body_size`.
|
- [ ] Добавить ошибку несовпадения `body_size`.
|
||||||
|
|
||||||
### 10. Добавить лимит размера файла
|
### 10. Добавить лимит размера файла
|
||||||
|
|
||||||
* [ ] Добавить временную настройку `MAX_FILE_SIZE`.
|
- [ ] Добавить временную настройку `MAX_FILE_SIZE`.
|
||||||
* [ ] Проверять `file_size` до создания файла.
|
- [ ] Проверять `file_size` до создания файла.
|
||||||
* [ ] Не связывать `MAX_FILE_SIZE` с размером сетевого буфера.
|
- [ ] Не связывать `MAX_FILE_SIZE` с размером сетевого буфера.
|
||||||
* [ ] Позднее вынести лимит в конфигурацию сервера.
|
- [ ] Позднее вынести лимит в конфигурацию сервера.
|
||||||
|
|
||||||
### 11. Создать модуль хранилища
|
### 11. Создать модуль хранилища
|
||||||
|
|
||||||
* [ ] Создать `storage.rs`.
|
- [ ] Создать `storage.rs`.
|
||||||
* [ ] Подключить его в `main.rs`.
|
- [ ] Подключить его в `main.rs`.
|
||||||
* [ ] Создать тип `Storage`.
|
- [ ] Создать тип `Storage`.
|
||||||
* [ ] Передавать `Storage` в `Connection`.
|
- [ ] Передавать `Storage` в `Connection`.
|
||||||
* [ ] Передавать ссылку на `Storage` в `upload::handle()`.
|
- [ ] Передавать ссылку на `Storage` в `upload::handle()`.
|
||||||
* [ ] Пока реализовать только выбор каталога хранения.
|
- [ ] Пока реализовать только выбор каталога хранения.
|
||||||
|
|
||||||
### 12. Проверить имя на уровне хранилища
|
### 12. Проверить имя на уровне хранилища
|
||||||
|
|
||||||
* [ ] Запретить пустое имя.
|
- [ ] Запретить пустое имя.
|
||||||
* [ ] Запретить абсолютный путь.
|
- [ ] Запретить абсолютный путь.
|
||||||
* [ ] Запретить компоненты `..`.
|
- [ ] Запретить компоненты `..`.
|
||||||
* [ ] Запретить нулевой байт.
|
- [ ] Запретить нулевой байт.
|
||||||
* [ ] Убедиться, что итоговый путь остаётся внутри хранилища.
|
- [ ] Убедиться, что итоговый путь остаётся внутри хранилища.
|
||||||
* [ ] Определить политику перезаписи существующего файла.
|
- [ ] Определить политику перезаписи существующего файла.
|
||||||
|
|
||||||
### 13. Создать временный файл
|
### 13. Создать временный файл
|
||||||
|
|
||||||
* [ ] Формировать имя с `request_id`, например:
|
- [ ] Формировать имя с `request_id`, например:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
a.txt.nocloud-part-17
|
a.txt.nocloud-part-17
|
||||||
```
|
```
|
||||||
|
|
||||||
* [ ] Создавать временный файл только после всех проверок метаданных.
|
- [ ] Создавать временный файл только после всех проверок метаданных.
|
||||||
* [ ] Не создавать сразу итоговый файл.
|
- [ ] Не создавать сразу итоговый файл.
|
||||||
* [ ] Убедиться, что ошибка создания корректно возвращается обработчику.
|
- [ ] Убедиться, что ошибка создания корректно возвращается обработчику.
|
||||||
|
|
||||||
### 14. Принять содержимое файла порциями
|
### 14. Принять содержимое файла порциями
|
||||||
|
|
||||||
* [ ] Создать фиксированный буфер, например 64 КиБ.
|
- [ ] Создать фиксированный буфер, например 64 КиБ.
|
||||||
* [ ] Завести счётчик `remaining_file_bytes = file_size`.
|
- [ ] Завести счётчик `remaining_file_bytes = file_size`.
|
||||||
* [ ] На каждой итерации читать не больше:
|
- [ ] На каждой итерации читать не больше:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
min(remaining_file_bytes, buffer.len())
|
min(remaining_file_bytes, buffer.len())
|
||||||
```
|
```
|
||||||
|
|
||||||
* [ ] Записывать прочитанную порцию во временный файл.
|
- [ ] Записывать прочитанную порцию во временный файл.
|
||||||
* [ ] Уменьшать `remaining_file_bytes`.
|
- [ ] Уменьшать `remaining_file_bytes`.
|
||||||
* [ ] Завершить цикл при достижении нуля.
|
- [ ] Завершить цикл при достижении нуля.
|
||||||
* [ ] Не читать байты следующего сообщения.
|
- [ ] Не читать байты следующего сообщения.
|
||||||
|
|
||||||
### 15. Обработать оборванную передачу
|
### 15. Обработать оборванную передачу
|
||||||
|
|
||||||
* [ ] Если клиент отключился раньше `file_size`, считать загрузку незавершённой.
|
- [ ] Если клиент отключился раньше `file_size`, считать загрузку незавершённой.
|
||||||
* [ ] Закрыть временный файл.
|
- [ ] Закрыть временный файл.
|
||||||
* [ ] Удалить временный файл либо сохранить для будущей докачки.
|
- [ ] Удалить временный файл либо сохранить для будущей докачки.
|
||||||
* [ ] Пока выбрать простую политику: удалять.
|
- [ ] Пока выбрать простую политику: удалять.
|
||||||
* [ ] Не создавать итоговый файл при ошибке.
|
- [ ] Не создавать итоговый файл при ошибке.
|
||||||
|
|
||||||
### 16. Завершить загрузку
|
### 16. Завершить загрузку
|
||||||
|
|
||||||
* [ ] Сбросить буферы файла на диск.
|
- [ ] Сбросить буферы файла на диск.
|
||||||
* [ ] Закрыть временный файл.
|
- [ ] Закрыть временный файл.
|
||||||
* [ ] Переименовать временный файл в итоговый.
|
- [ ] Переименовать временный файл в итоговый.
|
||||||
* [ ] Считать переименование точкой успешного завершения операции.
|
- [ ] Считать переименование точкой успешного завершения операции.
|
||||||
|
|
||||||
### 17. Отправить ответ
|
### 17. Отправить ответ
|
||||||
|
|
||||||
* [ ] Сформировать `RESPONSE` с тем же `request_id`.
|
- [ ] Сформировать `RESPONSE` с тем же `request_id`.
|
||||||
* [ ] При успехе вернуть статус `0`.
|
- [ ] При успехе вернуть статус `0`.
|
||||||
* [ ] При ошибке вернуть соответствующий код.
|
- [ ] При ошибке вернуть соответствующий код.
|
||||||
* [ ] Не отправлять текстовую строку вместо сообщения протокола.
|
- [ ] Не отправлять текстовую строку вместо сообщения протокола.
|
||||||
* [ ] После ответа вернуться в цикл `Connection`.
|
- [ ] После ответа вернуться в цикл `Connection`.
|
||||||
|
|
||||||
### 18. Провести испытания
|
### 18. Провести испытания
|
||||||
|
|
||||||
* [ ] Корректный маленький файл.
|
- [ ] Корректный маленький файл.
|
||||||
* [ ] Пустой файл размером `0`.
|
- [ ] Пустой файл размером `0`.
|
||||||
* [ ] Имя длиной `0`.
|
- [ ] Имя длиной `0`.
|
||||||
* [ ] Имя длиннее лимита.
|
- [ ] Имя длиннее лимита.
|
||||||
* [ ] Некорректный UTF-8.
|
- [ ] Некорректный UTF-8.
|
||||||
* [ ] `body_size` меньше вычисленного.
|
- [ ] `body_size` меньше вычисленного.
|
||||||
* [ ] `body_size` больше вычисленного.
|
- [ ] `body_size` больше вычисленного.
|
||||||
* [ ] `file_size` превышает лимит.
|
- [ ] `file_size` превышает лимит.
|
||||||
* [ ] Обрыв во время метаданных.
|
- [ ] Обрыв во время метаданных.
|
||||||
* [ ] Обрыв посередине файла.
|
- [ ] Обрыв посередине файла.
|
||||||
* [ ] Попытка передать `../test.txt`.
|
- [ ] Попытка передать `../test.txt`.
|
||||||
* [ ] Повторная загрузка существующего файла.
|
- [ ] Повторная загрузка существующего файла.
|
||||||
* [ ] Два последовательных `UPLOAD` в одном соединении.
|
- [ ] Два последовательных `UPLOAD` в одном соединении.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -5,11 +5,12 @@
|
|||||||
2. Вернуть структуру Header c понятными полями заголовка:
|
2. Вернуть структуру Header c понятными полями заголовка:
|
||||||
a. version: u8 //Версия протокола
|
a. version: u8 //Версия протокола
|
||||||
b. command: u8 //Тип сообщения
|
b. command: u8 //Тип сообщения
|
||||||
|
Код Команда Кто отправляет
|
||||||
0x01 UPLOAD клиент → сервер
|
0x01 UPLOAD клиент → сервер
|
||||||
0x02 DOWNLOAD клиент → сервер
|
0x02 DOWNLOAD клиент → сервер
|
||||||
0x03 DELETE клиент → сервер
|
0x03 DELETE клиент → сервер
|
||||||
0x04 STAT клиент → сервер
|
0x04 STAT клиент → сервер
|
||||||
0x80 RESPONSE в обе стороны
|
0x80 RESPONSE клиент ↔ сервер
|
||||||
0x81 FILE_DATA сервер → клиент
|
0x81 FILE_DATA сервер → клиент
|
||||||
c. flags: u16 //Дополнительные признаки
|
c. flags: u16 //Дополнительные признаки
|
||||||
0x0001 — разрешено перезаписать существующий файл
|
0x0001 — разрешено перезаписать существующий файл
|
||||||
|
|||||||
+41
-7
@@ -44,6 +44,43 @@ pub struct Header {
|
|||||||
body_size: u64,
|
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(
|
pub fn decode_header(
|
||||||
input: &[u8; HEADER_SIZE],
|
input: &[u8; HEADER_SIZE],
|
||||||
) -> Result<Header, HeaderError> {
|
) -> Result<Header, HeaderError> {
|
||||||
@@ -97,8 +134,9 @@ impl Header {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Возвращает код команды (например, 1, 2, 80 или 81)
|
/// Возвращает код команды (например, 1, 2, 80 или 81)
|
||||||
pub fn command(&self) -> u8 {
|
pub fn command(&self) -> Command {
|
||||||
self.command
|
Command::try_from(self.command)
|
||||||
|
.expect("Header уже прошёл валидацию команды")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Возвращает битовые флаги пакета (уже прошедшие валидацию)
|
/// Возвращает битовые флаги пакета (уже прошедшие валидацию)
|
||||||
@@ -128,11 +166,7 @@ impl Header {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Валидация command
|
// Валидация command
|
||||||
if self.command != 1 {
|
Command::try_from(self.command)?;
|
||||||
return Err(
|
|
||||||
HeaderError::UnsupportedCommand(self.command)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Валидация flags
|
// Валидация flags
|
||||||
if !(0..=7).contains(&self.flags){
|
if !(0..=7).contains(&self.flags){
|
||||||
|
|||||||
Reference in New Issue
Block a user