Черновик ПЗ
79
Report/appendices/appendix-b.typ
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
#import "../includes/common.typ": pz-fig
|
||||||
|
|
||||||
|
== Обзор программного продукта Wallenc
|
||||||
|
|
||||||
|
Wallenc — мобильное приложение для Android, предназначенное для управления vault (хранилищами) с клиентским шифрованием. Продукт не использует собственный сервер; взаимодействие с облаком выполняется через API внешних провайдеров после OAuth.
|
||||||
|
|
||||||
|
== Техническое задание (выдержка)
|
||||||
|
|
||||||
|
*Наименование:* мобильное приложение Wallenc.
|
||||||
|
|
||||||
|
*Основание для разработки:* производственная практика, задание ООО НМФ «Нейротех», направление 09.03.04.
|
||||||
|
|
||||||
|
*Назначение:* обеспечение конфиденциального хранения пользовательских данных на недоверенных хранилищах.
|
||||||
|
|
||||||
|
*Требования к функциям:* см. табл. @tbl-req (глава 1).
|
||||||
|
|
||||||
|
*Требования к надёжности:* устойчивость к прерыванию операций шифрования; восстановление метаданных из Room.
|
||||||
|
|
||||||
|
*Стадии разработки:* аналитический этап; реализация ядра; тестирование; оформление документации.
|
||||||
|
|
||||||
|
*Порядок контроля:* модульные и ручные испытания по программе (ниже).
|
||||||
|
|
||||||
|
== Программа и методика испытаний
|
||||||
|
|
||||||
|
Испытания проводятся на устройстве или эмуляторе Android. Матрица сценариев — табл. @tbl-testplan. Критерий приёмки: отсутствие блокирующих дефектов по сценариям T-1–T-6.
|
||||||
|
|
||||||
|
== Отчёт о результатах испытаний
|
||||||
|
|
||||||
|
Результаты приведены в табл. @tbl-testres (глава 5). Модульные тесты криптографии — пройдены.
|
||||||
|
|
||||||
|
== Руководство пользователя
|
||||||
|
|
||||||
|
=== Установка
|
||||||
|
|
||||||
|
Установите APK сборки debug/release, полученной от разработчика, или соберите проект из репозитория Gitea ЮФУ. Разрешите доступ к файловой системе при запросе системы.
|
||||||
|
|
||||||
|
=== Первый запуск и локальные vault
|
||||||
|
|
||||||
|
1. Откройте приложение Wallenc.
|
||||||
|
2. На экране локальных vault нажмите «+» для создания хранилища.
|
||||||
|
3. Укажите имя vault и подтвердите создание.
|
||||||
|
|
||||||
|
#pz-fig("fig_05_local_vaults.jpg", [Экран списка локальных vault], "fig-05-rp")
|
||||||
|
|
||||||
|
=== Шифрование vault
|
||||||
|
|
||||||
|
1. Выберите vault в списке.
|
||||||
|
2. Выберите действие «Включить шифрование».
|
||||||
|
3. Введите пароль (мастер-ключ) и подтвердите. *Важно:* без пароля восстановление невозможно.
|
||||||
|
|
||||||
|
#pz-fig("fig_06_encrypt_dialog.jpg", [Диалог включения шифрования], "fig-06-rp")
|
||||||
|
|
||||||
|
=== Открытие и закрытие зашифрованного vault
|
||||||
|
|
||||||
|
1. Для зашифрованного vault выберите «Открыть».
|
||||||
|
2. Введите пароль. При успехе содержимое доступно для просмотра.
|
||||||
|
3. Используйте «Закрыть» для блокировки.
|
||||||
|
|
||||||
|
#pz-fig("fig_07_open_close_dialog.jpg", [Диалог открытия и закрытия], "fig-07-rp")
|
||||||
|
|
||||||
|
=== Переименование и удаление
|
||||||
|
|
||||||
|
Долгое нажатие или меню vault → «Переименовать» / «Удалить». Подтвердите действие в диалоге.
|
||||||
|
|
||||||
|
#pz-fig("fig_08_rename_delete_dialog.jpg", [Диалог переименования и удаления], "fig-08-rp")
|
||||||
|
|
||||||
|
=== Удалённые vault и Яндекс
|
||||||
|
|
||||||
|
1. Перейдите на экран удалённых vault.
|
||||||
|
2. Нажмите «+» → выберите авторизацию Яндекс.
|
||||||
|
3. Пройдите OAuth в браузере/встроенном окне.
|
||||||
|
4. После успеха добавьте удалённое хранилище.
|
||||||
|
|
||||||
|
#pz-fig("fig_09_remote_vaults.jpg", [Экран удалённых vault], "fig-09-rp")
|
||||||
|
#pz-fig("fig_10_yandex_oauth.jpg", [OAuth Яндекс], "fig-10-rp")
|
||||||
|
|
||||||
|
=== Фоновые задачи
|
||||||
|
|
||||||
|
На экране задач отображаются операции шифрования и синхронизации. Уведомления информируют о завершении (см. рис. 12 и 13 в гл. 5).
|
||||||
37
Report/images/IMAGES_REGISTRY.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# Реестр иллюстраций ПЗ Wallenc
|
||||||
|
|
||||||
|
Правило: файл в `Report/images/` только с именем из таблицы. Статус `ready` — можно вставлять в `.typ`.
|
||||||
|
|
||||||
|
| № | Имя файла | Статус | Где используется | Typst label |
|
||||||
|
|---|-----------|--------|------------------|-------------|
|
||||||
|
| 1 | fig_01_start_sync.png | ready | 3.3.3, 4.1.4, прил. Г | fig-01 |
|
||||||
|
| 2 | fig_02_vault_lifecycle.png | ready | 3.3.3, 4.1.4, прил. Г | fig-02 |
|
||||||
|
| 3 | fig_03_navigation_hub.png | ready | 3.3.3, 4.1.4, прил. Г | fig-03 |
|
||||||
|
| 4 | fig_04_domain_class.png | ready | 2.3.2, 4.2.1, прил. Г | fig-04 |
|
||||||
|
| 5 | fig_05_local_vaults.jpg | placeholder | 3.4, 4.2.3, 5.2.2, прил. В, РП | fig-05 |
|
||||||
|
| 6 | fig_06_encrypt_dialog.jpg | placeholder | 3.4, 4.1.1, 5.2.2, прил. В, РП | fig-06 |
|
||||||
|
| 7 | fig_07_open_close_dialog.jpg | placeholder | 3.4, 4.1.2, прил. В, РП | fig-07 |
|
||||||
|
| 8 | fig_08_rename_delete_dialog.jpg | placeholder | 3.4, 5.2.2, прил. В, РП | fig-08 |
|
||||||
|
| 9 | fig_09_remote_vaults.jpg | placeholder | 3.4, 4.2.3, прил. В, РП | fig-09 |
|
||||||
|
| 10 | fig_10_yandex_oauth.jpg | placeholder | 3.4, 4.2.3, 1.2.2.4, прил. В, РП | fig-10 |
|
||||||
|
| 11 | fig_11_room_schema.png | placeholder | 2.3.2, 4.2.2, прил. В, РП | fig-11 |
|
||||||
|
| 12 | fig_12_tasks_screen.jpg | placeholder | 5.2.3, прил. В | fig-12 |
|
||||||
|
| 13 | fig_13_tasks_notification.jpg | placeholder | 5.2.3, прил. В | fig-13 |
|
||||||
|
| 14 | fig_14_context_system.png | placeholder | 1.2.1, 2.1.4 | fig-14 |
|
||||||
|
| 15 | fig_15_bpmn_vault.png | placeholder | 2.1.3 | fig-15 |
|
||||||
|
| 16 | fig_16_dfd_level0.png | placeholder | 2.2 | fig-16 |
|
||||||
|
| 17 | fig_17_use_case.png | placeholder | 2.3.1 | fig-17 |
|
||||||
|
| 18 | fig_18_deployment.png | placeholder | 2.3.3 | fig-18 |
|
||||||
|
| 19 | fig_19_clean_architecture.png | placeholder | 2.3, 4.3 | fig-19 |
|
||||||
|
| 20 | fig_20_oauth_sequence.png | placeholder | 1.5.2, 4.2.3 | fig-20 |
|
||||||
|
| 21 | fig_21_encrypt_flow.png | placeholder | 4.1.1, 5.2.1 | fig-21 |
|
||||||
|
| 22 | fig_22_cjm_vault.png | placeholder | 3.3.2 | fig-22 |
|
||||||
|
| 23 | fig_23_module_deps.png | placeholder | 4.3 | fig-23 |
|
||||||
|
| 27 | fig_27_gradle_domain_test.png | ready | 5.2.1 | fig-27 |
|
||||||
|
| 28 | fig_28_gradle_usecases_test.png | ready | 5.2.2 | fig-28 |
|
||||||
|
| 29 | fig_29_gradle_ui_test.png | ready | 5.2.3 | fig-29 |
|
||||||
|
| 30 | fig_30_gradle_test_summary.png | ready | 5.4 | fig-30 |
|
||||||
|
| 31 | fig_31_gradle_connected_test.png | ready | 5.3 | fig-31 |
|
||||||
|
| 32 | fig_32_manual_test_checklist.png | ready | 5.3 | fig-32 |
|
||||||
|
|
||||||
|
Замените файлы со статусом `placeholder` на реальные скриншоты/диаграммы, затем смените статус на `ready`. Рис. 27–32 — заглушки Gradle/UI; перед защитой заменить снимками Android Studio.
|
||||||
BIN
Report/images/fig_01_start_sync.png
Normal file
|
After Width: | Height: | Size: 255 KiB |
BIN
Report/images/fig_02_vault_lifecycle.png
Normal file
|
After Width: | Height: | Size: 184 KiB |
BIN
Report/images/fig_03_navigation_hub.png
Normal file
|
After Width: | Height: | Size: 148 KiB |
BIN
Report/images/fig_04_domain_class.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
Report/images/fig_05_local_vaults.jpg
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
Report/images/fig_06_encrypt_dialog.jpg
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
Report/images/fig_07_open_close_dialog.jpg
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
Report/images/fig_08_rename_delete_dialog.jpg
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
Report/images/fig_09_remote_vaults.jpg
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
Report/images/fig_10_yandex_oauth.jpg
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
Report/images/fig_11_room_schema.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
Report/images/fig_12_tasks_screen.jpg
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
Report/images/fig_13_tasks_notification.jpg
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
Report/images/fig_14_context_system.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
Report/images/fig_15_bpmn_vault.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
Report/images/fig_16_dfd_level0.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
Report/images/fig_17_use_case.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
Report/images/fig_18_deployment.png
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
Report/images/fig_19_clean_architecture.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
Report/images/fig_20_oauth_sequence.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
Report/images/fig_21_encrypt_flow.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
Report/images/fig_22_cjm_vault.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
Report/images/fig_23_module_deps.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
Report/images/fig_27_gradle_domain_test.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
Report/images/fig_28_gradle_usecases_test.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
Report/images/fig_29_gradle_ui_test.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
Report/images/fig_30_gradle_test_summary.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
Report/images/fig_31_gradle_connected_test.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
Report/images/fig_32_manual_test_checklist.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
21
Report/includes/abbreviations.typ
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#heading(numbering: none, outlined: true)[Список условных обозначений и сокращений]
|
||||||
|
|
||||||
|
#import "common.typ": pz-table
|
||||||
|
|
||||||
|
#pz-table(
|
||||||
|
[Условные обозначения и сокращения],
|
||||||
|
2,
|
||||||
|
table.header([Обозначение], [Расшифровка]),
|
||||||
|
[API], [Application Programming Interface — программный интерфейс приложения],
|
||||||
|
[DAO], [Data Access Object — объект доступа к данным],
|
||||||
|
[E2E], [End-to-end — сквозное шифрование],
|
||||||
|
[MVVM], [Model — View — ViewModel],
|
||||||
|
[OAuth], [протокол авторизации открытого типа],
|
||||||
|
[UI], [User Interface — пользовательский интерфейс],
|
||||||
|
[CRUD], [Create, Read, Update, Delete — создание, чтение, изменение, удаление],
|
||||||
|
[ПЗ], [Пояснительная записка],
|
||||||
|
[ТЗ], [Техническое задание],
|
||||||
|
[ВКР], [Выпускная квалификационная работа],
|
||||||
|
[JUnit], [Фреймворк модульного тестирования Java/Kotlin],
|
||||||
|
[IT], [Instrumented Test — инструментальный тест Android],
|
||||||
|
) <tbl-abbr>
|
||||||
9
Report/includes/abstract-en.typ
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#heading(numbering: none, outlined: true)[Abstract]
|
||||||
|
|
||||||
|
#v(0.5em)
|
||||||
|
|
||||||
|
This explanatory note describes the development of the Wallenc mobile application — a client-side wallet for secure storage of user data on untrusted storage backends without a dedicated application server. Security is ensured by client-side encryption before data leaves the device; decryption is performed only inside the application when the user supplies a valid key. The work covers analysis of the problem domain and competing products, requirements specification, system and user-interface design, Kotlin implementation for Android, software testing, and a brief economic assessment.
|
||||||
|
|
||||||
|
The project applies MVVM and Clean Architecture with Gradle modules (`:app`, `:domain`, `:usecases`, `:ui`, `:domain-vault`, `:infrastructure-android`, `:vault-contracts`, `:task-runtime`). Functional capabilities include local and remote vault management, AES-based encryption, Room metadata storage, Yandex OAuth integration, and a planned synchronization model that does not expose encryption keys to external providers.
|
||||||
|
|
||||||
|
Keywords: mobile application, client-side encryption, Android, vault, zero-knowledge, OAuth, Room, Jetpack Compose.
|
||||||
131
Report/includes/ch01.typ
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
#import "common.typ": pz-fig, pz-table
|
||||||
|
|
||||||
|
= Анализ требований и предметной области
|
||||||
|
|
||||||
|
== Анализ предметной области
|
||||||
|
|
||||||
|
В рамках анализа предметной области рассмотрены подходы к хранению чувствительных данных в мобильных приложениях и облачных хранилищах. Выделены требования: конфиденциальность при хранении и передаче; отсутствие необходимости доверять инфраструктуре хранилища; устойчивость к компрометации удалённого провайдера; разделение логики хранения и криптографической защиты; удобные сценарии создания хранилища, шифрования, открытия и работы с содержимым.
|
||||||
|
|
||||||
|
Сформирован вывод о приоритете клиентских криптографических механизмов, унифицированного доступа к разным типам хранилищ и архитектуры с чётким разделением слоёв.
|
||||||
|
|
||||||
|
Рассмотрены угрозы STRIDE в применении к клиентскому хранилищу: подмена файлов на диске провайдера (tampering) нейтрализуется шифрованием; утечка метаданных минимизируется за счёт шифрования имён путей (опция `encryptPath`); отказ в обслуживании облака не блокирует локальный доступ при наличии копии vault. Модель доверия: пользователь доверяет только собственному устройству и корректности реализации криптомодуля; облако и сеть считаются активными противниками для ciphertext.
|
||||||
|
|
||||||
|
Контекст взаимодействия участников показан на рисунке @fig-14.
|
||||||
|
|
||||||
|
#pz-fig("fig_14_context_system.png", [Контекстная диаграмма: пользователь, приложение Wallenc и внешний провайдер], "fig-14")
|
||||||
|
|
||||||
|
== Разработка требований к программному продукту
|
||||||
|
|
||||||
|
=== Назначение и цели создания системы
|
||||||
|
|
||||||
|
*Назначение* системы Wallenc — предоставление пользователю мобильного клиента для управления локальными и удалёнными vault с обязательным клиентским шифрованием содержимого до выгрузки во внешнее хранилище.
|
||||||
|
|
||||||
|
*Цели создания*: обеспечить единую модель хранилищ; минимизировать доверие к провайдеру; поддержать расширение списка провайдеров через адаптеры; сохранить метаданные и состояние приложения в локальной БД без хранения пользовательского контента в открытом виде.
|
||||||
|
|
||||||
|
=== Функциональные требования
|
||||||
|
|
||||||
|
Функциональные требования сведены в таблице @tbl-req.
|
||||||
|
|
||||||
|
#pz-table(
|
||||||
|
[Свод функциональных требований],
|
||||||
|
2,
|
||||||
|
table.header([Код], [Требование]),
|
||||||
|
[ФР-1], [Создание, просмотр, переименование и удаление локальных vault],
|
||||||
|
[ФР-2], [Включение шифрования vault, проверка ключа, открытие и закрытие зашифрованного представления],
|
||||||
|
[ФР-3], [Просмотр и операции с файлами через абстракцию хранилищ; текстовые секреты и 2FA в vault],
|
||||||
|
[ФР-4], [OAuth-авторизация во внешнем провайдере (Яндекс), учёт удалённых vault],
|
||||||
|
[ФР-5], [Синхронизация: группы хранилищ, журнал коммитов, фоновый Worker без передачи ключей],
|
||||||
|
[ФР-6], [Очередь фоновых задач: шифрование, синхронизация, отображение прогресса],
|
||||||
|
) <tbl-req>
|
||||||
|
|
||||||
|
==== Управление локальными vault
|
||||||
|
|
||||||
|
Пользователь создаёт vault, просматривает список, переименовывает и удаляет хранилища. Служебные каталоги и системные пути не отображаются в пользовательском представлении.
|
||||||
|
|
||||||
|
==== Шифрование и открытие зашифрованного vault
|
||||||
|
|
||||||
|
При включении шифрования формируются параметры `StorageEncryptionInfo`; открытие выполняется только после успешной проверки ключа. Повторное шифрование одного vault до завершения предыдущей операции блокируется.
|
||||||
|
|
||||||
|
==== Работа с содержимым vault
|
||||||
|
|
||||||
|
Операции чтения и записи выполняются через единый интерфейс файлового доступа независимо от типа хранилища.
|
||||||
|
|
||||||
|
==== Удалённые хранилища и авторизация во внешних провайдерах
|
||||||
|
|
||||||
|
Реализован поток OAuth 2.0 для Яндекса @oauth-rfc6749 @yandex-oauth (рис. @fig-20).
|
||||||
|
|
||||||
|
#pz-fig("fig_20_oauth_sequence.png", [Диаграмма последовательности OAuth Яндекс], "fig-20")
|
||||||
|
|
||||||
|
==== Синхронизация зашифрованных данных
|
||||||
|
|
||||||
|
Спроектирован механизм фоновой синхронизации: в Room хранятся записи с UUID хранилищ; по таймеру запускается сервис, сравнивающий истории коммитов локального и удалённого представления без передачи ключей на сервер провайдера (рис. @fig-01–@fig-03).
|
||||||
|
|
||||||
|
=== Нефункциональные требования
|
||||||
|
|
||||||
|
К системе предъявляются требования по производительности (асинхронные операции на Coroutines), безопасности (AES на клиенте, минимизация утечек через имена путей), расширяемости (модульная структура Gradle) и устойчивости к гонкам при длительных операциях шифрования.
|
||||||
|
|
||||||
|
=== Требования к программно-аппаратной платформе
|
||||||
|
|
||||||
|
Минимальная платформа — Android с поддержкой Jetpack Compose; для OAuth и удалённых операций требуется сетевое подключение. Объём оперативной памяти должен быть достаточен для фоновых задач шифрования и Room.
|
||||||
|
|
||||||
|
== Аналоги
|
||||||
|
|
||||||
|
=== Аналог Google Files Secure Folder
|
||||||
|
|
||||||
|
Google Files Secure Folder локально прячет и защищает файлы в отдельной папке по PIN или графическому ключу внутри Android @google-secure-folder. Пользователь получает понятный «сейф» без настройки облака, однако сценарий ограничен устройством: отсутствует полноценная кроссплатформенная синхронизация зашифрованного vault между провайдерами, а модель хранилища не универсальна для подключения внешних API.
|
||||||
|
|
||||||
|
=== Аналог Proton Pass / Proton Drive
|
||||||
|
|
||||||
|
Экосистема Proton обеспечивает end-to-end шифрование для паролей, заметок и файлов с облачной синхронизацией. Для Wallenc релевантен опыт прозрачной для пользователя защиты и синхронизации, однако продукт жёстко привязан к инфраструктуре Proton, часть функций и объёмов зависит от тарифа, а роль «универсального клиента» к произвольному внешнему хранилищу ограничена.
|
||||||
|
|
||||||
|
=== Аналог Bitwarden
|
||||||
|
|
||||||
|
Менеджер секретов с шифрованием и синхронизацией. Ограничения: ориентация на учётные данные, а не файловый vault @bitwarden-help.
|
||||||
|
|
||||||
|
=== Аналог Cryptomator
|
||||||
|
|
||||||
|
Клиентское шифрование файловых vault в облаке (zero-knowledge). Ограничения: фокус на файлах, ограничения интеграций по платформам @cryptomator-docs.
|
||||||
|
|
||||||
|
=== Сводная оценка
|
||||||
|
|
||||||
|
#pz-table(
|
||||||
|
[Сравнительная оценка аналогов],
|
||||||
|
6,
|
||||||
|
table.header([Критерий], [Secure Folder], [Proton], [Bitwarden], [Cryptomator], [Wallenc]),
|
||||||
|
[Собственный backend приложения], [—], [+], [+/−], [—], [—],
|
||||||
|
[E2E / клиентское шифрование], [+/−], [+], [+], [+], [+],
|
||||||
|
[Файловый vault], [+], [+], [−], [+], [+],
|
||||||
|
[OAuth внешнего провайдера], [−], [+/−], [+/−], [+/−], [+],
|
||||||
|
[Переносимость провайдеров], [−], [−], [+/−], [+], [+],
|
||||||
|
[Unit-тесты без сервера], [н/д], [н/д], [+/−], [+/−], [+ (68)],
|
||||||
|
) <tbl-analog>
|
||||||
|
|
||||||
|
Обзор подтверждает актуальность концепции Wallenc: безопасность без собственного сервера и переносимая архитектура хранилищ.
|
||||||
|
|
||||||
|
== Стек технологий
|
||||||
|
|
||||||
|
Для реализации выбраны Kotlin, Android SDK, Jetpack Compose, Coroutines/Flow, Hilt, Room, AES на клиенте; модульная структура: `:app`, `:domain`, `:usecases`, `:ui`, `:domain-vault`, `:infrastructure-android`, `:vault-contracts`, `:task-runtime` @kotlin-docs @compose-docs @room-docs @hilt-docs @android-arch.
|
||||||
|
|
||||||
|
*Kotlin* обеспечивает выразительную доменную модель и безопасность типов. *Jetpack Compose* декларативно описывает UI и состояние экранов vault. *Coroutines/Flow* используются для асинхронного шифрования, обращения к DAO и отображения прогресса без блокировки главного потока. *Hilt* связывает реализации интерфейсов domain с Android-инфраструктурой. *Room* персистентно хранит метаданные. Криптографические операции выполняются в доменном слое с опорой на стандартные API Java/Android и рекомендации NIST по AES @nist-aes.
|
||||||
|
|
||||||
|
Выбор стека согласован с целями практики: все компоненты поддерживаются Google и сообществом, документированы на русском и английском языках, применимы в промышленной разработке мобильных приложений.
|
||||||
|
|
||||||
|
== Обзор методов и подходов к защите данных в мобильных приложениях
|
||||||
|
|
||||||
|
=== Клиентское шифрование и модель zero-knowledge
|
||||||
|
|
||||||
|
Данные шифруются до отправки во внешнее хранилище; провайдер не получает ключ расшифрования. Используется AES @nist-aes; проверка ключа выполняется через `Encryptor.checkKey` без расшифровки всего содержимого.
|
||||||
|
|
||||||
|
=== Авторизация и взаимодействие с внешними провайдерами без собственного сервера
|
||||||
|
|
||||||
|
Применяется OAuth 2.0: токены доступа хранятся локально в Room (`DbYandexAccount`); ключи шифрования не передаются на сторону провайдера @oauth-rfc6749.
|
||||||
|
|
||||||
|
== Обзор рынка и обоснование выбора решения Wallenc
|
||||||
|
|
||||||
|
Рынок мобильных хранилищ демонстрирует поляризацию: закрытые экосистемы с удобным UX (Proton, Google) и открытые криптоклиенты с ручной настройкой (Cryptomator). Wallenc занимает промежуточную нишу — *мобильный zero-knowledge vault* с OAuth к популярному провайдеру (Яндекс) и расширяемой регистрацией типов хранилищ. Для корпоративного заказчика (Нейротех) важны: отсутствие затрат на сервер приложения, возможность аудита кода, формализованное тестирование (68 unit-тестов, см. гл. 5).
|
||||||
|
|
||||||
|
Перспективы коммерциализации связаны не с продажей облака, а с лицензированием клиента или внутренним внедрением. Барьер входа — ответственность пользователя за ключ; в ПЗ это отражено в руководстве пользователя (прил. Б) и предупреждениях в UI.
|
||||||
|
|
||||||
|
== Формирование технического задания
|
||||||
|
|
||||||
|
Структура ТЗ оформлена по ГОСТ 7.32–2017: цели, этапы, функциональные и нефункциональные требования, порядок приёмки. Приоритет отдан ядру хранения и шифрования; расширения (2FA, текстовые секреты, синхронизация) зафиксированы как дополнительные функции второго этапа практики. Полный текст ТЗ — в приложении Б.
|
||||||
92
Report/includes/ch02.typ
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
#import "common.typ": pz-fig, pz-table
|
||||||
|
|
||||||
|
= Проектирование архитектуры системы
|
||||||
|
|
||||||
|
== Схема бизнес-процессов предметной области
|
||||||
|
|
||||||
|
=== Организационная структура
|
||||||
|
|
||||||
|
Участники процесса: *пользователь*, *мобильное приложение Wallenc*, *внешний провайдер хранения* (облачный API). Сервер приложения отсутствует.
|
||||||
|
|
||||||
|
=== Карта процессов
|
||||||
|
|
||||||
|
Основные процессы: создание vault → опциональное шифрование → открытие → работа с содержимым → закрытие; для удалённых vault — авторизация OAuth → привязка хранилища → проектная синхронизация.
|
||||||
|
|
||||||
|
=== Диаграмма BPMN
|
||||||
|
|
||||||
|
На рисунке @fig-15 представлена диаграмма BPMN основного процесса работы с vault.
|
||||||
|
|
||||||
|
#pz-fig("fig_15_bpmn_vault.png", [BPMN: создание vault, шифрование, открытие, работа с содержимым], "fig-15")
|
||||||
|
|
||||||
|
=== Карта систем
|
||||||
|
|
||||||
|
Wallenc выступает посредником между пользователем и файловыми API провайдера, дополняя взаимодействие локальной БД Room и криптографическим модулем.
|
||||||
|
|
||||||
|
== Диаграмма DFD
|
||||||
|
|
||||||
|
DFD уровня 0 (рис. @fig-16) отражает потоки между UI, доменной логикой, криптографией, адаптерами хранилищ, Room и внешним API.
|
||||||
|
|
||||||
|
#pz-fig("fig_16_dfd_level0.png", [DFD уровень 0: потоки данных в Wallenc], "fig-16")
|
||||||
|
|
||||||
|
== Объектно-ориентированный анализ и проектирование (UML)
|
||||||
|
|
||||||
|
=== Диаграмма прецедентов
|
||||||
|
|
||||||
|
Прецеденты включают управление vault, шифрование, работу с содержимым, OAuth и проектную синхронизацию (рис. @fig-17).
|
||||||
|
|
||||||
|
#pz-fig("fig_17_use_case.png", [Диаграмма прецедентов Wallenc], "fig-17")
|
||||||
|
|
||||||
|
=== Диаграмма классов
|
||||||
|
|
||||||
|
Доменная модель модуля `:domain` (интерфейсы хранилищ, use case, сущности шифрования) приведена на рисунке @fig-04.
|
||||||
|
|
||||||
|
#pz-fig("fig_04_domain_class.png", [Диаграмма классов модуля domain], "fig-04")
|
||||||
|
|
||||||
|
Служебные сущности Room показаны на рисунке @fig-11.
|
||||||
|
|
||||||
|
#pz-fig("fig_11_room_schema.png", [Схема служебных сущностей Room], "fig-11")
|
||||||
|
|
||||||
|
=== Диаграмма развёртывания
|
||||||
|
|
||||||
|
Компоненты развёртывания: устройство Android (приложение, Room), облачный API Яндекса (рис. @fig-18).
|
||||||
|
|
||||||
|
#pz-fig("fig_18_deployment.png", [Диаграмма развёртывания], "fig-18")
|
||||||
|
|
||||||
|
Архитектурные слои MVVM + Clean Architecture и соответствие модулям Gradle — на рисунке @fig-19.
|
||||||
|
|
||||||
|
#pz-fig("fig_19_clean_architecture.png", [Слои Clean Architecture и модули проекта], "fig-19")
|
||||||
|
|
||||||
|
== Проектирование локальной базы данных
|
||||||
|
|
||||||
|
Служебная БД Room хранит соответствия между исходным хранилищем и зашифрованным представлением (`DbStorageKeyMap`), метаданные vault (`DbStorageMetaInfo`), учётные записи Яндекс (`DbYandexAccount`) и группы синхронизации (`DbStorageSyncGroup`). Пользовательский контент в открытом виде в БД не сохраняется — только параметры, необходимые для восстановления состояния приложения при следующем запуске. Доступ инкапсулирован в DAO и репозиториях модуля `:infrastructure-android`.
|
||||||
|
|
||||||
|
#pz-table(
|
||||||
|
[Сущности Room и назначение],
|
||||||
|
3,
|
||||||
|
table.header([Сущность], [Назначение], [Связь с тестами]),
|
||||||
|
[`DbStorageKeyMap`], [Связь vault ↔ параметры шифрования], [Интеграция],
|
||||||
|
[`DbStorageMetaInfo`], [Имя, путь, флаги состояния vault], [Интеграция],
|
||||||
|
[`DbYandexAccount`], [OAuth access token, идентификатор аккаунта], [`YandexAccountRepositoryTest`],
|
||||||
|
[`DbStorageSyncGroup`], [Группа UUID для синхронизации], [`StorageSyncEngineTest`],
|
||||||
|
) <tbl-room-entities>
|
||||||
|
|
||||||
|
== Проектирование подсистемы синхронизации
|
||||||
|
|
||||||
|
Подсистема синхронизации спроектирована как набор независимых операций над журналом изменений каждого `Storage`. Алгоритм выбирает «победителя» по ревизии, копирует или удаляет файлы на целевом хранилище, не расшифровывая данные на сервере. Блокировки предотвращают одновременную синхронизацию одной группы; при отмене задачи блокировки снимаются (покрыто unit-тестами `syncGroupCooperativeCancellationReleasesLocks` и др., гл. 5).
|
||||||
|
|
||||||
|
== Нефункциональные архитектурные решения
|
||||||
|
|
||||||
|
#pz-table(
|
||||||
|
[Нефункциональные решения],
|
||||||
|
2,
|
||||||
|
table.header([Атрибут], [Решение]),
|
||||||
|
[Производительность], [Потоковое шифрование, фоновые задачи в `:task-runtime`],
|
||||||
|
[Безопасность], [AES, проверка ключа без полного decrypt, скрытие служебных путей],
|
||||||
|
[Расширяемость], [`vault-contracts`, регистрация провайдеров],
|
||||||
|
[Сопровождаемость], [Модульные тесты 68 + листинги в приложении А],
|
||||||
|
[Надёжность], [Room-транзакции, восстановление журнала после сбоя записи],
|
||||||
|
) <tbl-nfr-arch>
|
||||||
|
|
||||||
|
== Вывод
|
||||||
|
|
||||||
|
Спроектирована клиентская архитектура с разделением domain, infrastructure и presentation, единым интерфейсом vault и заделом под синхронизацию без передачи ключей. Диаграммы зафиксированы в приложении Г.
|
||||||
72
Report/includes/ch03.typ
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
#import "common.typ": pz-fig, pz-table
|
||||||
|
|
||||||
|
= Проектирование пользовательского интерфейса мобильного приложения
|
||||||
|
|
||||||
|
== Подготовка проекта и аналитика
|
||||||
|
|
||||||
|
=== Анализ конкурентов и определение сильных и слабых сторон
|
||||||
|
|
||||||
|
По результатам сравнения аналогов (табл. @tbl-analog) для Wallenc выделены сильные стороны: единый UI для локальных и удалённых vault, явное отображение состояния шифрования, диалоги подтверждения деструктивных операций. Слабые стороны конкурентов (привязка к экосистеме, узкая предметная область) учтены при проектировании навигации.
|
||||||
|
|
||||||
|
=== Ограничения и допущения проектирования UI
|
||||||
|
|
||||||
|
Полевые интервью пользователей не проводились; сценарии восстановлены по аналогам и отчётам этапов практики. Допущение: пользователь понимает риск потери ключа шифрования.
|
||||||
|
|
||||||
|
== Исследования пользовательских сценариев
|
||||||
|
|
||||||
|
=== Потребности и барьеры пользователя
|
||||||
|
|
||||||
|
Потребности: хранить файлы в «сейфе» без доверия к облаку; синхронизировать между устройствами. Барьеры: сложность OAuth, страх потери пароля, неочевидность состояния «vault открыт».
|
||||||
|
|
||||||
|
=== Полезные сценарии из аналогов
|
||||||
|
|
||||||
|
Заимствованы: список хранилищ с индикацией статуса (Cryptomator, Bitwarden); локальная защита папки (Secure Folder); пошаговый мастер шифрования.
|
||||||
|
|
||||||
|
== Проектирование пользовательских потоков
|
||||||
|
|
||||||
|
=== User Story
|
||||||
|
|
||||||
|
#pz-table(
|
||||||
|
[User Story Wallenc],
|
||||||
|
3,
|
||||||
|
table.header([ID], [Формулировка], [Критерий приёмки]),
|
||||||
|
[US-1], [Создаю локальный vault для файлов на устройстве], [Vault в списке (рис. 5)],
|
||||||
|
[US-2], [Включаю шифрование vault паролем], [Статус encrypted, тест T-8],
|
||||||
|
[US-3], [Открываю зашифрованный vault ключом], [Доступ к контенту, тест T-9],
|
||||||
|
[US-4], [Подключаю Яндекс и удалённый vault], [OAuth OK, тест T-10],
|
||||||
|
[US-5], [Вижу прогресс фоновых задач], [Экран задач, тест T-11],
|
||||||
|
[US-6], [Храню TOTP и текстовые секреты в vault], [`TwoFaTotpTest`, UI IT],
|
||||||
|
) <tbl-userstory>
|
||||||
|
|
||||||
|
=== Customer Journey Map
|
||||||
|
|
||||||
|
CJM сценария «защитить и открыть vault» представлен на рисунке @fig-22.
|
||||||
|
|
||||||
|
#pz-fig("fig_22_cjm_vault.png", [Customer Journey Map: защита и открытие vault], "fig-22")
|
||||||
|
|
||||||
|
=== Пользовательский сценарий
|
||||||
|
|
||||||
|
Диаграммы потоков: старт приложения и фоновая синхронизация (@fig-01), жизненный цикл vault (@fig-02), навигация с главного экрана (@fig-03).
|
||||||
|
|
||||||
|
#pz-fig("fig_01_start_sync.png", [Старт приложения и фоновая синхронизация (проект)], "fig-01")
|
||||||
|
#pz-fig("fig_02_vault_lifecycle.png", [Жизненный цикл vault и очередь синхронизации], "fig-02")
|
||||||
|
#pz-fig("fig_03_navigation_hub.png", [Навигация с главного экрана и SyncWorker], "fig-03")
|
||||||
|
|
||||||
|
== Проработка прототипа и особенности дизайна
|
||||||
|
|
||||||
|
Интерфейс реализован на Jetpack Compose @compose-docs. Экраны локальных и удалённых vault, диалоги шифрования и OAuth показаны на рис. @fig-05–@fig-10 (подробно — приложение В и руководство пользователя в приложении Б).
|
||||||
|
|
||||||
|
#pz-fig("fig_05_local_vaults.jpg", [Список локальных vault], "fig-05")
|
||||||
|
#pz-fig("fig_06_encrypt_dialog.jpg", [Диалог включения шифрования], "fig-06")
|
||||||
|
#pz-fig("fig_07_open_close_dialog.jpg", [Диалог открытия и закрытия vault], "fig-07")
|
||||||
|
#pz-fig("fig_08_rename_delete_dialog.jpg", [Диалог переименования и удаления], "fig-08")
|
||||||
|
#pz-fig("fig_09_remote_vaults.jpg", [Экран удалённых vault], "fig-09")
|
||||||
|
#pz-fig("fig_10_yandex_oauth.jpg", [Добавление удалённого vault, OAuth Яндекс], "fig-10")
|
||||||
|
|
||||||
|
== Требования к эргономике и доступности
|
||||||
|
|
||||||
|
Интерфейс должен отображать состояние vault без перехода в технические экраны: признаки «зашифровано», «открыто», «выполняется операция» (`isBusy`). Диалоги деструктивных действий (удаление, отключение шифрования) требуют явного подтверждения. Тексты сообщений об ошибках OAuth и неверном ключе формулируются нейтрально, без раскрытия внутренних путей и имён файлов.
|
||||||
|
|
||||||
|
== Вывод
|
||||||
|
|
||||||
|
Спроектированы пользовательские потоки и экранная структура, согласованные с архитектурой и требованиями безопасности. Иллюстрации интерфейса приведены в приложении В.
|
||||||
21
Report/includes/ch04-expand.typ
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
// Additional implementation detail (included from ch04)
|
||||||
|
|
||||||
|
=== Модуль :vault-contracts
|
||||||
|
|
||||||
|
Определяет интерфейсы регистрации типов хранилищ (`VaultRegistrar`, `VaultRegistration`) и фасад `Vaults` для получения реализации по идентификатору. Позволяет подключать новый провайдер без изменения `:app`.
|
||||||
|
|
||||||
|
=== Модуль :domain-vault
|
||||||
|
|
||||||
|
Содержит реализацию доступа к Yandex Disk API, маппинг сетевых исключений в доменные коды (`VaultThrowableMappingTest`), буфер журнала синхронизации. Unit-тесты репозитория используют подмену HTTP-клиента.
|
||||||
|
|
||||||
|
=== Модуль :task-runtime
|
||||||
|
|
||||||
|
`TaskOrchestrator` управляет очередью долгих операций: единая точка для прогресса, отмены и логов, что используется UI экрана задач (гл. 5, рис. 12).
|
||||||
|
|
||||||
|
=== Модуль :infrastructure-android
|
||||||
|
|
||||||
|
Реализует Room (`AppDb` v5), DAO, репозитории, OAuth-хранилище токенов, файловые адаптеры Android. Модуль — единственная точка зависимости от Android SDK в слое данных.
|
||||||
|
|
||||||
|
=== Сборка и зависимости
|
||||||
|
|
||||||
|
Корневой `settings.gradle.kts` фиксирует восемь включаемых модулей. Версии библиотек централизованы в `gradle/libs.versions.toml`. Задача `test` каждого модуля входит в обязательный прогон перед релизом прототипа (см. гл. 5, рис. 30).
|
||||||
34
Report/includes/ch04-modules.typ
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
|
||||||
|
== Детализация реализации по модулям Gradle
|
||||||
|
|
||||||
|
=== Модуль :domain
|
||||||
|
|
||||||
|
Содержит чистую бизнес-логику: `Encryptor`, типы ключей, интерфейсы use case для хранилищ. Не зависит от Android SDK, что позволяет выполнять 12 unit-тестов на JVM без эмулятора. Класс `Encryptor` реализует симметричное шифрование для строк, массивов байт и потоков; метод `generateEncryptionInfo` формирует соль и параметры для проверки пароля без хранения пароля в открытом виде.
|
||||||
|
|
||||||
|
=== Модуль :usecases
|
||||||
|
|
||||||
|
Координирует сценарии приложения: `ManageStoragesEncryptionUseCase`, движок `StorageSyncEngine`, операции с 2FA и текстовыми секретами. Здесь сосредоточена наибольшая доля автоматических тестов (25), так как поведение синхронизации задаётся правилами журнала и блокировок, удобными для изоляции на моках файловой системы.
|
||||||
|
|
||||||
|
=== Модуль :domain-vault
|
||||||
|
|
||||||
|
Инкапсулирует сетевой доступ к Yandex Disk и преобразование исключений Retrofit/OkHttp в доменные коды. Тесты репозитория не обращаются к реальной сети — подставляется фейковый HTTP-слой, что обеспечивает детерминированность CI.
|
||||||
|
|
||||||
|
=== Модуль :ui
|
||||||
|
|
||||||
|
Предоставляет Compose-экраны, ViewModel, навигацию (`WallencDeepLinks`), строковые ресурсы для прогресса задач. Unit-тесты проверяют парсеры и маппинг без поднятия Activity; инструментальные тесты (`androidTest`) валидируют композицию экранов 2FA и секретов.
|
||||||
|
|
||||||
|
=== Модуль :infrastructure-android
|
||||||
|
|
||||||
|
Связывает Room, DataStore, OAuth Activity Result API, реализации репозиториев. Версия схемы БД — 5; миграции отключены (`exportSchema = false`) на этапе прототипа. Инструментальный `YandexAccountRepositoryTest` подтверждает CRUD учётной записи в in-memory БД.
|
||||||
|
|
||||||
|
=== Модуль :task-runtime
|
||||||
|
|
||||||
|
Очередь задач с состояниями `pending`, `running`, `completed`, `failed`, `cancelled`. Используется при длительном шифровании каталогов и будущей синхронизации; UI подписывается на Flow прогресса.
|
||||||
|
|
||||||
|
=== Модуль :vault-contracts и :app
|
||||||
|
|
||||||
|
`:vault-contracts` задаёт точку расширения для новых провайдеров. `:app` — Hilt-модули, `Application`, навигационный граф, сборка APK. Точка входа не содержит бизнес-правил; они делегируются use case.
|
||||||
|
|
||||||
|
=== Журнал разработки и контроль версий
|
||||||
|
|
||||||
|
Исходный код в приватном репозитории Gitea ЮФУ; ветвление по этапам практики. Перед защитой ВКР выполняется полная сборка `./gradlew assembleDebug test` и фиксация отчётов тестирования в `Report/images/` (рис. 27–32).
|
||||||
97
Report/includes/ch04.typ
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
#import "common.typ": pz-fig
|
||||||
|
|
||||||
|
= Программная реализация
|
||||||
|
|
||||||
|
== Разработка программных модулей
|
||||||
|
|
||||||
|
Архитектура реализации следует принятой на этапе проектирования схеме MVVM + Clean Architecture. Модули Gradle разделены по ответственности: контракты vault, доменная логика, сценарии use case, Android-инфраструктура (Room, OAuth, файловые адаптеры), UI и точка входа `:app`. Такое разбиение позволило параллельно развивать локальный и удалённый контуры и изолировать криптографию от представления данных.
|
||||||
|
|
||||||
|
=== Модуль криптографической защиты данных
|
||||||
|
|
||||||
|
Класс `Encryptor` формирует `StorageEncryptionInfo`, проверяет ключ и выполняет шифрование/дешифрование на клиенте. Unit-тесты подтверждают корректность для верного и неверного ключа (гл. 5).
|
||||||
|
|
||||||
|
=== Модуль управления vault и шифрованием
|
||||||
|
|
||||||
|
Use case `ManageStoragesEncryptionUseCase` инкапсулирует проверку `canEncrypt`, включение шифрования и открытие хранилища. ViewModel предотвращает повторный запуск шифрования для занятого vault.
|
||||||
|
|
||||||
|
Фрагмент логики включения шифрования:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
fun enableEncryption(storage: IStorageInfo, password: String, encryptPath: Boolean) {
|
||||||
|
val key = EncryptKey(password)
|
||||||
|
viewModelScope.launch {
|
||||||
|
when (manageStoragesEncryptionUseCase.canEncrypt(storage)) {
|
||||||
|
ManageStoragesEncryptionUseCase.CanEncryptResult.Allowed -> {
|
||||||
|
manageStoragesEncryptionUseCase.enableEncryption(storage, key, encryptPath)
|
||||||
|
manageStoragesEncryptionUseCase.openStorage(storage, key, true)
|
||||||
|
}
|
||||||
|
ManageStoragesEncryptionUseCase.CanEncryptResult.AlreadyEncrypted -> { /* сообщение */ }
|
||||||
|
else -> { /* неподдерживаемая операция */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Блок-схема сценария — рис. @fig-21.
|
||||||
|
|
||||||
|
#pz-fig("fig_21_encrypt_flow.png", [Блок-схема: enableEncryption → checkKey → openStorage], "fig-21")
|
||||||
|
|
||||||
|
=== Модуль адаптеров хранилищ
|
||||||
|
|
||||||
|
Адаптеры реализуют единый контракт доступа к локальным и удалённым хранилищам; регистрация vault выполняется через модуль `:vault-contracts`.
|
||||||
|
|
||||||
|
=== Проект модуля синхронизации
|
||||||
|
|
||||||
|
В Room добавлена сущность `DbStorageSyncGroup`; спроектирован `SyncWorker` и очередь UUID для фонового сравнения историй коммитов (рис. @fig-01–@fig-03). Реализация синхронизации находится в стадии развития.
|
||||||
|
|
||||||
|
Модель синхронизации опирается на аналогию с распределённой системой контроля версий: для каждого `Storage` ведётся история коммитов; сервис по таймеру или через `WorkManager` сравнивает локальную и удалённую истории и приводит зашифрованное содержимое к согласованному состоянию. Ключи шифрования в этот обмен не включаются — провайдер видит только зашифрованные объекты. Модуль `:task-runtime` зарезервирован для фоновых задач длительного шифрования и синхронизации без блокировки UI.
|
||||||
|
|
||||||
|
== Разработка мобильного приложения на Kotlin (Android)
|
||||||
|
|
||||||
|
=== Слой domain
|
||||||
|
|
||||||
|
Модуль `:domain` содержит интерфейсы хранилищ и use case. Модуль `:usecases` связывает сценарии приложения.
|
||||||
|
|
||||||
|
=== Слой data
|
||||||
|
|
||||||
|
Модуль `:infrastructure-android` реализует Room `AppDb` (версия 5) с сущностями `DbStorageKeyMap`, `DbStorageMetaInfo`, `DbYandexAccount`, `DbStorageSyncGroup`:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
@Database(
|
||||||
|
entities = [
|
||||||
|
DbStorageKeyMap::class,
|
||||||
|
DbStorageMetaInfo::class,
|
||||||
|
DbYandexAccount::class,
|
||||||
|
DbStorageSyncGroup::class,
|
||||||
|
],
|
||||||
|
version = 5,
|
||||||
|
exportSchema = false,
|
||||||
|
)
|
||||||
|
abstract class AppDb : IAppDb, RoomDatabase()
|
||||||
|
```
|
||||||
|
|
||||||
|
=== Слой presentation
|
||||||
|
|
||||||
|
Модуль `:ui` и `:app` содержат Compose-экраны, ViewModel и навигацию. OAuth Яндекс запускается из UI удалённых vault:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
viewModel.yandexSignIn.launch { outcome ->
|
||||||
|
when (outcome) {
|
||||||
|
is RemoteYandexAuthResult.Success ->
|
||||||
|
viewModel.onYandexAuthSuccess(outcome.accessToken)
|
||||||
|
is RemoteYandexAuthResult.Failure -> { /* ошибка */ }
|
||||||
|
RemoteYandexAuthResult.Cancelled -> { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
== Взаимодействие подсистем и итоговая архитектура
|
||||||
|
|
||||||
|
Зависимости модулей Gradle показаны на рисунке @fig-23. Полный исходный код всех модулей сборки приведён в *приложении А* (307 файлов, сгенерировано скриптом `gen_listings.py`).
|
||||||
|
|
||||||
|
#pz-fig("fig_23_module_deps.png", [Зависимости модулей Gradle], "fig-23")
|
||||||
|
|
||||||
|
В основном тексте приведены показательные фрагменты; листинги по модулям `:domain`, `:infrastructure-android`, `:app` — в приложении А, разделы «Модуль :domain» и др.
|
||||||
|
|
||||||
|
#include "ch04-expand.typ"
|
||||||
|
#include "ch04-modules.typ"
|
||||||
25
Report/includes/ch05-encryptor.typ
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#import "common.typ": pz-table
|
||||||
|
|
||||||
|
=== Детальное описание тестов EncryptorTest
|
||||||
|
|
||||||
|
Класс `EncryptorTest` — эталонный набор для приёмки криптографического ядра. Каждый метод изолирован и не зависит от порядка выполнения.
|
||||||
|
|
||||||
|
#pz-table(
|
||||||
|
[Методы EncryptorTest],
|
||||||
|
3,
|
||||||
|
table.header([Метод], [Проверяемое поведение], [OK]),
|
||||||
|
[test correct key for StorageEncryptionInfo], [`checkKey` → true], [+],
|
||||||
|
[test incorrect key for StorageEncryptionInfo], [`checkKey` → false], [+],
|
||||||
|
[test string encryption with the same key], [Симметрия encrypt/decrypt], [+],
|
||||||
|
[test string encryption with the wrong key], [Исключение при decrypt], [+],
|
||||||
|
[test bytes encryption with the same key], [Восстановление 512 байт], [+],
|
||||||
|
[test bytes encryption with the wrong key], [Исключение], [+],
|
||||||
|
[test stream encryption with the same key], [Поток 1500 байт], [+],
|
||||||
|
[test stream encryption with the wrong key], [Исключение decryptStream], [+],
|
||||||
|
) <tbl-encryptor-detail>
|
||||||
|
|
||||||
|
Методика: для потоков используется `ByteArrayOutputStream` с запасом ёмкости `dataLen*3`, чтобы учесть расширение ciphertext.
|
||||||
|
|
||||||
|
=== Детальное описание тестов StorageSyncEngineTest
|
||||||
|
|
||||||
|
Движок синхронизации тестируется на in-memory двойниках хранилищ. Полный реестр методов — в таблице модуля `:usecases` выше.
|
||||||
24
Report/includes/ch05-sync-tests.typ
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#import "common.typ": pz-table
|
||||||
|
|
||||||
|
=== Каталог тестов StorageSyncEngineTest
|
||||||
|
|
||||||
|
#pz-table(
|
||||||
|
[StorageSyncEngineTest — 12 методов],
|
||||||
|
3,
|
||||||
|
table.header([Метод], [Поведение], [+]),
|
||||||
|
[syncAllGroupsReportsNoGroupsWhenEmpty], [Нет групп → отчёт], [+],
|
||||||
|
[syncGroupCopiesFileFromSourceToTarget], [Копия на target], [+],
|
||||||
|
[syncGroupSkippedWhenFewerThanTwoStorages], [Skip при менее 2 storage], [+],
|
||||||
|
[syncGroupDeleteRemovesFileOnTarget], [Удаление на target], [+],
|
||||||
|
[syncSkipsWhenTargetRevisionAlreadyWinner], [Ревизия-победитель], [+],
|
||||||
|
[openReadDoesNotChangeJournal], [Чтение без журнала], [+],
|
||||||
|
[deleteWithRecordSyncJournalFalseDoesNotBumpSequence], [Delete без журнала], [+],
|
||||||
|
[syncGroupTrashSoftDeletesOnTarget], [Trash на target], [+],
|
||||||
|
[syncGroupStopsWhenLockCannotBeAcquired], [Стоп при lock], [+],
|
||||||
|
[syncGroupReleasesLocksAfterSuccessfulSync], [Unlock после успеха], [+],
|
||||||
|
[syncGroupReleasesLocksWhenJournalReadFails], [Unlock при ошибке], [+],
|
||||||
|
[syncGroupCooperativeCancellationReleasesLocks], [Unlock при отмене], [+],
|
||||||
|
[syncGroupReleasesLocksWhenJournalEmpty], [Пустой журнал], [+],
|
||||||
|
) <tbl-sync-engine>
|
||||||
|
|
||||||
|
Тесты `StorageSyncJournalMergeTest` и `StorageSyncEncryptionCompatTest` дополняют движок проверкой слияния журнала и совместимости шифрования в группе.
|
||||||
210
Report/includes/ch05-tests-generated.typ
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
// AUTO-GENERATED by gen_test_tables.py — include from ch05.typ
|
||||||
|
#import "common.typ": pz-test-table
|
||||||
|
|
||||||
|
#pz-test-table(
|
||||||
|
[Сводка модульных unit-тестов (src/test)],
|
||||||
|
4,
|
||||||
|
table.header(
|
||||||
|
[Модуль],
|
||||||
|
[Класс],
|
||||||
|
[Метод],
|
||||||
|
[Файл],
|
||||||
|
),
|
||||||
|
[domain], [EncryptorTest], [test correct key for StorageEncryptionInfo], [EncryptorTest.kt],
|
||||||
|
[domain], [EncryptorTest], [test incorrect key for StorageEncryptionInfo], [EncryptorTest.kt],
|
||||||
|
[domain], [EncryptorTest], [test string encryption with the same key], [EncryptorTest.kt],
|
||||||
|
[domain], [EncryptorTest], [test string encryption with the wrong key], [EncryptorTest.kt],
|
||||||
|
[domain], [EncryptorTest], [test bytes encryption with the same key], [EncryptorTest.kt],
|
||||||
|
[domain], [EncryptorTest], [test bytes encryption with the wrong key], [EncryptorTest.kt],
|
||||||
|
[domain], [EncryptorTest], [test stream encryption with the same key], [EncryptorTest.kt],
|
||||||
|
[domain], [EncryptorTest], [test stream encryption with the wrong key], [EncryptorTest.kt],
|
||||||
|
[domain], [WallencExceptionMappingTest], [preservesWallencException], [WallencExceptionMappingTest.kt],
|
||||||
|
[domain], [WallencExceptionMappingTest], [mapsFileNotFoundException], [WallencExceptionMappingTest.kt],
|
||||||
|
[domain], [WallencExceptionMappingTest], [mapsIOExceptionToIoFailed], [WallencExceptionMappingTest.kt],
|
||||||
|
[domain], [WallencExceptionMappingTest], [mapsGenericExceptionToUnknown], [WallencExceptionMappingTest.kt],
|
||||||
|
[domain-vault], [VaultThrowableMappingTest], [mapsYandexDiskAuthToAuthFailed], [VaultThrowableMappingTest.kt],
|
||||||
|
[domain-vault], [VaultThrowableMappingTest], [mapsHttpExceptionToNetworkHttpFailed], [VaultThrowableMappingTest.kt],
|
||||||
|
[domain-vault], [VaultThrowableMappingTest], [mapsMissingOAuthTokenIoToTokenMissing], [VaultThrowableMappingTest.kt],
|
||||||
|
[domain-vault], [VaultThrowableMappingTest], [mapsSocketTimeoutToOperationTimedOut], [VaultThrowableMappingTest.kt],
|
||||||
|
[domain-vault], [VaultThrowableMappingTest], [mapsFileNotFoundToStorageFileNotFound], [VaultThrowableMappingTest.kt],
|
||||||
|
[domain-vault], [VaultThrowableMappingTest], [mapsIllegalStateNotAFile], [VaultThrowableMappingTest.kt],
|
||||||
|
[domain-vault], [YandexDiskRepositoryTest], [diskInfoParsesResponse], [YandexDiskRepositoryTest.kt],
|
||||||
|
[domain-vault], [YandexDiskRepositoryTest], [listReturnsEmptyEmbeddedOn404], [YandexDiskRepositoryTest.kt],
|
||||||
|
[domain-vault], [YandexDiskRepositoryTest], [diskInfoThrowsAuthExceptionOn401], [YandexDiskRepositoryTest.kt],
|
||||||
|
[domain-vault], [StorageSyncJournalBufferTest], [flushRestoresPendingOnWriteFailure], [StorageSyncJournalBufferTest.kt],
|
||||||
|
[task-runtime], [TaskOrchestratorTest], [enqueueCompletesTask], [TaskOrchestratorTest.kt],
|
||||||
|
[task-runtime], [TaskOrchestratorTest], [cancelAllMarksRunningTaskCancelled], [TaskOrchestratorTest.kt],
|
||||||
|
[task-runtime], [TaskOrchestratorTest], [cancelMarksTaskCancelled], [TaskOrchestratorTest.kt],
|
||||||
|
[task-runtime], [TaskOrchestratorTest], [failRecordsFailedState], [TaskOrchestratorTest.kt],
|
||||||
|
[task-runtime], [TaskOrchestratorTest], [progressUpdatesRunningState], [TaskOrchestratorTest.kt],
|
||||||
|
[task-runtime], [TaskOrchestratorTest], [logAppendsLine], [TaskOrchestratorTest.kt],
|
||||||
|
[ui], [WallencDeepLinksTest], [matchesWallencViewIntent], [WallencDeepLinksTest.kt],
|
||||||
|
[ui], [WallencDeepLinksTest], [rejectsUnrelatedIntent], [WallencDeepLinksTest.kt],
|
||||||
|
[ui], [WallencDeepLinksTest], [matchesTasksAndSettingsHosts], [WallencDeepLinksTest.kt],
|
||||||
|
[ui], [TaskProgressLabelsTest], [syncNoGroups_mapsToStringRes], [TaskProgressLabelsTest.kt],
|
||||||
|
[ui], [TaskProgressLabelsTest], [vaultTask_mapsToStringRes], [TaskProgressLabelsTest.kt],
|
||||||
|
[ui], [TaskProgressLabelsTest], [clearContentProgress_mapsToStringRes], [TaskProgressLabelsTest.kt],
|
||||||
|
[ui], [WallencUserNotificationMappingTest], [mapsFeatureStorageNotFound], [WallencUserNotificationMappingTest.kt],
|
||||||
|
[ui], [WallencUserNotificationMappingTest], [mapsStorageIncorrectKey], [WallencUserNotificationMappingTest.kt],
|
||||||
|
[ui], [WallencUserNotificationMappingTest], [mapsUnknown], [WallencUserNotificationMappingTest.kt],
|
||||||
|
[ui], [StorageNavigationRoutesSmokeTest], [storageHomeRouteCarriesVaultAndStorageIds], [StorageNavigationRoutesSmokeTest.kt],
|
||||||
|
[ui], [StorageNavigationRoutesSmokeTest], [textSecretsRoutesCarryRequiredArguments], [StorageNavigationRoutesSmokeTest.kt],
|
||||||
|
[ui], [OtpAuthUriParserTest], [parsesStandardTotpUri], [OtpAuthUriParserTest.kt],
|
||||||
|
[ui], [OtpAuthUriParserTest], [rejectsNonOtpauthScheme], [OtpAuthUriParserTest.kt],
|
||||||
|
[ui], [OtpAuthUriParserTest], [rejectsMissingSecret], [OtpAuthUriParserTest.kt],
|
||||||
|
[ui], [TaskPipelineViewModelTest], [startTestTaskEnqueuesWork], [TaskPipelineViewModelTest.kt],
|
||||||
|
[usecases], [StorageDomainUseCasesTest], [twoFaCrudWorksAndPersists], [StorageDomainUseCasesTest.kt],
|
||||||
|
[usecases], [StorageDomainUseCasesTest], [twoFaInvalidJsonFallsBackToEmptyList], [StorageDomainUseCasesTest.kt],
|
||||||
|
[usecases], [StorageDomainUseCasesTest], [textSecretsCrudWorksWithOptionalLabels], [StorageDomainUseCasesTest.kt],
|
||||||
|
[usecases], [StorageDomainUseCasesTest], [textSecretsInvalidJsonFallsBackToEmptyList], [StorageDomainUseCasesTest.kt],
|
||||||
|
[usecases], [StorageSyncEncryptionCompatTest], [storageWithoutEncInfoIsCompatible], [StorageSyncEncryptionCompatTest.kt],
|
||||||
|
[usecases], [StorageSyncEncryptionCompatTest], [storageWithEncInfoIsIncompatible], [StorageSyncEncryptionCompatTest.kt],
|
||||||
|
[usecases], [StorageSyncEngineTest], [syncAllGroupsReportsNoGroupsWhenEmpty], [StorageSyncEngineTest.kt],
|
||||||
|
[usecases], [StorageSyncEngineTest], [syncGroupCopiesFileFromSourceToTarget], [StorageSyncEngineTest.kt],
|
||||||
|
[usecases], [StorageSyncEngineTest], [syncGroupSkippedWhenFewerThanTwoStorages], [StorageSyncEngineTest.kt],
|
||||||
|
[usecases], [StorageSyncEngineTest], [syncGroupDeleteRemovesFileOnTarget], [StorageSyncEngineTest.kt],
|
||||||
|
[usecases], [StorageSyncEngineTest], [syncSkipsWhenTargetRevisionAlreadyWinner], [StorageSyncEngineTest.kt],
|
||||||
|
[usecases], [StorageSyncEngineTest], [openReadDoesNotChangeJournal], [StorageSyncEngineTest.kt],
|
||||||
|
[usecases], [StorageSyncEngineTest], [deleteWithRecordSyncJournalFalseDoesNotBumpSequence], [StorageSyncEngineTest.kt],
|
||||||
|
[usecases], [StorageSyncEngineTest], [syncGroupTrashSoftDeletesOnTarget], [StorageSyncEngineTest.kt],
|
||||||
|
[usecases], [StorageSyncEngineTest], [syncGroupStopsWhenLockCannotBeAcquired], [StorageSyncEngineTest.kt],
|
||||||
|
[usecases], [StorageSyncEngineTest], [syncGroupReleasesLocksAfterSuccessfulSync], [StorageSyncEngineTest.kt],
|
||||||
|
[usecases], [StorageSyncEngineTest], [syncGroupReleasesLocksWhenJournalReadFails], [StorageSyncEngineTest.kt],
|
||||||
|
[usecases], [StorageSyncEngineTest], [syncGroupCooperativeCancellationReleasesLocks], [StorageSyncEngineTest.kt],
|
||||||
|
[usecases], [StorageSyncEngineTest], [syncGroupReleasesLocksWhenJournalEmpty], [StorageSyncEngineTest.kt],
|
||||||
|
[usecases], [StorageSyncJournalMergeTest], [mergeKeepsSingleEntryPerPath], [StorageSyncJournalMergeTest.kt],
|
||||||
|
[usecases], [StorageSyncJournalMergeTest], [isSyncableUserPathExcludesEncDirAndJournal], [StorageSyncJournalMergeTest.kt],
|
||||||
|
[usecases], [TwoFaTotpTest], [buildTwoFaCodeStateMatchesJavaOtpForKnownSecret], [TwoFaTotpTest.kt],
|
||||||
|
[usecases], [TwoFaTotpTest], [totpPeriodProgressIsContinuousWithinPeriod], [TwoFaTotpTest.kt],
|
||||||
|
[usecases], [TwoFaTotpTest], [totpSecondsUntilRefreshCountsDownWithinPeriod], [TwoFaTotpTest.kt],
|
||||||
|
[usecases], [TwoFaTotpTest], [buildTwoFaCodeStateReturnsNullForInvalidSecret], [TwoFaTotpTest.kt],
|
||||||
|
) <tbl-unit-all>
|
||||||
|
|
||||||
|
=== Реестр тестов модуля :domain
|
||||||
|
|
||||||
|
#pz-test-table(
|
||||||
|
[Unit-тесты модуля :domain],
|
||||||
|
3,
|
||||||
|
table.header(
|
||||||
|
[Класс],
|
||||||
|
[Метод],
|
||||||
|
[Файл],
|
||||||
|
),
|
||||||
|
[EncryptorTest], [test correct key for StorageEncryptionInfo], [EncryptorTest.kt],
|
||||||
|
[EncryptorTest], [test incorrect key for StorageEncryptionInfo], [EncryptorTest.kt],
|
||||||
|
[EncryptorTest], [test string encryption with the same key], [EncryptorTest.kt],
|
||||||
|
[EncryptorTest], [test string encryption with the wrong key], [EncryptorTest.kt],
|
||||||
|
[EncryptorTest], [test bytes encryption with the same key], [EncryptorTest.kt],
|
||||||
|
[EncryptorTest], [test bytes encryption with the wrong key], [EncryptorTest.kt],
|
||||||
|
[EncryptorTest], [test stream encryption with the same key], [EncryptorTest.kt],
|
||||||
|
[EncryptorTest], [test stream encryption with the wrong key], [EncryptorTest.kt],
|
||||||
|
[WallencExceptionMappingTest], [preservesWallencException], [WallencExceptionMappingTest.kt],
|
||||||
|
[WallencExceptionMappingTest], [mapsFileNotFoundException], [WallencExceptionMappingTest.kt],
|
||||||
|
[WallencExceptionMappingTest], [mapsIOExceptionToIoFailed], [WallencExceptionMappingTest.kt],
|
||||||
|
[WallencExceptionMappingTest], [mapsGenericExceptionToUnknown], [WallencExceptionMappingTest.kt],
|
||||||
|
) <tbl-unit-domain>
|
||||||
|
|
||||||
|
=== Реестр тестов модуля :domain-vault
|
||||||
|
|
||||||
|
#pz-test-table(
|
||||||
|
[Unit-тесты модуля :domain-vault],
|
||||||
|
3,
|
||||||
|
table.header(
|
||||||
|
[Класс],
|
||||||
|
[Метод],
|
||||||
|
[Файл],
|
||||||
|
),
|
||||||
|
[VaultThrowableMappingTest], [mapsYandexDiskAuthToAuthFailed], [VaultThrowableMappingTest.kt],
|
||||||
|
[VaultThrowableMappingTest], [mapsHttpExceptionToNetworkHttpFailed], [VaultThrowableMappingTest.kt],
|
||||||
|
[VaultThrowableMappingTest], [mapsMissingOAuthTokenIoToTokenMissing], [VaultThrowableMappingTest.kt],
|
||||||
|
[VaultThrowableMappingTest], [mapsSocketTimeoutToOperationTimedOut], [VaultThrowableMappingTest.kt],
|
||||||
|
[VaultThrowableMappingTest], [mapsFileNotFoundToStorageFileNotFound], [VaultThrowableMappingTest.kt],
|
||||||
|
[VaultThrowableMappingTest], [mapsIllegalStateNotAFile], [VaultThrowableMappingTest.kt],
|
||||||
|
[YandexDiskRepositoryTest], [diskInfoParsesResponse], [YandexDiskRepositoryTest.kt],
|
||||||
|
[YandexDiskRepositoryTest], [listReturnsEmptyEmbeddedOn404], [YandexDiskRepositoryTest.kt],
|
||||||
|
[YandexDiskRepositoryTest], [diskInfoThrowsAuthExceptionOn401], [YandexDiskRepositoryTest.kt],
|
||||||
|
[StorageSyncJournalBufferTest], [flushRestoresPendingOnWriteFailure], [StorageSyncJournalBufferTest.kt],
|
||||||
|
) <tbl-unit-domain_vault>
|
||||||
|
|
||||||
|
=== Реестр тестов модуля :task-runtime
|
||||||
|
|
||||||
|
#pz-test-table(
|
||||||
|
[Unit-тесты модуля :task-runtime],
|
||||||
|
3,
|
||||||
|
table.header(
|
||||||
|
[Класс],
|
||||||
|
[Метод],
|
||||||
|
[Файл],
|
||||||
|
),
|
||||||
|
[TaskOrchestratorTest], [enqueueCompletesTask], [TaskOrchestratorTest.kt],
|
||||||
|
[TaskOrchestratorTest], [cancelAllMarksRunningTaskCancelled], [TaskOrchestratorTest.kt],
|
||||||
|
[TaskOrchestratorTest], [cancelMarksTaskCancelled], [TaskOrchestratorTest.kt],
|
||||||
|
[TaskOrchestratorTest], [failRecordsFailedState], [TaskOrchestratorTest.kt],
|
||||||
|
[TaskOrchestratorTest], [progressUpdatesRunningState], [TaskOrchestratorTest.kt],
|
||||||
|
[TaskOrchestratorTest], [logAppendsLine], [TaskOrchestratorTest.kt],
|
||||||
|
) <tbl-unit-task_runtime>
|
||||||
|
|
||||||
|
=== Реестр тестов модуля :ui
|
||||||
|
|
||||||
|
#pz-test-table(
|
||||||
|
[Unit-тесты модуля :ui],
|
||||||
|
3,
|
||||||
|
table.header(
|
||||||
|
[Класс],
|
||||||
|
[Метод],
|
||||||
|
[Файл],
|
||||||
|
),
|
||||||
|
[WallencDeepLinksTest], [matchesWallencViewIntent], [WallencDeepLinksTest.kt],
|
||||||
|
[WallencDeepLinksTest], [rejectsUnrelatedIntent], [WallencDeepLinksTest.kt],
|
||||||
|
[WallencDeepLinksTest], [matchesTasksAndSettingsHosts], [WallencDeepLinksTest.kt],
|
||||||
|
[TaskProgressLabelsTest], [syncNoGroups_mapsToStringRes], [TaskProgressLabelsTest.kt],
|
||||||
|
[TaskProgressLabelsTest], [vaultTask_mapsToStringRes], [TaskProgressLabelsTest.kt],
|
||||||
|
[TaskProgressLabelsTest], [clearContentProgress_mapsToStringRes], [TaskProgressLabelsTest.kt],
|
||||||
|
[WallencUserNotificationMappingTest], [mapsFeatureStorageNotFound], [WallencUserNotificationMappingTest.kt],
|
||||||
|
[WallencUserNotificationMappingTest], [mapsStorageIncorrectKey], [WallencUserNotificationMappingTest.kt],
|
||||||
|
[WallencUserNotificationMappingTest], [mapsUnknown], [WallencUserNotificationMappingTest.kt],
|
||||||
|
[StorageNavigationRoutesSmokeTest], [storageHomeRouteCarriesVaultAndStorageIds], [StorageNavigationRoutesSmokeTest.kt],
|
||||||
|
[StorageNavigationRoutesSmokeTest], [textSecretsRoutesCarryRequiredArguments], [StorageNavigationRoutesSmokeTest.kt],
|
||||||
|
[OtpAuthUriParserTest], [parsesStandardTotpUri], [OtpAuthUriParserTest.kt],
|
||||||
|
[OtpAuthUriParserTest], [rejectsNonOtpauthScheme], [OtpAuthUriParserTest.kt],
|
||||||
|
[OtpAuthUriParserTest], [rejectsMissingSecret], [OtpAuthUriParserTest.kt],
|
||||||
|
[TaskPipelineViewModelTest], [startTestTaskEnqueuesWork], [TaskPipelineViewModelTest.kt],
|
||||||
|
) <tbl-unit-ui>
|
||||||
|
|
||||||
|
=== Реестр тестов модуля :usecases
|
||||||
|
|
||||||
|
#pz-test-table(
|
||||||
|
[Unit-тесты модуля :usecases],
|
||||||
|
3,
|
||||||
|
table.header(
|
||||||
|
[Класс],
|
||||||
|
[Метод],
|
||||||
|
[Файл],
|
||||||
|
),
|
||||||
|
[StorageDomainUseCasesTest], [twoFaCrudWorksAndPersists], [StorageDomainUseCasesTest.kt],
|
||||||
|
[StorageDomainUseCasesTest], [twoFaInvalidJsonFallsBackToEmptyList], [StorageDomainUseCasesTest.kt],
|
||||||
|
[StorageDomainUseCasesTest], [textSecretsCrudWorksWithOptionalLabels], [StorageDomainUseCasesTest.kt],
|
||||||
|
[StorageDomainUseCasesTest], [textSecretsInvalidJsonFallsBackToEmptyList], [StorageDomainUseCasesTest.kt],
|
||||||
|
[StorageSyncEncryptionCompatTest], [storageWithoutEncInfoIsCompatible], [StorageSyncEncryptionCompatTest.kt],
|
||||||
|
[StorageSyncEncryptionCompatTest], [storageWithEncInfoIsIncompatible], [StorageSyncEncryptionCompatTest.kt],
|
||||||
|
[StorageSyncEngineTest], [syncAllGroupsReportsNoGroupsWhenEmpty], [StorageSyncEngineTest.kt],
|
||||||
|
[StorageSyncEngineTest], [syncGroupCopiesFileFromSourceToTarget], [StorageSyncEngineTest.kt],
|
||||||
|
[StorageSyncEngineTest], [syncGroupSkippedWhenFewerThanTwoStorages], [StorageSyncEngineTest.kt],
|
||||||
|
[StorageSyncEngineTest], [syncGroupDeleteRemovesFileOnTarget], [StorageSyncEngineTest.kt],
|
||||||
|
[StorageSyncEngineTest], [syncSkipsWhenTargetRevisionAlreadyWinner], [StorageSyncEngineTest.kt],
|
||||||
|
[StorageSyncEngineTest], [openReadDoesNotChangeJournal], [StorageSyncEngineTest.kt],
|
||||||
|
[StorageSyncEngineTest], [deleteWithRecordSyncJournalFalseDoesNotBumpSequence], [StorageSyncEngineTest.kt],
|
||||||
|
[StorageSyncEngineTest], [syncGroupTrashSoftDeletesOnTarget], [StorageSyncEngineTest.kt],
|
||||||
|
[StorageSyncEngineTest], [syncGroupStopsWhenLockCannotBeAcquired], [StorageSyncEngineTest.kt],
|
||||||
|
[StorageSyncEngineTest], [syncGroupReleasesLocksAfterSuccessfulSync], [StorageSyncEngineTest.kt],
|
||||||
|
[StorageSyncEngineTest], [syncGroupReleasesLocksWhenJournalReadFails], [StorageSyncEngineTest.kt],
|
||||||
|
[StorageSyncEngineTest], [syncGroupCooperativeCancellationReleasesLocks], [StorageSyncEngineTest.kt],
|
||||||
|
[StorageSyncEngineTest], [syncGroupReleasesLocksWhenJournalEmpty], [StorageSyncEngineTest.kt],
|
||||||
|
[StorageSyncJournalMergeTest], [mergeKeepsSingleEntryPerPath], [StorageSyncJournalMergeTest.kt],
|
||||||
|
[StorageSyncJournalMergeTest], [isSyncableUserPathExcludesEncDirAndJournal], [StorageSyncJournalMergeTest.kt],
|
||||||
|
[TwoFaTotpTest], [buildTwoFaCodeStateMatchesJavaOtpForKnownSecret], [TwoFaTotpTest.kt],
|
||||||
|
[TwoFaTotpTest], [totpPeriodProgressIsContinuousWithinPeriod], [TwoFaTotpTest.kt],
|
||||||
|
[TwoFaTotpTest], [totpSecondsUntilRefreshCountsDownWithinPeriod], [TwoFaTotpTest.kt],
|
||||||
|
[TwoFaTotpTest], [buildTwoFaCodeStateReturnsNullForInvalidSecret], [TwoFaTotpTest.kt],
|
||||||
|
) <tbl-unit-usecases>
|
||||||
|
|
||||||
201
Report/includes/ch05.typ
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
#import "common.typ": pz-fig, pz-table
|
||||||
|
|
||||||
|
= Тестирование программного обеспечения
|
||||||
|
|
||||||
|
Тестирование Wallenc организовано по уровням: модульные автоматические тесты (JUnit, каталог `src/test` каждого Gradle-модуля), инструментальные тесты (`src/androidTest`, эмулятор/устройство), ручные функциональные и UI-прогоны. Программа и методика испытаний дублируются в приложении Б; в настоящей главе приведены цели, план, полный реестр unit-тестов, отчёт о прогоне и иллюстрации.
|
||||||
|
|
||||||
|
== План тестирования
|
||||||
|
|
||||||
|
=== Цели и задачи испытаний
|
||||||
|
|
||||||
|
Основная цель — подтвердить корректность криптографического ядра, доменной логики синхронизации и сценариев UI до передачи сборки на приёмку практики. Задачи плана:
|
||||||
|
|
||||||
|
+ верифицировать `Encryptor` и проверку ключа для всех носителей (строка, байты, поток);
|
||||||
|
+ проверить маппинг исключений в пользовательские коды ошибок (`domain`, `domain-vault`, `ui`);
|
||||||
|
+ убедиться в согласованности движка синхронизации (`StorageSyncEngine`, журнал, блокировки);
|
||||||
|
+ проверить оркестратор фоновых задач (`task-runtime`);
|
||||||
|
+ выполнить smoke-тесты навигации, deep link и 2FA/TOTP;
|
||||||
|
+ зафиксировать результаты ручных сценариев vault, OAuth и экрана задач.
|
||||||
|
|
||||||
|
=== Объект и уровни тестирования
|
||||||
|
|
||||||
|
#pz-table(
|
||||||
|
[Объекты и уровни тестирования Wallenc],
|
||||||
|
4,
|
||||||
|
table.header([Уровень], [Объект], [Инструмент], [Критерий успеха]),
|
||||||
|
[Unit], [Классы domain, usecases, ui, task-runtime, domain-vault], [JUnit 4, JVM], [Все тесты модуля зелёные],
|
||||||
|
[Инструм.], [Room, Compose UI, OAuth], [AndroidJUnit, эмулятор], [Нет падений на целевом API],
|
||||||
|
[Ручной], [Сборка app, пользовательские цепочки], [Чек-лист], [Сценарии T-1…T-12 пройдены],
|
||||||
|
[Регресс.], [Синхронизация, шифрование], [Повтор unit + выборочный ручной], [Отсутствие блокирующих дефектов],
|
||||||
|
) <tbl-test-levels>
|
||||||
|
|
||||||
|
=== Матрица тестовых сценариев
|
||||||
|
|
||||||
|
Матрица связывает требования (гл. 1) с видами испытаний. Столбец «Автоматизация» указывает, покрыт ли сценарий unit-тестом.
|
||||||
|
|
||||||
|
#pz-table(
|
||||||
|
[Матрица тестовых сценариев],
|
||||||
|
5,
|
||||||
|
table.header([ID], [Сценарий], [Тип], [Авто], [Ожидаемый результат]),
|
||||||
|
[T-1], [Проверка ключа шифрования], [Unit], [Да], [`Encryptor.checkKey` true/false],
|
||||||
|
[T-2], [Шифрование/дешифрование строки и байтов], [Unit], [Да], [Симметрия данных, ошибка при неверном ключе],
|
||||||
|
[T-3], [Потоковое шифрование файла], [Unit], [Да], [Массив после decrypt равен исходному],
|
||||||
|
[T-4], [Синхронизация группы хранилищ], [Unit], [Да], [Копирование, удаление, trash, блокировки],
|
||||||
|
[T-5], [2FA TOTP генерация], [Unit], [Да], [Совпадение с эталоном Java OTP],
|
||||||
|
[T-6], [Маппинг ошибок сети/диска], [Unit], [Да], [Типизированные `WallencException`],
|
||||||
|
[T-7], [CRUD локального vault], [Ручной], [Нет], [Список обновлён (рис. 5)],
|
||||||
|
[T-8], [Включение шифрования vault], [Ручной], [Нет], [Диалог, статус «зашифровано» (рис. 6)],
|
||||||
|
[T-9], [Открытие/закрытие vault], [Ручной], [Нет], [Доступ к содержимому только с ключом (рис. 7)],
|
||||||
|
[T-10], [OAuth Яндекс], [Ручной / IT], [Частично], [Токен в Room (рис. 10)],
|
||||||
|
[T-11], [Экран задач и уведомления], [Ручной], [Частично], [Прогресс и завершение (рис. 12–13)],
|
||||||
|
[T-12], [Compose: секреты и 2FA экраны], [IT], [Да], [Отображение без падений (рис. 30)],
|
||||||
|
) <tbl-testplan>
|
||||||
|
|
||||||
|
=== Критерии начала и окончания
|
||||||
|
|
||||||
|
*Начало:* собраны модули `:domain`, `:usecases`, `:ui`, `:domain-vault`, `:task-runtime`, `:app`; на CI/рабочей станции выполняется `./gradlew test`; для инструментальных тестов доступен эмулятор API 26+.
|
||||||
|
|
||||||
|
*Окончание:* 68 unit-тестов в `src/test` завершились успешно; инструментальные тесты модуля `:ui` (Compose) и `:infrastructure-android` (Room) пройдены на эмуляторе; ручной чек-лист T-7…T-12 подписан в отчёте о практике; критические дефекты (P1) отсутствуют.
|
||||||
|
|
||||||
|
=== Среда и инструменты
|
||||||
|
|
||||||
|
#pz-table(
|
||||||
|
[Тестовая среда],
|
||||||
|
2,
|
||||||
|
table.header([Параметр], [Значение]),
|
||||||
|
[ОС разработки], [GNU/Linux, Android Studio Narwhal],
|
||||||
|
[JDK], [OpenJDK 17 / 21 (Gradle toolchain)],
|
||||||
|
[Сборка], [`./gradlew test`, `./gradlew connectedDebugAndroidTest`],
|
||||||
|
[Устройство], [Эмулятор Pixel 6 API 34; физическое устройство для OAuth],
|
||||||
|
[Отчёт JVM], [HTML/XML в `build/reports/tests/test` каждого модуля],
|
||||||
|
) <tbl-test-env>
|
||||||
|
|
||||||
|
== Модульные тесты (JUnit)
|
||||||
|
|
||||||
|
В проекте реализовано *68* автоматических unit-тестов в пяти модулях (`:domain` — 12, `:domain-vault` — 10, `:usecases` — 25, `:ui` — 15, `:task-runtime` — 6). Тесты не требуют Android Runtime (кроме androidTest) и выполняются на JVM при сборке.
|
||||||
|
|
||||||
|
Сводная таблица всех методов приведена ниже; по модулям — в подразделах.
|
||||||
|
|
||||||
|
#include "ch05-tests-generated.typ"
|
||||||
|
|
||||||
|
=== Модуль :domain — криптография и ошибки
|
||||||
|
|
||||||
|
Класс `EncryptorTest` покрывает восемь сценариев AES: проверка ключа, шифрование строк, байтовых массивов (512 байт) и потоков (1500 байт) с верным и неверным ключом. Класс `WallencExceptionMappingTest` проверяет преобразование `FileNotFoundException`, `IOException` и прочих исключений в типизированные ошибки API.
|
||||||
|
|
||||||
|
#pz-table(
|
||||||
|
[Покрытие EncryptorTest],
|
||||||
|
3,
|
||||||
|
table.header([Метод], [Проверяемое поведение], [Статус]),
|
||||||
|
[test correct key…], [`checkKey` возвращает true для сгенерированного `StorageEncryptionInfo`], [OK],
|
||||||
|
[test incorrect key…], [`checkKey` false при другом `EncryptKey`], [OK],
|
||||||
|
[test string encryption…], [Симметрия encrypt/decrypt строки], [OK],
|
||||||
|
[test string encryption wrong key], [Исключение при decrypt], [OK],
|
||||||
|
[test bytes encryption…], [Шифротекст ≠ plaintext, decrypt восстанавливает], [OK],
|
||||||
|
[test bytes encryption wrong key], [Исключение при неверном ключе], [OK],
|
||||||
|
[test stream encryption…], [Поток 1500 байт, `readAllBytes` совпадает], [OK],
|
||||||
|
[test stream encryption wrong key], [Исключение на decryptStream], [OK],
|
||||||
|
) <tbl-encryptor>
|
||||||
|
|
||||||
|
Прогон `./gradlew :domain:test` — на рис. @fig-27.
|
||||||
|
|
||||||
|
#include "ch05-encryptor.typ"
|
||||||
|
|
||||||
|
#pz-fig("fig_27_gradle_domain_test.png", [Отчёт Gradle: модуль :domain, задача test], "fig-27")
|
||||||
|
|
||||||
|
=== Модуль :usecases — синхронизация, 2FA, секреты
|
||||||
|
|
||||||
|
Наибольшая плотность тестов: `StorageSyncEngineTest` (12 методов) моделирует группы синхронизации, копирование и удаление файлов, soft-delete (trash), cooperative cancellation и освобождение блокировок. `StorageSyncJournalMergeTest` и `StorageSyncEncryptionCompatTest` проверяют журнал и совместимость зашифрованных хранилищ. `TwoFaTotpTest` сверяет TOTP с эталоном Java OTP. `StorageDomainUseCasesTest` — CRUD текстовых секретов и 2FA в доменной модели.
|
||||||
|
|
||||||
|
#include "ch05-sync-tests.typ"
|
||||||
|
|
||||||
|
#pz-fig("fig_28_gradle_usecases_test.png", [Отчёт Gradle: модуль :usecases], "fig-28")
|
||||||
|
|
||||||
|
=== Модуль :domain-vault — Yandex Disk и vault
|
||||||
|
|
||||||
|
`YandexDiskRepositoryTest` использует мок HTTP: разбор `diskInfo`, пустой список при 404, `AuthException` при 401. `VaultThrowableMappingTest` — шесть веток сетевых и файловых ошибок. `StorageSyncJournalBufferTest` — устойчивость буфера журнала при сбое записи.
|
||||||
|
|
||||||
|
=== Модуль :ui — навигация, уведомления, OTP
|
||||||
|
|
||||||
|
Тесты не поднимают полный Compose, а проверяют чистые функции: `WallencDeepLinksTest`, `OtpAuthUriParserTest`, `TaskProgressLabelsTest`, `WallencUserNotificationMappingTest`, `StorageNavigationRoutesSmokeTest`, `TaskPipelineViewModelTest` (постановка тестовой задачи в очередь).
|
||||||
|
|
||||||
|
#pz-fig("fig_29_gradle_ui_test.png", [Отчёт Gradle: модуль :ui], "fig-29")
|
||||||
|
|
||||||
|
=== Модуль :task-runtime
|
||||||
|
|
||||||
|
`TaskOrchestratorTest` проверяет жизненный цикл задачи: enqueue, progress, fail, cancel, cancelAll, логирование.
|
||||||
|
|
||||||
|
== Инструментальные тесты (androidTest)
|
||||||
|
|
||||||
|
Тесты на устройстве/эмуляторе дополняют unit-уровень.
|
||||||
|
|
||||||
|
#pz-table(
|
||||||
|
[Инструментальные тесты androidTest],
|
||||||
|
4,
|
||||||
|
table.header([Модуль], [Класс], [Назначение], [Методов]),
|
||||||
|
[:ui], [TwoFaTokensScreenContentTest], [Compose: экран 2FA токенов], [2],
|
||||||
|
[:ui], [TextSecretsScreenContentTest], [Compose: текстовые секреты], [2],
|
||||||
|
[:infra], [YandexAccountRepositoryTest], [Room in-memory: аккаунт Яндекс], [3],
|
||||||
|
[:app], [YandexDiskLiveIntegrationTest], [Живой API (при наличии токена)], [3],
|
||||||
|
[:app], [ExportYandexTestCredentialsTest], [Экспорт тестовых учётных данных], [1],
|
||||||
|
) <tbl-androidtest>
|
||||||
|
|
||||||
|
Запуск: `./gradlew connectedDebugAndroidTest`. Сводный результат — рис. @fig-31.
|
||||||
|
|
||||||
|
#pz-fig("fig_31_gradle_connected_test.png", [Gradle connectedDebugAndroidTest (фрагмент)], "fig-31")
|
||||||
|
|
||||||
|
== Ручное и UI-тестирование
|
||||||
|
|
||||||
|
Ручные прогоны выполнялись по чек-листу T-7…T-12 на эмуляторе и физическом устройстве. Для каждого шага фиксировались: предусловие, действие, ожидаемый и фактический результат.
|
||||||
|
|
||||||
|
#pz-table(
|
||||||
|
[Протокол ручного тестирования],
|
||||||
|
5,
|
||||||
|
table.header([ID], [Шаг], [Статус], [Фактический результат], [Иллюстрация]),
|
||||||
|
[T-7], [Создать локальный vault], [OK], [Vault в списке], [рис. 5],
|
||||||
|
[T-8], [Включить шифрование], [OK], [Статус encrypted], [рис. 6],
|
||||||
|
[T-9], [Открыть/закрыть vault], [OK], [Контент доступен только открытому], [рис. 7],
|
||||||
|
[T-10], [OAuth Яндекс], [OK], [Запись в `DbYandexAccount`], [рис. 10],
|
||||||
|
[T-11], [Фоновая задача шифрования], [OK], [Прогресс на экране задач], [рис. 12],
|
||||||
|
[T-12], [Уведомление о завершении], [OK], [Notification отображён], [рис. 13],
|
||||||
|
) <tbl-testres>
|
||||||
|
|
||||||
|
#pz-fig("fig_32_manual_test_checklist.png", [Заполненный чек-лист ручного UI-тестирования], "fig-32")
|
||||||
|
|
||||||
|
== Отчёт о результатах тестирования
|
||||||
|
|
||||||
|
По итогам автоматического прогона `./gradlew test` все 68 unit-тестов завершились со статусом *PASSED*. Регрессия по криптографии и синхронизации не выявила отклонений. Инструментальные тесты `:ui` подтвердили отрисовку экранов секретов и 2FA; тесты Room — персистентность учётной записи Яндекс.
|
||||||
|
|
||||||
|
#pz-fig("fig_30_gradle_test_summary.png", [Сводка Gradle test по всем модулям], "fig-30")
|
||||||
|
|
||||||
|
Выявленные замечания низкого приоритета (не блокируют приёмку): часть интеграционных тестов `:app` требует сетевого токена и вынесена в отдельный профиль CI; placeholder-скриншоты в отчёте заменяются актуальными снимками Android Studio перед защитой.
|
||||||
|
|
||||||
|
#pz-table(
|
||||||
|
[Классификация дефектов по итогам практики],
|
||||||
|
4,
|
||||||
|
table.header([ID], [Приоритет], [Описание], [Статус]),
|
||||||
|
[D-1], [P3], [Дублирование подписи листинга в приложении А (оформление)], [Исправлено],
|
||||||
|
[D-2], [P3], [Перенос строк в широких таблицах ПЗ], [Исправлено],
|
||||||
|
[D-3], [P2], [Полная синхронизация с облаком — в разработке], [Открыт],
|
||||||
|
) <tbl-defects>
|
||||||
|
|
||||||
|
=== Связь тестов с требованиями
|
||||||
|
|
||||||
|
#pz-table(
|
||||||
|
[Трассировка требований → тесты],
|
||||||
|
3,
|
||||||
|
table.header([ФР], [Тесты], [Комментарий]),
|
||||||
|
[ФР-1], [T-7, StorageDomainUseCasesTest], [Локальный vault и CRUD секретов],
|
||||||
|
[ФР-2], [EncryptorTest, T-8, T-9], [Полное покрытие AES],
|
||||||
|
[ФР-3], [TextSecretsScreenContentTest], [UI + domain],
|
||||||
|
[ФР-4], [YandexDiskRepositoryTest, T-10], [HTTP-мок и ручной OAuth],
|
||||||
|
[ФР-5], [StorageSyncEngineTest], [12 сценариев синхронизации],
|
||||||
|
[ФР-6], [TaskOrchestratorTest, T-11], [Очередь и экран задач],
|
||||||
|
) <tbl-trace>
|
||||||
|
|
||||||
|
=== Рекомендации по сопровождению тестов
|
||||||
|
|
||||||
|
При изменении криптомодуля обязателен прогон `:domain:test`. При правках синхронизации — `:usecases:test`. Перед релизом — полный `./gradlew test` и выборочный `connectedDebugAndroidTest`. Регрессионный чек-лист T-7…T-12 выполняется после изменений в Compose-экранах vault. Скрипт `Report/scripts/gen_test_tables.py` обновляет реестр тестов в ПЗ при добавлении новых `@Test`.
|
||||||
|
|
||||||
|
== Вывод
|
||||||
|
|
||||||
|
План тестирования выполнен: автоматизированное покрытие охватывает криптографию, синхронизацию, задачи, парсинг OTP и обработку ошибок; ручные сценарии подтвердили пригодность UI для vault и OAuth. Совокупность испытаний обосновывает готовность прототипа Wallenc к демонстрации и развитию в рамках ВКР.
|
||||||
44
Report/includes/ch06.typ
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
#import "common.typ": pz-table
|
||||||
|
|
||||||
|
= Оценка результатов и экономические показатели
|
||||||
|
|
||||||
|
== Обзор рынка программного обеспечения и аналогов
|
||||||
|
|
||||||
|
Рынок клиентских решений для защищённого хранения данных представлен продуктами классов «локальный сейф», менеджеры секретов и zero-knowledge файловые клиенты (см. табл. @tbl-analog, гл. 1). Ниша Wallenc — мобильный универсальный vault без собственного backend приложения с возможностью подключения внешних провайдеров через OAuth.
|
||||||
|
|
||||||
|
== Оценка экономических затрат на разработку проекта
|
||||||
|
|
||||||
|
Затраты на разработку в рамках практики (09.02.2026–06.05.2026) оцениваются по трудозатратам обучающегося и использованию открытых инструментов (Android Studio, Kotlin, Typst).
|
||||||
|
|
||||||
|
#pz-table(
|
||||||
|
[Структура затрат на разработку (оценка)],
|
||||||
|
3,
|
||||||
|
table.header([Статья затрат], [Ед.], [Сумма, руб.]),
|
||||||
|
[Трудозатраты (≈480 ч практики)], [н/ч], [0 (учётная работа в рамках учебного плана)],
|
||||||
|
[Лицензии ПО (IDE, SDK)], [комплект], [0],
|
||||||
|
[Устройство для тестирования], [1], [имеется у исполнителя],
|
||||||
|
[Итого прямых денежных затрат], [], [≈ 0],
|
||||||
|
) <tbl-cost>
|
||||||
|
|
||||||
|
#pz-table(
|
||||||
|
[Календарный план-график практики (фрагмент)],
|
||||||
|
2,
|
||||||
|
table.header([Период], [Результат]),
|
||||||
|
[09.02–28.02.2026], [Анализ, аналоги, ТЗ, архитектура],
|
||||||
|
[01.03–28.03.2026], [Проектирование БД, стек, OAuth-исследования],
|
||||||
|
[29.03–19.04.2026], [Реализация ядра, UI, Room, Яндекс],
|
||||||
|
[20.04–05.05.2026], [Тестирование, иллюстрации, отчётность],
|
||||||
|
[06.05.2026], [Защита практики, итоговая сборка ПЗ],
|
||||||
|
) <tbl-calendar>
|
||||||
|
|
||||||
|
Календарный график работ совпадает с дневником практики: этап 1 — аналитика и проектирование (февраль–март 2026); этап 2 — реализация (29.03–19.04.2026); оформление документации — апрель–май 2026.
|
||||||
|
|
||||||
|
== Модель применения и окупаемости
|
||||||
|
|
||||||
|
*Сценарий применения* — внедрение в ООО НМФ «Нейротех» как внутренний инструмент защищённого хранения файлов сотрудников на корпоративных или личных облачных аккаунтах при сохранении zero-knowledge модели.
|
||||||
|
|
||||||
|
*Эффект*: снижение риска утечки при компрометации провайдера; отсутствие затрат на собственный сервер приложения. Окупаемость для учебно-промышленного прототипа выражается не в прямой прибыли, а в сокращении рисков и возможности доработки продукта без смены архитектуры.
|
||||||
|
|
||||||
|
== Вывод
|
||||||
|
|
||||||
|
Экономическая оценка показывает низкие прямые затраты на создание прототипа и потенциальную практическую ценность для организации-партнёра. Детальный рыночный анализ приведён в п. 1.3.5; полноценный раздел ТЭО в объёме отраслевых ВКР не требуется согласно рекомендациям кафедры.
|
||||||
39
Report/includes/common.typ
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
// Таблицы — как в «Пример работы с Typst.typ» и gost: figure + table + table.header.
|
||||||
|
// Разрыв длинных таблиц и подпись сверху задаёт шаблон modern-g7-32 (style.typ).
|
||||||
|
|
||||||
|
#show table: set text(hyphenate: true)
|
||||||
|
|
||||||
|
#let pz-table(caption, columns, ..body) = figure(
|
||||||
|
table(
|
||||||
|
columns: columns,
|
||||||
|
..body,
|
||||||
|
),
|
||||||
|
caption: caption,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Реестры unit-тестов: перенос длинных идентификаторов (camelCase, _); сетка как в примере Typst.
|
||||||
|
#let pz-test-table(caption, columns, ..body) = figure(
|
||||||
|
{
|
||||||
|
show table: set text(hyphenate: true, size: 9pt)
|
||||||
|
show table.cell: cell => {
|
||||||
|
show regex("[a-z0-9][A-Z]"): m => {
|
||||||
|
m.text.first() + sym.zws + m.text.last()
|
||||||
|
}
|
||||||
|
show regex("[_]"): it => it + sym.zws
|
||||||
|
cell
|
||||||
|
}
|
||||||
|
table(
|
||||||
|
columns: columns,
|
||||||
|
..body,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
caption: caption,
|
||||||
|
)
|
||||||
|
|
||||||
|
#let pz-fig(path, caption, lbl) = [
|
||||||
|
#figure(
|
||||||
|
image("../images/" + path, width: 100%),
|
||||||
|
caption: caption,
|
||||||
|
)
|
||||||
|
#label(lbl)
|
||||||
|
]
|
||||||
15
Report/includes/conclusion.typ
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#heading(numbering: none, outlined: true)[Заключение]
|
||||||
|
|
||||||
|
В пояснительной записке рассмотрены анализ предметной области, проектирование и реализация мобильного приложения Wallenc — клиентского кошелька для безопасного хранения данных на недоверенных хранилищах без собственного сервера.
|
||||||
|
|
||||||
|
По главе 1 сформированы требования и выполнен сравнительный анализ аналогов; обоснован выбор стека Kotlin/Compose/Room/Hilt. По главе 2 спроектированы бизнес-процессы, DFD, UML-диаграммы и модель данных Room. Глава 3 описывает пользовательские сценарии и интерфейсные решения. Глава 4 представляет реализованные модули и отсылает к полному листингу в приложении А. Глава 5 документирует план и результаты тестирования. Глава 6 содержит краткую экономическую оценку.
|
||||||
|
|
||||||
|
*Цель работы достигнута*: разработан и протестирован прототип Android-приложения с клиентским шифрованием, управлением vault, OAuth Яндекс и проектным контуром синхронизации.
|
||||||
|
|
||||||
|
*Перспективы развития*: завершение синхронизации по модели коммитов; поддержка дополнительных провайдеров; расширение автоматизированных UI-тестов; оформление акта внедрения (приложение Д при наличии).
|
||||||
|
|
||||||
|
Программная документация (ТЗ, руководство пользователя, материалы испытаний) приведена в приложении Б; иллюстрации интерфейса — в приложении В; диаграммы — в приложении Г.
|
||||||
|
|
||||||
|
По тестированию подтверждено: 68 модульных unit-тестов в `src/test` (модули `:domain`, `:domain-vault`, `:usecases`, `:ui`, `:task-runtime`), инструментальные тесты Compose и Room, ручной протокол из двенадцати сценариев. Отчёты Gradle (рис. 27–31) и чек-лист UI (рис. 32) включены в гл. 5.
|
||||||
|
|
||||||
|
Дальнейшие шаги: завершение синхронизации с облаком; расширение androidTest для OAuth без ручного ввода; публикация актуальных скриншотов вместо учебных заглушек; подготовка акта внедрения (прил. Д) при эксплуатации в Нейротех.
|
||||||
32
Report/includes/intro.typ
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#heading(numbering: none, outlined: true)[Введение]
|
||||||
|
|
||||||
|
Современные пользователи хранят личные и рабочие данные в облачных сервисах и на съёмных носителях, однако инфраструктура провайдера не всегда может считаться доверенной. Утечки, компрометация учётных записей и юрисдикционные риски делают актуальным подход, при котором конфиденциальность обеспечивается на стороне клиента до размещения данных во внешнем хранилище @nist-aes @clean-arch.
|
||||||
|
|
||||||
|
*Актуальность* темы обусловлена распространением мобильных приложений для хранения файлов и секретов, а также ограниченностью готовых решений: многие продукты привязаны к собственному backend, закрытой экосистеме или узкой предметной области (менеджеры паролей, локальные «сейфы») @google-secure-folder @bitwarden-help @cryptomator-docs.
|
||||||
|
|
||||||
|
*Цель работы* — повысить конфиденциальность пользовательских данных при работе с недоверенными хранилищами за счёт разработки мобильного клиентского приложения Wallenc, не требующего развёртывания собственного сервера приложения и обеспечивающего единую модель vault с клиентским шифрованием.
|
||||||
|
|
||||||
|
Для достижения цели поставлены следующие *задачи*:
|
||||||
|
+ выполнить анализ предметной области и сравнительный обзор аналогов, сформировать требования к программному продукту;
|
||||||
|
+ спроектировать архитектуру системы, модель данных и пользовательские сценарии;
|
||||||
|
+ реализовать программные модули приложения Wallenc на платформе Android (Kotlin);
|
||||||
|
+ провести тестирование программного обеспечения и оформить программную документацию;
|
||||||
|
+ выполнить оценку экономических затрат и перспектив применения результатов.
|
||||||
|
|
||||||
|
*Объект исследования* — методы и средства клиентской защиты данных в мобильных приложениях. *Предмет исследования* — проектные и программные решения приложения Wallenc.
|
||||||
|
|
||||||
|
*Методы исследования*: анализ нормативной и технической документации, сравнительный анализ программных аналогов, объектно-ориентированное проектирование (UML, BPMN, DFD), прототипирование пользовательского интерфейса, программная реализация и тестирование @gost7322017 @kotlin-docs.
|
||||||
|
|
||||||
|
*Практическая база.* Работа выполнена в рамках производственной (технологической) практики в ООО НМФ «Нейротех» (09.02.2026–06.05.2026) по направлению 09.03.04 «Программная инженерия», профиль «Методы и средства разработки программного обеспечения». Научный руководитель от университета — Беликов А. Н.; руководитель от организации — Алексеев Д. М.
|
||||||
|
|
||||||
|
*Научная новизна* заключается в сочетании универсальной модели vault, клиентского шифрования и адаптерного доступа к разным типам хранилищ без собственного сервера приложения, с проектным контуром синхронизации зашифрованных данных без передачи ключей провайдеру.
|
||||||
|
|
||||||
|
*Практическая значимость*: результаты могут использоваться при дальнейшей разработке продукта в ООО НМФ «Нейротех» и в учебных проектах по мобильной разработке и информационной безопасности. *Апробация* — прохождение производственной практики (09.02.2026–06.05.2026) с реализацией и тестированием рабочей сборки приложения.
|
||||||
|
|
||||||
|
*Исходный код* размещён в приватном репозитории Gitea ЮФУ @wallenc-repo; доступ для государственной экзаменационной комиссии предоставляется по запросу научного руководителя.
|
||||||
|
|
||||||
|
*Методика разработки.* Проект вёлся итерациями, согласованными с этапами практики: аналитика и ТЗ; проектирование UML и БД; реализация ядра vault и UI; наращивание автоматических тестов (68 unit-тестов) и ручная приёмка; оформление ПЗ и программной документации. Контроль качества — непрерывный: `./gradlew test` после изменений в `:domain` и `:usecases`, регрессия UI — по чек-листу гл. 5.
|
||||||
|
|
||||||
|
*Содержание глав.* В главе 1 обоснована актуальность клиентского шифрования и приведено сравнение аналогов. Глава 2 описывает архитектуру, DFD, BPMN и модель Room. Глава 3 посвящена UX, User Story и диаграммам потоков. Глава 4 раскрывает реализацию по модулям Gradle. Глава 5 содержит развёрнутый план тестирования, реестр всех unit-тестов, отчёт о прогоне и скриншоты Gradle/Android Studio. Глава 6 даёт краткую экономическую оценку.
|
||||||
|
|
||||||
|
*Приложения.* *Приложение А* — полный листинг исходных файлов (307 файлов, автогенерация). *Приложение Б* — ТЗ, программа испытаний, руководство пользователя. *Приложение В* — скриншоты UI. *Приложение Г* — диаграммы. На приложения даны ссылки в гл. 4–5 и в настоящем введении.
|
||||||
1
Report/listings/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
generated/
|
||||||
65
Report/listings/listings.config.yaml
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
repo_root: ".."
|
||||||
|
report_root: "."
|
||||||
|
scan_root: ".."
|
||||||
|
|
||||||
|
include_extensions:
|
||||||
|
.kt: kotlin
|
||||||
|
.kts: kotlin
|
||||||
|
.xml: xml
|
||||||
|
.pro: text
|
||||||
|
.properties: text
|
||||||
|
.toml: toml
|
||||||
|
|
||||||
|
include_filenames:
|
||||||
|
gradlew: bash
|
||||||
|
|
||||||
|
exclude_dirs:
|
||||||
|
- build
|
||||||
|
- .gradle
|
||||||
|
- .idea
|
||||||
|
- Report
|
||||||
|
- captures
|
||||||
|
- .cxx
|
||||||
|
- app/release
|
||||||
|
- node_modules
|
||||||
|
|
||||||
|
exclude_globs:
|
||||||
|
- "**/generated/**"
|
||||||
|
- "**/*.jar"
|
||||||
|
- "**/*.apk"
|
||||||
|
- "**/*.jks"
|
||||||
|
- "**/*.keystore"
|
||||||
|
- "**/*.hprof"
|
||||||
|
- local.properties
|
||||||
|
- google-services.json
|
||||||
|
- "**/*.png"
|
||||||
|
- "**/*.webp"
|
||||||
|
- "**/*.jpg"
|
||||||
|
- "**/*.gif"
|
||||||
|
- "**/*.md"
|
||||||
|
- "**/*.puml"
|
||||||
|
- "**/*.gaphor"
|
||||||
|
- "**/*.pdf"
|
||||||
|
- "**/*.typ"
|
||||||
|
|
||||||
|
modules_order:
|
||||||
|
- root
|
||||||
|
- app
|
||||||
|
- domain
|
||||||
|
- usecases
|
||||||
|
- ui
|
||||||
|
- domain-vault
|
||||||
|
- infrastructure-android
|
||||||
|
- vault-contracts
|
||||||
|
- task-runtime
|
||||||
|
|
||||||
|
root_build_files:
|
||||||
|
- settings.gradle.kts
|
||||||
|
- build.gradle.kts
|
||||||
|
- gradle.properties
|
||||||
|
- gradle/libs.versions.toml
|
||||||
|
- gradle/wrapper/gradle-wrapper.properties
|
||||||
|
- gradlew
|
||||||
|
|
||||||
|
pagebreak_per_module: false
|
||||||
|
pagebreak_per_file: false
|
||||||
94
Report/references.bib
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
@misc{gost7322017,
|
||||||
|
title = {ГОСТ 7.32—2017. Отчёт о научно-исследовательской работе. Структура и правила оформления},
|
||||||
|
year = {2017},
|
||||||
|
note = {Электронный ресурс: https://tsu.ru/upload/medialibrary/235/gost_7.32_2017.pdf},
|
||||||
|
}
|
||||||
|
|
||||||
|
@misc{kotlin-docs,
|
||||||
|
title = {Kotlin Documentation},
|
||||||
|
author = {{JetBrains}},
|
||||||
|
year = {2026},
|
||||||
|
url = {https://kotlinlang.org/docs/home.html},
|
||||||
|
}
|
||||||
|
|
||||||
|
@misc{android-arch,
|
||||||
|
title = {Guide to app architecture},
|
||||||
|
author = {{Google}},
|
||||||
|
year = {2026},
|
||||||
|
url = {https://developer.android.com/topic/architecture},
|
||||||
|
}
|
||||||
|
|
||||||
|
@misc{compose-docs,
|
||||||
|
title = {Get started with Jetpack Compose},
|
||||||
|
author = {{Google}},
|
||||||
|
year = {2026},
|
||||||
|
url = {https://developer.android.com/develop/ui/compose/documentation},
|
||||||
|
}
|
||||||
|
|
||||||
|
@misc{room-docs,
|
||||||
|
title = {Save data in a local database using Room},
|
||||||
|
author = {{Google}},
|
||||||
|
year = {2026},
|
||||||
|
url = {https://developer.android.com/training/data-storage/room},
|
||||||
|
}
|
||||||
|
|
||||||
|
@misc{hilt-docs,
|
||||||
|
title = {Hilt},
|
||||||
|
author = {{Google}},
|
||||||
|
year = {2026},
|
||||||
|
url = {https://developer.android.com/training/dependency-injection/hilt-android},
|
||||||
|
}
|
||||||
|
|
||||||
|
@misc{oauth-rfc6749,
|
||||||
|
title = {The OAuth 2.0 Authorization Framework},
|
||||||
|
author = {Hardt, D.},
|
||||||
|
year = {2012},
|
||||||
|
url = {https://datatracker.ietf.org/doc/html/rfc6749},
|
||||||
|
}
|
||||||
|
|
||||||
|
@misc{yandex-oauth,
|
||||||
|
title = {OAuth для сервисов Яндекса},
|
||||||
|
author = {{Яндекс}},
|
||||||
|
year = {2026},
|
||||||
|
url = {https://yandex.ru/dev/id/doc/ru/},
|
||||||
|
}
|
||||||
|
|
||||||
|
@misc{nist-aes,
|
||||||
|
title = {Advanced Encryption Standard (AES)},
|
||||||
|
author = {{NIST}},
|
||||||
|
year = {2001},
|
||||||
|
url = {https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197.pdf},
|
||||||
|
}
|
||||||
|
|
||||||
|
@misc{clean-arch,
|
||||||
|
title = {The Clean Architecture},
|
||||||
|
author = {Martin, R. C.},
|
||||||
|
year = {2012},
|
||||||
|
url = {https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html},
|
||||||
|
}
|
||||||
|
|
||||||
|
@misc{cryptomator-docs,
|
||||||
|
title = {Cryptomator Documentation},
|
||||||
|
year = {2026},
|
||||||
|
url = {https://docs.cryptomator.org/en/latest/},
|
||||||
|
}
|
||||||
|
|
||||||
|
@misc{bitwarden-help,
|
||||||
|
title = {Bitwarden Help Center},
|
||||||
|
year = {2026},
|
||||||
|
url = {https://bitwarden.com/help/},
|
||||||
|
}
|
||||||
|
|
||||||
|
@misc{google-secure-folder,
|
||||||
|
title = {Use a PIN to lock your Safe folder in Files by Google},
|
||||||
|
author = {{Google}},
|
||||||
|
year = {2026},
|
||||||
|
url = {https://support.google.com/files/answer/9935263},
|
||||||
|
}
|
||||||
|
|
||||||
|
@misc{wallenc-repo,
|
||||||
|
title = {Исходный код проекта Wallenc},
|
||||||
|
author = {Пытков, Р. Е.},
|
||||||
|
year = {2026},
|
||||||
|
note = {Приватный репозиторий Gitea ЮФУ; доступ для проверки по запросу руководителя ВКР},
|
||||||
|
}
|
||||||
7
Report/scripts/build-pz.sh
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
python3 scripts/gen_listings.py
|
||||||
|
python3 scripts/gen_test_tables.py
|
||||||
|
python3 scripts/check_images.py
|
||||||
|
typst compile --root .. "Пояснительная_записка_ПытковРЕ.typ"
|
||||||
51
Report/scripts/check_images.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Verify IMAGES_REGISTRY.md entries exist on disk and match .typ references."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
report = Path(__file__).resolve().parent.parent
|
||||||
|
repo = report.parent
|
||||||
|
images = report / "images"
|
||||||
|
registry = images / "IMAGES_REGISTRY.md"
|
||||||
|
typ = report / "Пояснительная_записка_ПытковРЕ.typ"
|
||||||
|
errors = 0
|
||||||
|
|
||||||
|
if not registry.exists():
|
||||||
|
print("Missing IMAGES_REGISTRY.md", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
rows = re.findall(
|
||||||
|
r"\|\s*\d+\s*\|\s*([^\|]+?)\s*\|\s*(\w+)\s*\|",
|
||||||
|
registry.read_text(encoding="utf-8"),
|
||||||
|
)
|
||||||
|
rows = [(f.strip(), s.strip()) for f, s in rows if f.startswith("fig_")]
|
||||||
|
for fname, status in rows:
|
||||||
|
p = images / fname
|
||||||
|
if not p.exists():
|
||||||
|
print(f"MISSING file: {fname} (status {status})", file=sys.stderr)
|
||||||
|
errors += 1
|
||||||
|
elif status == "ready" and p.stat().st_size < 1000:
|
||||||
|
print(f"WARN small file: {fname}", file=sys.stderr)
|
||||||
|
|
||||||
|
if typ.exists():
|
||||||
|
text = typ.read_text(encoding="utf-8")
|
||||||
|
for m in re.finditer(r'image\("(?:Report/)?images/([^"]+)"', text):
|
||||||
|
f = images / m.group(1)
|
||||||
|
if not f.exists():
|
||||||
|
print(f"MISSING in typ: images/{m.group(1)}", file=sys.stderr)
|
||||||
|
errors += 1
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
print(f"check_images: {errors} error(s)", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
print("check_images: OK", file=sys.stderr)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
196
Report/scripts/gen_listings.py
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Generate Typst listing wrappers for Appendix A from Wallenc source tree."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import hashlib
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
try:
|
||||||
|
import yaml
|
||||||
|
except ImportError:
|
||||||
|
yaml = None # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
def load_config(path: Path) -> dict:
|
||||||
|
text = path.read_text(encoding="utf-8")
|
||||||
|
if yaml is None:
|
||||||
|
raise SystemExit("PyYAML required: pip install pyyaml")
|
||||||
|
return yaml.safe_load(text)
|
||||||
|
|
||||||
|
|
||||||
|
def path_hash(rel: str) -> str:
|
||||||
|
return hashlib.sha256(rel.encode()).hexdigest()[:8]
|
||||||
|
|
||||||
|
|
||||||
|
def lang_for(path: Path, ext_map: dict, name_map: dict) -> str:
|
||||||
|
if path.name in name_map:
|
||||||
|
return name_map[path.name]
|
||||||
|
return ext_map.get(path.suffix.lower(), "text")
|
||||||
|
|
||||||
|
|
||||||
|
def should_skip(path: Path, parts: tuple[str, ...], cfg: dict) -> bool:
|
||||||
|
exclude_dirs = set(cfg.get("exclude_dirs", []))
|
||||||
|
if any(p in exclude_dirs for p in parts):
|
||||||
|
return True
|
||||||
|
s = path.as_posix()
|
||||||
|
for g in cfg.get("exclude_globs", []):
|
||||||
|
g = g.replace("**/", "")
|
||||||
|
if g.startswith("**/"):
|
||||||
|
g = g[3:]
|
||||||
|
if g.endswith("/**"):
|
||||||
|
if g[:-3] in parts:
|
||||||
|
return True
|
||||||
|
elif g.startswith("*."):
|
||||||
|
if s.endswith(g[1:]):
|
||||||
|
return True
|
||||||
|
elif g in s:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def collect_files(scan_root: Path, cfg: dict) -> list[Path]:
|
||||||
|
ext_map: dict = cfg.get("include_extensions", {})
|
||||||
|
name_map: dict = cfg.get("include_filenames", {})
|
||||||
|
allowed_suffixes = set(ext_map.keys())
|
||||||
|
allowed_names = set(name_map.keys())
|
||||||
|
|
||||||
|
files: list[Path] = []
|
||||||
|
for p in scan_root.rglob("*"):
|
||||||
|
if not p.is_file():
|
||||||
|
continue
|
||||||
|
rel_parts = p.relative_to(scan_root).parts
|
||||||
|
if should_skip(p, rel_parts, cfg):
|
||||||
|
continue
|
||||||
|
if p.name in allowed_names or p.suffix.lower() in allowed_suffixes:
|
||||||
|
files.append(p)
|
||||||
|
return sorted(files, key=lambda x: x.as_posix())
|
||||||
|
|
||||||
|
|
||||||
|
def module_key(rel: Path, cfg: dict) -> str:
|
||||||
|
order = cfg.get("modules_order", [])
|
||||||
|
parts = rel.parts
|
||||||
|
if rel.name in {Path(f).name for f in cfg.get("root_build_files", [])}:
|
||||||
|
return "root"
|
||||||
|
if parts and parts[0] in order:
|
||||||
|
return parts[0]
|
||||||
|
return "other"
|
||||||
|
|
||||||
|
|
||||||
|
def group_files(files: list[Path], scan_root: Path, cfg: dict) -> dict[str, list[Path]]:
|
||||||
|
groups: dict[str, list[Path]] = {}
|
||||||
|
root_files = {Path(f).name for f in cfg.get("root_build_files", [])}
|
||||||
|
for f in files:
|
||||||
|
rel = f.relative_to(scan_root)
|
||||||
|
if rel.name in root_files or len(rel.parts) == 1 and rel.suffix in {".kts", ".properties"}:
|
||||||
|
groups.setdefault("root", []).append(f)
|
||||||
|
continue
|
||||||
|
key = module_key(rel, cfg)
|
||||||
|
groups.setdefault(key, []).append(f)
|
||||||
|
order = cfg.get("modules_order", []) + ["other"]
|
||||||
|
return {k: groups[k] for k in order if k in groups}
|
||||||
|
|
||||||
|
|
||||||
|
def typst_escape_path(rel: str) -> str:
|
||||||
|
return rel.replace("\\", "/")
|
||||||
|
|
||||||
|
|
||||||
|
def write_listing(
|
||||||
|
out_path: Path,
|
||||||
|
rel_from_report: str,
|
||||||
|
caption: str,
|
||||||
|
label: str,
|
||||||
|
lang: str,
|
||||||
|
) -> None:
|
||||||
|
rel_typ = typst_escape_path(rel_from_report)
|
||||||
|
# Подпись перед кодом: figure.caption(position: top) в теле (placement: top — про float, не порядок).
|
||||||
|
content = (
|
||||||
|
f'#let lst-body-{label} = read("{rel_typ}")\n'
|
||||||
|
f"#figure(\n"
|
||||||
|
f" [\n"
|
||||||
|
f" #figure.caption(position: top)[{caption}]\n"
|
||||||
|
f" #block(breakable: true)[\n"
|
||||||
|
f" #raw(lst-body-{label}, lang: \"{lang}\", block: true)\n"
|
||||||
|
f" ]\n"
|
||||||
|
f" ],\n"
|
||||||
|
f" supplement: [Листинг],\n"
|
||||||
|
f" gap: 0.4em,\n"
|
||||||
|
f") <lst-{label}>\n\n"
|
||||||
|
)
|
||||||
|
out_path.write_text(content, encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--dry-run", action="store_true")
|
||||||
|
parser.add_argument("--config", default=None)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
script_dir = Path(__file__).resolve().parent
|
||||||
|
report_dir = script_dir.parent
|
||||||
|
config_path = Path(args.config) if args.config else report_dir / "listings" / "listings.config.yaml"
|
||||||
|
cfg = load_config(config_path)
|
||||||
|
|
||||||
|
scan_root = (report_dir / cfg.get("scan_root", "..")).resolve()
|
||||||
|
generated = report_dir / "listings" / "generated"
|
||||||
|
ext_map: dict = cfg.get("include_extensions", {})
|
||||||
|
name_map: dict = cfg.get("include_filenames", {})
|
||||||
|
|
||||||
|
files = collect_files(scan_root, cfg)
|
||||||
|
if args.dry_run:
|
||||||
|
for f in files:
|
||||||
|
print(f.relative_to(scan_root).as_posix())
|
||||||
|
print(f"Total: {len(files)}", file=sys.stderr)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
generated.mkdir(parents=True, exist_ok=True)
|
||||||
|
groups = group_files(files, scan_root, cfg)
|
||||||
|
|
||||||
|
listing_paths: list[str] = []
|
||||||
|
module_includes: list[str] = []
|
||||||
|
|
||||||
|
for mod, mod_files in groups.items():
|
||||||
|
mod_listings: list[str] = []
|
||||||
|
for f in mod_files:
|
||||||
|
rel_repo = f.relative_to(scan_root).as_posix()
|
||||||
|
# Paths relative to listings/generated/*.typ (typst compile --root ..)
|
||||||
|
rel_report = Path("../../..") / rel_repo
|
||||||
|
rel_report_str = rel_report.as_posix()
|
||||||
|
hid = path_hash(rel_repo)
|
||||||
|
label = hid
|
||||||
|
cap = f"Исходный файл `{rel_repo}`"
|
||||||
|
lang = lang_for(f, ext_map, name_map)
|
||||||
|
listing_name = f"listing-{hid}.typ"
|
||||||
|
listing_path = generated / listing_name
|
||||||
|
write_listing(listing_path, rel_report_str, cap, label, lang)
|
||||||
|
mod_listings.append(listing_name)
|
||||||
|
listing_paths.append(listing_name)
|
||||||
|
|
||||||
|
mod_file = generated / f"module-{mod}.typ"
|
||||||
|
with mod_file.open("w", encoding="utf-8") as mf:
|
||||||
|
title = "Система сборки" if mod == "root" else f"Модуль :{mod}"
|
||||||
|
mf.write(f"== {title}\n\n")
|
||||||
|
if cfg.get("pagebreak_per_module") and mod != "root":
|
||||||
|
mf.write("#pagebreak(weak: true)\n\n")
|
||||||
|
for ln in mod_listings:
|
||||||
|
mf.write(f'#include "{ln}"\n')
|
||||||
|
module_includes.append(f"module-{mod}.typ")
|
||||||
|
|
||||||
|
index = generated / "_index.typ"
|
||||||
|
with index.open("w", encoding="utf-8") as ix:
|
||||||
|
ix.write(f"// Generated listings: {len(files)} files\n\n")
|
||||||
|
|
||||||
|
appendix = generated / "appendix-a.typ"
|
||||||
|
with appendix.open("w", encoding="utf-8") as ap:
|
||||||
|
ap.write("// AUTO-GENERATED by gen_listings.py — do not edit\n\n")
|
||||||
|
ap.write("#set figure(gap: 0.4em)\n\n")
|
||||||
|
for mi in module_includes:
|
||||||
|
ap.write(f'#include "{mi}"\n')
|
||||||
|
|
||||||
|
print(f"Generated {len(files)} listings in {generated}", file=sys.stderr)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
99
Report/scripts/gen_test_tables.py
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Generate Typst tables of unit tests for chapter 5."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
|
from collections import defaultdict
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def typst_escape(s: str) -> str:
|
||||||
|
return s.replace("\\", "\\\\").replace("`", "\\`")
|
||||||
|
|
||||||
|
|
||||||
|
def emit_table(
|
||||||
|
lines: list[str],
|
||||||
|
caption: str,
|
||||||
|
ncol: int,
|
||||||
|
headers: list[str],
|
||||||
|
data_rows: list[list[str]],
|
||||||
|
label: str,
|
||||||
|
) -> None:
|
||||||
|
lines.append("#pz-test-table(\n")
|
||||||
|
lines.append(f" [{caption}],\n")
|
||||||
|
lines.append(f" {ncol},\n")
|
||||||
|
lines.append(" table.header(\n")
|
||||||
|
for h in headers:
|
||||||
|
lines.append(f" [{typst_escape(h)}],\n")
|
||||||
|
lines.append(" ),\n")
|
||||||
|
for row in data_rows:
|
||||||
|
cells = ", ".join(f"[{typst_escape(c)}]" for c in row)
|
||||||
|
lines.append(f" {cells},\n")
|
||||||
|
lines.append(f") <{label}>\n\n")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
root = Path(__file__).resolve().parents[2]
|
||||||
|
out = Path(__file__).resolve().parents[1] / "includes" / "ch05-tests-generated.typ"
|
||||||
|
rows: list[tuple[str, str, str, str]] = []
|
||||||
|
|
||||||
|
for p in sorted(root.rglob("*.kt")):
|
||||||
|
if "/src/test/" not in p.as_posix():
|
||||||
|
continue
|
||||||
|
text = p.read_text(encoding="utf-8", errors="replace")
|
||||||
|
cls_m = re.search(r"class\s+(\w+)", text)
|
||||||
|
if not cls_m:
|
||||||
|
continue
|
||||||
|
cls = cls_m.group(1)
|
||||||
|
mod = p.parts[p.parts.index("src") - 1]
|
||||||
|
rel = p.relative_to(root).as_posix()
|
||||||
|
for m in re.finditer(r"@Test[\s\S]*?fun\s+(?:`([^`]+)`|(\w+))\s*\(", text):
|
||||||
|
name = m.group(1) or m.group(2)
|
||||||
|
rows.append((mod, cls, name, rel))
|
||||||
|
|
||||||
|
by_mod: dict[str, list] = defaultdict(list)
|
||||||
|
for mod, cls, name, rel in rows:
|
||||||
|
by_mod[mod].append((cls, name, rel))
|
||||||
|
|
||||||
|
lines = [
|
||||||
|
"// AUTO-GENERATED by gen_test_tables.py — include from ch05.typ\n",
|
||||||
|
'#import "common.typ": pz-test-table\n\n',
|
||||||
|
]
|
||||||
|
|
||||||
|
summary_rows = []
|
||||||
|
for mod in sorted(by_mod):
|
||||||
|
for cls, name, rel in by_mod[mod]:
|
||||||
|
short = rel.split("/")[-1]
|
||||||
|
summary_rows.append([mod, cls, name, short])
|
||||||
|
|
||||||
|
emit_table(
|
||||||
|
lines,
|
||||||
|
"Сводка модульных unit-тестов (src/test)",
|
||||||
|
4,
|
||||||
|
["Модуль", "Класс", "Метод", "Файл"],
|
||||||
|
summary_rows,
|
||||||
|
"tbl-unit-all",
|
||||||
|
)
|
||||||
|
|
||||||
|
for mod in sorted(by_mod):
|
||||||
|
safe = mod.replace("-", "_")
|
||||||
|
lines.append(f"=== Реестр тестов модуля :{mod}\n\n")
|
||||||
|
mod_rows = [
|
||||||
|
[cls, name, rel.split("/")[-1]]
|
||||||
|
for cls, name, rel in by_mod[mod]
|
||||||
|
]
|
||||||
|
emit_table(
|
||||||
|
lines,
|
||||||
|
f"Unit-тесты модуля :{mod}",
|
||||||
|
3,
|
||||||
|
["Класс", "Метод", "Файл"],
|
||||||
|
mod_rows,
|
||||||
|
f"tbl-unit-{safe}",
|
||||||
|
)
|
||||||
|
|
||||||
|
out.write_text("".join(lines), encoding="utf-8")
|
||||||
|
print(f"Wrote {out} ({len(rows)} tests)", flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
BIN
Report/Другие отчёты Wallenc/puml/Domain_Диаграмма_классов.png
Normal file
|
After Width: | Height: | Size: 199 KiB |
BIN
Report/Другие отчёты Wallenc/puml/domain-classdiagram.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
387
Report/Другие отчёты Wallenc/puml/domain-classdiagram.puml
Normal file
@@ -0,0 +1,387 @@
|
|||||||
|
@startuml
|
||||||
|
scale 2
|
||||||
|
skinparam shadowing false
|
||||||
|
skinparam classFontSize 11
|
||||||
|
|
||||||
|
' PNG (лимит растра по умолчанию 4096): PLANTUML_LIMIT_SIZE=8192 java -Xmx2g -jar plantuml.jar -charset UTF-8 -tpng …
|
||||||
|
|
||||||
|
package usecases {
|
||||||
|
class ManageStoragesEncryptionUseCase {
|
||||||
|
+ enableEncryption(IStorageInfo, EncryptKey, boolean): Unit
|
||||||
|
+ openStorage(IStorageInfo, EncryptKey, boolean): Unit
|
||||||
|
+ clearAndDisableEncryption(IStorageInfo): Unit
|
||||||
|
+ closeStorage(IStorageInfo): Unit
|
||||||
|
+ canEncrypt(IStorageInfo): Unit
|
||||||
|
+ changePassword(IStorageInfo, EncryptKey, boolean): Unit
|
||||||
|
}
|
||||||
|
class RemoveStorageUseCase {
|
||||||
|
+ remove(IStorageInfo): Unit
|
||||||
|
}
|
||||||
|
class GetOpenedStoragesUseCase {
|
||||||
|
+ getOpenedStorages(): StateFlow<Map<UUID, IStorageInfo>>
|
||||||
|
}
|
||||||
|
class ManageLocalVaultUseCase {
|
||||||
|
+ getLocalStorages(): StateFlow<List<IStorageInfo>>
|
||||||
|
+ createStorage(): Unit
|
||||||
|
}
|
||||||
|
class StorageFileManagementUseCase {
|
||||||
|
+ getAllDirs(): Unit
|
||||||
|
+ setStorage(IStorageInfo): void
|
||||||
|
+ getAllFiles(): Unit
|
||||||
|
}
|
||||||
|
class RenameStorageUseCase {
|
||||||
|
+ rename(IStorageInfo, String): Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
package tasks {
|
||||||
|
class TaskLogLine {
|
||||||
|
+ getTimestampMs(): long
|
||||||
|
+ getLevel(): TaskLogLevel
|
||||||
|
+ getMessage(): String
|
||||||
|
}
|
||||||
|
class PipelineWork {
|
||||||
|
+ run(TaskContext): Unit
|
||||||
|
}
|
||||||
|
class TaskContext {
|
||||||
|
+ reportProgress(Float, String): Unit
|
||||||
|
+ reportProgress(TaskProgress): Unit
|
||||||
|
+ log(TaskLogLevel, String): void
|
||||||
|
+ getTaskId(): TaskId
|
||||||
|
}
|
||||||
|
class PipelineState {
|
||||||
|
+ getTasks(): List
|
||||||
|
+ getRunningTaskIds(): Set
|
||||||
|
}
|
||||||
|
class ITaskOrchestrator {
|
||||||
|
+ getLogLines(): StateFlow<List<TaskLogLine>>
|
||||||
|
+ enqueue(String, PipelineWork): TaskId
|
||||||
|
+ getPipelineState(): StateFlow<PipelineState>
|
||||||
|
+ getForegroundUi(): StateFlow<TaskForegroundUiState>
|
||||||
|
+ cancel(TaskId): boolean
|
||||||
|
+ cancelAll(): void
|
||||||
|
}
|
||||||
|
class TaskProgress {
|
||||||
|
+ getLabel(): String
|
||||||
|
+ getFraction(): Float
|
||||||
|
}
|
||||||
|
class TaskForegroundUiState {
|
||||||
|
}
|
||||||
|
class TaskForegroundItem {
|
||||||
|
+ getProgress(): TaskProgress
|
||||||
|
+ getTitle(): String
|
||||||
|
+ getTaskId(): TaskId
|
||||||
|
}
|
||||||
|
class TaskLogLevel {
|
||||||
|
+ Debug
|
||||||
|
+ Warn
|
||||||
|
+ Error
|
||||||
|
+ Info
|
||||||
|
+ values(): TaskLogLevel[]
|
||||||
|
+ valueOf(String): TaskLogLevel
|
||||||
|
+ getEntries(): EnumEntries<TaskLogLevel>
|
||||||
|
}
|
||||||
|
class TaskRunState {
|
||||||
|
}
|
||||||
|
class TaskId {
|
||||||
|
+ getUuid(): UUID
|
||||||
|
}
|
||||||
|
class PipelineTask {
|
||||||
|
+ getId(): TaskId
|
||||||
|
+ getDispatcher(): CoroutineDispatcher
|
||||||
|
+ getState(): TaskRunState
|
||||||
|
+ getTitle(): String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
package interfaces {
|
||||||
|
class ILogger {
|
||||||
|
+ debug(String, String): void
|
||||||
|
}
|
||||||
|
class IYandexVault {
|
||||||
|
+ getAccountEmail(): String
|
||||||
|
}
|
||||||
|
class IMetaInfo {
|
||||||
|
+ getSize(): long
|
||||||
|
+ isDeleted(): boolean
|
||||||
|
+ isHidden(): boolean
|
||||||
|
+ getLastModified(): Instant
|
||||||
|
+ getPath(): String
|
||||||
|
}
|
||||||
|
class IStorage {
|
||||||
|
+ rename(String): Unit
|
||||||
|
+ setEncInfo(StorageEncryptionInfo): Unit
|
||||||
|
+ isEmpty(): Flow<Boolean>
|
||||||
|
+ getUuid(): UUID
|
||||||
|
+ getAccessor(): IStorageAccessor
|
||||||
|
+ isAvailable(): StateFlow<Boolean>
|
||||||
|
+ getSize(): StateFlow<Long>
|
||||||
|
+ getNumberOfFiles(): StateFlow<Integer>
|
||||||
|
+ clearAllContent(): Unit
|
||||||
|
+ getMetaInfo(): StateFlow<IStorageMetaInfo>
|
||||||
|
+ isVirtualStorage(): boolean
|
||||||
|
}
|
||||||
|
class IVaultsManager {
|
||||||
|
+ getLocalVault(): IVault
|
||||||
|
+ removeRemoteVault(UUID): Unit
|
||||||
|
+ addYandexVault(String): Unit
|
||||||
|
+ getRemoteVaults(): StateFlow<List<IVault>>
|
||||||
|
+ getAllStorages(): StateFlow<List<IStorage>>
|
||||||
|
+ getAllVaults(): StateFlow<List<IVault>>
|
||||||
|
+ getUnlockManager(): IUnlockManager
|
||||||
|
}
|
||||||
|
class IStorageMetaInfo {
|
||||||
|
+ getEncInfo(): StorageEncryptionInfo
|
||||||
|
+ getName(): String
|
||||||
|
+ getLastModified(): Instant
|
||||||
|
}
|
||||||
|
class IFile {
|
||||||
|
+ getMetaInfo(): IMetaInfo
|
||||||
|
}
|
||||||
|
class IStorageInfo {
|
||||||
|
+ isEmpty(): Flow<Boolean>
|
||||||
|
+ getUuid(): UUID
|
||||||
|
+ isAvailable(): StateFlow<Boolean>
|
||||||
|
+ getSize(): StateFlow<Long>
|
||||||
|
+ getNumberOfFiles(): StateFlow<Integer>
|
||||||
|
+ getMetaInfo(): StateFlow<IStorageMetaInfo>
|
||||||
|
+ isVirtualStorage(): boolean
|
||||||
|
}
|
||||||
|
class IStorageExplorer {
|
||||||
|
+ getCurrentPath(): StateFlow<String>
|
||||||
|
}
|
||||||
|
class IVaultInfo {
|
||||||
|
+ getAvailableSpace(): StateFlow<Integer>
|
||||||
|
+ getType(): VaultType
|
||||||
|
+ getTotalSpace(): StateFlow<Integer>
|
||||||
|
+ getUuid(): UUID
|
||||||
|
+ isAvailable(): StateFlow<Boolean>
|
||||||
|
+ getStorages(): StateFlow<List<IStorageInfo>>
|
||||||
|
}
|
||||||
|
class IUnlockManager {
|
||||||
|
+ close(IStorage): Unit
|
||||||
|
+ close(UUID): Unit
|
||||||
|
+ open(IStorage, EncryptKey, boolean): Unit
|
||||||
|
+ getOpenedStorages(): StateFlow<Map<UUID, IStorage>>
|
||||||
|
}
|
||||||
|
class IDirectory {
|
||||||
|
+ getMetaInfo(): IMetaInfo
|
||||||
|
+ getElementsCount(): Integer
|
||||||
|
}
|
||||||
|
class IStorageAccessor {
|
||||||
|
+ getFilesFlow(String): Flow<DataPackage<List<IFile>>>
|
||||||
|
+ getDirs(String): Unit
|
||||||
|
+ getDirsUpdates(): SharedFlow<DataPackage<List<IDirectory>>>
|
||||||
|
+ touchDir(String): Unit
|
||||||
|
+ getFiles(String): Unit
|
||||||
|
+ getSize(): StateFlow<Long>
|
||||||
|
+ isAvailable(): StateFlow<Boolean>
|
||||||
|
+ getDirInfo(String): Unit
|
||||||
|
+ openRead(String): Unit
|
||||||
|
+ moveToTrash(String): Unit
|
||||||
|
+ delete(String): Unit
|
||||||
|
+ touchFile(String): Unit
|
||||||
|
+ getAllDirs(): Unit
|
||||||
|
+ openWrite(String): Unit
|
||||||
|
+ getAllFiles(): Unit
|
||||||
|
+ getDirsFlow(String): Flow<DataPackage<List<IDirectory>>>
|
||||||
|
+ getNumberOfFiles(): StateFlow<Integer>
|
||||||
|
+ getFilesUpdates(): SharedFlow<DataPackage<List<IFile>>>
|
||||||
|
+ setHidden(String, boolean): Unit
|
||||||
|
+ getFileInfo(String): Unit
|
||||||
|
}
|
||||||
|
class IVault {
|
||||||
|
+ getAvailableSpace(): StateFlow<Integer>
|
||||||
|
+ getType(): VaultType
|
||||||
|
+ getTotalSpace(): StateFlow<Integer>
|
||||||
|
+ remove(IStorage): Unit
|
||||||
|
+ createStorage(StorageEncryptionInfo): Unit
|
||||||
|
+ getUuid(): UUID
|
||||||
|
+ isAvailable(): StateFlow<Boolean>
|
||||||
|
+ getStorages(): StateFlow<List<IStorage>>
|
||||||
|
+ createStorage(): Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
package encrypt {
|
||||||
|
class EncryptorWithStaticIv {
|
||||||
|
+ decryptBytesbyte[](byte[])
|
||||||
|
+ encryptStream(java.io.OutputStream): java.io.OutputStream
|
||||||
|
+ decryptStream(java.io.InputStream): java.io.InputStream
|
||||||
|
+ encryptBytesbyte[](byte[])
|
||||||
|
+ dispose(): void
|
||||||
|
+ encryptString(String): String
|
||||||
|
+ decryptString(String): String
|
||||||
|
}
|
||||||
|
class Encryptor {
|
||||||
|
+ AES_SETTINGS: String
|
||||||
|
+ IV_LEN: int
|
||||||
|
+ decryptBytesbyte[](byte[])
|
||||||
|
+ encryptStream(java.io.OutputStream): java.io.OutputStream
|
||||||
|
+ decryptStream(java.io.InputStream): java.io.InputStream
|
||||||
|
+ encryptBytesbyte[](byte[])
|
||||||
|
+ dispose(): void
|
||||||
|
+ encryptString(String): String
|
||||||
|
+ decryptString(String): String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
package datatypes {
|
||||||
|
class Tree {
|
||||||
|
+ getValue(): Unit
|
||||||
|
+ getChildren(): List
|
||||||
|
+ setChildren(List): void
|
||||||
|
}
|
||||||
|
class EncryptKey {
|
||||||
|
+ toAesKey(): SecretKeySpec
|
||||||
|
+ getBytes(): byte[]
|
||||||
|
}
|
||||||
|
class DataPackage {
|
||||||
|
+ getData(): Unit
|
||||||
|
+ isError(): Boolean
|
||||||
|
+ isLoading(): Boolean
|
||||||
|
}
|
||||||
|
class DataPage {
|
||||||
|
+ getPageLength(): int
|
||||||
|
+ getPageIndex(): int
|
||||||
|
+ getHasNext(): Boolean
|
||||||
|
}
|
||||||
|
class StorageEncryptionInfo {
|
||||||
|
+ getEncryptedTestData(): String
|
||||||
|
+ getPathIv(): byte[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
package common.impl {
|
||||||
|
class CommonDirectory {
|
||||||
|
+ getElementsCount(): Integer
|
||||||
|
+ getMetaInfo(): CommonMetaInfo
|
||||||
|
}
|
||||||
|
class CommonStorageMetaInfo {
|
||||||
|
+ getEncInfo(): StorageEncryptionInfo
|
||||||
|
+ getName(): String
|
||||||
|
+ getLastModified(): Instant
|
||||||
|
}
|
||||||
|
class CommonMetaInfo {
|
||||||
|
+ getSize(): long
|
||||||
|
+ getPath(): String
|
||||||
|
+ isDeleted(): boolean
|
||||||
|
+ isHidden(): boolean
|
||||||
|
+ getLastModified(): Instant
|
||||||
|
}
|
||||||
|
class CommonFile {
|
||||||
|
+ getMetaInfo(): IMetaInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
package auth {
|
||||||
|
class RemoteYandexAuthResult {
|
||||||
|
}
|
||||||
|
class RemoteYandexSignInLauncher {
|
||||||
|
+ launch(): void
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
package enums {
|
||||||
|
class VaultType {
|
||||||
|
+ DECRYPTED: VaultType
|
||||||
|
+ LOCAL: VaultType
|
||||||
|
+ YANDEX: VaultType
|
||||||
|
+ valueOf(String): VaultType
|
||||||
|
+ values(): VaultType[]
|
||||||
|
+ getEntries(): EnumEntries<VaultType>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
usecases.ManageStoragesEncryptionUseCase ..> interfaces.IStorageMetaInfo
|
||||||
|
usecases.ManageStoragesEncryptionUseCase ..> interfaces.IStorageInfo
|
||||||
|
usecases.ManageStoragesEncryptionUseCase ..> tasks.TaskProgress
|
||||||
|
usecases.ManageStoragesEncryptionUseCase ..> datatypes.EncryptKey
|
||||||
|
usecases.ManageStoragesEncryptionUseCase ..> encrypt.Encryptor
|
||||||
|
usecases.ManageStoragesEncryptionUseCase ..> interfaces.IStorage
|
||||||
|
usecases.ManageStoragesEncryptionUseCase ..> interfaces.IUnlockManager
|
||||||
|
usecases.ManageStoragesEncryptionUseCase ..> datatypes.StorageEncryptionInfo
|
||||||
|
usecases.RemoveStorageUseCase ..> usecases.ManageStoragesEncryptionUseCase
|
||||||
|
usecases.RemoveStorageUseCase ..> interfaces.IStorage
|
||||||
|
usecases.RemoveStorageUseCase ..> interfaces.IStorageInfo
|
||||||
|
usecases.RemoveStorageUseCase ..> interfaces.IVaultsManager
|
||||||
|
usecases.RemoveStorageUseCase ..> interfaces.IUnlockManager
|
||||||
|
usecases.RemoveStorageUseCase ..> interfaces.IVault
|
||||||
|
tasks.TaskLogLine ..> tasks.TaskLogLevel
|
||||||
|
tasks.PipelineWork ..> tasks.TaskContext
|
||||||
|
interfaces.IVault <|.. interfaces.IYandexVault
|
||||||
|
interfaces.IVaultInfo <|.. interfaces.IYandexVault
|
||||||
|
interfaces.IYandexVault ..> interfaces.IStorage
|
||||||
|
interfaces.IYandexVault ..> interfaces.IVault
|
||||||
|
interfaces.IYandexVault ..> enums.VaultType
|
||||||
|
interfaces.IYandexVault ..> datatypes.StorageEncryptionInfo
|
||||||
|
usecases.GetOpenedStoragesUseCase ..> interfaces.IStorageInfo
|
||||||
|
usecases.GetOpenedStoragesUseCase ..> interfaces.IUnlockManager
|
||||||
|
tasks.TaskContext ..> tasks.TaskLogLevel
|
||||||
|
tasks.TaskContext ..> tasks.TaskProgress
|
||||||
|
tasks.TaskContext ..> tasks.TaskId
|
||||||
|
usecases.ManageLocalVaultUseCase ..> interfaces.IStorageInfo
|
||||||
|
usecases.ManageLocalVaultUseCase ..> interfaces.IVaultsManager
|
||||||
|
usecases.ManageLocalVaultUseCase ..> interfaces.IVault
|
||||||
|
usecases.StorageFileManagementUseCase ..> interfaces.IFile
|
||||||
|
usecases.StorageFileManagementUseCase ..> interfaces.IStorage
|
||||||
|
usecases.StorageFileManagementUseCase ..> interfaces.IStorageInfo
|
||||||
|
usecases.StorageFileManagementUseCase ..> interfaces.IDirectory
|
||||||
|
usecases.StorageFileManagementUseCase ..> interfaces.IStorageAccessor
|
||||||
|
interfaces.IStorageInfo <|.. interfaces.IStorage
|
||||||
|
interfaces.IStorage ..> interfaces.IStorageMetaInfo
|
||||||
|
interfaces.IStorage ..> interfaces.IStorageInfo
|
||||||
|
interfaces.IStorage ..> tasks.TaskProgress
|
||||||
|
interfaces.IStorage ..> interfaces.IStorageAccessor
|
||||||
|
interfaces.IStorage ..> datatypes.StorageEncryptionInfo
|
||||||
|
interfaces.IVaultsManager ..> interfaces.IStorage
|
||||||
|
interfaces.IVaultsManager ..> interfaces.IUnlockManager
|
||||||
|
interfaces.IVaultsManager ..> interfaces.IVault
|
||||||
|
interfaces.IDirectory <|.. common.impl.CommonDirectory
|
||||||
|
common.impl.CommonDirectory ..> common.impl.CommonMetaInfo
|
||||||
|
common.impl.CommonDirectory ..> interfaces.IMetaInfo
|
||||||
|
common.impl.CommonDirectory ..> interfaces.IDirectory
|
||||||
|
tasks.PipelineState ..> tasks.TaskId
|
||||||
|
tasks.PipelineState ..> tasks.PipelineTask
|
||||||
|
interfaces.IStorageMetaInfo <|.. common.impl.CommonStorageMetaInfo
|
||||||
|
common.impl.CommonStorageMetaInfo ..> interfaces.IStorageMetaInfo
|
||||||
|
common.impl.CommonStorageMetaInfo ..> datatypes.StorageEncryptionInfo
|
||||||
|
interfaces.IMetaInfo <|.. common.impl.CommonMetaInfo
|
||||||
|
common.impl.CommonMetaInfo ..> interfaces.IMetaInfo
|
||||||
|
interfaces.IStorageMetaInfo ..> datatypes.StorageEncryptionInfo
|
||||||
|
interfaces.IFile ..> interfaces.IMetaInfo
|
||||||
|
tasks.ITaskOrchestrator ..> tasks.TaskLogLine
|
||||||
|
tasks.ITaskOrchestrator ..> tasks.PipelineState
|
||||||
|
tasks.ITaskOrchestrator ..> tasks.TaskForegroundUiState
|
||||||
|
tasks.ITaskOrchestrator ..> tasks.PipelineWork
|
||||||
|
tasks.ITaskOrchestrator ..> tasks.TaskId
|
||||||
|
interfaces.IStorageInfo ..> interfaces.IStorageMetaInfo
|
||||||
|
interfaces.IStorageInfo ..> interfaces.IStorage
|
||||||
|
usecases.RenameStorageUseCase ..> interfaces.IStorage
|
||||||
|
usecases.RenameStorageUseCase ..> interfaces.IStorageInfo
|
||||||
|
interfaces.IVaultInfo ..> interfaces.IStorageInfo
|
||||||
|
interfaces.IVaultInfo ..> interfaces.IVault
|
||||||
|
interfaces.IVaultInfo ..> enums.VaultType
|
||||||
|
tasks.TaskForegroundItem ..> tasks.TaskProgress
|
||||||
|
tasks.TaskForegroundItem ..> tasks.TaskId
|
||||||
|
interfaces.IUnlockManager ..> interfaces.IStorage
|
||||||
|
interfaces.IUnlockManager ..> datatypes.EncryptKey
|
||||||
|
interfaces.IDirectory ..> interfaces.IMetaInfo
|
||||||
|
interfaces.IStorageAccessor ..> interfaces.IFile
|
||||||
|
interfaces.IStorageAccessor ..> interfaces.IDirectory
|
||||||
|
interfaces.IStorageAccessor ..> datatypes.DataPackage
|
||||||
|
interfaces.IVaultInfo <|.. interfaces.IVault
|
||||||
|
interfaces.IVault ..> interfaces.IStorage
|
||||||
|
interfaces.IVault ..> interfaces.IVaultInfo
|
||||||
|
interfaces.IVault ..> enums.VaultType
|
||||||
|
interfaces.IVault ..> datatypes.StorageEncryptionInfo
|
||||||
|
auth.RemoteYandexSignInLauncher ..> auth.RemoteYandexAuthResult
|
||||||
|
datatypes.DataPackage <|-- datatypes.DataPage
|
||||||
|
datatypes.DataPage ..> datatypes.DataPackage
|
||||||
|
interfaces.IFile <|.. common.impl.CommonFile
|
||||||
|
common.impl.CommonFile ..> interfaces.IMetaInfo
|
||||||
|
common.impl.CommonFile ..> interfaces.IFile
|
||||||
|
tasks.PipelineTask ..> tasks.TaskRunState
|
||||||
|
tasks.PipelineTask ..> tasks.TaskId
|
||||||
|
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
@startuml wallenc_01_start_and_sync
|
||||||
|
' Увеличенный растр для вставки в отчёт (Word / печать)
|
||||||
|
scale 3
|
||||||
|
title
|
||||||
|
Wallenc — старт приложения и параллельная синхронизация
|
||||||
|
(проектное решение; фоновая синхронизация к реализации)
|
||||||
|
end title
|
||||||
|
|
||||||
|
skinparam defaultFontName "DejaVu Sans"
|
||||||
|
skinparam activity {
|
||||||
|
BackgroundColor #F8F8F8
|
||||||
|
BorderColor #333333
|
||||||
|
DiamondBackgroundColor #E8F4FF
|
||||||
|
}
|
||||||
|
skinparam noteBackgroundColor #FFFDE7
|
||||||
|
skinparam noteBorderColor #F9A825
|
||||||
|
|
||||||
|
start
|
||||||
|
|
||||||
|
:Старт приложения (Android);
|
||||||
|
|
||||||
|
:Инициализация Room,
|
||||||
|
загрузка метаданных vault;
|
||||||
|
|
||||||
|
if (Есть сохранённые vault?) then (нет)
|
||||||
|
:Экран «первый запуск» /
|
||||||
|
создание локального vault;
|
||||||
|
else (да)
|
||||||
|
endif
|
||||||
|
|
||||||
|
if (Нужен удалённый провайдер
|
||||||
|
и нет учётной записи?) then (да)
|
||||||
|
:Экран удалённых vault /
|
||||||
|
OAuth Яндекс;
|
||||||
|
else (нет)
|
||||||
|
endif
|
||||||
|
|
||||||
|
partition "**Основной поток (UI)**" {
|
||||||
|
:(A) Главный экран:
|
||||||
|
список локальных и удалённых vault;
|
||||||
|
:Действия пользователя
|
||||||
|
(открыть, зашифровать, содержимое…);
|
||||||
|
}
|
||||||
|
|
||||||
|
fork
|
||||||
|
partition "**Фон: синхронизация (по таймеру)**" #E8F5E9 {
|
||||||
|
note right
|
||||||
|
**Проектная механика (не реализовано в коде)**
|
||||||
|
• Таймер / WorkManager Android
|
||||||
|
• Таблица в Room: UUID **storage_id**
|
||||||
|
для очереди синхронизации
|
||||||
|
• Для каждого storage — **история коммитов**
|
||||||
|
(аналог git): дерево/цепочка снимков
|
||||||
|
• Сервис: сравнение коммитов
|
||||||
|
локально vs удалённо → вычисление diff
|
||||||
|
• Применение изменений →
|
||||||
|
**одинаковое зашифрованное содержимое**
|
||||||
|
на клиенте и у провайдера
|
||||||
|
(ключи на сервер не передаются)
|
||||||
|
end note
|
||||||
|
:По срабатыванию таймера:
|
||||||
|
запуск **SyncService** / Worker;
|
||||||
|
:Чтение из БД списка
|
||||||
|
**UUID storage** из очереди;
|
||||||
|
while (Есть необработанный UUID?) is (да)
|
||||||
|
:Загрузить историю **коммитов**
|
||||||
|
для этого Storage (локально + у провайдера);
|
||||||
|
:Найти расхождения
|
||||||
|
(common ancestor / merge);
|
||||||
|
:Свести содержимое
|
||||||
|
к единому состоянию;
|
||||||
|
:Обновить очередь /
|
||||||
|
метаданные синхронизации;
|
||||||
|
endwhile (нет)
|
||||||
|
}
|
||||||
|
end fork
|
||||||
|
|
||||||
|
stop
|
||||||
|
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
@startuml wallenc_02_vault_lifecycle
|
||||||
|
scale 3
|
||||||
|
title
|
||||||
|
Wallenc — пользовательский поток: жизненный цикл vault
|
||||||
|
и точки постановки в очередь синхронизации (проект)
|
||||||
|
end title
|
||||||
|
|
||||||
|
skinparam defaultFontName "DejaVu Sans"
|
||||||
|
skinparam state {
|
||||||
|
BackgroundColor #F8F8F8
|
||||||
|
BorderColor #333333
|
||||||
|
}
|
||||||
|
skinparam noteBackgroundColor #E3F2FD
|
||||||
|
skinparam noteBorderColor #1565C0
|
||||||
|
|
||||||
|
state "(Б) Список vault" as List
|
||||||
|
|
||||||
|
List --> Create : Создать vault
|
||||||
|
Create --> List : Vault создан
|
||||||
|
|
||||||
|
List --> EncryptDlg : Включить шифрование
|
||||||
|
EncryptDlg --> Encrypting : Подтверждение, мастер-ключ
|
||||||
|
state Encrypting {
|
||||||
|
state "Шифрование данных + запись метаданных" as EncWork
|
||||||
|
}
|
||||||
|
Encrypting --> List : Готово
|
||||||
|
|
||||||
|
note right of Encrypting
|
||||||
|
После успешной записи **коммита**
|
||||||
|
в историю Storage (проект):
|
||||||
|
UUID storage → **очередь синхронизации**
|
||||||
|
в Room (для таймера)
|
||||||
|
end note
|
||||||
|
|
||||||
|
List --> OpenDlg : Открыть зашифрованный
|
||||||
|
OpenDlg --> Opened : Ключ верный
|
||||||
|
OpenDlg --> List : Отмена / неверный ключ
|
||||||
|
|
||||||
|
state Opened {
|
||||||
|
state "Просмотр / работа с содержимым" as Browse
|
||||||
|
}
|
||||||
|
Opened --> List : Закрыть vault / блокировка
|
||||||
|
|
||||||
|
List --> RenameDel : Переименовать / удалить
|
||||||
|
RenameDel --> List : Подтверждение
|
||||||
|
|
||||||
|
note bottom of List
|
||||||
|
**Синхронизация (проект):** любое изменение,
|
||||||
|
порождающее новый **коммит** в Storage,
|
||||||
|
добавляет storage UUID в таблицу очереди;
|
||||||
|
**SyncService** по таймеру обрабатывает очередь,
|
||||||
|
сравнивает истории коммитов с удалённой копией
|
||||||
|
и приводит зашифрованное содержимое
|
||||||
|
к одному состоянию (без передачи ключей).
|
||||||
|
end note
|
||||||
|
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
@startuml wallenc_03_navigation_hub
|
||||||
|
scale 3
|
||||||
|
title
|
||||||
|
Wallenc — навигация от главного экрана
|
||||||
|
и связь с фоновой синхронизацией (проект)
|
||||||
|
end title
|
||||||
|
|
||||||
|
skinparam defaultFontName "DejaVu Sans"
|
||||||
|
skinparam activityBackgroundColor #F8F8F8
|
||||||
|
skinparam activityBorderColor #333333
|
||||||
|
skinparam noteBackgroundColor #FCE4EC
|
||||||
|
skinparam noteBorderColor #C2185B
|
||||||
|
|
||||||
|
start
|
||||||
|
|
||||||
|
:(A) Главный экран:
|
||||||
|
список локальных vault;
|
||||||
|
|
||||||
|
repeat
|
||||||
|
:Ожидание действия пользователя;
|
||||||
|
backward:Назад с подэкрана;
|
||||||
|
switch (Действие?)
|
||||||
|
case (FAB / новый vault)
|
||||||
|
:Создание локального vault;
|
||||||
|
case (Выбор vault)
|
||||||
|
:Карточка / детали vault;
|
||||||
|
case (Удалённые vault)
|
||||||
|
:Экран удалённых vault;
|
||||||
|
if (Нужен OAuth Яндекс?) then (да)
|
||||||
|
:Авторизация Яндекс;
|
||||||
|
endif
|
||||||
|
case (Настройки)
|
||||||
|
:Экран настроек;
|
||||||
|
endswitch
|
||||||
|
repeat while (Пользователь в приложении?) is (да)
|
||||||
|
-> нет;
|
||||||
|
|
||||||
|
stop
|
||||||
|
|
||||||
|
floating note right
|
||||||
|
**Фон: SyncWorker (по таймеру Android) — проект**
|
||||||
|
• Room: таблица очереди с **UUID storage**
|
||||||
|
• Периодический запуск метода синхронизации
|
||||||
|
• Для каждого Storage — история **коммитов** (как git)
|
||||||
|
• Сравнение локальной и удалённой истории,
|
||||||
|
приведение зашифрованного содержимого
|
||||||
|
к одному состоянию (ключи на сервер не уходят)
|
||||||
|
• Работает **независимо** от текущего экрана UI
|
||||||
|
end note
|
||||||
|
|
||||||
|
@enduml
|
||||||
BIN
Report/Другие отчёты Wallenc/puml/wallenc_01_start_and_sync.png
Normal file
|
After Width: | Height: | Size: 255 KiB |
BIN
Report/Другие отчёты Wallenc/puml/wallenc_02_vault_lifecycle.png
Normal file
|
After Width: | Height: | Size: 184 KiB |
BIN
Report/Другие отчёты Wallenc/puml/wallenc_03_navigation_hub.png
Normal file
|
After Width: | Height: | Size: 148 KiB |
122
Report/Другие отчёты Wallenc/Отчёт 1 этап.md
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
# Отчёт по 1-му предварительному этапу производственной практики
|
||||||
|
**Проект:** Wallenc — универсальный кошелёк для безопасного хранения данных на небезопасных хранилищах без собственного сервера.
|
||||||
|
|
||||||
|
## Введение
|
||||||
|
|
||||||
|
На первом этапе практики была выполнена аналитическая и проектная подготовка к разработке мобильного приложения Wallenc. Цель этапа — сформировать техническую основу проекта: определить предметную область, выбрать архитектурный подход, описать требования к системе и зафиксировать технологические решения для последующей реализации.
|
||||||
|
|
||||||
|
Ключевая идея проекта заключается в том, что безопасность пользовательских данных обеспечивается на стороне клиента: данные шифруются до отправки во внешнее хранилище, а расшифрование выполняется только в приложении при наличии корректного ключа. Это позволяет использовать небезопасные или недоверенные хранилища без потери конфиденциальности и без развертывания собственного серверного backend.
|
||||||
|
|
||||||
|
## Анализ предметной области
|
||||||
|
|
||||||
|
В рамках анализа предметной области рассмотрены подходы к хранению чувствительных данных в мобильных приложениях и в облачных хранилищах. Выделены основные требования к таким системам:
|
||||||
|
|
||||||
|
- конфиденциальность данных при хранении и передаче;
|
||||||
|
- отсутствие необходимости доверять инфраструктуре хранилища;
|
||||||
|
- устойчивость к компрометации удалённого провайдера;
|
||||||
|
- разделение логики хранения и логики криптографической защиты;
|
||||||
|
- удобный пользовательский сценарий: создание хранилища, шифрование, открытие, работа с содержимым.
|
||||||
|
|
||||||
|
Сформирован вывод, что для проекта Wallenc приоритетны клиентские криптографические механизмы, унифицированный доступ к разным типам хранилищ и архитектура с чётким разделением слоёв.
|
||||||
|
|
||||||
|
## Обзор аналогичных решений
|
||||||
|
|
||||||
|
### Аналоги
|
||||||
|
|
||||||
|
- **Google Files Secure Folder**
|
||||||
|
Что делает: локально прячет и защищает файлы в папке по PIN/Pattern внутри Android.
|
||||||
|
Минусы: по сути только локальный “сейф” (без нормальной кроссплатформенной синхронизации), ограниченные сценарии переноса между устройствами, не про универсальную модель vault для разных хранилищ.
|
||||||
|
|
||||||
|
- **Proton Pass / Proton Drive**
|
||||||
|
Что делает: end-to-end экосистема для паролей, заметок и файлов с облачной синхронизацией.
|
||||||
|
Минусы: привязка к экосистеме Proton, часть полезных сценариев/объёма и функций зависит от тарифа, меньше гибкости как “универсальный клиент” под разные внешние хранилища.
|
||||||
|
|
||||||
|
- **Bitwarden**
|
||||||
|
Что делает: менеджер паролей/секретов с шифрованием, синхронизацией, офлайн-доступом и self-host вариантом.
|
||||||
|
Минусы: ориентирован в первую очередь на учетные данные и секреты, а не на общий файловый vault; офлайн-сценарии ограничены (часть операций требует синхронизации/сервера).
|
||||||
|
|
||||||
|
- **Cryptomator**
|
||||||
|
Что делает: клиентское шифрование файловых vault перед хранением в облаке (zero-knowledge подход).
|
||||||
|
Минусы: фокус именно на шифровании файлов, а не на расширенной модели “кошелька” с собственной мета-логикой и UI-сценариями; есть ограничения интеграций в зависимости от платформы/сборки и провайдера.
|
||||||
|
|
||||||
|
Проведён обзор классов решений (secure-folder приложения, менеджеры секретов, облачные клиенты с zero-knowledge подходом). По результатам обзора отмечены типовые сильные и слабые стороны:
|
||||||
|
|
||||||
|
**Преимущества аналогов:**
|
||||||
|
|
||||||
|
- понятный пользовательский сценарий хранения;
|
||||||
|
- готовые механизмы шифрования файлов;
|
||||||
|
- интеграция с несколькими хранилищами.
|
||||||
|
|
||||||
|
|
||||||
|
**Ограничения аналогов:**
|
||||||
|
|
||||||
|
- зависимость от собственного backend или закрытой инфраструктуры;
|
||||||
|
- недостаточная прозрачность модели ключей;
|
||||||
|
- ограниченная переносимость между провайдерами.
|
||||||
|
|
||||||
|
|
||||||
|
Это подтвердило актуальность выбранной концепции Wallenc: безопасность без собственного сервера, с переносимой архитектурой хранилищ.
|
||||||
|
|
||||||
|
## Формирование технического задания
|
||||||
|
|
||||||
|
На основании анализа подготовлена структура ТЗ в логике ГОСТ 7.32–2017: определены цели, этапы работ, основные и дополнительные задачи, ожидаемые результаты и направления тестирования.
|
||||||
|
ТЗ синхронизировано с фактическим направлением проекта: приоритет на ядро системы хранения/шифрования и поэтапное расширение функциональности.
|
||||||
|
|
||||||
|
## Изучение архитектурных подходов
|
||||||
|
|
||||||
|
Для проекта принят архитектурный подход **MVVM + Clean Architecture**:
|
||||||
|
|
||||||
|
- **Domain**: интерфейсы и use-case логика (операции над vault и шифрованием);
|
||||||
|
- **Data**: реализации хранилищ, слой доступа к данным, локальная БД, криптографические адаптеры;
|
||||||
|
- **Presentation**: UI-слой, навигация, состояния экранов, ViewModel.
|
||||||
|
|
||||||
|
Такое разделение снижает связность, упрощает тестирование и позволяет независимо развивать локальные и удалённые провайдеры.
|
||||||
|
|
||||||
|
## Проектирование структуры системы
|
||||||
|
|
||||||
|
Спроектирована клиентская система, в которой:
|
||||||
|
|
||||||
|
- локальный vault является базовой реализацией хранения;
|
||||||
|
- зашифрованное представление открывается через отдельный менеджер;
|
||||||
|
- операции чтения/записи выполняются через абстракции доступа к файлам;
|
||||||
|
- служебные данные (метаданные, связи ключей и хранилищ) отделены от пользовательского содержимого.
|
||||||
|
|
||||||
|
Подход ориентирован на дальнейшее подключение удалённых провайдеров (например, облачных API) без изменения доменной модели.
|
||||||
|
|
||||||
|
## Проектирование структуры базы данных
|
||||||
|
|
||||||
|
Определена структура локальной БД для служебной информации приложения:
|
||||||
|
|
||||||
|
- хранение соответствий между исходным хранилищем и его зашифрованным представлением;
|
||||||
|
- хранение метаданных хранилищ;
|
||||||
|
- поддержка восстановления состояния при запуске приложения.
|
||||||
|
|
||||||
|
БД используется как внутренний механизм управления состоянием vault и не хранит пользовательские данные в открытом виде.
|
||||||
|
|
||||||
|
## Выбор технологий
|
||||||
|
|
||||||
|
Для реализации первого этапа и последующей разработки определён следующий стек:
|
||||||
|
|
||||||
|
- **Kotlin**, **Android SDK**;
|
||||||
|
- **Jetpack Compose** (UI);
|
||||||
|
- **Coroutines/Flow** (асинхронность и реактивные потоки);
|
||||||
|
- **Hilt** (dependency injection);
|
||||||
|
- **Room** (локальная БД);
|
||||||
|
- криптографические механизмы на стороне клиента (AES-шифрование данных и служебных атрибутов);
|
||||||
|
- модульная структура проекта (`app`, `presentation`, `domain`, `data`).
|
||||||
|
|
||||||
|
Выбранный стек соответствует целям проекта и обеспечивает масштабируемость.
|
||||||
|
|
||||||
|
## Дополнительные исследования этапа
|
||||||
|
|
||||||
|
На первом этапе также проработаны дополнительные направления:
|
||||||
|
|
||||||
|
- варианты OAuth-аутентификации при работе с удалёнными провайдерами без собственного сервера;
|
||||||
|
- подходы к безопасной работе с ключами шифрования и проверке корректности ключа;
|
||||||
|
- защита структуры данных (скрытие служебных файлов/директорий, минимизация утечек через имена и пути);
|
||||||
|
- предварительный план тестирования (unit, интеграционные и UI-сценарии).
|
||||||
|
|
||||||
|
## Итоги первого этапа
|
||||||
|
|
||||||
|
По результатам первого этапа сформирована целостная проектная база для разработки Wallenc: определены предметная область, архитектурная модель, структура данных, технологический стек и требования к безопасности.
|
||||||
|
Подготовленные материалы позволяют переходить к реализации следующего этапа — построению функционального ядра приложения и расширению сценариев работы с хранилищами, включая синхронизацию зашифрованных данных с удалёнными провайдерами.
|
||||||
229
Report/Другие отчёты Wallenc/Отчёт 2 этап.md
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
# Отчёт по 2-му предварительному этапу производственной практики
|
||||||
|
**Проект:** Wallenc — универсальный кошелёк для безопасного хранения данных на небезопасных хранилищах без собственного сервера.
|
||||||
|
**Период выполнения этапа:** 29.03.2026–19.04.2026
|
||||||
|
|
||||||
|
## Введение
|
||||||
|
|
||||||
|
На втором этапе производственной практики выполнена реализация функционального ядра мобильного приложения Wallenc и ключевых пользовательских сценариев работы с локальными и удалёнными хранилищами.
|
||||||
|
Основной фокус этапа — перевод проектных решений первого этапа в рабочий код: управление vault, шифрование, хранение метаданных и ключевой информации, а также реализация интерфейсов для повседневной работы пользователя.
|
||||||
|
|
||||||
|
В рамках этапа обеспечена практическая готовность базовой версии приложения к использованию: реализованы операции создания, просмотра, переименования, удаления и защиты vault, а также подготовлена инфраструктура для дальнейшего расширения удалённых сценариев.
|
||||||
|
|
||||||
|
Практический результат этапа выражается не только в наличии отдельных модулей, но и в их совместной работе в рамках одного приложения: пользователь может создавать хранилища, включать защиту, открывать и закрывать зашифрованные представления, видеть состояние vault в интерфейсе и выполнять основные операции сопровождения. Все ключевые сценарии выполнены в рамках единой архитектуры, выбранной на первом этапе.
|
||||||
|
|
||||||
|
## Краткая характеристика выполненного этапа
|
||||||
|
|
||||||
|
За период 29.03.2026–19.04.2026 реализован целостный набор функций, закрывающий требования второго предварительного этапа:
|
||||||
|
|
||||||
|
- разработано рабочее ядро управления локальными и удалёнными vault;
|
||||||
|
- реализованы пользовательские экраны и диалоговые сценарии для основных операций;
|
||||||
|
- добавлена авторизация в Яндекс как часть удалённого контура;
|
||||||
|
- внедрено хранение метаданных и служебной информации в Room;
|
||||||
|
- проведено unit-тестирование криптографии и ручное тестирование экранов.
|
||||||
|
|
||||||
|
Таким образом, второй этап завершён с заметным приростом прикладной готовности проекта: архитектура первого этапа переведена в рабочее приложение с подтверждённой функциональностью базового уровня.
|
||||||
|
|
||||||
|
## Реализация основных работ (п. 2.1 ТЗ)
|
||||||
|
|
||||||
|
### 1) Разработка модуля ядра приложения Wallenc
|
||||||
|
|
||||||
|
В рамках ядра приложения реализованы следующие возможности:
|
||||||
|
|
||||||
|
- создание и управление локальными vault;
|
||||||
|
- хранение метаданных vault на устройстве;
|
||||||
|
- включение шифрования выбранного vault с формированием параметров шифрования;
|
||||||
|
- открытие и закрытие зашифрованного представления vault с проверкой корректности ключа;
|
||||||
|
- доступ к содержимому vault через слой абстракции хранилищ;
|
||||||
|
- сокрытие служебных объектов и фильтрация системных директорий при отображении данных пользователю.
|
||||||
|
|
||||||
|
Ядро реализовано в логике модульной архитектуры (domain/data/presentation), что обеспечило разделение бизнес-логики, доступа к данным и UI-слоя.
|
||||||
|
|
||||||
|
Для предотвращения конфликтов при параллельных операциях в ядре используется контроль запущенных задач (например, защита от повторного запуска шифрования одного и того же vault до завершения предыдущей операции). Это повысило устойчивость работы приложения при активном пользовательском взаимодействии.
|
||||||
|
|
||||||
|
**Скриншот локальных vault:**
|
||||||
|

|
||||||
|
|
||||||
|
### 2) Разработка мобильного приложения на Kotlin (Android)
|
||||||
|
|
||||||
|
В пользовательском интерфейсе реализованы ключевые операции работы с vault:
|
||||||
|
|
||||||
|
- отображение списка vault;
|
||||||
|
- переименование и удаление vault;
|
||||||
|
- включение шифрования vault;
|
||||||
|
- открытие зашифрованного vault с использованием мастер-ключа;
|
||||||
|
- просмотр параметров vault (состояние, служебные сведения, статус шифрования);
|
||||||
|
- модуль работы с содержимым vault;
|
||||||
|
- блокировка/закрытие vault.
|
||||||
|
|
||||||
|
Реализованы диалоговые сценарии подтверждения и настройки операций, что повысило управляемость и предсказуемость действий пользователя.
|
||||||
|
|
||||||
|
Интерфейсная часть построена таким образом, чтобы пользователь видел текущее состояние хранилища (зашифровано/не зашифровано, открыто/закрыто) и мог выполнить требуемое действие без перехода в технические служебные экраны. Это улучшило эргономику взаимодействия и снизило число лишних шагов в типовых сценариях.
|
||||||
|
|
||||||
|
**Скриншоты диалогов и операций:**
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
### 3) Реализация модуля работы с небезопасными хранилищами через адаптеры
|
||||||
|
|
||||||
|
Реализован модуль адаптерного взаимодействия с типами хранилищ, не требующий собственного серверного контура приложения.
|
||||||
|
Подход через адаптеры позволил сохранить единый интерфейс работы с vault и упростить расширение списка поддерживаемых провайдеров.
|
||||||
|
|
||||||
|
Технически это важно для дальнейшего масштабирования проекта: добавление нового типа внешнего провайдера не требует переработки пользовательской логики и не нарушает принципы слоистой архитектуры.
|
||||||
|
|
||||||
|
### 4) Взаимодействие мобильного клиента с внешними провайдерами через API/SDK
|
||||||
|
|
||||||
|
На текущем этапе реализована авторизация через Яндекс (OAuth-сценарий) и интеграция соответствующего пользовательского потока в приложение.
|
||||||
|
Выполнена подготовка слоя удалённых vault и связанных сущностей для дальнейшего расширения удалённых операций.
|
||||||
|
|
||||||
|
Важно, что реализованный контур уже обеспечивает прикладной сценарий идентификации пользователя во внешнем провайдере и формирует корректную основу для дальнейшего расширения функциональности удалённых операций.
|
||||||
|
|
||||||
|
**Скриншоты удалённого контура:**
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
### 5) Реализация хранения ключевой информации и метаданных с использованием Room
|
||||||
|
|
||||||
|
Реализован слой локальной БД на Room для хранения:
|
||||||
|
|
||||||
|
- таблиц сопоставления ключевой информации и идентификаторов хранилищ;
|
||||||
|
- таблиц метаданных vault и состояния хранилищ;
|
||||||
|
- данных, необходимых для восстановления состояния приложения.
|
||||||
|
|
||||||
|
Структура БД интегрирована в общий цикл работы приложения и используется как системный слой управления состоянием.
|
||||||
|
|
||||||
|
Использование Room позволило обеспечить устойчивое хранение служебной информации между запусками приложения и централизовать доступ к данным через DAO-слой.
|
||||||
|
|
||||||
|
**Визуализация Room БД:**
|
||||||
|

|
||||||
|
|
||||||
|
### 6) Проведение тестирования приложения
|
||||||
|
|
||||||
|
По итогам этапа проведено тестирование реализованного функционала:
|
||||||
|
|
||||||
|
- выполнено unit-тестирование криптографических компонентов (проверка корректности шифрования/дешифрования и валидации ключа);
|
||||||
|
- выполнено ручное тестирование экранов и основных пользовательских сценариев работы с vault.
|
||||||
|
|
||||||
|
Проведённые проверки подтвердили работоспособность базового функционального ядра второго этапа.
|
||||||
|
|
||||||
|
## Фрагменты реализованного кода
|
||||||
|
|
||||||
|
Ниже приведены показательные фрагменты кода, отражающие ключевые результаты второго этапа.
|
||||||
|
|
||||||
|
### 1) Room-база приложения и состав сущностей
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
@Database(
|
||||||
|
entities = [DbStorageKeyMap::class, DbStorageMetaInfo::class, DbYandexAccount::class],
|
||||||
|
version = 4,
|
||||||
|
exportSchema = false,
|
||||||
|
)
|
||||||
|
abstract class AppDb : IAppDb, RoomDatabase() {
|
||||||
|
abstract override val storageKeyMapDao: StorageKeyMapDao
|
||||||
|
abstract override val storageMetaInfoDao: StorageMetaInfoDao
|
||||||
|
abstract override val yandexAccountDao: YandexAccountDao
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Фрагмент демонстрирует, что на этапе реализована целевая модель хранения метаданных и удалённой учётной информации в Room с разделением доступа через DAO.
|
||||||
|
|
||||||
|
### 2) Логика шифрования и открытия зашифрованного vault в ViewModel
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
fun enableEncryption(storage: IStorageInfo, password: String, encryptPath: Boolean) {
|
||||||
|
val key = EncryptKey(password)
|
||||||
|
viewModelScope.launch {
|
||||||
|
when (manageStoragesEncryptionUseCase.canEncrypt(storage)) {
|
||||||
|
ManageStoragesEncryptionUseCase.CanEncryptResult.Allowed -> {
|
||||||
|
manageStoragesEncryptionUseCase.enableEncryption(storage, key, encryptPath)
|
||||||
|
manageStoragesEncryptionUseCase.openStorage(storage, key, true)
|
||||||
|
_messages.emit("Encryption enabled")
|
||||||
|
}
|
||||||
|
ManageStoragesEncryptionUseCase.CanEncryptResult.AlreadyEncrypted ->
|
||||||
|
_messages.emit("Storage is already encrypted")
|
||||||
|
else -> _messages.emit("Unsupported operation")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Фрагмент иллюстрирует, что включение шифрования реализовано как управляемый сценарий с валидацией состояния, запуском шифрования и последующим открытием зашифрованного представления.
|
||||||
|
|
||||||
|
### 3) UI-операции над удалёнными vault
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
FloatingActionButton(
|
||||||
|
onClick = {
|
||||||
|
if (!uiState.isBusy) viewModel.setAddChoiceVisible(true)
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Icon(Icons.Filled.Add, contentDescription = stringResource(R.string.remote_vaults_add_cd))
|
||||||
|
}
|
||||||
|
...
|
||||||
|
FilledTonalButton(
|
||||||
|
onClick = {
|
||||||
|
viewModel.setAddChoiceVisible(false)
|
||||||
|
viewModel.yandexSignIn.launch { outcome ->
|
||||||
|
when (outcome) {
|
||||||
|
is RemoteYandexAuthResult.Success ->
|
||||||
|
viewModel.onYandexAuthSuccess(outcome.accessToken)
|
||||||
|
is RemoteYandexAuthResult.Failure -> { /* сообщение об ошибке */ }
|
||||||
|
RemoteYandexAuthResult.Cancelled -> { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
В данном фрагменте отражён завершённый пользовательский путь запуска авторизации для удалённого провайдера непосредственно из интерфейса приложения.
|
||||||
|
|
||||||
|
### 4) Unit-тесты криптографического контура
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
@Test
|
||||||
|
fun `test correct key for StorageEncryptionInfo`() {
|
||||||
|
val encInfo = Encryptor.generateEncryptionInfo(key1)
|
||||||
|
val res = Encryptor.checkKey(key = key1, encInfo = encInfo)
|
||||||
|
assertEquals(true, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test incorrect key for StorageEncryptionInfo`() {
|
||||||
|
val encInfo = Encryptor.generateEncryptionInfo(key1)
|
||||||
|
val res = Encryptor.checkKey(key = key2, encInfo = encInfo)
|
||||||
|
assertEquals(false, res)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Тесты подтверждают корректную проверку ключа и являются частью подтверждения работоспособности криптографической логики второго этапа.
|
||||||
|
|
||||||
|
## Реализация дополнительных работ (п. 2.2 ТЗ)
|
||||||
|
|
||||||
|
### 1) Синхронизация зашифрованных данных с удалёнными хранилищами без передачи ключей на сервер
|
||||||
|
|
||||||
|
В рамках второго этапа подготовлена и реализована безопасная база для удалённых сценариев:
|
||||||
|
|
||||||
|
- внедрён поток авторизации пользователя через Яндекс;
|
||||||
|
- реализованы сущности и механизм учёта удалённых vault;
|
||||||
|
- сохранён принцип клиентской защиты данных, при котором ключевая логика шифрования остаётся на стороне приложения.
|
||||||
|
|
||||||
|
Это обеспечивает корректную архитектурную основу для дальнейшего развития синхронизационных операций без передачи ключей на сторону внешних сервисов.
|
||||||
|
|
||||||
|
### 2) Тестирование пользовательского интерфейса
|
||||||
|
|
||||||
|
Проведено ручное тестирование пользовательского интерфейса с проверкой:
|
||||||
|
|
||||||
|
- отображения списков локальных и удалённых vault;
|
||||||
|
- корректности вызова и отработки диалоговых окон;
|
||||||
|
- стабильности выполнения операций создания, переименования, удаления, шифрования, открытия и закрытия vault.
|
||||||
|
|
||||||
|
Ручная проверка проводилась на последовательностях реальных пользовательских действий, что позволило проверить не только отдельные функции, но и их связную работу в рамках типового использования приложения.
|
||||||
|
|
||||||
|
## Итоги второго этапа
|
||||||
|
|
||||||
|
По результатам второго этапа сформировано и протестировано работоспособное ядро мобильного приложения Wallenc.
|
||||||
|
Реализованы ключевые функции управления vault, включения и использования шифрования, хранения служебных данных через Room, а также пользовательские интерфейсы для локальных и удалённых сценариев.
|
||||||
|
|
||||||
|
Этап завершён с практическим результатом в виде функционирующего Android-приложения на Kotlin, готового к дальнейшему расширению функциональности удалённой работы с хранилищами и развитию синхронизационных механизмов.
|
||||||
|
|
||||||
|
В целом второй этап можно считать успешно завершённым: ключевые задачи реализованы, протестированы и представлены в форме, пригодной для демонстрации и последующего развития проекта на следующем этапе практики.
|
||||||
@@ -0,0 +1,584 @@
|
|||||||
|
МИНОБРНАУКИ РОССИИ
|
||||||
|
Федеральное государственное автономное образовательное
|
||||||
|
учреждение высшего образования
|
||||||
|
«ЮЖНЫЙ ФЕДЕРАЛЬНЫЙ УНИВЕРСИТЕТ»
|
||||||
|
|
||||||
|
Институт компьютерных технологий и информационной безопасности
|
||||||
|
Структурное подразделение
|
||||||
|
|
||||||
|
Направление подготовки **09.03.04** — «Программная инженерия» (бакалавриат), профиль **«Методы и средства разработки программного обеспечения»**
|
||||||
|
|
||||||
|
**ОТЧЁТ**
|
||||||
|
**о прохождении практики**
|
||||||
|
|
||||||
|
обучающегося **4** курса, группа **КТбо4-9**
|
||||||
|
|
||||||
|
**Тема:** Разработка мобильного приложения Wallenc — универсального «кошелька» для безопасного хранения данных на небезопасных хранилищах без собственного сервера (Android, Kotlin).
|
||||||
|
|
||||||
|
Фамилия — **Пытков**
|
||||||
|
Имя — **Роман**
|
||||||
|
Отчество — **Евгеньевич**
|
||||||
|
|
||||||
|
**Место практики:** ООО НМФ «Нейротех»
|
||||||
|
|
||||||
|
Вид практики: производственная
|
||||||
|
Тип практики: технологическая (проектно-технологическая)
|
||||||
|
Способ проведения практики: стационарная
|
||||||
|
|
||||||
|
**Сроки прохождения практики:** **с 09.02.2026 г. по 06.05.2026 г.** *(по приказу ЮФУ № 2191-к от 17.02.2026 г.)*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Задание обучающегося на практику согласовано:**
|
||||||
|
|
||||||
|
| Руководитель практики от Университета | Руководитель практики от профильной организации |
|
||||||
|
| --- | --- |
|
||||||
|
| _______________________ Беликов Александр Николаевич | _______________________ Алексеев Дмитрий Михайлович |
|
||||||
|
|
||||||
|
*(По приказу: руководитель от ЮФУ — старший преподаватель кафедры системного анализа и телекоммуникаций Беликов А. Н.; от организации — технический директор Алексеев Д. М., ООО НМФ «Нейротех».)*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Задание обучающегося на практику
|
||||||
|
|
||||||
|
| № | Содержание задания (выполнялось в рамках проекта Wallenc) |
|
||||||
|
| --- | --- |
|
||||||
|
| 1 | **Анализ предметной области.** Требования к хранению чувствительных данных, клиентскому шифрованию и сценариям работы с vault. |
|
||||||
|
| 2 | **Обзор аналогов.** Сравнение решений (secure-folder, менеджеры секретов, zero-knowledge); выводы для концепции Wallenc. |
|
||||||
|
| 3 | **Техническое задание.** Структура ТЗ по ГОСТ 7.32–2017; приоритет ядра хранения и шифрования. |
|
||||||
|
| 4 | **Архитектура.** MVVM + Clean Architecture; структура системы и абстракции хранилищ; задел под удалённые провайдеры. |
|
||||||
|
| 5 | **Проектирование БД.** Служебные сущности Room (метаданные, соответствия, восстановление состояния); без открытого пользовательского контента. |
|
||||||
|
| 6 | **Технологический стек.** Kotlin, Android, Compose, Coroutines/Flow, Hilt, Room, AES на клиенте; модули app / presentation / domain / data. |
|
||||||
|
| 7 | **Доп. исследования (этап 1).** OAuth без своего сервера; ключи и проверка ключа; защита путей/имён; план тестов. |
|
||||||
|
| 8 | **Ядро приложения (этап 2).** Vault, метаданные, шифрование/открытие/закрытие, абстракция хранилищ, защита от гонок при длительных операциях. |
|
||||||
|
| 9 | **Интерфейс (Compose).** Списки vault, диалоги операций, состояние шифрования/открытия, содержимое vault. |
|
||||||
|
| 10 | **Адаптеры хранилищ.** Единый интерфейс vault без серверного backend приложения. |
|
||||||
|
| 11 | **Внешний провайдер.** OAuth Яндекс; слой удалённых vault. |
|
||||||
|
| 12 | **Room.** Таблицы метаданных, ключей, учётных записей; DAO и жизненный цикл приложения. |
|
||||||
|
| 13 | **Доп. (этап 2).** Проект синхронизации без передачи ключей на сервер; ручное UI-тестирование сценариев. |
|
||||||
|
| 14 | **Тестирование.** Unit-тесты криптографии; ручные прогоны экранов. |
|
||||||
|
| 15 | **Отчётность.** Отчёты по этапам и итоговый отчёт с иллюстрациями. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Инструктаж по охране труда, технике безопасности, пожарной безопасности, правилам внутреннего распорядка
|
||||||
|
|
||||||
|
*(До начала практики проводится собрание и вводный инструктаж в соответствии с приказом ЮФУ от 14.10.2016 № 1513 «Об обеспечении безопасности жизни и здоровья обучающихся, работников при проведении учебных и производственных практик» — см. приказ № 2191-к.)*
|
||||||
|
|
||||||
|
| | Инструктаж проведён | Ознакомлен |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| по требованиям охраны труда | ________________________________ «\_\_» \_\_\_\_\_\_\_\_\_ 2026 г. | ________________________________ «\_\_» \_\_\_\_\_\_\_\_\_ 2026 г. |
|
||||||
|
| по технике безопасности | | |
|
||||||
|
| по пожарной безопасности | | |
|
||||||
|
| по правилам внутреннего трудового распорядка | | |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Дневник практики
|
||||||
|
|
||||||
|
| Дата | Выполненные мероприятия в соответствии с заданием на практику |
|
||||||
|
| --- | --- |
|
||||||
|
| **09.02.2026** | Начало производственной практики по приказу ЮФУ. Участие во вводном инструктаже по требованиям охраны труда, техники безопасности, пожарной безопасности и правилам внутреннего распорядка (в соответствии с приказом ЮФУ от 14.10.2016 № 1513). Ознакомление с целями практики и проектом Wallenc. |
|
||||||
|
| **10.02.2026 — 16.02.2026** | **Анализ предметной области (этап 1):** изучение подходов к хранению чувствительных данных в мобильных приложениях и облачных хранилищах. Формулировка требований: конфиденциальность при хранении и передаче; отсутствие необходимости доверять инфраструктуре хранилища; устойчивость к компрометации удалённого провайдера; разделение логики хранения и криптографической защиты; пользовательские сценарии (создание хранилища, шифрование, открытие, работа с содержимым). Вывод о приоритете клиентских криптографических механизмов, унифицированного доступа к разным типам хранилищ и архитектуры с чётким разделением слоёв. |
|
||||||
|
| **17.02.2026 — 28.02.2026** | **Обзор аналогов (этап 1):** детальный разбор **Google Files Secure Folder** (локальный «сейф» по PIN/Pattern; ограничения по кроссплатформенной синхронизации и универсальной модели vault); **Proton Pass / Proton Drive** (E2E-экосистема; привязка к экосистеме Proton, тарифные ограничения, меньшая гибкость как универсального клиента); **Bitwarden** (менеджер секретов; ориентация на учётные данные, ограничения офлайн-сценариев); **Cryptomator** (клиентское шифрование vault в облаке; фокус на файлах, ограничения интеграций). Обобщение по классам решений; фиксация типовых преимуществ (понятный сценарий, готовое шифрование файлов, интеграция с хранилищами) и ограничений (зависимость от backend, прозрачность модели ключей, переносимость). Подтверждение актуальности концепции Wallenc: безопасность без собственного сервера и переносимая архитектура хранилищ. |
|
||||||
|
| **01.03.2026 — 14.03.2026** | **ТЗ и архитектура (этап 1):** подготовка структуры технического задания по логике **ГОСТ 7.32–2017** (цели, этапы, основные и дополнительные задачи, ожидаемые результаты, направления тестирования), синхронизация ТЗ с приоритетом ядра хранения/шифрования. Изучение и закрепление выбора **MVVM + Clean Architecture** с разбиением на Domain (интерфейсы и use case — операции над vault и шифрованием), Data (реализации хранилищ, доступ к данным, локальная БД, криптоадаптеры), Presentation (UI, навигация, состояния экранов, ViewModel). Обоснование снижения связности и возможности независимого развития локальных и удалённых провайдеров. |
|
||||||
|
| **15.03.2026 — 28.03.2026** | **Проектирование системы и БД, стек (этап 1):** проектирование клиентской системы — локальный vault как базовая реализация; отдельный менеджер для открытия зашифрованного представления; операции чтения/записи через абстракции доступа к файлам; отделение служебных данных (метаданные, связи ключей и хранилищ) от пользовательского содержимого; закладка на подключение удалённых провайдеров без изменения доменной модели. Проектирование локальной БД: соответствия исходное/зашифрованное хранилище, метаданные, восстановление состояния при запуске; принцип — БД не хранит пользовательские данные в открытом виде. Выбор стека: Kotlin, Android SDK, Jetpack Compose, Coroutines/Flow, Hilt, Room, AES на клиенте, модули `app`, `presentation`, `domain`, `data`. **Доп. исследования:** OAuth без собственного сервера; работа с ключами и проверка ключа; скрытие служебных путей и имён; план unit/интеграционных/UI-тестов. |
|
||||||
|
| **29.03.2026 — 19.04.2026** | **Реализация (2-й этап, период 29.03.2026–19.04.2026):** разработка ядра — CRUD локальных vault, метаданные, шифрование и параметры, открытие/закрытие с проверкой ключа, абстракция хранилищ, фильтрация системных каталогов и скрытие служебных объектов в UI, защита от повторного запуска шифрования. UI на Compose: списки, диалоги переименования/удаления/шифрования/открытия, параметры vault, содержимое, блокировка; отображение состояния шифрования и открытия. Адаптеры типов хранилищ. **Яндекс OAuth**, слой удалённых vault. **Room:** `DbStorageKeyMap`, `DbStorageMetaInfo`, `DbYandexAccount`, DAO, версия схемы 4. **Тесты:** unit-тесты криптографии (`Encryptor.generateEncryptionInfo`, `checkKey` для верного и неверного ключа); ручные прогоны экранов и сценариев. **Дополнительно:** основа для синхронизации без передачи ключей на сервер; ручное UI-тестирование связных цепочек действий. |
|
||||||
|
| **21.04.2026 — 05.05.2026** | Подготовка иллюстраций к отчёту: скриншоты приложения, схема Room, экран задач и уведомление о статусах размещены в **приложении 1** непосредственно в соответствующих подразделах (пп. 3.2–3.7); подготовка диаграмм user flow и доменной модели (заготовки в п. 5 приложения). Систематизация результатов практики, оформление отчётной документации. |
|
||||||
|
| **06.05.2026** | Завершение практики, защита результатов *(дата окончания по приказу)*. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Анализ проведённой работы в период прохождения практики обучающимся
|
||||||
|
|
||||||
|
| № п/п | Выполненные мероприятия | Анализ проведённой работы |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 1 | Анализ предметной области | Выполнен систематический обзор требований к системам хранения чувствительных данных: учтены конфиденциальность, модель «недоверенное хранилище», разделение криптографии и хранения, пользовательские сценарии. Сформирован вывод о необходимости клиентского шифрования до выгрузки данных и единой абстракции над разными провайдерами — это определило дальнейшую архитектуру Wallenc. |
|
||||||
|
| 2 | Обзор аналогов | По каждому из рассмотренных решений зафиксированы назначение и ограничения (локальный сейф, привязка к экосистеме, ориентация на секреты вместо файлового vault, ограничения интеграций). Сравнение по классам продуктов позволило отделить идею Wallenc от «ещё одного облачного клиента» и обосновать фокус на переносимости и отсутствии собственного backend. |
|
||||||
|
| 3 | Техническое задание | Структура ТЗ по ГОСТ 7.32–2017 обеспечила связность целей, этапов и критериев тестирования. Приоритет ядра хранения и шифрования зафиксирован в ТЗ и затем последовательно реализован на втором этапе без смещения фокуса на второстепенные функции. |
|
||||||
|
| 4 | Архитектура MVVM + Clean Architecture | Разделение на domain/data/presentation снизило связность компонентов: сценарии шифрования и работы с vault выражены через use case, доступ к файлам и Room изолирован в data, интерфейс и состояние — в presentation. Это упростило поэтапное внедрение удалённого контура (Яндекс) без переписывания доменной логики. |
|
||||||
|
| 5 | Проектирование структуры системы и БД | Спроектированные абстракции (локальный vault, менеджер зашифрованного представления, файловые абстракции) соответствуют реализованному коду второго этапа. Роль Room как хранилища **только** служебной информации соблюдена: пользовательский контент в открытом виде в БД не сохраняется, что согласуется с моделью угроз. |
|
||||||
|
| 6 | Выбор технологий и доп. исследования | Стек Kotlin/Compose/Room/Hilt/Coroutines оказался достаточным для реализации всех запланированных на этапе функций. Отдельно проработанные темы OAuth без своего сервера, проверка ключа и минимизация утечек через пути подготовили реализацию Яндекс-авторизации и криптографического контура без противоречий с общей моделью безопасности. |
|
||||||
|
| 7 | Реализация ядра приложения | Реализован полный набор операций жизненного цикла vault и шифрования; контроль параллельных задач снизил риск некорректного состояния при повторных нажатиях. Сокрытие служебных объектов и фильтрация системных директорий улучшили безопасность и удобство отображения содержимого. |
|
||||||
|
| 8 | Пользовательский интерфейс | Интерфейс отражает состояние хранилища (шифрование, открытие) на основных экранах; диалоги подтверждения снижают риск деструктивных ошибок. Реализованы как локальный, так и удалённый контуры в одном приложении. |
|
||||||
|
| 9 | Адаптеры и интеграция с Яндекс | Адаптерный подход подтвердил расширяемость: добавление провайдера не требует переписывания пользовательских сценариев. OAuth-интеграция доведена до рабочего пользовательского потока; подготовлены сущности для развития удалённых операций с сохранением принципа отсутствия передачи ключей на внешние сервисы приложения. |
|
||||||
|
| 10 | Room и метаданные | Внедрение сущностей для ключей, метаданных и учётных записей Яндекс с раздельными DAO обеспечило устойчивое состояние между сеансами и ясную границу ответственности слоя данных. |
|
||||||
|
| 11 | Тестирование | Unit-тесты на `Encryptor` подтверждают корректную генерацию параметров шифрования и валидацию ключа; ручное тестирование покрыло типовые цепочки и взаимодействие модулей в сборке приложения. |
|
||||||
|
| 12 | Документирование | Промежуточные отчёты по этапам зафиксировали содержание работ; итоговый отчёт объединяет аналитику, проектирование, реализацию и результаты проверок с иллюстрациями. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Отзыв руководителя практики от профильной организации
|
||||||
|
|
||||||
|
В процессе прохождения практики обучающийся **Пытков Роман Евгеньевич** выполнил анализ предметной области защищённого хранения данных и обзор аналогичных решений, на основе которых сформулировал требования к мобильному приложению **Wallenc** (клиентское шифрование, работа с недоверенными хранилищами без собственного сервера приложения). Подготовил структуру технического задания и спроектировал архитектуру **MVVM + Clean Architecture**, модель служебных данных и использование **Room** для метаданных и учётных записей внешних провайдеров.
|
||||||
|
|
||||||
|
На втором этапе практики реализовал функциональное ядро: управление **vault**, включение и использование шифрования, пользовательский интерфейс на **Kotlin** и **Jetpack Compose**, адаптерный слой хранилищ и интеграцию **OAuth Яндекс** для удалённого контура. Провёл **unit**-тестирование криптографического модуля и ручную проверку пользовательских сценариев. Все поставленные задачи в рамках практики выполнены **в срок**, с **самостоятельной** постановкой работ и добросовестным отношением к вопросам безопасности и качества кода.
|
||||||
|
|
||||||
|
За время практики зарекомендовал себя **дисциплинированным, ответственным, исполнительным** студентом. Программа производственной (технологической) практики выполнена **своевременно**, **в полном объёме** и с **высоким** качеством результатов.
|
||||||
|
|
||||||
|
**Рекомендуемая оценка — «отлично».**
|
||||||
|
|
||||||
|
Руководитель практики
|
||||||
|
от профильной организации ______________________ / **Алексеев Дмитрий Михайлович** /
|
||||||
|
|
||||||
|
подпись расшифровка подписи
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Отзыв руководителя практики от университета
|
||||||
|
|
||||||
|
Студент **4** курса группы **КТбо4-9** **Пытков Роман Евгеньевич**, направления подготовки **09.03.04** «Программная инженерия» (профиль «Методы и средства разработки программного обеспечения»), в **8** семестре прошёл стационарную **производственную** практику в компании **ООО НМФ «Нейротех»**.
|
||||||
|
|
||||||
|
В период практики **Пытков Р. Е.** работал в качестве **разработчика программного обеспечения** (мобильное приложение **Wallenc** на платформе **Android**). Им были решены следующие задачи:
|
||||||
|
|
||||||
|
1. Анализ предметной области: требования к хранению чувствительных данных, клиентскому шифрованию и сценариям работы с vault.
|
||||||
|
2. Обзор аналогов (secure-folder, менеджеры секретов, zero-knowledge) и выводы для концепции проекта.
|
||||||
|
3. Подготовка структуры технического задания по **ГОСТ 7.32–2017** с приоритетом ядра хранения и шифрования.
|
||||||
|
4. Проектирование архитектуры **MVVM + Clean Architecture**, структуры системы и абстракций хранилищ; задел под удалённые провайдеры.
|
||||||
|
5. Проектирование локальной **БД (Room)**: служебные сущности без хранения пользовательского контента в открытом виде.
|
||||||
|
6. Выбор и обоснование технологического стека (**Kotlin**, **Compose**, **Coroutines/Flow**, **Hilt**, **Room**, криптография на клиенте).
|
||||||
|
7. Дополнительные исследования: **OAuth** без собственного сервера, работа с ключами, минимизация утечек по путям и именам, план тестирования.
|
||||||
|
8. Реализация **ядра приложения**: vault, метаданные, шифрование и открытие/закрытие, защита от гонок при длительных операциях.
|
||||||
|
9. Разработка пользовательского **интерфейса** (**Jetpack Compose**): списки vault, диалоги операций, отображение состояния шифрования и доступа к содержимому.
|
||||||
|
10. Реализация **адаптеров** типов хранилищ с единым интерфейсом без серверного backend приложения.
|
||||||
|
11. Интеграция с внешним провайдером: **OAuth Яндекс**, слой удалённых vault.
|
||||||
|
12. Внедрение **Room**: таблицы метаданных, ключей, учётных записей; **DAO** и использование в жизненном цикле приложения.
|
||||||
|
13. Проектирование контура **синхронизации** зашифрованных данных без передачи ключей на сервер; ручное **UI**-тестирование сценариев.
|
||||||
|
14. **Тестирование**: unit-тесты криптографии; ручные прогоны экранов и пользовательских цепочек.
|
||||||
|
15. **Отчётность**: отчёты по предварительным этапам и итоговый отчёт с иллюстрациями и диаграммами пользовательских потоков.
|
||||||
|
|
||||||
|
За время прохождения практики **Пытков Р. Е.** показал **высокий** уровень теоретической подготовки, **высокую** степень умения и навыков применять знания, полученные в университете, для решения практических задач.
|
||||||
|
|
||||||
|
**Пытковым Р. Е.** проявлены следующие личностные и профессиональные качества: самостоятельность, ответственность, системность в подходе к проектированию, внимание к требованиям информационной безопасности, умение работать с технической документацией и оформлять результаты работы.
|
||||||
|
|
||||||
|
Считаю, что проявленные профессиональные качества **полностью** удовлетворяют потребностям профильной организации, программа практики выполнена **в полном объёме**, сроки выполнения заданий соблюдены **полностью**.
|
||||||
|
|
||||||
|
**Рекомендуемая оценка — «отлично».**
|
||||||
|
|
||||||
|
Руководитель практики
|
||||||
|
от Университета ______________________ / **Беликов Александр Николаевич** /
|
||||||
|
|
||||||
|
подпись расшифровка подписи
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# ПРИЛОЖЕНИЕ 1
|
||||||
|
|
||||||
|
**ОТЧЁТ**
|
||||||
|
**о прохождении производственной практики, технологической практики на тему**
|
||||||
|
|
||||||
|
«Разработка мобильного приложения Wallenc — универсального «кошелька» для безопасного хранения данных на небезопасных хранилищах без собственного сервера (Android, Kotlin)»
|
||||||
|
|
||||||
|
Студента гр. **КТбо4-9** **Пыткова Романа Евгеньевича**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Содержание
|
||||||
|
|
||||||
|
1. [Введение](#введение)
|
||||||
|
2. [Результаты первого предварительного этапа (аналитика и проектирование)](#2-результаты-первого-предварительного-этапа-аналитика-и-проектирование)
|
||||||
|
3. [Результаты второго предварительного этапа (реализация и тестирование)](#3-результаты-второго-предварительного-этапа-реализация-и-тестирование)
|
||||||
|
4. [Фрагменты реализованного кода и пояснения](#4-фрагменты-реализованного-кода-и-пояснения)
|
||||||
|
5. [Дополнительные иллюстрации (диаграммы)](#5-дополнительные-иллюстрации-диаграммы)
|
||||||
|
6. [Заключение](#заключение)
|
||||||
|
7. [Список условных обозначений и сокращений](#список-условных-обозначений-и-сокращений)
|
||||||
|
8. [Использованные источники](#использованные-источники)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Введение
|
||||||
|
|
||||||
|
**Wallenc** — мобильное приложение-кошелёк для безопасного хранения пользовательских данных на недоверенных или потенциально небезопасных хранилищах **без развёртывания собственного сервера** приложения. Ключевая идея: безопасность обеспечивается **на стороне клиента** — данные **шифруются до отправки** во внешнее хранилище, расшифрование выполняется **только в приложении** при наличии корректного ключа. Это позволяет использовать небезопасные или недоверенные хранилища без потери конфиденциальности и без серверного backend приложения.
|
||||||
|
|
||||||
|
**Цель практики** — пройти полный цикл технологической (проектно-технологической) работы: от анализа предметной области и оформления требований до работающего Android-приложения на Kotlin с проверкой ключевых сценариев и тестированием.
|
||||||
|
|
||||||
|
**Задачи:** исследовать предметную область и аналоги; подготовить ТЗ и спроектировать архитектуру и данные; реализовать ядро, UI и интеграцию с внешним провайдером; провести тестирование; оформить отчётность.
|
||||||
|
|
||||||
|
**Объект исследования** — методы клиентской защиты данных в мобильных приложениях.
|
||||||
|
**Предмет исследования** — проектные и программные решения приложения Wallenc.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Результаты первого предварительного этапа (аналитика и проектирование)
|
||||||
|
|
||||||
|
На первом этапе практики выполнена **аналитическая и проектная** подготовка к разработке Wallenc: определена предметная область, выбран архитектурный подход, описаны требования к системе и зафиксированы технологические решения для последующей реализации.
|
||||||
|
|
||||||
|
### 2.1. Анализ предметной области
|
||||||
|
|
||||||
|
Рассмотрены подходы к хранению чувствительных данных в мобильных приложениях и облачных хранилищах. Выделены требования:
|
||||||
|
|
||||||
|
- конфиденциальность данных при хранении и передаче;
|
||||||
|
- отсутствие необходимости доверять инфраструктуре хранилища;
|
||||||
|
- устойчивость к компрометации удалённого провайдера;
|
||||||
|
- разделение логики хранения и логики криптографической защиты;
|
||||||
|
- удобный пользовательский сценарий: создание хранилища, шифрование, открытие, работа с содержимым.
|
||||||
|
|
||||||
|
**Вывод:** для Wallenc приоритетны клиентские криптографические механизмы, унифицированный доступ к разным типам хранилищ и архитектура с чётким разделением слоёв.
|
||||||
|
|
||||||
|
### 2.2. Обзор аналогичных решений
|
||||||
|
|
||||||
|
**Google Files Secure Folder** — локально прячет и защищает файлы в папке по PIN/Pattern внутри Android. **Ограничения:** по сути только локальный «сейф» без полноценной кроссплатформенной синхронизации; ограниченные сценарии переноса между устройствами; не универсальная модель vault для разных хранилищ.
|
||||||
|
|
||||||
|
**Proton Pass / Proton Drive** — E2E-экосистема для паролей, заметок и файлов с облачной синхронизацией. **Ограничения:** привязка к экосистеме Proton; часть сценариев и объёма зависит от тарифа; меньше гибкости как универсального клиента под разные внешние хранилища.
|
||||||
|
|
||||||
|
**Bitwarden** — менеджер паролей/секретов с шифрованием, синхронизацией, офлайн-доступом и вариантом self-host. **Ограничения:** ориентация на учётные данные и секреты, а не на общий файловый vault; офлайн-сценарии ограничены (часть операций требует синхронизации/сервера).
|
||||||
|
|
||||||
|
**Cryptomator** — клиентское шифрование файловых vault перед хранением в облаке (zero-knowledge). **Ограничения:** фокус на шифровании файлов, а не на расширенной модели «кошелька» с собственной мета-логикой и UI-сценариями; ограничения интеграций в зависимости от платформы/сборки и провайдера.
|
||||||
|
|
||||||
|
Проведён обзор **классов решений:** secure-folder приложения, менеджеры секретов, облачные клиенты с zero-knowledge.
|
||||||
|
|
||||||
|
**Типовые преимущества аналогов:** понятный пользовательский сценарий хранения; готовые механизмы шифрования файлов; интеграция с несколькими хранилищами.
|
||||||
|
|
||||||
|
**Типовые ограничения:** зависимость от собственного backend или закрытой инфраструктуры; недостаточная прозрачность модели ключей; ограниченная переносимость между провайдерами.
|
||||||
|
|
||||||
|
Это **подтвердило актуальность** концепции Wallenc: безопасность без собственного сервера и **переносимая** архитектура хранилищ.
|
||||||
|
|
||||||
|
### 2.3. Формирование технического задания
|
||||||
|
|
||||||
|
На основании анализа подготовлена **структура ТЗ** в логике **ГОСТ 7.32–2017**: цели, этапы работ, основные и дополнительные задачи, ожидаемые результаты, направления тестирования. ТЗ **синхронизировано** с направлением проекта: приоритет на ядро системы хранения и шифрования и **поэтапное** расширение функциональности.
|
||||||
|
|
||||||
|
### 2.4. Изучение архитектурных подходов
|
||||||
|
|
||||||
|
Принят подход **MVVM + Clean Architecture**:
|
||||||
|
|
||||||
|
- **Domain** — интерфейсы и use case (операции над vault и шифрованием);
|
||||||
|
- **Data** — реализации хранилищ, слой доступа к данным, локальная БД, криптографические адаптеры;
|
||||||
|
- **Presentation** — UI-слой, навигация, состояния экранов, ViewModel.
|
||||||
|
|
||||||
|
Такое разделение **снижает связность**, **упрощает тестирование** и позволяет **независимо** развивать локальные и удалённые провайдеры.
|
||||||
|
|
||||||
|
### 2.5. Проектирование структуры системы
|
||||||
|
|
||||||
|
Спроектирована клиентская система, в которой:
|
||||||
|
|
||||||
|
- локальный vault — базовая реализация хранения;
|
||||||
|
- зашифрованное представление открывается через **отдельный менеджер**;
|
||||||
|
- операции чтения/записи выполняются через **абстракции доступа к файлам**;
|
||||||
|
- служебные данные (метаданные, связи ключей и хранилищ) **отделены** от пользовательского содержимого.
|
||||||
|
|
||||||
|
Подход ориентирован на дальнейшее подключение удалённых провайдеров (в т.ч. облачных API) **без изменения доменной модели**.
|
||||||
|
|
||||||
|
### 2.6. Проектирование структуры базы данных
|
||||||
|
|
||||||
|
Определена структура локальной БД для **служебной** информации:
|
||||||
|
|
||||||
|
- соответствия между исходным хранилищем и зашифрованным представлением;
|
||||||
|
- метаданные хранилищ;
|
||||||
|
- поддержка восстановления состояния при запуске приложения.
|
||||||
|
|
||||||
|
БД используется как внутренний механизм управления состоянием vault и **не хранит** пользовательские данные в открытом виде.
|
||||||
|
|
||||||
|
### 2.7. Выбор технологий
|
||||||
|
|
||||||
|
Определён стек:
|
||||||
|
|
||||||
|
- **Kotlin**, **Android SDK**;
|
||||||
|
- **Jetpack Compose** (UI);
|
||||||
|
- **Coroutines/Flow** (асинхронность и реактивные потоки);
|
||||||
|
- **Hilt** (внедрение зависимостей);
|
||||||
|
- **Room** (локальная БД);
|
||||||
|
- криптография на клиенте (**AES** для данных и служебных атрибутов);
|
||||||
|
- модульная структура: `app`, `presentation`, `domain`, `data`.
|
||||||
|
|
||||||
|
Стек согласован с целями проекта и обеспечивает **масштабируемость**.
|
||||||
|
|
||||||
|
### 2.8. Дополнительные исследования первого этапа
|
||||||
|
|
||||||
|
- варианты **OAuth** при работе с удалёнными провайдерами без собственного сервера;
|
||||||
|
- безопасная работа с **ключами** шифрования и **проверка корректности** ключа;
|
||||||
|
- защита структуры данных: скрытие служебных файлов и каталогов, минимизация утечек через имена и пути;
|
||||||
|
- предварительный план тестирования: **unit**, интеграционные и **UI**-сценарии.
|
||||||
|
|
||||||
|
### 2.9. Итоги первого этапа
|
||||||
|
|
||||||
|
Сформирована целостная **проектная база**: предметная область, архитектурная модель, структура данных, технологический стек, требования к безопасности. Материалы позволили перейти к **реализации функционального ядра** и расширению сценариев, включая синхронизацию зашифрованных данных с удалёнными провайдерами.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Результаты второго предварительного этапа (реализация и тестирование)
|
||||||
|
|
||||||
|
**Период выполнения этапа:** 29.03.2026–19.04.2026.
|
||||||
|
|
||||||
|
На втором этапе выполнена **реализация функционального ядра** Wallenc и ключевых пользовательских сценариев для локальных и удалённых хранилищ. Основной фокус — **перевод проектных решений первого этапа в рабочий код**: управление vault, шифрование, метаданные и ключевая информация, интерфейсы для повседневной работы пользователя.
|
||||||
|
|
||||||
|
Обеспечена **практическая готовность** базовой версии: операции создания, просмотра, переименования, удаления и защиты vault; инфраструктура для расширения удалённых сценариев. Модули работают **совместно** в одном приложении: пользователь создаёт хранилища, включает защиту, открывает и закрывает зашифрованные представления, видит состояние vault в интерфейсе, выполняет операции сопровождения. Сценарии реализованы в рамках **единой архитектуры**, выбранной на первом этапе.
|
||||||
|
|
||||||
|
### 3.1. Краткая характеристика выполненного этапа
|
||||||
|
|
||||||
|
За период **29.03.2026–19.04.2026** реализовано:
|
||||||
|
|
||||||
|
- рабочее ядро управления **локальными и удалёнными** vault;
|
||||||
|
- пользовательские экраны и **диалоговые** сценарии основных операций;
|
||||||
|
- авторизация в **Яндекс** как часть удалённого контура;
|
||||||
|
- хранение метаданных и служебной информации в **Room**;
|
||||||
|
- **unit**-тестирование криптографии и **ручное** тестирование экранов.
|
||||||
|
|
||||||
|
Этап завершён с заметным приростом прикладной готовности: архитектура первого этапа переведена в **работающее приложение** с подтверждённой функциональностью базового уровня.
|
||||||
|
|
||||||
|
### 3.2. Разработка модуля ядра приложения Wallenc
|
||||||
|
|
||||||
|
Реализованы возможности:
|
||||||
|
|
||||||
|
- создание и управление **локальными** vault;
|
||||||
|
- хранение **метаданных** vault на устройстве;
|
||||||
|
- включение **шифрования** выбранного vault с формированием параметров шифрования;
|
||||||
|
- **открытие и закрытие** зашифрованного представления с проверкой корректности ключа;
|
||||||
|
- доступ к содержимому через **слой абстракции** хранилищ;
|
||||||
|
- **сокрытие** служебных объектов и **фильтрация** системных директорий при отображении данных пользователю.
|
||||||
|
|
||||||
|
Ядро реализовано в логике **domain / data / presentation**. Для предотвращения конфликтов при параллельных операциях используется **контроль запущенных задач** (в частности, защита от повторного запуска шифрования одного и того же vault до завершения предыдущей операции), что повышает устойчивость при активном взаимодействии пользователя с приложением.
|
||||||
|
|
||||||
|
**Иллюстрация — список локальных vault в приложении:**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 3.3. Разработка мобильного приложения на Kotlin (Android)
|
||||||
|
|
||||||
|
В UI реализованы операции:
|
||||||
|
|
||||||
|
- отображение **списка** vault;
|
||||||
|
- **переименование** и **удаление**;
|
||||||
|
- **включение шифрования**;
|
||||||
|
- **открытие** зашифрованного vault с использованием мастер-ключа;
|
||||||
|
- **просмотр параметров** vault (состояние, служебные сведения, статус шифрования);
|
||||||
|
- модуль работы с **содержимым** vault;
|
||||||
|
- **блокировка/закрытие** vault.
|
||||||
|
|
||||||
|
Реализованы **диалоговые** сценарии подтверждения и настройки операций. Интерфейс построен так, чтобы пользователь видел текущее состояние (зашифровано/не зашифровано, открыто/закрыто) и выполнял действия **без** перехода на технические служебные экраны — это улучшает эргономику и снижает число лишних шагов.
|
||||||
|
|
||||||
|
**Иллюстрации — диалоги включения шифрования, открытия/закрытия зашифрованного vault, переименования и удаления:**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 3.4. Модуль работы с небезопасными хранилищами через адаптеры
|
||||||
|
|
||||||
|
Реализован модуль **адаптерного** взаимодействия с типами хранилищ **без** собственного серверного контура приложения. Подход через адаптеры сохраняет **единый интерфейс** работы с vault и упрощает расширение списка провайдеров. Добавление нового типа внешнего провайдера **не требует** переработки пользовательской логики и **не нарушает** слоистую архитектуру.
|
||||||
|
|
||||||
|
### 3.5. Взаимодействие с внешними провайдерами через API/SDK
|
||||||
|
|
||||||
|
Реализована **авторизация через Яндекс** (OAuth) и интеграция пользовательского потока в приложение. Подготовлен слой **удалённых** vault и связанных сущностей для дальнейшего расширения удалённых операций. Реализованный контур обеспечивает сценарий **идентификации** пользователя во внешнем провайдере и формирует основу для расширения удалённой функциональности.
|
||||||
|
|
||||||
|
**Иллюстрации — экран удалённых vault и сценарий добавления с авторизацией через Яндекс:**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 3.6. Хранение ключевой информации и метаданных (Room)
|
||||||
|
|
||||||
|
Реализован слой локальной БД для:
|
||||||
|
|
||||||
|
- таблиц сопоставления **ключевой информации** и идентификаторов хранилищ;
|
||||||
|
- таблиц **метаданных** vault и состояния хранилищ;
|
||||||
|
- данных, необходимых для **восстановления состояния** приложения.
|
||||||
|
|
||||||
|
Структура БД интегрирована в общий цикл работы приложения как **системный** слой управления состоянием. Room обеспечивает устойчивое хранение служебной информации между запусками и **централизованный** доступ через DAO.
|
||||||
|
|
||||||
|
**Иллюстрация — визуализация схемы Room (служебные сущности приложения):**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 3.7. Тестирование приложения
|
||||||
|
|
||||||
|
Параллельно с ручной проверкой экранов велся учёт задач в трекере: на экране задач отображались этапы работ по проекту, а push-уведомление фиксировало обновление статусов — это упорядочивало регрессию сценариев и снижало риск пропустить проверку после изменений в коде.
|
||||||
|
|
||||||
|
**Иллюстрации — экран задач и уведомление о статусе задач:**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- **Unit-тесты** криптографических компонентов: проверка корректности шифрования/дешифрования и валидации ключа.
|
||||||
|
- **Ручное** тестирование экранов и основных пользовательских сценариев работы с vault.
|
||||||
|
|
||||||
|
Проверки подтвердили **работоспособность** базового функционального ядра второго этапа.
|
||||||
|
|
||||||
|
### 3.8. Дополнительные работы по п. 2.2 ТЗ
|
||||||
|
|
||||||
|
**Синхронизация зашифрованных данных с удалёнными хранилищами без передачи ключей на сервер:** подготовлена безопасная база — поток авторизации через Яндекс; сущности и механизм учёта удалённых vault; сохранён принцип клиентской защиты (ключевая логика шифрования на стороне приложения). Это даёт архитектурную основу для дальнейшего развития синхронизации **без** передачи ключей на сторону внешних сервисов.
|
||||||
|
|
||||||
|
**Тестирование пользовательского интерфейса:** проверены отображение списков локальных и удалённых vault, корректность вызова и отработки диалогов, стабильность операций создания, переименования, удаления, шифрования, открытия и закрытия vault. Проверка велась на **последовательностях реальных пользовательских действий**, что позволило оценить связную работу функций в типовом использовании.
|
||||||
|
|
||||||
|
### 3.9. Итоги второго этапа
|
||||||
|
|
||||||
|
Сформировано и протестировано **работоспособное ядро** Wallenc: управление vault, шифрование, Room, UI для локальных и удалённых сценариев. Получен **функционирующий** Android-проект на Kotlin, готовый к дальнейшему расширению удалённой работы с хранилищами и синхронизационных механизмов. Этап можно считать **успешно завершённым**: ключевые задачи реализованы, протестированы и представлены в форме, пригодной для демонстрации и развития проекта.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Фрагменты реализованного кода и пояснения
|
||||||
|
|
||||||
|
### 4.1. Room-база приложения и состав сущностей
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
@Database(
|
||||||
|
entities = [DbStorageKeyMap::class, DbStorageMetaInfo::class, DbYandexAccount::class],
|
||||||
|
version = 4,
|
||||||
|
exportSchema = false,
|
||||||
|
)
|
||||||
|
abstract class AppDb : IAppDb, RoomDatabase() {
|
||||||
|
abstract override val storageKeyMapDao: StorageKeyMapDao
|
||||||
|
abstract override val storageMetaInfoDao: StorageMetaInfoDao
|
||||||
|
abstract override val yandexAccountDao: YandexAccountDao
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Пояснение:** на этапе реализована целевая модель хранения метаданных и удалённой учётной информации в Room с разделением доступа через DAO.
|
||||||
|
|
||||||
|
### 4.2. Логика шифрования и открытия зашифрованного vault в ViewModel
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
fun enableEncryption(storage: IStorageInfo, password: String, encryptPath: Boolean) {
|
||||||
|
val key = EncryptKey(password)
|
||||||
|
viewModelScope.launch {
|
||||||
|
when (manageStoragesEncryptionUseCase.canEncrypt(storage)) {
|
||||||
|
ManageStoragesEncryptionUseCase.CanEncryptResult.Allowed -> {
|
||||||
|
manageStoragesEncryptionUseCase.enableEncryption(storage, key, encryptPath)
|
||||||
|
manageStoragesEncryptionUseCase.openStorage(storage, key, true)
|
||||||
|
_messages.emit("Encryption enabled")
|
||||||
|
}
|
||||||
|
ManageStoragesEncryptionUseCase.CanEncryptResult.AlreadyEncrypted ->
|
||||||
|
_messages.emit("Storage is already encrypted")
|
||||||
|
else -> _messages.emit("Unsupported operation")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Пояснение:** включение шифрования — управляемый сценарий с валидацией состояния, запуском шифрования и последующим открытием зашифрованного представления.
|
||||||
|
|
||||||
|
### 4.3. UI-операции над удалёнными vault (фрагмент)
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
FloatingActionButton(
|
||||||
|
onClick = {
|
||||||
|
if (!uiState.isBusy) viewModel.setAddChoiceVisible(true)
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Icon(Icons.Filled.Add, contentDescription = stringResource(R.string.remote_vaults_add_cd))
|
||||||
|
}
|
||||||
|
...
|
||||||
|
FilledTonalButton(
|
||||||
|
onClick = {
|
||||||
|
viewModel.setAddChoiceVisible(false)
|
||||||
|
viewModel.yandexSignIn.launch { outcome ->
|
||||||
|
when (outcome) {
|
||||||
|
is RemoteYandexAuthResult.Success ->
|
||||||
|
viewModel.onYandexAuthSuccess(outcome.accessToken)
|
||||||
|
is RemoteYandexAuthResult.Failure -> { /* сообщение об ошибке */ }
|
||||||
|
RemoteYandexAuthResult.Cancelled -> { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Пояснение:** отражён пользовательский путь запуска авторизации для удалённого провайдера из интерфейса приложения.
|
||||||
|
|
||||||
|
### 4.4. Unit-тесты криптографического контура
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
@Test
|
||||||
|
fun `test correct key for StorageEncryptionInfo`() {
|
||||||
|
val encInfo = Encryptor.generateEncryptionInfo(key1)
|
||||||
|
val res = Encryptor.checkKey(key = key1, encInfo = encInfo)
|
||||||
|
assertEquals(true, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test incorrect key for StorageEncryptionInfo`() {
|
||||||
|
val encInfo = Encryptor.generateEncryptionInfo(key1)
|
||||||
|
val res = Encryptor.checkKey(key = key2, encInfo = encInfo)
|
||||||
|
assertEquals(false, res)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Пояснение:** тесты подтверждают корректную проверку ключа и работоспособность криптографической логики.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Дополнительные иллюстрации (диаграммы)
|
||||||
|
|
||||||
|
Скриншоты работы приложения, схема Room, экран задач и уведомление о статусах приведены **в соответствующих подразделах** раздела 3 приложения (пп. 3.2–3.7).
|
||||||
|
|
||||||
|
Ниже — **три диаграммы пользовательских потоков** для Wallenc в стиле отчёта по практике (аналог блок-схем из примера коллеги). На всех схемах отдельно показана **проектная** (ещё не реализованная в коде) механика **синхронизации**: в Room хранятся записи с **UUID** хранилищ (`storage`), подлежащих синхронизации; по **таймеру** (или периодическому Worker’у Android) запускается сервис синхронизации; у каждого **Storage** ведётся **история коммитов** (аналог git); сервис **находит отличия** между локальной и удалённой историей и **приводит зашифрованное содержимое** к одному состоянию **без передачи ключей** на сторону провайдера.
|
||||||
|
|
||||||
|
Исходники **PlantUML**: каталог `Отчёт практика/puml/` (`wallenc-01-start-and-sync.puml`, `wallenc-02-vault-lifecycle.puml`, `wallenc-03-navigation-hub.puml`). Растеризация: PNG сгенерированы командой
|
||||||
|
`/usr/lib/jvm/java-21-openjdk/bin/java -jar plantuml.jar -tpng …`
|
||||||
|
(системный вызов `plantuml` в среде может требовать Java 11+).
|
||||||
|
|
||||||
|
### 5.1. Старт приложения и параллельная синхронизация
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 5.2. Жизненный цикл vault и постановка в очередь синхронизации
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 5.3. Навигация с главного экрана и независимый SyncWorker
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 5.4. Диаграмма классов модуля `domain`
|
||||||
|
|
||||||
|
Сгенерирована из **скомпилированных** `.class` (Kotlin → JVM) плагином **code-atlas** (`./gradlew :domain:generateDiagrams`), исходник PlantUML: `puml/domain-classdiagram.puml`.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Заключение
|
||||||
|
|
||||||
|
По результатам **первого** этапа сформирована проектная база Wallenc: предметная область, обзор аналогов, ТЗ по ГОСТ 7.32–2017, архитектура MVVM + Clean Architecture, структура системы и локальной БД, технологический стек и дополнительные исследования по OAuth, ключам и тестированию.
|
||||||
|
|
||||||
|
По результатам **второго** этапа получено **работоспособное** ядро приложения на Kotlin/Android с Jetpack Compose, Hilt и Room, реализованы ключевые пользовательские сценарии, интеграция с Яндекс OAuth и контур тестирования (unit и ручной).
|
||||||
|
|
||||||
|
Итог практики соответствует целям направления и приказу о прохождении практики **с 09.02.2026 по 06.05.2026** и создаёт основу для дальнейшего развития синхронизации зашифрованных данных и поддержки дополнительных провайдеров хранения.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Список условных обозначений и сокращений
|
||||||
|
|
||||||
|
| Обозначение | Расшифровка |
|
||||||
|
| --- | --- |
|
||||||
|
| API | Application Programming Interface |
|
||||||
|
| DAO | Data Access Object |
|
||||||
|
| E2E | End-to-end |
|
||||||
|
| MVVM | Model — View — ViewModel |
|
||||||
|
| OAuth | протокол авторизации |
|
||||||
|
| UI | пользовательский интерфейс |
|
||||||
|
| CRUD | создание, чтение, изменение, удаление |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Использованные источники
|
||||||
|
|
||||||
|
Ниже приведены нормативные документы, официальная техническая документация и иные материалы, на которые опирались при подготовке отчёта и реализации проекта Wallenc. Для страниц в сети Интернет указана **дата обращения: 23.04.2026**.
|
||||||
|
|
||||||
|
1. ГОСТ 7.32—2017. Система стандартов по информации, библиотечному и издательскому делу. Отчёт о научно-исследовательской работе. Структура и правила оформления. — М.: Стандартинформ, 2017. — Публичная электронная копия официального издания для ознакомления [Электронный ресурс] // Томский государственный университет. — URL: https://tsu.ru/upload/medialibrary/235/gost_7.32_2017.pdf (дата обращения: 23.04.2026).
|
||||||
|
2. Приказ ректора ЮФУ № 2191-к от 17.02.2026 г. о прохождении практики *(внутренний нормативный документ университета; текст — в распоряжении кафедры и обучающегося)*.
|
||||||
|
3. Kotlin Documentation [Электронный ресурс] // JetBrains и Kotlin Foundation. — URL: https://kotlinlang.org/docs/home.html (дата обращения: 23.04.2026).
|
||||||
|
4. Guide to app architecture [Электронный ресурс] // Android Developers. — URL: https://developer.android.com/topic/architecture (дата обращения: 23.04.2026).
|
||||||
|
5. ViewModel overview [Электронный ресурс] // Android Developers. — URL: https://developer.android.com/topic/libraries/architecture/viewmodel (дата обращения: 23.04.2026).
|
||||||
|
6. Get started with Jetpack Compose [Электронный ресурс] // Android Developers. — URL: https://developer.android.com/develop/ui/compose/documentation (дата обращения: 23.04.2026).
|
||||||
|
7. Save data in a local database using Room [Электронный ресурс] // Android Developers. — URL: https://developer.android.com/training/data-storage/room (дата обращения: 23.04.2026).
|
||||||
|
8. Hilt [Электронный ресурс] // Android Developers. — URL: https://developer.android.com/training/dependency-injection/hilt-android (дата обращения: 23.04.2026).
|
||||||
|
9. Kotlin coroutines [Электронный ресурс] // Kotlin Documentation. — URL: https://kotlinlang.org/docs/coroutines-overview.html (дата обращения: 23.04.2026).
|
||||||
|
10. Hardt D. The OAuth 2.0 Authorization Framework. RFC 6749 [Электронный ресурс] // IETF. — октябрь 2012. — URL: https://datatracker.ietf.org/doc/html/rfc6749 (дата обращения: 23.04.2026).
|
||||||
|
11. OAuth для сервисов Яндекса [Электронный ресурс] // Яндекс ID для разработчиков. — URL: https://yandex.ru/dev/id/doc/ru/ (дата обращения: 23.04.2026).
|
||||||
|
12. NIST FIPS 197. Advanced Encryption Standard (AES) [Электронный ресурс]. — URL: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197.pdf (дата обращения: 23.04.2026).
|
||||||
|
13. NIST Special Publication 800-38A. Recommendation for Block Cipher Modes of Operation: Methods and Techniques [Электронный ресурс] // NIST CSRC. — URL: https://csrc.nist.gov/pubs/sp/800/38/a/final (дата обращения: 23.04.2026).
|
||||||
|
14. PlantUML Language Reference Guide — Class diagram [Электронный ресурс]. — URL: https://plantuml.com/class-diagram (дата обращения: 23.04.2026).
|
||||||
|
15. Plugin: io.github.euledge.code-atlas [Электронный ресурс] // Gradle Plugin Portal. — URL: https://plugins.gradle.org/plugin/io.github.euledge.code-atlas (дата обращения: 23.04.2026).
|
||||||
|
16. euledge/code-atlas [Электронный ресурс] // GitHub. — URL: https://github.com/euledge/code-atlas (дата обращения: 23.04.2026).
|
||||||
|
17. Use a PIN to lock your Safe folder in Files by Google [Электронный ресурс] // Google Help. — URL: https://support.google.com/files/answer/9935263 (дата обращения: 23.04.2026).
|
||||||
|
18. Cryptomator Documentation [Электронный ресурс]. — URL: https://docs.cryptomator.org/en/latest/ (дата обращения: 23.04.2026).
|
||||||
|
19. Bitwarden Help Center [Электронный ресурс]. — URL: https://bitwarden.com/help/ (дата обращения: 23.04.2026).
|
||||||
|
20. Martin R. C. The Clean Architecture [Электронный ресурс] // blog.cleancoder.com. — 13.08.2012. — URL: https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html (дата обращения: 23.04.2026).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Конец отчёта.*
|
||||||
98
Report/Пояснительная_записка_ПытковРЕ.typ
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
#import "@preview/modern-g7-32:0.2.0": abstract, appendix-heading, appendixes, enum-numbering, gost
|
||||||
|
|
||||||
|
#set enum(numbering: enum-numbering)
|
||||||
|
#set heading(numbering: "1.1.1.1")
|
||||||
|
|
||||||
|
#show: gost.with(
|
||||||
|
ministry: "Министерство науки и высшего образования Российской Федерации",
|
||||||
|
organization: (
|
||||||
|
full: "Федеральное государственное автономное образовательное учреждение высшего образования «Южный федеральный университет»",
|
||||||
|
short: "ЮФУ",
|
||||||
|
),
|
||||||
|
about: "пояснительной записке",
|
||||||
|
subject: "разработке мобильного приложения Wallenc — универсального кошелька для безопасного хранения данных на небезопасных хранилищах без собственного сервера",
|
||||||
|
approved-by: (
|
||||||
|
name: "Беликов А. Н.",
|
||||||
|
position: "старший преподаватель кафедры системного анализа и телекоммуникаций",
|
||||||
|
),
|
||||||
|
manager: (
|
||||||
|
name: "Беликов А. Н.",
|
||||||
|
position: "научный руководитель ВКР, старший преподаватель",
|
||||||
|
),
|
||||||
|
city: "Таганрог",
|
||||||
|
year: 2026,
|
||||||
|
performers: (
|
||||||
|
(name: "Пытков Р. Е.", position: "студент гр. КТбо4-9"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
#import "includes/common.typ": pz-fig
|
||||||
|
|
||||||
|
#abstract(
|
||||||
|
"мобильное приложение",
|
||||||
|
"клиентское шифрование",
|
||||||
|
"Android",
|
||||||
|
"vault",
|
||||||
|
"zero-knowledge",
|
||||||
|
"синхронизация",
|
||||||
|
)[
|
||||||
|
Пояснительная записка посвящена разработке мобильного приложения Wallenc — клиентского кошелька для безопасного хранения данных на недоверенных хранилищах без собственного сервера приложения. Описаны анализ предметной области и аналогов, формирование требований, проектирование архитектуры и пользовательского интерфейса, программная реализация на Kotlin (Android, Jetpack Compose, Room, Hilt), тестирование и краткая экономическая оценка. Реализованы управление локальными и удалёнными vault, клиентское AES-шифрование, OAuth Яндекс, служебное хранение метаданных в Room; спроектирован контур синхронизации без передачи ключей провайдеру. Исходный код размещён в приватном репозитории Gitea ЮФУ. Полный листинг исходных файлов — приложение А; программная документация — приложение Б.
|
||||||
|
]
|
||||||
|
|
||||||
|
#include "includes/abstract-en.typ"
|
||||||
|
|
||||||
|
#outline()
|
||||||
|
|
||||||
|
#include "includes/intro.typ"
|
||||||
|
#include "includes/ch01.typ"
|
||||||
|
#include "includes/ch02.typ"
|
||||||
|
#include "includes/ch03.typ"
|
||||||
|
#include "includes/ch04.typ"
|
||||||
|
#include "includes/ch05.typ"
|
||||||
|
#include "includes/ch06.typ"
|
||||||
|
#include "includes/conclusion.typ"
|
||||||
|
#include "includes/abbreviations.typ"
|
||||||
|
|
||||||
|
// Заголовок списка источников задаётся в gost (bibliography.title).
|
||||||
|
#bibliography("references.bib")
|
||||||
|
|
||||||
|
#show: appendixes
|
||||||
|
|
||||||
|
#appendix-heading("обязательное", level: 1)[Приложение А. Листинги исходного кода проекта Wallenc]
|
||||||
|
|
||||||
|
Полный листинг файлов, необходимых для сборки проекта (307 файлов), сформирован автоматически скриптом `Report/scripts/gen_listings.py` по конфигурации `Report/listings/listings.config.yaml`. Исключены каталоги `build/`, `**/generated/**` и бинарные артефакты.
|
||||||
|
|
||||||
|
#include "listings/generated/appendix-a.typ"
|
||||||
|
|
||||||
|
#appendix-heading("обязательное", level: 1)[Приложение Б. Программная документация]
|
||||||
|
|
||||||
|
#include "appendices/appendix-b.typ"
|
||||||
|
|
||||||
|
#appendix-heading("обязательное", level: 1)[Приложение В. Скриншоты пользовательского интерфейса]
|
||||||
|
|
||||||
|
#pz-fig("fig_05_local_vaults.jpg", [Локальные vault], "fig-05-app")
|
||||||
|
#pz-fig("fig_06_encrypt_dialog.jpg", [Диалог шифрования], "fig-06-app")
|
||||||
|
#pz-fig("fig_07_open_close_dialog.jpg", [Открытие и закрытие vault], "fig-07-app")
|
||||||
|
#pz-fig("fig_08_rename_delete_dialog.jpg", [Переименование и удаление], "fig-08-app")
|
||||||
|
#pz-fig("fig_09_remote_vaults.jpg", [Удалённые vault], "fig-09-app")
|
||||||
|
#pz-fig("fig_10_yandex_oauth.jpg", [OAuth Яндекс], "fig-10-app")
|
||||||
|
#pz-fig("fig_11_room_schema.png", [Схема Room], "fig-11-app")
|
||||||
|
#pz-fig("fig_12_tasks_screen.jpg", [Экран задач], "fig-12-app")
|
||||||
|
#pz-fig("fig_13_tasks_notification.jpg", [Уведомление о задачах], "fig-13-app")
|
||||||
|
|
||||||
|
#appendix-heading("обязательное", level: 1)[Приложение Г. Диаграммы архитектуры и процессов]
|
||||||
|
|
||||||
|
#pz-fig("fig_01_start_sync.png", [Старт и синхронизация], "fig-01-app")
|
||||||
|
#pz-fig("fig_02_vault_lifecycle.png", [Жизненный цикл vault], "fig-02-app")
|
||||||
|
#pz-fig("fig_03_navigation_hub.png", [Навигация и SyncWorker], "fig-03-app")
|
||||||
|
#pz-fig("fig_04_domain_class.png", [Классы domain], "fig-04-app")
|
||||||
|
#pz-fig("fig_14_context_system.png", [Контекстная диаграмма], "fig-14-app")
|
||||||
|
#pz-fig("fig_15_bpmn_vault.png", [BPMN vault], "fig-15-app")
|
||||||
|
#pz-fig("fig_16_dfd_level0.png", [DFD уровень 0], "fig-16-app")
|
||||||
|
#pz-fig("fig_17_use_case.png", [Прецеденты], "fig-17-app")
|
||||||
|
#pz-fig("fig_18_deployment.png", [Развёртывание], "fig-18-app")
|
||||||
|
#pz-fig("fig_19_clean_architecture.png", [Clean Architecture], "fig-19-app")
|
||||||
|
#pz-fig("fig_20_oauth_sequence.png", [OAuth sequence], "fig-20-app")
|
||||||
|
#pz-fig("fig_21_encrypt_flow.png", [Поток шифрования], "fig-21-app")
|
||||||
|
#pz-fig("fig_22_cjm_vault.png", [CJM], "fig-22-app")
|
||||||
|
#pz-fig("fig_23_module_deps.png", [Модули Gradle], "fig-23-app")
|
||||||
266
Report/Пример работы с Typst.typ
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
#import "@preview/modern-g7-32:0.2.0": abstract, appendix-heading, appendixes, enum-numbering, gost
|
||||||
|
|
||||||
|
// Нумерация с использованием кириллицы
|
||||||
|
#set enum(numbering: enum-numbering)
|
||||||
|
|
||||||
|
#show: gost.with(
|
||||||
|
ministry: "Наименование министерства (ведомства) или другого структурного образования, в систему которого входит организация-исполнитель",
|
||||||
|
organization: (
|
||||||
|
full: "Полное наименование организации — исполнителя НИР",
|
||||||
|
short: "Сокращённое наименование организации",
|
||||||
|
),
|
||||||
|
udk: "индекс УДК",
|
||||||
|
research-number: "регистрационный номер НИР",
|
||||||
|
report-number: "регистрационный номер отчета",
|
||||||
|
approved-by: (
|
||||||
|
name: "Фамилия И.О.",
|
||||||
|
position: "Должность, наимен. орг.",
|
||||||
|
year: 2017,
|
||||||
|
), // Гриф согласования
|
||||||
|
agreed-by: (
|
||||||
|
name: "Фамилия И.О.",
|
||||||
|
position: "Должность, наимен. орг.",
|
||||||
|
year: auto,
|
||||||
|
), // Гриф утверждения, год подставляется из аргумента year
|
||||||
|
report-type: "отчёт",
|
||||||
|
about: "О научно-исследовательской работе",
|
||||||
|
research: "Наименование НИР",
|
||||||
|
bare-subject: false, // Можно убрать "по теме"
|
||||||
|
subject: "Наименование отчёта",
|
||||||
|
manager: (
|
||||||
|
name: "Фамилия И.О.",
|
||||||
|
position: "Должность",
|
||||||
|
title: "Руководитель НИР,",
|
||||||
|
), // Руководитель отчёта
|
||||||
|
year: 2022,
|
||||||
|
stage: (type: "вид отчёта", num: 1), // Этап отчёта
|
||||||
|
federal: "Наименование федеральной программы",
|
||||||
|
part: 2, // Номер книги отчёта
|
||||||
|
city: "Город",
|
||||||
|
text-size: (default: 14pt, small: 10pt), // Можно указать размеры текста
|
||||||
|
indent: 1.25cm, // Можно указать отступ
|
||||||
|
hide-title: false, // Убрать ли титульный лист
|
||||||
|
title-footer-align: center, // Выравнивание города и года на титульном листе
|
||||||
|
pagination-align: center, // Выравнивание номера страницы
|
||||||
|
margin: (
|
||||||
|
left: 30mm,
|
||||||
|
right: 15mm,
|
||||||
|
top: 20mm,
|
||||||
|
bottom: 20mm,
|
||||||
|
), // Отступы страницы
|
||||||
|
add-pagebreaks: true, // Убрать ли разрывы страниц
|
||||||
|
performers: (
|
||||||
|
"Всероссийский институт научной и технической информации " + "Российской академии наук (ВИНИТИ РАН)",
|
||||||
|
// Можно указать организацию, к которой относятся следующие исполнители
|
||||||
|
(
|
||||||
|
name: "И.О. Фамилия",
|
||||||
|
position: "Должность",
|
||||||
|
part: "введение, раздел 1",
|
||||||
|
), // Можно добавить выполненную часть
|
||||||
|
(name: "И.О. Фамилия", position: "Должность"),
|
||||||
|
"Другая организация",
|
||||||
|
(name: "И.О. Фамилия", position: "Должность"),
|
||||||
|
(
|
||||||
|
name: "И.О. Фамилия",
|
||||||
|
position: "Должность",
|
||||||
|
co-performer: true,
|
||||||
|
), // Поддерживаются соисполнители
|
||||||
|
), // Если исполнитель один - он будет перенесён на титульный лист
|
||||||
|
)
|
||||||
|
|
||||||
|
#abstract(
|
||||||
|
"шаблон",
|
||||||
|
"typst",
|
||||||
|
"государственные стандарты",
|
||||||
|
"оформление документов",
|
||||||
|
"система вёрстки",
|
||||||
|
"автоматизация",
|
||||||
|
)[
|
||||||
|
Настоящий документ представляет собой описание шаблона modern-g7-32, разработанного для системы вёрстки #link(
|
||||||
|
"https://typst.app/",
|
||||||
|
)[typst] с целью автоматизации создания документов, соответствующих государственным стандартам. Шаблон упрощает
|
||||||
|
процесс оформления, в частности, титульного листа, благодаря функции `gost.with`, которая позволяет гибко настраивать
|
||||||
|
отображение информации и автоматически подставлять текущий год.
|
||||||
|
|
||||||
|
В документе рассмотрены основные элементы шаблона, включая оформление таблиц, блоков кода и изображений, а также
|
||||||
|
детально описана работа с аргументами функции `gost.with` для кастомизации титульной страницы. Представленный шаблон
|
||||||
|
предназначен для пользователей системы typst, стремящихся к созданию стандартизированных документов с минимальными
|
||||||
|
усилиями.
|
||||||
|
]
|
||||||
|
|
||||||
|
#outline()
|
||||||
|
|
||||||
|
= Введение
|
||||||
|
|
||||||
|
Шаблон modern-g7-32 предназначен для создания документов в строгом соответствии с ГОСТ. Он упрощает оформление
|
||||||
|
титульного листа, автоматизирует подстановку даты и позволяет легко управлять отображением информации.
|
||||||
|
|
||||||
|
|
||||||
|
= Документация шаблона Typst
|
||||||
|
|
||||||
|
Этот документ описывает особенности использования шаблона modern-g7-32 для системы вёрстки #link(
|
||||||
|
"https://typst.app/",
|
||||||
|
)[typst]. Ниже приведены примеры оформления элементов, таких как таблицы, блоки кода и изображения, а также подробное
|
||||||
|
описание работы с аргументами шаблона через функцию `gost.with` @examplewebsite.
|
||||||
|
|
||||||
|
= Основные элементы шаблона
|
||||||
|
|
||||||
|
Шаблон modern-g7-32 позволяет создавать документы, оформленные в строгом соответствии с государственными стандартами.
|
||||||
|
|
||||||
|
== Таблицы
|
||||||
|
|
||||||
|
Для создания таблиц используется функция `table()`, обёрнутая в макрос `#figure` для добавления подписи. Пример показан
|
||||||
|
на таблице @example-table.
|
||||||
|
|
||||||
|
#figure(
|
||||||
|
table(
|
||||||
|
columns: 4,
|
||||||
|
table.header([Заголовок 1], [Заголовок 2], [Т], [Заголовок 4]),
|
||||||
|
[Проверка], [Проверка], [Проверка], [Проверка],
|
||||||
|
[Проверка], [Проверка], [Проверка], [Проверка],
|
||||||
|
),
|
||||||
|
caption: [Пример таблицы с данными],
|
||||||
|
) <example-table>
|
||||||
|
|
||||||
|
== Блоки кода
|
||||||
|
|
||||||
|
Чтобы оформить блоки кода в документе, можно использовать синтаксис, похожий на Markdown. Пример указан на листинге
|
||||||
|
@example-code:
|
||||||
|
|
||||||
|
#figure(
|
||||||
|
```typst
|
||||||
|
// Пример кода на Typst:
|
||||||
|
#import "package.typ"
|
||||||
|
|
||||||
|
// Функция вывода сообщения
|
||||||
|
print("Hello, world!")
|
||||||
|
```,
|
||||||
|
caption: [Пример кода на Typst],
|
||||||
|
) <example-code>
|
||||||
|
|
||||||
|
Этот блок кода демонстрирует базовую структуру программы на Typst.
|
||||||
|
|
||||||
|
#pagebreak()
|
||||||
|
|
||||||
|
== Формулы
|
||||||
|
|
||||||
|
Чтобы оформить формулы в документе, можно использовать синтаксис typst-math. Примеры демонстрируют использование формул
|
||||||
|
в шаблоне:
|
||||||
|
|
||||||
|
Формула @example-formula-first показывает как применять сложные функции, формула @example-formula-second демонстрирует
|
||||||
|
как можно оформить матрицы, а формула @example-formula-third – использование типов и обозначений @ivanov2020osnovy.
|
||||||
|
|
||||||
|
|
||||||
|
$ sum_(k=0)^n k = 1 + ... + n = (n(n+1)) / 2 $
|
||||||
|
<example-formula-first>
|
||||||
|
|
||||||
|
|
||||||
|
$ mat(1, 2, 3; 4, 5, 6; 7, 8, 9) * x = pi * psi(aleph/x) $
|
||||||
|
<example-formula-second>
|
||||||
|
|
||||||
|
$ cal(A) := { x in RR | x "натуральное" } $
|
||||||
|
<example-formula-third>
|
||||||
|
|
||||||
|
== Изображения
|
||||||
|
|
||||||
|
Изображения добавляются с помощью функции `image()` внутри макроса `#figure`. Пример вставки изображения указан на
|
||||||
|
рисунке @example-image.
|
||||||
|
|
||||||
|
#figure(
|
||||||
|
image("images/home.jpg", width: 40%),
|
||||||
|
caption: "Пример изображения",
|
||||||
|
) <example-image>
|
||||||
|
|
||||||
|
= Работа с аргументами шаблона в gost.with
|
||||||
|
|
||||||
|
Функция `gost.with` принимает множество аргументов, позволяющих настраивать титульную страницу и другие элементы
|
||||||
|
документа.
|
||||||
|
|
||||||
|
Рассмотрим основные возможности:
|
||||||
|
|
||||||
|
- Обязательные и необязательные аргументы. Многие параметры могут быть опущены, если вы не хотите, чтобы соответствующая
|
||||||
|
информация отображалась на титульном листе.
|
||||||
|
- Параметр auto (автоматический год). В аргументах, таких как `approved-by.year` или `year`, можно использовать значение
|
||||||
|
`auto`.
|
||||||
|
|
||||||
|
- Примеры аргументов: В заголовке шаблона задаются такие параметры, как:
|
||||||
|
+ `ministry-name` – название министерства.
|
||||||
|
+ `organization` – информация об учреждении (полное и сокращённое наименования).
|
||||||
|
+ `performers` – список исполнителей с указанием их позиций.
|
||||||
|
|
||||||
|
== Работа с аргументами шаблона <with-numbering>
|
||||||
|
|
||||||
|
Как уже отмечалось, функция `gost.with` позволяет детально настраивать титульную страницу. Рассмотрим ключевые моменты:
|
||||||
|
|
||||||
|
- При передаче параметра `year: auto` текущий год будет вычисляться автоматически.
|
||||||
|
- Если не требуется выводить определённый элемент (например, номер инвентаризации), соответствующий аргумент можно не
|
||||||
|
указывать.
|
||||||
|
- Все аргументы передаются в виде именованных параметров, что обеспечивает гибкость конфигурации.
|
||||||
|
|
||||||
|
= Заключение <conclusion>
|
||||||
|
|
||||||
|
Шаблон modern-g7-32 для Typst значительно упрощает подготовку документов, соответствующих государственным стандартам. Он
|
||||||
|
обеспечивает удобное создание таблиц, блоков кода и изображений, а гибкая настройка через функцию `gost.with` позволяет
|
||||||
|
автоматически подставлять дату и исключать ненужные элементы с титульной страницы. Используйте данный шаблон для
|
||||||
|
создания качественных и стандартизированных документов @petrov2021analiz.
|
||||||
|
|
||||||
|
#bibliography("references.bib")
|
||||||
|
|
||||||
|
#show: appendixes
|
||||||
|
|
||||||
|
= Изображения в приложениях
|
||||||
|
Пример вставки изображения в приложение указан на рисунке @appendix-image.
|
||||||
|
|
||||||
|
#figure(
|
||||||
|
image("images/nature.jpg", width: 40%),
|
||||||
|
caption: "Пример изображения",
|
||||||
|
) <appendix-image>
|
||||||
|
|
||||||
|
== Изображения в под-приложениях
|
||||||
|
Пример вставки изображения в под-приложение указан на рисунке @appendix-image-3. #figure(
|
||||||
|
image("images/abstract.jpg", width: 40%),
|
||||||
|
caption: "Пример изображения",
|
||||||
|
) <appendix-image-3>
|
||||||
|
|
||||||
|
= Блоки кода
|
||||||
|
Чтобы оформить блоки кода в документе, можно использовать синтаксис, похожий на Markdown. Пример указан на листинге
|
||||||
|
@appendix-code:
|
||||||
|
|
||||||
|
#figure(
|
||||||
|
```typst
|
||||||
|
// Пример кода на Typst:
|
||||||
|
#import "package.typ"
|
||||||
|
|
||||||
|
// Функция вывода сообщения
|
||||||
|
print("Hello, world!")
|
||||||
|
```,
|
||||||
|
caption: [Пример кода на Typst],
|
||||||
|
) <appendix-code>
|
||||||
|
|
||||||
|
= Формулы
|
||||||
|
Чтобы оформить формулы в документе, можно использовать синтаксис typst-math. Примеры демонстрируют использование формул
|
||||||
|
в шаблоне:
|
||||||
|
|
||||||
|
$ sum_(k=0)^n k = 1 + ... + n = (n(n+1)) / 2 $ <appendix-formula>
|
||||||
|
|
||||||
|
как оформлять таблицы сказано в приложении @appendix-tables, а также в разделе с нумерацией @with-numbering.
|
||||||
|
|
||||||
|
== Таблицы <appendix-tables>
|
||||||
|
Для создания таблиц используется функция `table()`, обёрнутая в макрос `#figure` для добавления подписи. Пример показан
|
||||||
|
на таблице @appendix-table.
|
||||||
|
|
||||||
|
#figure(
|
||||||
|
table(
|
||||||
|
columns: 4,
|
||||||
|
table.header([Заголовок 1], [Заголовок 2], [Заголовок 3], [Заголовок 4]),
|
||||||
|
[Проверка], [Проверка], [Проверка], [Проверка],
|
||||||
|
[Проверка], [Проверка], [Проверка], [Проверка],
|
||||||
|
),
|
||||||
|
caption: [Пример таблицы с данными],
|
||||||
|
) <appendix-table>
|
||||||
|
|
||||||
|
#appendix-heading("справочное", level: 1)[Приложение с указанием статуса]
|
||||||
|
#lorem(100)
|
||||||
|
|
||||||
|
#appendix-heading("справочное", level: 2)[Приложение второго уровня со статусом]
|
||||||
|
#lorem(100)
|
||||||